From 7f8eee126801e9d799452e40ec5e797553ec06e3 Mon Sep 17 00:00:00 2001 From: Christian Marillat Date: Sun, 17 Jul 2022 22:55:43 +0100 Subject: [PATCH] Import libtorrent-rasterbar_2.0.7.orig.tar.gz [dgit import orig libtorrent-rasterbar_2.0.7.orig.tar.gz] --- AUTHORS | 25 + CMakeLists.txt | 985 + COPYING | 28 + ChangeLog | 2133 ++ Jamfile | 1119 + Jamroot.jam | 0 LICENSE | 183 + LibtorrentRasterbarConfig.cmake.in | 9 + Makefile | 1138 + NEWS | 1 + README.rst | 70 + bindings/CMakeLists.txt | 3 + bindings/python/CMakeLists.txt | 128 + bindings/python/Jamfile | 363 + bindings/python/client.py | 381 + bindings/python/make_torrent.py | 61 + bindings/python/setup.py | 463 + bindings/python/setup.py.cmake.in | 15 + bindings/python/simple_client.py | 34 + bindings/python/src/alert.cpp | 1160 + bindings/python/src/boost_python.hpp | 43 + bindings/python/src/bytes.hpp | 22 + bindings/python/src/converters.cpp | 556 + bindings/python/src/create_torrent.cpp | 277 + bindings/python/src/datetime.cpp | 144 + bindings/python/src/entry.cpp | 185 + bindings/python/src/error_code.cpp | 240 + bindings/python/src/fingerprint.cpp | 34 + bindings/python/src/gil.hpp | 186 + bindings/python/src/info_hash.cpp | 41 + bindings/python/src/ip_filter.cpp | 49 + bindings/python/src/load_torrent.cpp | 55 + bindings/python/src/magnet_uri.cpp | 101 + bindings/python/src/module.cpp | 62 + bindings/python/src/optional.hpp | 31 + bindings/python/src/peer_info.cpp | 160 + bindings/python/src/session.cpp | 1356 ++ bindings/python/src/session_settings.cpp | 131 + bindings/python/src/sha1_hash.cpp | 46 + bindings/python/src/sha256_hash.cpp | 44 + bindings/python/src/string.cpp | 53 + bindings/python/src/torrent_handle.cpp | 665 + bindings/python/src/torrent_info.cpp | 514 + bindings/python/src/torrent_status.cpp | 143 + bindings/python/src/utility.cpp | 130 + bindings/python/src/version.cpp | 21 + cmake/Modules/FindLibGcrypt.cmake | 127 + cmake/Modules/GeneratePkgConfig.cmake | 183 + .../generate-pkg-config.cmake.in | 60 + .../GeneratePkgConfig/pkg-config.cmake.in | 8 + .../target-compile-settings.cmake.in | 13 + cmake/Modules/LibtorrentMacros.cmake | 80 + cmake/Modules/ucm_flags.cmake | 118 + deps/asio-gnutls/Jamfile | 6 + deps/asio-gnutls/LICENSE_1_0.txt | 23 + deps/asio-gnutls/README.md | 17 + .../asio-gnutls/include/boost/asio/gnutls.hpp | 23 + .../include/boost/asio/gnutls/context.hpp | 404 + .../boost/asio/gnutls/context_base.hpp | 92 + .../include/boost/asio/gnutls/error.hpp | 88 + .../asio/gnutls/host_name_verification.hpp | 53 + .../asio/gnutls/rfc2818_verification.hpp | 28 + .../include/boost/asio/gnutls/stream.hpp | 942 + .../include/boost/asio/gnutls/stream_base.hpp | 71 + .../boost/asio/gnutls/verify_context.hpp | 40 + deps/asio-gnutls/test/gnutls/Jamfile.v2 | 55 + deps/asio-gnutls/test/gnutls/context.cpp | 65 + deps/asio-gnutls/test/gnutls/context_base.cpp | 21 + deps/asio-gnutls/test/gnutls/error.cpp | 21 + .../test/gnutls/host_name_verification.cpp | 21 + .../test/gnutls/rfc2818_verification.cpp | 21 + deps/asio-gnutls/test/gnutls/stream.cpp | 149 + deps/asio-gnutls/test/gnutls/stream_base.cpp | 21 + deps/asio-gnutls/test/unit_test.hpp | 177 + deps/try_signal/CMakeLists.txt | 6 + deps/try_signal/Jamfile | 18 + deps/try_signal/LICENSE | 29 + deps/try_signal/README.rst | 53 + deps/try_signal/signal_error_code.cpp | 209 + deps/try_signal/signal_error_code.hpp | 159 + deps/try_signal/try_signal.cpp | 144 + deps/try_signal/try_signal.hpp | 49 + deps/try_signal/try_signal_mingw.hpp | 78 + deps/try_signal/try_signal_msvc.hpp | 61 + deps/try_signal/try_signal_posix.hpp | 82 + docs/building.html | 943 + docs/building.rst | 781 + docs/client_test.html | 94 + docs/client_test.rst | 49 + docs/contributing.html | 116 + docs/contributing.rst | 63 + docs/dht_extensions.html | 119 + docs/dht_extensions.rst | 68 + docs/dht_rss.html | 407 + docs/dht_rss.rst | 396 + docs/dht_sec.html | 277 + docs/dht_sec.rst | 255 + docs/dht_store.html | 510 + docs/dht_store.rst | 480 + docs/examples.html | 567 + docs/examples.rst | 46 + docs/extension_protocol.html | 473 + docs/extension_protocol.rst | 324 + docs/features-ref.html | 335 + docs/features.rst | 323 + docs/hacking.html | 174 + docs/hacking.rst | 123 + docs/header.rst | 1 + docs/img/bitcoin.png | Bin 0 -> 2611 bytes docs/img/complete_bit_prefixes.png | Bin 0 -> 7795 bytes docs/img/cwnd.png | Bin 0 -> 18998 bytes docs/img/cwnd_thumb.png | Bin 0 -> 30401 bytes docs/img/delays.png | Bin 0 -> 10875 bytes docs/img/delays_thumb.png | Bin 0 -> 10933 bytes docs/img/hacking.diagram | 30 + docs/img/hacking.png | Bin 0 -> 15692 bytes docs/img/hash_distribution.png | Bin 0 -> 9035 bytes docs/img/ip_id_v4.png | Bin 0 -> 5825 bytes docs/img/ip_id_v6.png | Bin 0 -> 6416 bytes docs/img/logo-color-text.png | Bin 0 -> 17420 bytes docs/img/our_delay_base.png | Bin 0 -> 34999 bytes docs/img/our_delay_base_thumb.png | Bin 0 -> 55809 bytes docs/img/pp-acceptance-medium.png | Bin 0 -> 1159 bytes docs/img/read_disk_buffers.diagram | 16 + docs/img/read_disk_buffers.png | Bin 0 -> 8371 bytes docs/img/screenshot.png | Bin 0 -> 501532 bytes docs/img/screenshot_thumb.png | Bin 0 -> 124784 bytes docs/img/storage.png | Bin 0 -> 16537 bytes docs/img/troubleshooting.dot | 108 + docs/img/troubleshooting.png | Bin 0 -> 351799 bytes docs/img/troubleshooting_thumb.png | Bin 0 -> 46499 bytes docs/img/utp_stack.diagram | 9 + docs/img/utp_stack.png | Bin 0 -> 2017 bytes docs/img/write_disk_buffers.diagram | 15 + docs/img/write_disk_buffers.png | Bin 0 -> 9269 bytes docs/index.html | 174 + docs/index.rst | 225 + docs/manual-ref.html | 3348 +++ docs/manual-ref.rst | 1434 ++ docs/manual.rst | 1322 + docs/projects.html | 183 + docs/projects.rst | 209 + docs/python_binding.html | 322 + docs/python_binding.rst | 263 + docs/reference-Add_Torrent.html | 421 + docs/reference-Alerts.html | 3611 +++ docs/reference-Bdecoding.html | 409 + docs/reference-Bencoding.html | 370 + docs/reference-Core.html | 1307 + docs/reference-Create_Torrents.html | 480 + docs/reference-Custom_Storage.html | 947 + docs/reference-DHT.html | 390 + docs/reference-Error_Codes.html | 1430 ++ docs/reference-Filter.html | 244 + docs/reference-PeerClass.html | 232 + docs/reference-Plugins.html | 864 + docs/reference-Resume_Data.html | 140 + docs/reference-Session.html | 1178 + docs/reference-Settings.html | 5063 ++++ docs/reference-Stats.html | 170 + docs/reference-Storage.html | 807 + docs/reference-Torrent_Handle.html | 1454 ++ docs/reference-Torrent_Info.html | 735 + docs/reference-Torrent_Status.html | 645 + docs/reference-Trackers.html | 269 + docs/reference-Utility.html | 366 + docs/reference-ed25519.html | 147 + docs/reference.html | 367 + docs/security-audit.html | 495 + docs/settings.rst | 3404 +++ docs/single-page-ref.html | 20293 ++++++++++++++++ docs/stats_counters.rst | 2133 ++ docs/streaming.html | 172 + docs/streaming.rst | 147 + docs/style.css | 426 + docs/todo.html | 12089 +++++++++ docs/troubleshooting.html | 62 + docs/troubleshooting.rst | 20 + docs/tuning-ref.html | 373 + docs/tuning.rst | 344 + docs/tutorial-ref.html | 690 + docs/tutorial-ref.rst | 411 + docs/tutorial.rst | 305 + docs/udp_tracker_protocol.html | 580 + docs/udp_tracker_protocol.rst | 320 + docs/upgrade_to_1.2-ref.html | 206 + docs/upgrade_to_2.0-ref.html | 358 + docs/utp.html | 342 + docs/utp.rst | 345 + examples/CMakeLists.txt | 27 + examples/Jamfile | 62 + examples/bt-get.cpp | 82 + examples/bt-get2.cpp | 179 + examples/bt-get3.cpp | 213 + examples/check_files.cpp | 170 + examples/client_test.cpp | 2192 ++ examples/cmake/FindLibtorrentRasterbar.cmake | 190 + examples/connection_tester.cpp | 1217 + examples/custom_storage.cpp | 328 + examples/dump_bdecode.cpp | 120 + examples/dump_torrent.cpp | 193 + examples/magnet2torrent.cpp | 125 + examples/make_torrent.cpp | 319 + examples/print.cpp | 553 + examples/print.hpp | 53 + examples/session_view.cpp | 163 + examples/session_view.hpp | 103 + examples/simple_client.cpp | 63 + examples/stats_counters.cpp | 50 + examples/torrent2magnet.cpp | 95 + examples/torrent_view.cpp | 481 + examples/torrent_view.hpp | 112 + examples/upnp_test.cpp | 108 + include/libtorrent/add_torrent_params.hpp | 401 + include/libtorrent/address.hpp | 79 + include/libtorrent/alert.hpp | 333 + include/libtorrent/alert_types.hpp | 3047 +++ include/libtorrent/announce_entry.hpp | 291 + include/libtorrent/assert.hpp | 130 + include/libtorrent/aux_/alert_manager.hpp | 180 + include/libtorrent/aux_/aligned_storage.hpp | 61 + include/libtorrent/aux_/aligned_union.hpp | 72 + include/libtorrent/aux_/alloca.hpp | 117 + .../libtorrent/aux_/allocating_handler.hpp | 362 + include/libtorrent/aux_/announce_entry.hpp | 216 + include/libtorrent/aux_/apply_pad_files.hpp | 105 + include/libtorrent/aux_/array.hpp | 48 + include/libtorrent/aux_/bandwidth_limit.hpp | 105 + include/libtorrent/aux_/bandwidth_manager.hpp | 94 + .../libtorrent/aux_/bandwidth_queue_entry.hpp | 77 + include/libtorrent/aux_/bandwidth_socket.hpp | 51 + include/libtorrent/aux_/bind_to_device.hpp | 137 + include/libtorrent/aux_/buffer.hpp | 170 + include/libtorrent/aux_/byteswap.hpp | 100 + include/libtorrent/aux_/chained_buffer.hpp | 235 + include/libtorrent/aux_/container_wrapper.hpp | 136 + include/libtorrent/aux_/cpuid.hpp | 47 + include/libtorrent/aux_/deferred_handler.hpp | 81 + include/libtorrent/aux_/deprecated.hpp | 68 + include/libtorrent/aux_/deque.hpp | 47 + include/libtorrent/aux_/dev_random.hpp | 80 + include/libtorrent/aux_/directory.hpp | 79 + .../disable_deprecation_warnings_push.hpp | 51 + .../libtorrent/aux_/disable_warnings_pop.hpp | 43 + .../libtorrent/aux_/disable_warnings_push.hpp | 113 + include/libtorrent/aux_/disk_buffer_pool.hpp | 132 + .../libtorrent/aux_/disk_io_thread_pool.hpp | 150 + include/libtorrent/aux_/disk_job_fence.hpp | 118 + include/libtorrent/aux_/disk_job_pool.hpp | 75 + include/libtorrent/aux_/ed25519.hpp | 18 + include/libtorrent/aux_/escape_string.hpp | 104 + include/libtorrent/aux_/export.hpp | 154 + include/libtorrent/aux_/ffs.hpp | 71 + include/libtorrent/aux_/file_pointer.hpp | 78 + include/libtorrent/aux_/file_progress.hpp | 109 + include/libtorrent/aux_/file_view_pool.hpp | 238 + include/libtorrent/aux_/generate_peer_id.hpp | 48 + include/libtorrent/aux_/has_block.hpp | 57 + include/libtorrent/aux_/hasher512.hpp | 134 + .../libtorrent/aux_/heterogeneous_queue.hpp | 260 + .../aux_/instantiate_connection.hpp | 58 + include/libtorrent/aux_/invariant_check.hpp | 86 + include/libtorrent/aux_/io.hpp | 180 + include/libtorrent/aux_/ip_helpers.hpp | 68 + include/libtorrent/aux_/ip_notifier.hpp | 59 + include/libtorrent/aux_/keepalive.hpp | 102 + .../libtorrent/aux_/listen_socket_handle.hpp | 94 + include/libtorrent/aux_/lsd.hpp | 55 + include/libtorrent/aux_/merkle.hpp | 162 + include/libtorrent/aux_/merkle_tree.hpp | 226 + include/libtorrent/aux_/mmap.hpp | 210 + include/libtorrent/aux_/mmap_disk_job.hpp | 233 + include/libtorrent/aux_/noexcept_movable.hpp | 112 + include/libtorrent/aux_/numeric_cast.hpp | 70 + include/libtorrent/aux_/open_mode.hpp | 62 + include/libtorrent/aux_/packet_buffer.hpp | 122 + include/libtorrent/aux_/packet_pool.hpp | 226 + include/libtorrent/aux_/path.hpp | 187 + .../libtorrent/aux_/polymorphic_socket.hpp | 188 + include/libtorrent/aux_/pool.hpp | 60 + include/libtorrent/aux_/portmap.hpp | 105 + include/libtorrent/aux_/posix_part_file.hpp | 139 + include/libtorrent/aux_/posix_storage.hpp | 122 + include/libtorrent/aux_/proxy_settings.hpp | 93 + include/libtorrent/aux_/range.hpp | 70 + include/libtorrent/aux_/receive_buffer.hpp | 230 + include/libtorrent/aux_/resolver.hpp | 102 + .../libtorrent/aux_/resolver_interface.hpp | 79 + include/libtorrent/aux_/route.h | 478 + include/libtorrent/aux_/scope_end.hpp | 64 + include/libtorrent/aux_/session_call.hpp | 52 + include/libtorrent/aux_/session_impl.hpp | 1392 ++ include/libtorrent/aux_/session_interface.hpp | 316 + include/libtorrent/aux_/session_settings.hpp | 190 + .../libtorrent/aux_/session_udp_sockets.hpp | 78 + include/libtorrent/aux_/set_socket_buffer.hpp | 92 + include/libtorrent/aux_/set_traffic_class.hpp | 60 + include/libtorrent/aux_/sha512.hpp | 33 + include/libtorrent/aux_/socket_type.hpp | 100 + include/libtorrent/aux_/storage_free_list.hpp | 73 + include/libtorrent/aux_/storage_utils.hpp | 127 + include/libtorrent/aux_/store_buffer.hpp | 150 + include/libtorrent/aux_/string_ptr.hpp | 76 + include/libtorrent/aux_/strview_less.hpp | 52 + include/libtorrent/aux_/suggest_piece.hpp | 128 + include/libtorrent/aux_/throw.hpp | 54 + include/libtorrent/aux_/time.hpp | 48 + include/libtorrent/aux_/timestamp_history.hpp | 88 + include/libtorrent/aux_/torrent_impl.hpp | 80 + include/libtorrent/aux_/torrent_list.hpp | 261 + include/libtorrent/aux_/unique_ptr.hpp | 66 + .../libtorrent/aux_/utp_socket_manager.hpp | 202 + include/libtorrent/aux_/utp_stream.hpp | 974 + include/libtorrent/aux_/vector.hpp | 48 + include/libtorrent/aux_/win_cng.hpp | 208 + .../libtorrent/aux_/win_crypto_provider.hpp | 148 + include/libtorrent/aux_/win_util.hpp | 108 + include/libtorrent/aux_/windows.hpp | 51 + include/libtorrent/bdecode.hpp | 483 + include/libtorrent/bencode.hpp | 408 + include/libtorrent/bitfield.hpp | 326 + include/libtorrent/bloom_filter.hpp | 80 + include/libtorrent/bt_peer_connection.hpp | 519 + include/libtorrent/choker.hpp | 60 + include/libtorrent/client_data.hpp | 104 + include/libtorrent/close_reason.hpp | 156 + include/libtorrent/config.hpp | 664 + include/libtorrent/copy_ptr.hpp | 68 + include/libtorrent/crc32c.hpp | 46 + include/libtorrent/create_torrent.hpp | 532 + include/libtorrent/deadline_timer.hpp | 56 + include/libtorrent/debug.hpp | 288 + include/libtorrent/disabled_disk_io.hpp | 55 + include/libtorrent/disk_buffer_holder.hpp | 112 + include/libtorrent/disk_interface.hpp | 425 + include/libtorrent/disk_observer.hpp | 52 + include/libtorrent/download_priority.hpp | 57 + include/libtorrent/entry.hpp | 334 + include/libtorrent/enum_net.hpp | 234 + include/libtorrent/error.hpp | 53 + include/libtorrent/error_code.hpp | 600 + include/libtorrent/extensions.hpp | 548 + include/libtorrent/extensions/smart_ban.hpp | 61 + include/libtorrent/extensions/ut_metadata.hpp | 63 + include/libtorrent/extensions/ut_pex.hpp | 64 + include/libtorrent/file.hpp | 109 + include/libtorrent/file_storage.hpp | 722 + include/libtorrent/fingerprint.hpp | 103 + include/libtorrent/flags.hpp | 141 + include/libtorrent/fwd.hpp | 319 + include/libtorrent/gzip.hpp | 137 + include/libtorrent/hash_picker.hpp | 234 + include/libtorrent/hasher.hpp | 185 + include/libtorrent/hex.hpp | 97 + include/libtorrent/http_connection.hpp | 262 + include/libtorrent/http_parser.hpp | 168 + include/libtorrent/http_seed_connection.hpp | 114 + include/libtorrent/http_stream.hpp | 230 + .../libtorrent/http_tracker_connection.hpp | 97 + include/libtorrent/i2p_stream.hpp | 624 + include/libtorrent/identify_client.hpp | 86 + include/libtorrent/index_range.hpp | 72 + include/libtorrent/info_hash.hpp | 167 + include/libtorrent/io.hpp | 189 + include/libtorrent/io_context.hpp | 63 + include/libtorrent/io_service.hpp | 47 + include/libtorrent/ip_filter.hpp | 241 + include/libtorrent/ip_voter.hpp | 136 + .../libtorrent/kademlia/announce_flags.hpp | 63 + include/libtorrent/kademlia/dht_observer.hpp | 100 + include/libtorrent/kademlia/dht_settings.hpp | 184 + include/libtorrent/kademlia/dht_state.hpp | 81 + include/libtorrent/kademlia/dht_storage.hpp | 245 + include/libtorrent/kademlia/dht_tracker.hpp | 230 + .../libtorrent/kademlia/direct_request.hpp | 98 + include/libtorrent/kademlia/dos_blocker.hpp | 94 + include/libtorrent/kademlia/ed25519.hpp | 105 + include/libtorrent/kademlia/find_data.hpp | 91 + include/libtorrent/kademlia/get_item.hpp | 98 + include/libtorrent/kademlia/get_peers.hpp | 115 + include/libtorrent/kademlia/io.hpp | 65 + include/libtorrent/kademlia/item.hpp | 130 + include/libtorrent/kademlia/msg.hpp | 103 + include/libtorrent/kademlia/node.hpp | 305 + include/libtorrent/kademlia/node_entry.hpp | 97 + include/libtorrent/kademlia/node_id.hpp | 77 + include/libtorrent/kademlia/observer.hpp | 156 + include/libtorrent/kademlia/put_data.hpp | 93 + include/libtorrent/kademlia/refresh.hpp | 67 + include/libtorrent/kademlia/routing_table.hpp | 345 + include/libtorrent/kademlia/rpc_manager.hpp | 147 + .../libtorrent/kademlia/sample_infohashes.hpp | 84 + .../kademlia/traversal_algorithm.hpp | 176 + include/libtorrent/kademlia/types.hpp | 100 + include/libtorrent/libtorrent.hpp | 168 + include/libtorrent/link.hpp | 83 + include/libtorrent/load_torrent.hpp | 69 + include/libtorrent/lsd.hpp | 95 + include/libtorrent/magnet_uri.hpp | 111 + include/libtorrent/mmap_disk_io.hpp | 57 + include/libtorrent/mmap_storage.hpp | 227 + include/libtorrent/natpmp.hpp | 220 + include/libtorrent/netlink.hpp | 203 + include/libtorrent/operations.hpp | 265 + include/libtorrent/optional.hpp | 51 + include/libtorrent/parse_url.hpp | 66 + include/libtorrent/part_file.hpp | 138 + include/libtorrent/pe_crypto.hpp | 162 + include/libtorrent/peer.hpp | 70 + include/libtorrent/peer_class.hpp | 159 + include/libtorrent/peer_class_set.hpp | 70 + include/libtorrent/peer_class_type_filter.hpp | 146 + include/libtorrent/peer_connection.hpp | 1255 + include/libtorrent/peer_connection_handle.hpp | 158 + .../libtorrent/peer_connection_interface.hpp | 84 + include/libtorrent/peer_id.hpp | 43 + include/libtorrent/peer_info.hpp | 470 + include/libtorrent/peer_list.hpp | 270 + include/libtorrent/peer_request.hpp | 59 + include/libtorrent/performance_counters.hpp | 500 + include/libtorrent/pex_flags.hpp | 65 + include/libtorrent/piece_block.hpp | 69 + include/libtorrent/piece_block_progress.hpp | 61 + include/libtorrent/piece_picker.hpp | 907 + include/libtorrent/platform_util.hpp | 14 + include/libtorrent/portmap.hpp | 58 + include/libtorrent/posix_disk_io.hpp | 55 + include/libtorrent/proxy_base.hpp | 344 + include/libtorrent/puff.hpp | 35 + include/libtorrent/random.hpp | 85 + include/libtorrent/read_resume_data.hpp | 71 + include/libtorrent/request_blocks.hpp | 56 + include/libtorrent/resolve_links.hpp | 97 + include/libtorrent/session.hpp | 299 + include/libtorrent/session_handle.hpp | 1130 + include/libtorrent/session_params.hpp | 156 + include/libtorrent/session_settings.hpp | 118 + include/libtorrent/session_stats.hpp | 79 + include/libtorrent/session_status.hpp | 238 + include/libtorrent/session_types.hpp | 56 + include/libtorrent/settings_pack.hpp | 2186 ++ include/libtorrent/sha1.hpp | 43 + include/libtorrent/sha1_hash.hpp | 316 + include/libtorrent/sha256.hpp | 33 + include/libtorrent/sliding_average.hpp | 93 + include/libtorrent/socket.hpp | 298 + include/libtorrent/socket_io.hpp | 146 + include/libtorrent/socket_type.hpp | 65 + include/libtorrent/socks5_stream.hpp | 561 + include/libtorrent/span.hpp | 194 + include/libtorrent/ssl.hpp | 186 + include/libtorrent/ssl_stream.hpp | 355 + include/libtorrent/stack_allocator.hpp | 96 + include/libtorrent/stat.hpp | 287 + include/libtorrent/stat_cache.hpp | 108 + include/libtorrent/storage.hpp | 7 + include/libtorrent/storage_defs.hpp | 150 + include/libtorrent/string_util.hpp | 153 + include/libtorrent/string_view.hpp | 109 + include/libtorrent/tailqueue.hpp | 214 + include/libtorrent/time.hpp | 92 + include/libtorrent/torrent.hpp | 1800 ++ include/libtorrent/torrent_flags.hpp | 312 + include/libtorrent/torrent_handle.hpp | 1371 ++ include/libtorrent/torrent_info.hpp | 778 + include/libtorrent/torrent_peer.hpp | 287 + include/libtorrent/torrent_peer_allocator.hpp | 102 + include/libtorrent/torrent_status.hpp | 608 + include/libtorrent/tracker_manager.hpp | 411 + include/libtorrent/truncate.hpp | 48 + include/libtorrent/udp_socket.hpp | 173 + include/libtorrent/udp_tracker_connection.hpp | 133 + include/libtorrent/union_endpoint.hpp | 115 + include/libtorrent/units.hpp | 186 + include/libtorrent/upnp.hpp | 395 + include/libtorrent/utf8.hpp | 52 + include/libtorrent/vector_utils.hpp | 59 + include/libtorrent/version.hpp | 72 + include/libtorrent/web_connection_base.hpp | 137 + include/libtorrent/web_peer_connection.hpp | 146 + include/libtorrent/write_resume_data.hpp | 78 + include/libtorrent/xml_parse.hpp | 70 + project-config.jam | 0 setup.py | 6 + simulation/Jamfile | 66 + simulation/create_torrent.cpp | 73 + simulation/create_torrent.hpp | 45 + simulation/disk_io.cpp | 719 + simulation/disk_io.hpp | 131 + simulation/fake_peer.hpp | 473 + simulation/libsimulator/CMakeLists.txt | 57 + simulation/libsimulator/Jamfile | 134 + simulation/libsimulator/Jamroot.jam | 0 simulation/libsimulator/LICENSE | 675 + simulation/libsimulator/README.rst | 202 + .../libsimulator/include/simulator/chrono.hpp | 88 + .../libsimulator/include/simulator/config.hpp | 51 + .../include/simulator/function.hpp | 220 + .../include/simulator/handler_allocator.hpp | 81 + .../include/simulator/http_proxy.hpp | 103 + .../include/simulator/http_server.hpp | 122 + .../include/simulator/noexcept_movable.hpp | 56 + .../libsimulator/include/simulator/packet.hpp | 94 + .../libsimulator/include/simulator/pcap.hpp | 42 + .../include/simulator/pop_warnings.hpp | 44 + .../include/simulator/push_warnings.hpp | 89 + .../libsimulator/include/simulator/queue.hpp | 100 + .../include/simulator/simulator.hpp | 1348 + .../libsimulator/include/simulator/sink.hpp | 47 + .../include/simulator/sink_forwarder.hpp | 43 + .../include/simulator/socks_server.hpp | 180 + .../libsimulator/include/simulator/utils.hpp | 47 + simulation/libsimulator/src/acceptor.cpp | 379 + .../libsimulator/src/default_config.cpp | 106 + .../src/high_resolution_clock.cpp | 51 + .../src/high_resolution_timer.cpp | 141 + simulation/libsimulator/src/http_proxy.cpp | 362 + simulation/libsimulator/src/http_server.cpp | 393 + simulation/libsimulator/src/io_service.cpp | 257 + simulation/libsimulator/src/pcap.cpp | 204 + simulation/libsimulator/src/queue.cpp | 142 + simulation/libsimulator/src/resolver.cpp | 143 + simulation/libsimulator/src/simulation.cpp | 349 + simulation/libsimulator/src/simulator.cpp | 262 + .../libsimulator/src/sink_forwarder.cpp | 45 + simulation/libsimulator/src/socks_server.cpp | 911 + simulation/libsimulator/src/tcp_socket.cpp | 884 + simulation/libsimulator/src/udp_socket.cpp | 497 + simulation/make_proxy_settings.hpp | 57 + simulation/setup_dht.cpp | 318 + simulation/setup_dht.hpp | 74 + simulation/setup_swarm.cpp | 428 + simulation/setup_swarm.hpp | 101 + simulation/test_auto_manage.cpp | 923 + simulation/test_checking.cpp | 295 + simulation/test_dht.cpp | 331 + simulation/test_dht_bootstrap.cpp | 112 + simulation/test_dht_rate_limit.cpp | 271 + simulation/test_dht_storage.cpp | 219 + simulation/test_error_handling.cpp | 250 + simulation/test_fast_extensions.cpp | 360 + simulation/test_http_connection.cpp | 663 + simulation/test_ip_filter.cpp | 239 + simulation/test_metadata_extension.cpp | 250 + simulation/test_optimistic_unchoke.cpp | 173 + simulation/test_pause.cpp | 335 + simulation/test_pe_crypto.cpp | 197 + simulation/test_peer_connection.cpp | 309 + simulation/test_save_resume.cpp | 88 + simulation/test_session.cpp | 254 + simulation/test_socks5.cpp | 283 + simulation/test_super_seeding.cpp | 78 + simulation/test_swarm.cpp | 1032 + simulation/test_thread_pool.cpp | 216 + simulation/test_torrent_status.cpp | 589 + simulation/test_tracker.cpp | 1839 ++ simulation/test_transfer.cpp | 547 + simulation/test_transfer_matrix.cpp | 97 + simulation/test_utp.cpp | 232 + simulation/test_web_seed.cpp | 869 + simulation/transfer_sim.cpp | 53 + simulation/transfer_sim.hpp | 223 + simulation/utils.cpp | 256 + simulation/utils.hpp | 104 + src/add_torrent_params.cpp | 99 + src/alert.cpp | 3213 +++ src/alert_manager.cpp | 148 + src/announce_entry.cpp | 289 + src/assert.cpp | 407 + src/bandwidth_limit.cpp | 110 + src/bandwidth_manager.cpp | 225 + src/bandwidth_queue_entry.cpp | 81 + src/bdecode.cpp | 1187 + src/bitfield.cpp | 236 + src/bloom_filter.cpp | 77 + src/bt_peer_connection.cpp | 3803 +++ src/chained_buffer.cpp | 174 + src/choker.cpp | 311 + src/close_reason.cpp | 167 + src/copy_file.cpp | 456 + src/cpuid.cpp | 162 + src/crc32c.cpp | 151 + src/create_torrent.cpp | 996 + src/directory.cpp | 126 + src/disabled_disk_io.cpp | 206 + src/disk_buffer_holder.cpp | 65 + src/disk_buffer_pool.cpp | 235 + src/disk_interface.cpp | 44 + src/disk_io_thread_pool.cpp | 209 + src/disk_job_fence.cpp | 220 + src/disk_job_pool.cpp | 111 + src/ed25519/LICENSE | 22 + src/ed25519/add_scalar.cpp | 75 + src/ed25519/fe.cpp | 1490 ++ src/ed25519/fe.h | 41 + src/ed25519/fixedint.h | 10 + src/ed25519/ge.cpp | 472 + src/ed25519/ge.h | 74 + src/ed25519/hasher512.cpp | 152 + src/ed25519/key_exchange.cpp | 88 + src/ed25519/keypair.cpp | 24 + src/ed25519/precomp_data.h | 1392 ++ src/ed25519/sc.cpp | 816 + src/ed25519/sc.h | 13 + src/ed25519/sha512.cpp | 291 + src/ed25519/sign.cpp | 37 + src/ed25519/verify.cpp | 84 + src/entry.cpp | 774 + src/enum_net.cpp | 1538 ++ src/error_code.cpp | 371 + src/escape_string.cpp | 586 + src/ffs.cpp | 185 + src/file.cpp | 331 + src/file_progress.cpp | 208 + src/file_storage.cpp | 1585 ++ src/file_view_pool.cpp | 420 + src/fingerprint.cpp | 103 + src/generate_peer_id.cpp | 56 + src/gzip.cpp | 265 + src/hash_picker.cpp | 427 + src/hasher.cpp | 283 + src/hex.cpp | 106 + src/http_connection.cpp | 910 + src/http_parser.cpp | 648 + src/http_seed_connection.cpp | 488 + src/http_tracker_connection.cpp | 678 + src/i2p_stream.cpp | 141 + src/identify_client.cpp | 441 + src/instantiate_connection.cpp | 154 + src/ip_filter.cpp | 285 + src/ip_helpers.cpp | 124 + src/ip_notifier.cpp | 449 + src/ip_voter.cpp | 194 + src/kademlia/dht_settings.cpp | 116 + src/kademlia/dht_state.cpp | 135 + src/kademlia/dht_storage.cpp | 634 + src/kademlia/dht_tracker.cpp | 737 + src/kademlia/dos_blocker.cpp | 114 + src/kademlia/ed25519.cpp | 127 + src/kademlia/find_data.cpp | 197 + src/kademlia/get_item.cpp | 222 + src/kademlia/get_peers.cpp | 332 + src/kademlia/item.cpp | 212 + src/kademlia/msg.cpp | 138 + src/kademlia/node.cpp | 1251 + src/kademlia/node_entry.cpp | 65 + src/kademlia/node_id.cpp | 215 + src/kademlia/put_data.cpp | 118 + src/kademlia/refresh.cpp | 107 + src/kademlia/routing_table.cpp | 1239 + src/kademlia/rpc_manager.cpp | 523 + src/kademlia/sample_infohashes.cpp | 161 + src/kademlia/traversal_algorithm.cpp | 675 + src/listen_socket_handle.cpp | 83 + src/load_torrent.cpp | 129 + src/lsd.cpp | 340 + src/magnet_uri.cpp | 507 + src/merkle.cpp | 454 + src/merkle_tree.cpp | 1079 + src/mmap.cpp | 760 + src/mmap_disk_io.cpp | 1859 ++ src/mmap_disk_job.cpp | 136 + src/mmap_storage.cpp | 947 + src/natpmp.cpp | 929 + src/packet_buffer.cpp | 195 + src/parse_url.cpp | 216 + src/part_file.cpp | 447 + src/path.cpp | 973 + src/pe_crypto.cpp | 413 + src/peer_class.cpp | 126 + src/peer_class_set.cpp | 73 + src/peer_connection.cpp | 6811 ++++++ src/peer_connection_handle.cpp | 357 + src/peer_info.cpp | 89 + src/peer_list.cpp | 1335 + src/performance_counters.cpp | 160 + src/piece_picker.cpp | 3934 +++ src/platform_util.cpp | 132 + src/posix_disk_io.cpp | 406 + src/posix_part_file.cpp | 480 + src/posix_storage.cpp | 555 + src/proxy_base.cpp | 47 + src/proxy_settings.cpp | 67 + src/puff.cpp | 844 + src/random.cpp | 198 + src/read_resume_data.cpp | 444 + src/receive_buffer.cpp | 341 + src/request_blocks.cpp | 311 + src/resolve_links.cpp | 188 + src/resolver.cpp | 176 + src/session.cpp | 551 + src/session_call.cpp | 82 + src/session_handle.cpp | 1276 + src/session_impl.cpp | 7305 ++++++ src/session_params.cpp | 295 + src/session_settings.cpp | 65 + src/session_stats.cpp | 599 + src/settings_pack.cpp | 853 + src/sha1.cpp | 329 + src/sha1_hash.cpp | 179 + src/sha256.cpp | 180 + src/smart_ban.cpp | 329 + src/socket_io.cpp | 167 + src/socket_type.cpp | 269 + src/socks5_stream.cpp | 81 + src/ssl.cpp | 212 + src/stack_allocator.cpp | 143 + src/stat.cpp | 46 + src/stat_cache.cpp | 145 + src/storage_utils.cpp | 807 + src/string_util.cpp | 412 + src/time.cpp | 40 + src/timestamp_history.cpp | 108 + src/torrent.cpp | 12058 +++++++++ src/torrent_handle.cpp | 928 + src/torrent_info.cpp | 1873 ++ src/torrent_peer.cpp | 286 + src/torrent_peer_allocator.cpp | 117 + src/torrent_status.cpp | 55 + src/tracker_manager.cpp | 496 + src/truncate.cpp | 177 + src/udp_socket.cpp | 1000 + src/udp_tracker_connection.cpp | 780 + src/upnp.cpp | 1683 ++ src/ut_metadata.cpp | 640 + src/ut_pex.cpp | 644 + src/utf8.cpp | 178 + src/utp_socket_manager.cpp | 332 + src/utp_stream.cpp | 3343 +++ src/version.cpp | 42 + src/web_connection_base.cpp | 213 + src/web_peer_connection.cpp | 1244 + src/write_resume_data.cpp | 428 + src/xml_parse.cpp | 171 + test/CMakeLists.txt | 62 + test/Jamfile | 320 + test/bittorrent_peer.cpp | 563 + test/bittorrent_peer.hpp | 110 + test/broadcast_socket.cpp | 249 + test/broadcast_socket.hpp | 143 + test/corrupt.gz | Bin 0 -> 296 bytes test/dht_server.cpp | 183 + test/dht_server.hpp | 42 + test/enum_if.cpp | 134 + test/http_proxy.py | 550 + test/invalid1.gz | Bin 0 -> 27 bytes test/main.cpp | 601 + test/make_torrent.cpp | 214 + test/make_torrent.hpp | 69 + test/mutable_test_torrents/test1.torrent | 3 + .../test1_pad_files.torrent | 2 + .../test1_single.torrent | 2 + .../test1_single_padded.torrent | 2 + test/mutable_test_torrents/test2.torrent | 1 + .../test2_pad_files.torrent | Bin 0 -> 499 bytes test/mutable_test_torrents/test3.torrent | Bin 0 -> 366 bytes .../test3_pad_files.torrent | 4 + test/peer_server.cpp | 170 + test/peer_server.hpp | 47 + test/print_alerts.cpp | 72 + test/print_alerts.hpp | 43 + test/root1.xml | 135 + test/root2.xml | 1 + test/root3.xml | 1 + test/settings.cpp | 93 + test/settings.hpp | 37 + test/setup_transfer.cpp | 1238 + test/setup_transfer.hpp | 129 + test/socks.py | 317 + test/ssl/cert_request.pem | 12 + test/ssl/dhparams.pem | 13 + test/ssl/invalid_peer_certificate.pem | 82 + test/ssl/invalid_peer_private_key.pem | 30 + test/ssl/peer_certificate.pem | 79 + test/ssl/peer_private_key.pem | 30 + test/ssl/regenerate_test_certificate.sh | 39 + test/ssl/root_ca_cert.pem | 79 + test/ssl/root_ca_private.pem | 30 + test/ssl/server.pem | 79 + test/swarm_suite.cpp | 245 + test/swarm_suite.hpp | 52 + test/test.cpp | 102 + test/test.hpp | 197 + test/test_add_torrent.cpp | 305 + test/test_alert_manager.cpp | 364 + test/test_alert_types.cpp | 356 + test/test_alloca.cpp | 82 + test/test_apply_pad.cpp | 175 + test/test_auto_unchoke.cpp | 165 + test/test_bandwidth_limiter.cpp | 519 + test/test_bdecode.cpp | 1367 ++ test/test_bencoding.cpp | 274 + test/test_bitfield.cpp | 431 + test/test_bloom_filter.cpp | 137 + test/test_buffer.cpp | 311 + test/test_checking.cpp | 435 + test/test_copy_file.cpp | 233 + test/test_crc32.cpp | 71 + test/test_create_torrent.cpp | 550 + test/test_dht.cpp | 4089 ++++ test/test_dht_storage.cpp | 511 + test/test_direct_dht.cpp | 150 + test/test_dos_blocker.cpp | 105 + test/test_ed25519.cpp | 290 + test/test_enum_net.cpp | 278 + test/test_fast_extension.cpp | 1136 + test/test_fence.cpp | 233 + test/test_ffs.cpp | 139 + test/test_file.cpp | 616 + test/test_file_progress.cpp | 180 + test/test_file_storage.cpp | 1196 + test/test_flags.cpp | 235 + test/test_generate_peer_id.cpp | 56 + test/test_gzip.cpp | 101 + test/test_hash_picker.cpp | 626 + test/test_hasher.cpp | 146 + test/test_hasher512.cpp | 92 + test/test_heterogeneous_queue.cpp | 339 + test/test_http_connection.cpp | 250 + test/test_http_parser.cpp | 914 + test/test_identify_client.cpp | 48 + test/test_info_hash.cpp | 164 + test/test_io.cpp | 291 + test/test_ip_filter.cpp | 260 + test/test_ip_voter.cpp | 223 + test/test_listen_socket.cpp | 538 + test/test_lsd.cpp | 131 + test/test_magnet.cpp | 831 + test/test_merkle.cpp | 1317 + test/test_merkle_tree.cpp | 938 + test/test_mmap.cpp | 119 + test/test_packet_buffer.cpp | 186 + test/test_part_file.cpp | 258 + test/test_pe_crypto.cpp | 163 + test/test_peer_classes.cpp | 151 + test/test_peer_list.cpp | 976 + test/test_peer_priority.cpp | 99 + test/test_piece_picker.cpp | 2809 +++ test/test_primitives.cpp | 256 + test/test_priority.cpp | 713 + test/test_privacy.cpp | 348 + test/test_read_piece.cpp | 161 + test/test_read_resume.cpp | 398 + test/test_receive_buffer.cpp | 258 + test/test_recheck.cpp | 121 + test/test_remap_files.cpp | 225 + test/test_remove_torrent.cpp | 224 + test/test_resolve_links.cpp | 235 + test/test_resume.cpp | 1842 ++ test/test_session.cpp | 582 + test/test_session_params.cpp | 287 + test/test_settings_pack.cpp | 324 + test/test_sha1_hash.cpp | 147 + test/test_similar_torrent.cpp | 347 + test/test_sliding_average.cpp | 118 + test/test_socket_io.cpp | 218 + test/test_span.cpp | 153 + test/test_ssl.cpp | 635 + test/test_stack_allocator.cpp | 142 + test/test_stat_cache.cpp | 88 + test/test_storage.cpp | 1827 ++ test/test_store_buffer.cpp | 186 + test/test_string.cpp | 566 + test/test_tailqueue.cpp | 170 + test/test_threads.cpp | 129 + test/test_time.cpp | 105 + test/test_time_critical.cpp | 61 + test/test_timestamp_history.cpp | 57 + test/test_torrent.cpp | 882 + test/test_torrent_info.cpp | 1512 ++ test/test_torrent_list.cpp | 249 + test/test_torrents/absolute_filename.torrent | 1 + test/test_torrents/backslash_path.torrent | 1 + test/test_torrents/bad_name.torrent | Bin 0 -> 374 bytes test/test_torrents/base.torrent | 1 + test/test_torrents/collection.torrent | 1 + test/test_torrents/collection2.torrent | 1 + test/test_torrents/creation_date.torrent | 1 + test/test_torrents/dht_nodes.torrent | 1 + test/test_torrents/duplicate_files.torrent | 1 + .../test_torrents/duplicate_web_seeds.torrent | 1 + test/test_torrents/empty-files-1.torrent | Bin 0 -> 927 bytes test/test_torrents/empty-files-2.torrent | Bin 0 -> 927 bytes test/test_torrents/empty-files-3.torrent | Bin 0 -> 927 bytes test/test_torrents/empty-files-4.torrent | Bin 0 -> 927 bytes test/test_torrents/empty-files-5.torrent | Bin 0 -> 927 bytes test/test_torrents/empty_httpseed.torrent | 1 + test/test_torrents/empty_path.torrent | 1 + test/test_torrents/empty_path_multi.torrent | 1 + test/test_torrents/hidden_parent_path.torrent | 1 + test/test_torrents/httpseed.torrent | 1 + test/test_torrents/invalid_file_size.torrent | 1 + test/test_torrents/invalid_filename.torrent | 1 + test/test_torrents/invalid_filename2.torrent | 1 + test/test_torrents/invalid_info.torrent | 1 + test/test_torrents/invalid_name.torrent | 1 + test/test_torrents/invalid_name2.torrent | 1 + test/test_torrents/invalid_name3.torrent | 1 + test/test_torrents/invalid_path_list.torrent | 1 + test/test_torrents/invalid_piece_len.torrent | 1 + test/test_torrents/invalid_pieces.torrent | 1 + test/test_torrents/invalid_symlink.torrent | 1 + test/test_torrents/large.torrent | Bin 0 -> 100144 bytes test/test_torrents/long_name.torrent | 1 + test/test_torrents/many_pieces.torrent | 1 + test/test_torrents/missing_path_list.torrent | 1 + test/test_torrents/missing_piece_len.torrent | 1 + test/test_torrents/negative_file_size.torrent | 1 + test/test_torrents/negative_piece_len.torrent | 1 + test/test_torrents/negative_size.torrent | 1 + test/test_torrents/no_creation_date.torrent | 1 + test/test_torrents/no_files.torrent | 1 + test/test_torrents/no_name.torrent | 1 + .../overlapping_symlinks.torrent | Bin 0 -> 6119 bytes test/test_torrents/pad_file.torrent | 1 + test/test_torrents/pad_file_no_path.torrent | 1 + test/test_torrents/parent_path.torrent | 1 + test/test_torrents/sample.torrent | Bin 0 -> 504 bytes test/test_torrents/similar.torrent | 1 + test/test_torrents/similar2.torrent | 1 + test/test_torrents/single_multi_file.torrent | 1 + test/test_torrents/slash_path.torrent | 1 + test/test_torrents/slash_path2.torrent | 1 + test/test_torrents/slash_path3.torrent | 1 + test/test_torrents/string.torrent | 1 + test/test_torrents/symlink1.torrent | 1 + test/test_torrents/symlink2.torrent | 1 + test/test_torrents/symlink_zero_size.torrent | 1 + test/test_torrents/unaligned_pieces.torrent | 1 + test/test_torrents/unordered.torrent | 1 + test/test_torrents/url_list.torrent | 1 + test/test_torrents/url_list2.torrent | 1 + test/test_torrents/url_list3.torrent | 1 + test/test_torrents/url_seed.torrent | 1 + test/test_torrents/url_seed_multi.torrent | 1 + .../url_seed_multi_single_file.torrent | 1 + .../url_seed_multi_space.torrent | 1 + .../url_seed_multi_space_nolist.torrent | 1 + test/test_torrents/v2.torrent | 2 + .../v2_bad_file_alignment.torrent | Bin 0 -> 156905 bytes test/test_torrents/v2_deep_recursion.torrent | 1 + test/test_torrents/v2_hybrid.torrent | Bin 0 -> 91581 bytes .../v2_incomplete_piece_layer.torrent | Bin 0 -> 64532 bytes test/test_torrents/v2_invalid_file.torrent | 2 + .../test_torrents/v2_invalid_filename.torrent | 17 + .../test_torrents/v2_invalid_pad_file.torrent | 17 + .../v2_invalid_piece_layer.torrent | 1 + .../v2_invalid_piece_layer_size.torrent | 33 + .../v2_invalid_root_hash.torrent | Bin 0 -> 3134 bytes test/test_torrents/v2_large_file.torrent | 17 + test/test_torrents/v2_large_offset.torrent | 17 + .../v2_mismatching_metadata.torrent | 17 + ..._missing_file_root_invalid_symlink.torrent | Bin 0 -> 96516 bytes test/test_torrents/v2_multipiece_file.torrent | 17 + test/test_torrents/v2_multiple_files.torrent | Bin 0 -> 96605 bytes test/test_torrents/v2_no_piece_layers.torrent | 1 + test/test_torrents/v2_no_power2_piece.torrent | 1 + .../v2_non_multiple_piece_layer.torrent | 17 + test/test_torrents/v2_only.torrent | 17 + .../test_torrents/v2_overlong_integer.torrent | 2 + .../v2_piece_layer_invalid_file_hash.torrent | 17 + test/test_torrents/v2_piece_size.torrent | 1 + test/test_torrents/v2_symlinks.torrent | Bin 0 -> 3290 bytes test/test_torrents/v2_unordered_files.torrent | 2 + test/test_torrents/v2_zero_root.torrent | Bin 0 -> 767 bytes test/test_torrents/v2_zero_root_small.torrent | Bin 0 -> 214 bytes test/test_torrents/whitespace_url.torrent | 1 + test/test_torrents/zero.torrent | 1 + test/test_torrents/zero2.torrent | 1 + test/test_tracker.cpp | 734 + test/test_transfer.cpp | 403 + test/test_truncate.cpp | 107 + test/test_upnp.cpp | 365 + test/test_url_seed.cpp | 67 + test/test_utf8.cpp | 148 + test/test_utils.cpp | 149 + test/test_utils.hpp | 89 + test/test_utp.cpp | 169 + test/test_web_seed.cpp | 52 + test/test_web_seed_ban.cpp | 62 + test/test_web_seed_chunked.cpp | 62 + test/test_web_seed_http.cpp | 62 + test/test_web_seed_http_pw.cpp | 62 + test/test_web_seed_redirect.cpp | 99 + test/test_web_seed_socks4.cpp | 62 + test/test_web_seed_socks5.cpp | 62 + test/test_web_seed_socks5_no_peers.cpp | 52 + test/test_web_seed_socks5_pw.cpp | 62 + test/test_xml.cpp | 493 + test/udp_tracker.cpp | 260 + test/udp_tracker.hpp | 44 + test/utf8_test.txt | 150 + test/web_seed_suite.cpp | 403 + test/web_seed_suite.hpp | 43 + test/web_server.py | 226 + test/zeroes.gz | Bin 0 -> 538 bytes tools/CMakeLists.txt | 8 + tools/Jamfile | 47 + tools/dht_put.cpp | 430 + tools/dht_sample.cpp | 201 + tools/disk_io_stress_test.cpp | 457 + tools/parse_dht_log.py | 341 + tools/parse_dht_rtt.py | 55 + tools/parse_dht_stats.py | 64 + tools/parse_peer_log.py | 75 + tools/parse_sample.py | 162 + tools/parse_session_stats.py | 650 + tools/parse_utp_log.py | 417 + tools/session_log_alerts.cpp | 72 + 1009 files changed, 342943 insertions(+) create mode 100644 AUTHORS create mode 100644 CMakeLists.txt create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 Jamfile create mode 100644 Jamroot.jam create mode 100644 LICENSE create mode 100644 LibtorrentRasterbarConfig.cmake.in create mode 100644 Makefile create mode 100644 NEWS create mode 100644 README.rst create mode 100644 bindings/CMakeLists.txt create mode 100644 bindings/python/CMakeLists.txt create mode 100644 bindings/python/Jamfile create mode 100755 bindings/python/client.py create mode 100755 bindings/python/make_torrent.py create mode 100644 bindings/python/setup.py create mode 100644 bindings/python/setup.py.cmake.in create mode 100755 bindings/python/simple_client.py create mode 100644 bindings/python/src/alert.cpp create mode 100644 bindings/python/src/boost_python.hpp create mode 100644 bindings/python/src/bytes.hpp create mode 100644 bindings/python/src/converters.cpp create mode 100644 bindings/python/src/create_torrent.cpp create mode 100644 bindings/python/src/datetime.cpp create mode 100644 bindings/python/src/entry.cpp create mode 100644 bindings/python/src/error_code.cpp create mode 100644 bindings/python/src/fingerprint.cpp create mode 100644 bindings/python/src/gil.hpp create mode 100644 bindings/python/src/info_hash.cpp create mode 100644 bindings/python/src/ip_filter.cpp create mode 100644 bindings/python/src/load_torrent.cpp create mode 100644 bindings/python/src/magnet_uri.cpp create mode 100644 bindings/python/src/module.cpp create mode 100644 bindings/python/src/optional.hpp create mode 100644 bindings/python/src/peer_info.cpp create mode 100644 bindings/python/src/session.cpp create mode 100644 bindings/python/src/session_settings.cpp create mode 100644 bindings/python/src/sha1_hash.cpp create mode 100644 bindings/python/src/sha256_hash.cpp create mode 100644 bindings/python/src/string.cpp create mode 100644 bindings/python/src/torrent_handle.cpp create mode 100644 bindings/python/src/torrent_info.cpp create mode 100644 bindings/python/src/torrent_status.cpp create mode 100644 bindings/python/src/utility.cpp create mode 100644 bindings/python/src/version.cpp create mode 100644 cmake/Modules/FindLibGcrypt.cmake create mode 100644 cmake/Modules/GeneratePkgConfig.cmake create mode 100644 cmake/Modules/GeneratePkgConfig/generate-pkg-config.cmake.in create mode 100644 cmake/Modules/GeneratePkgConfig/pkg-config.cmake.in create mode 100644 cmake/Modules/GeneratePkgConfig/target-compile-settings.cmake.in create mode 100644 cmake/Modules/LibtorrentMacros.cmake create mode 100644 cmake/Modules/ucm_flags.cmake create mode 100644 deps/asio-gnutls/Jamfile create mode 100644 deps/asio-gnutls/LICENSE_1_0.txt create mode 100644 deps/asio-gnutls/README.md create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls.hpp create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls/context.hpp create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls/context_base.hpp create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls/error.hpp create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls/host_name_verification.hpp create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls/rfc2818_verification.hpp create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls/stream.hpp create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls/stream_base.hpp create mode 100644 deps/asio-gnutls/include/boost/asio/gnutls/verify_context.hpp create mode 100644 deps/asio-gnutls/test/gnutls/Jamfile.v2 create mode 100644 deps/asio-gnutls/test/gnutls/context.cpp create mode 100644 deps/asio-gnutls/test/gnutls/context_base.cpp create mode 100644 deps/asio-gnutls/test/gnutls/error.cpp create mode 100644 deps/asio-gnutls/test/gnutls/host_name_verification.cpp create mode 100644 deps/asio-gnutls/test/gnutls/rfc2818_verification.cpp create mode 100644 deps/asio-gnutls/test/gnutls/stream.cpp create mode 100644 deps/asio-gnutls/test/gnutls/stream_base.cpp create mode 100644 deps/asio-gnutls/test/unit_test.hpp create mode 100644 deps/try_signal/CMakeLists.txt create mode 100644 deps/try_signal/Jamfile create mode 100644 deps/try_signal/LICENSE create mode 100644 deps/try_signal/README.rst create mode 100644 deps/try_signal/signal_error_code.cpp create mode 100644 deps/try_signal/signal_error_code.hpp create mode 100644 deps/try_signal/try_signal.cpp create mode 100644 deps/try_signal/try_signal.hpp create mode 100644 deps/try_signal/try_signal_mingw.hpp create mode 100644 deps/try_signal/try_signal_msvc.hpp create mode 100644 deps/try_signal/try_signal_posix.hpp create mode 100644 docs/building.html create mode 100644 docs/building.rst create mode 100644 docs/client_test.html create mode 100644 docs/client_test.rst create mode 100644 docs/contributing.html create mode 100644 docs/contributing.rst 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/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-ref.html create mode 100644 docs/features.rst create mode 100644 docs/hacking.html create mode 100644 docs/hacking.rst create mode 100644 docs/header.rst create mode 100644 docs/img/bitcoin.png create mode 100644 docs/img/complete_bit_prefixes.png create mode 100644 docs/img/cwnd.png create mode 100644 docs/img/cwnd_thumb.png create mode 100644 docs/img/delays.png create mode 100644 docs/img/delays_thumb.png create mode 100644 docs/img/hacking.diagram create mode 100644 docs/img/hacking.png create mode 100644 docs/img/hash_distribution.png create mode 100644 docs/img/ip_id_v4.png create mode 100644 docs/img/ip_id_v6.png create mode 100644 docs/img/logo-color-text.png create mode 100644 docs/img/our_delay_base.png create mode 100644 docs/img/our_delay_base_thumb.png create mode 100644 docs/img/pp-acceptance-medium.png create mode 100644 docs/img/read_disk_buffers.diagram create mode 100644 docs/img/read_disk_buffers.png create mode 100644 docs/img/screenshot.png create mode 100644 docs/img/screenshot_thumb.png create mode 100644 docs/img/storage.png create mode 100644 docs/img/troubleshooting.dot create mode 100644 docs/img/troubleshooting.png create mode 100644 docs/img/troubleshooting_thumb.png create mode 100644 docs/img/utp_stack.diagram create mode 100644 docs/img/utp_stack.png create mode 100644 docs/img/write_disk_buffers.diagram create mode 100644 docs/img/write_disk_buffers.png create mode 100644 docs/index.html create mode 100644 docs/index.rst create mode 100644 docs/manual-ref.html create mode 100644 docs/manual-ref.rst create mode 100644 docs/manual.rst 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/reference-Add_Torrent.html create mode 100644 docs/reference-Alerts.html create mode 100644 docs/reference-Bdecoding.html create mode 100644 docs/reference-Bencoding.html create mode 100644 docs/reference-Core.html create mode 100644 docs/reference-Create_Torrents.html create mode 100644 docs/reference-Custom_Storage.html create mode 100644 docs/reference-DHT.html create mode 100644 docs/reference-Error_Codes.html create mode 100644 docs/reference-Filter.html create mode 100644 docs/reference-PeerClass.html create mode 100644 docs/reference-Plugins.html create mode 100644 docs/reference-Resume_Data.html create mode 100644 docs/reference-Session.html create mode 100644 docs/reference-Settings.html create mode 100644 docs/reference-Stats.html create mode 100644 docs/reference-Storage.html create mode 100644 docs/reference-Torrent_Handle.html create mode 100644 docs/reference-Torrent_Info.html create mode 100644 docs/reference-Torrent_Status.html create mode 100644 docs/reference-Trackers.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/security-audit.html create mode 100644 docs/settings.rst create mode 100644 docs/single-page-ref.html create mode 100644 docs/stats_counters.rst create mode 100644 docs/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.html create mode 100644 docs/troubleshooting.rst create mode 100644 docs/tuning-ref.html create mode 100644 docs/tuning.rst create mode 100644 docs/tutorial-ref.html create mode 100644 docs/tutorial-ref.rst create mode 100644 docs/tutorial.rst create mode 100644 docs/udp_tracker_protocol.html create mode 100644 docs/udp_tracker_protocol.rst create mode 100644 docs/upgrade_to_1.2-ref.html create mode 100644 docs/upgrade_to_2.0-ref.html create mode 100644 docs/utp.html create mode 100644 docs/utp.rst create mode 100644 examples/CMakeLists.txt create mode 100644 examples/Jamfile create mode 100644 examples/bt-get.cpp create mode 100644 examples/bt-get2.cpp create mode 100644 examples/bt-get3.cpp create mode 100644 examples/check_files.cpp create mode 100644 examples/client_test.cpp create mode 100644 examples/cmake/FindLibtorrentRasterbar.cmake create mode 100644 examples/connection_tester.cpp create mode 100644 examples/custom_storage.cpp create mode 100644 examples/dump_bdecode.cpp create mode 100644 examples/dump_torrent.cpp create mode 100644 examples/magnet2torrent.cpp create mode 100644 examples/make_torrent.cpp create mode 100644 examples/print.cpp create mode 100644 examples/print.hpp create mode 100644 examples/session_view.cpp create mode 100644 examples/session_view.hpp create mode 100644 examples/simple_client.cpp create mode 100644 examples/stats_counters.cpp create mode 100644 examples/torrent2magnet.cpp create mode 100644 examples/torrent_view.cpp create mode 100644 examples/torrent_view.hpp create mode 100644 examples/upnp_test.cpp create mode 100644 include/libtorrent/add_torrent_params.hpp create mode 100644 include/libtorrent/address.hpp create mode 100644 include/libtorrent/alert.hpp create mode 100644 include/libtorrent/alert_types.hpp create mode 100644 include/libtorrent/announce_entry.hpp create mode 100644 include/libtorrent/assert.hpp create mode 100644 include/libtorrent/aux_/alert_manager.hpp create mode 100644 include/libtorrent/aux_/aligned_storage.hpp create mode 100644 include/libtorrent/aux_/aligned_union.hpp create mode 100644 include/libtorrent/aux_/alloca.hpp create mode 100644 include/libtorrent/aux_/allocating_handler.hpp create mode 100644 include/libtorrent/aux_/announce_entry.hpp create mode 100644 include/libtorrent/aux_/apply_pad_files.hpp create mode 100644 include/libtorrent/aux_/array.hpp create mode 100644 include/libtorrent/aux_/bandwidth_limit.hpp create mode 100644 include/libtorrent/aux_/bandwidth_manager.hpp create mode 100644 include/libtorrent/aux_/bandwidth_queue_entry.hpp create mode 100644 include/libtorrent/aux_/bandwidth_socket.hpp create mode 100644 include/libtorrent/aux_/bind_to_device.hpp create mode 100644 include/libtorrent/aux_/buffer.hpp create mode 100644 include/libtorrent/aux_/byteswap.hpp create mode 100644 include/libtorrent/aux_/chained_buffer.hpp create mode 100644 include/libtorrent/aux_/container_wrapper.hpp create mode 100644 include/libtorrent/aux_/cpuid.hpp create mode 100644 include/libtorrent/aux_/deferred_handler.hpp create mode 100644 include/libtorrent/aux_/deprecated.hpp create mode 100644 include/libtorrent/aux_/deque.hpp create mode 100644 include/libtorrent/aux_/dev_random.hpp create mode 100644 include/libtorrent/aux_/directory.hpp create mode 100644 include/libtorrent/aux_/disable_deprecation_warnings_push.hpp create mode 100644 include/libtorrent/aux_/disable_warnings_pop.hpp create mode 100644 include/libtorrent/aux_/disable_warnings_push.hpp create mode 100644 include/libtorrent/aux_/disk_buffer_pool.hpp create mode 100644 include/libtorrent/aux_/disk_io_thread_pool.hpp create mode 100644 include/libtorrent/aux_/disk_job_fence.hpp create mode 100644 include/libtorrent/aux_/disk_job_pool.hpp create mode 100644 include/libtorrent/aux_/ed25519.hpp create mode 100644 include/libtorrent/aux_/escape_string.hpp create mode 100644 include/libtorrent/aux_/export.hpp create mode 100644 include/libtorrent/aux_/ffs.hpp create mode 100644 include/libtorrent/aux_/file_pointer.hpp create mode 100644 include/libtorrent/aux_/file_progress.hpp create mode 100644 include/libtorrent/aux_/file_view_pool.hpp create mode 100644 include/libtorrent/aux_/generate_peer_id.hpp create mode 100644 include/libtorrent/aux_/has_block.hpp create mode 100644 include/libtorrent/aux_/hasher512.hpp create mode 100644 include/libtorrent/aux_/heterogeneous_queue.hpp create mode 100644 include/libtorrent/aux_/instantiate_connection.hpp create mode 100644 include/libtorrent/aux_/invariant_check.hpp create mode 100644 include/libtorrent/aux_/io.hpp create mode 100644 include/libtorrent/aux_/ip_helpers.hpp create mode 100644 include/libtorrent/aux_/ip_notifier.hpp create mode 100644 include/libtorrent/aux_/keepalive.hpp create mode 100644 include/libtorrent/aux_/listen_socket_handle.hpp create mode 100644 include/libtorrent/aux_/lsd.hpp create mode 100644 include/libtorrent/aux_/merkle.hpp create mode 100644 include/libtorrent/aux_/merkle_tree.hpp create mode 100644 include/libtorrent/aux_/mmap.hpp create mode 100644 include/libtorrent/aux_/mmap_disk_job.hpp create mode 100644 include/libtorrent/aux_/noexcept_movable.hpp create mode 100644 include/libtorrent/aux_/numeric_cast.hpp create mode 100644 include/libtorrent/aux_/open_mode.hpp create mode 100644 include/libtorrent/aux_/packet_buffer.hpp create mode 100644 include/libtorrent/aux_/packet_pool.hpp create mode 100644 include/libtorrent/aux_/path.hpp create mode 100644 include/libtorrent/aux_/polymorphic_socket.hpp create mode 100644 include/libtorrent/aux_/pool.hpp create mode 100644 include/libtorrent/aux_/portmap.hpp create mode 100644 include/libtorrent/aux_/posix_part_file.hpp create mode 100644 include/libtorrent/aux_/posix_storage.hpp create mode 100644 include/libtorrent/aux_/proxy_settings.hpp create mode 100644 include/libtorrent/aux_/range.hpp create mode 100644 include/libtorrent/aux_/receive_buffer.hpp create mode 100644 include/libtorrent/aux_/resolver.hpp create mode 100644 include/libtorrent/aux_/resolver_interface.hpp create mode 100644 include/libtorrent/aux_/route.h create mode 100644 include/libtorrent/aux_/scope_end.hpp create mode 100644 include/libtorrent/aux_/session_call.hpp create mode 100644 include/libtorrent/aux_/session_impl.hpp create mode 100644 include/libtorrent/aux_/session_interface.hpp create mode 100644 include/libtorrent/aux_/session_settings.hpp create mode 100644 include/libtorrent/aux_/session_udp_sockets.hpp create mode 100644 include/libtorrent/aux_/set_socket_buffer.hpp create mode 100644 include/libtorrent/aux_/set_traffic_class.hpp create mode 100644 include/libtorrent/aux_/sha512.hpp create mode 100644 include/libtorrent/aux_/socket_type.hpp create mode 100644 include/libtorrent/aux_/storage_free_list.hpp create mode 100644 include/libtorrent/aux_/storage_utils.hpp create mode 100644 include/libtorrent/aux_/store_buffer.hpp create mode 100644 include/libtorrent/aux_/string_ptr.hpp create mode 100644 include/libtorrent/aux_/strview_less.hpp create mode 100644 include/libtorrent/aux_/suggest_piece.hpp create mode 100644 include/libtorrent/aux_/throw.hpp create mode 100644 include/libtorrent/aux_/time.hpp create mode 100644 include/libtorrent/aux_/timestamp_history.hpp create mode 100644 include/libtorrent/aux_/torrent_impl.hpp create mode 100644 include/libtorrent/aux_/torrent_list.hpp create mode 100644 include/libtorrent/aux_/unique_ptr.hpp create mode 100644 include/libtorrent/aux_/utp_socket_manager.hpp create mode 100644 include/libtorrent/aux_/utp_stream.hpp create mode 100644 include/libtorrent/aux_/vector.hpp create mode 100644 include/libtorrent/aux_/win_cng.hpp create mode 100644 include/libtorrent/aux_/win_crypto_provider.hpp create mode 100644 include/libtorrent/aux_/win_util.hpp create mode 100644 include/libtorrent/aux_/windows.hpp create mode 100644 include/libtorrent/bdecode.hpp create mode 100644 include/libtorrent/bencode.hpp create mode 100644 include/libtorrent/bitfield.hpp create mode 100644 include/libtorrent/bloom_filter.hpp create mode 100644 include/libtorrent/bt_peer_connection.hpp create mode 100644 include/libtorrent/choker.hpp create mode 100644 include/libtorrent/client_data.hpp create mode 100644 include/libtorrent/close_reason.hpp create mode 100644 include/libtorrent/config.hpp create mode 100644 include/libtorrent/copy_ptr.hpp create mode 100644 include/libtorrent/crc32c.hpp create mode 100644 include/libtorrent/create_torrent.hpp create mode 100644 include/libtorrent/deadline_timer.hpp create mode 100644 include/libtorrent/debug.hpp create mode 100644 include/libtorrent/disabled_disk_io.hpp create mode 100644 include/libtorrent/disk_buffer_holder.hpp create mode 100644 include/libtorrent/disk_interface.hpp create mode 100644 include/libtorrent/disk_observer.hpp create mode 100644 include/libtorrent/download_priority.hpp create mode 100644 include/libtorrent/entry.hpp create mode 100644 include/libtorrent/enum_net.hpp create mode 100644 include/libtorrent/error.hpp create mode 100644 include/libtorrent/error_code.hpp create mode 100644 include/libtorrent/extensions.hpp create mode 100644 include/libtorrent/extensions/smart_ban.hpp create mode 100644 include/libtorrent/extensions/ut_metadata.hpp create mode 100644 include/libtorrent/extensions/ut_pex.hpp create mode 100644 include/libtorrent/file.hpp create mode 100644 include/libtorrent/file_storage.hpp create mode 100644 include/libtorrent/fingerprint.hpp create mode 100644 include/libtorrent/flags.hpp create mode 100644 include/libtorrent/fwd.hpp create mode 100644 include/libtorrent/gzip.hpp create mode 100644 include/libtorrent/hash_picker.hpp create mode 100644 include/libtorrent/hasher.hpp create mode 100644 include/libtorrent/hex.hpp create mode 100644 include/libtorrent/http_connection.hpp create mode 100644 include/libtorrent/http_parser.hpp create mode 100644 include/libtorrent/http_seed_connection.hpp create mode 100644 include/libtorrent/http_stream.hpp create mode 100644 include/libtorrent/http_tracker_connection.hpp create mode 100644 include/libtorrent/i2p_stream.hpp create mode 100644 include/libtorrent/identify_client.hpp create mode 100644 include/libtorrent/index_range.hpp create mode 100644 include/libtorrent/info_hash.hpp create mode 100644 include/libtorrent/io.hpp create mode 100644 include/libtorrent/io_context.hpp create mode 100644 include/libtorrent/io_service.hpp create mode 100644 include/libtorrent/ip_filter.hpp create mode 100644 include/libtorrent/ip_voter.hpp create mode 100644 include/libtorrent/kademlia/announce_flags.hpp create mode 100644 include/libtorrent/kademlia/dht_observer.hpp create mode 100644 include/libtorrent/kademlia/dht_settings.hpp create mode 100644 include/libtorrent/kademlia/dht_state.hpp create mode 100644 include/libtorrent/kademlia/dht_storage.hpp create mode 100644 include/libtorrent/kademlia/dht_tracker.hpp create mode 100644 include/libtorrent/kademlia/direct_request.hpp create mode 100644 include/libtorrent/kademlia/dos_blocker.hpp create mode 100644 include/libtorrent/kademlia/ed25519.hpp create mode 100644 include/libtorrent/kademlia/find_data.hpp create mode 100644 include/libtorrent/kademlia/get_item.hpp create mode 100644 include/libtorrent/kademlia/get_peers.hpp create mode 100644 include/libtorrent/kademlia/io.hpp create mode 100644 include/libtorrent/kademlia/item.hpp create mode 100644 include/libtorrent/kademlia/msg.hpp create mode 100644 include/libtorrent/kademlia/node.hpp create mode 100644 include/libtorrent/kademlia/node_entry.hpp create mode 100644 include/libtorrent/kademlia/node_id.hpp create mode 100644 include/libtorrent/kademlia/observer.hpp create mode 100644 include/libtorrent/kademlia/put_data.hpp create mode 100644 include/libtorrent/kademlia/refresh.hpp create mode 100644 include/libtorrent/kademlia/routing_table.hpp create mode 100644 include/libtorrent/kademlia/rpc_manager.hpp create mode 100644 include/libtorrent/kademlia/sample_infohashes.hpp create mode 100644 include/libtorrent/kademlia/traversal_algorithm.hpp create mode 100644 include/libtorrent/kademlia/types.hpp create mode 100644 include/libtorrent/libtorrent.hpp create mode 100644 include/libtorrent/link.hpp create mode 100644 include/libtorrent/load_torrent.hpp create mode 100644 include/libtorrent/lsd.hpp create mode 100644 include/libtorrent/magnet_uri.hpp create mode 100644 include/libtorrent/mmap_disk_io.hpp create mode 100644 include/libtorrent/mmap_storage.hpp create mode 100644 include/libtorrent/natpmp.hpp create mode 100644 include/libtorrent/netlink.hpp create mode 100644 include/libtorrent/operations.hpp create mode 100644 include/libtorrent/optional.hpp create mode 100644 include/libtorrent/parse_url.hpp create mode 100644 include/libtorrent/part_file.hpp create mode 100644 include/libtorrent/pe_crypto.hpp create mode 100644 include/libtorrent/peer.hpp create mode 100644 include/libtorrent/peer_class.hpp create mode 100644 include/libtorrent/peer_class_set.hpp create mode 100644 include/libtorrent/peer_class_type_filter.hpp create mode 100644 include/libtorrent/peer_connection.hpp create mode 100644 include/libtorrent/peer_connection_handle.hpp create mode 100644 include/libtorrent/peer_connection_interface.hpp create mode 100644 include/libtorrent/peer_id.hpp create mode 100644 include/libtorrent/peer_info.hpp create mode 100644 include/libtorrent/peer_list.hpp create mode 100644 include/libtorrent/peer_request.hpp create mode 100644 include/libtorrent/performance_counters.hpp create mode 100644 include/libtorrent/pex_flags.hpp create mode 100644 include/libtorrent/piece_block.hpp create mode 100644 include/libtorrent/piece_block_progress.hpp create mode 100644 include/libtorrent/piece_picker.hpp create mode 100644 include/libtorrent/platform_util.hpp create mode 100644 include/libtorrent/portmap.hpp create mode 100644 include/libtorrent/posix_disk_io.hpp create mode 100644 include/libtorrent/proxy_base.hpp create mode 100644 include/libtorrent/puff.hpp create mode 100644 include/libtorrent/random.hpp create mode 100644 include/libtorrent/read_resume_data.hpp create mode 100644 include/libtorrent/request_blocks.hpp create mode 100644 include/libtorrent/resolve_links.hpp create mode 100644 include/libtorrent/session.hpp create mode 100644 include/libtorrent/session_handle.hpp create mode 100644 include/libtorrent/session_params.hpp create mode 100644 include/libtorrent/session_settings.hpp create mode 100644 include/libtorrent/session_stats.hpp create mode 100644 include/libtorrent/session_status.hpp create mode 100644 include/libtorrent/session_types.hpp create mode 100644 include/libtorrent/settings_pack.hpp create mode 100644 include/libtorrent/sha1.hpp create mode 100644 include/libtorrent/sha1_hash.hpp create mode 100644 include/libtorrent/sha256.hpp create mode 100644 include/libtorrent/sliding_average.hpp create mode 100644 include/libtorrent/socket.hpp create mode 100644 include/libtorrent/socket_io.hpp create mode 100644 include/libtorrent/socket_type.hpp create mode 100644 include/libtorrent/socks5_stream.hpp create mode 100644 include/libtorrent/span.hpp create mode 100644 include/libtorrent/ssl.hpp create mode 100644 include/libtorrent/ssl_stream.hpp create mode 100644 include/libtorrent/stack_allocator.hpp create mode 100644 include/libtorrent/stat.hpp create mode 100644 include/libtorrent/stat_cache.hpp create mode 100644 include/libtorrent/storage.hpp create mode 100644 include/libtorrent/storage_defs.hpp create mode 100644 include/libtorrent/string_util.hpp create mode 100644 include/libtorrent/string_view.hpp create mode 100644 include/libtorrent/tailqueue.hpp create mode 100644 include/libtorrent/time.hpp create mode 100644 include/libtorrent/torrent.hpp create mode 100644 include/libtorrent/torrent_flags.hpp create mode 100644 include/libtorrent/torrent_handle.hpp create mode 100644 include/libtorrent/torrent_info.hpp create mode 100644 include/libtorrent/torrent_peer.hpp create mode 100644 include/libtorrent/torrent_peer_allocator.hpp create mode 100644 include/libtorrent/torrent_status.hpp create mode 100644 include/libtorrent/tracker_manager.hpp create mode 100644 include/libtorrent/truncate.hpp create mode 100644 include/libtorrent/udp_socket.hpp create mode 100644 include/libtorrent/udp_tracker_connection.hpp create mode 100644 include/libtorrent/union_endpoint.hpp create mode 100644 include/libtorrent/units.hpp create mode 100644 include/libtorrent/upnp.hpp create mode 100644 include/libtorrent/utf8.hpp create mode 100644 include/libtorrent/vector_utils.hpp create mode 100644 include/libtorrent/version.hpp create mode 100644 include/libtorrent/web_connection_base.hpp create mode 100644 include/libtorrent/web_peer_connection.hpp create mode 100644 include/libtorrent/write_resume_data.hpp create mode 100644 include/libtorrent/xml_parse.hpp create mode 100644 project-config.jam create mode 100644 setup.py create mode 100644 simulation/Jamfile create mode 100644 simulation/create_torrent.cpp create mode 100644 simulation/create_torrent.hpp create mode 100644 simulation/disk_io.cpp create mode 100644 simulation/disk_io.hpp create mode 100644 simulation/fake_peer.hpp create mode 100644 simulation/libsimulator/CMakeLists.txt create mode 100644 simulation/libsimulator/Jamfile create mode 100644 simulation/libsimulator/Jamroot.jam create mode 100644 simulation/libsimulator/LICENSE create mode 100644 simulation/libsimulator/README.rst create mode 100644 simulation/libsimulator/include/simulator/chrono.hpp create mode 100644 simulation/libsimulator/include/simulator/config.hpp create mode 100644 simulation/libsimulator/include/simulator/function.hpp create mode 100644 simulation/libsimulator/include/simulator/handler_allocator.hpp create mode 100644 simulation/libsimulator/include/simulator/http_proxy.hpp create mode 100644 simulation/libsimulator/include/simulator/http_server.hpp create mode 100644 simulation/libsimulator/include/simulator/noexcept_movable.hpp create mode 100644 simulation/libsimulator/include/simulator/packet.hpp create mode 100644 simulation/libsimulator/include/simulator/pcap.hpp create mode 100644 simulation/libsimulator/include/simulator/pop_warnings.hpp create mode 100644 simulation/libsimulator/include/simulator/push_warnings.hpp create mode 100644 simulation/libsimulator/include/simulator/queue.hpp create mode 100644 simulation/libsimulator/include/simulator/simulator.hpp create mode 100644 simulation/libsimulator/include/simulator/sink.hpp create mode 100644 simulation/libsimulator/include/simulator/sink_forwarder.hpp create mode 100644 simulation/libsimulator/include/simulator/socks_server.hpp create mode 100644 simulation/libsimulator/include/simulator/utils.hpp create mode 100644 simulation/libsimulator/src/acceptor.cpp create mode 100644 simulation/libsimulator/src/default_config.cpp create mode 100644 simulation/libsimulator/src/high_resolution_clock.cpp create mode 100644 simulation/libsimulator/src/high_resolution_timer.cpp create mode 100644 simulation/libsimulator/src/http_proxy.cpp create mode 100644 simulation/libsimulator/src/http_server.cpp create mode 100644 simulation/libsimulator/src/io_service.cpp create mode 100644 simulation/libsimulator/src/pcap.cpp create mode 100644 simulation/libsimulator/src/queue.cpp create mode 100644 simulation/libsimulator/src/resolver.cpp create mode 100644 simulation/libsimulator/src/simulation.cpp create mode 100644 simulation/libsimulator/src/simulator.cpp create mode 100644 simulation/libsimulator/src/sink_forwarder.cpp create mode 100644 simulation/libsimulator/src/socks_server.cpp create mode 100644 simulation/libsimulator/src/tcp_socket.cpp create mode 100644 simulation/libsimulator/src/udp_socket.cpp create mode 100644 simulation/make_proxy_settings.hpp create mode 100644 simulation/setup_dht.cpp create mode 100644 simulation/setup_dht.hpp create mode 100644 simulation/setup_swarm.cpp create mode 100644 simulation/setup_swarm.hpp create mode 100644 simulation/test_auto_manage.cpp create mode 100644 simulation/test_checking.cpp create mode 100644 simulation/test_dht.cpp create mode 100644 simulation/test_dht_bootstrap.cpp create mode 100644 simulation/test_dht_rate_limit.cpp create mode 100644 simulation/test_dht_storage.cpp create mode 100644 simulation/test_error_handling.cpp create mode 100644 simulation/test_fast_extensions.cpp create mode 100644 simulation/test_http_connection.cpp create mode 100644 simulation/test_ip_filter.cpp create mode 100644 simulation/test_metadata_extension.cpp create mode 100644 simulation/test_optimistic_unchoke.cpp create mode 100644 simulation/test_pause.cpp create mode 100644 simulation/test_pe_crypto.cpp create mode 100644 simulation/test_peer_connection.cpp create mode 100644 simulation/test_save_resume.cpp create mode 100644 simulation/test_session.cpp create mode 100644 simulation/test_socks5.cpp create mode 100644 simulation/test_super_seeding.cpp create mode 100644 simulation/test_swarm.cpp create mode 100644 simulation/test_thread_pool.cpp create mode 100644 simulation/test_torrent_status.cpp create mode 100644 simulation/test_tracker.cpp create mode 100644 simulation/test_transfer.cpp create mode 100644 simulation/test_transfer_matrix.cpp create mode 100644 simulation/test_utp.cpp create mode 100644 simulation/test_web_seed.cpp create mode 100644 simulation/transfer_sim.cpp create mode 100644 simulation/transfer_sim.hpp create mode 100644 simulation/utils.cpp create mode 100644 simulation/utils.hpp create mode 100644 src/add_torrent_params.cpp create mode 100644 src/alert.cpp create mode 100644 src/alert_manager.cpp create mode 100644 src/announce_entry.cpp create mode 100644 src/assert.cpp create mode 100644 src/bandwidth_limit.cpp create mode 100644 src/bandwidth_manager.cpp create mode 100644 src/bandwidth_queue_entry.cpp create mode 100644 src/bdecode.cpp create mode 100644 src/bitfield.cpp create mode 100644 src/bloom_filter.cpp create mode 100644 src/bt_peer_connection.cpp create mode 100644 src/chained_buffer.cpp create mode 100644 src/choker.cpp create mode 100644 src/close_reason.cpp create mode 100644 src/copy_file.cpp create mode 100644 src/cpuid.cpp create mode 100644 src/crc32c.cpp create mode 100644 src/create_torrent.cpp create mode 100644 src/directory.cpp create mode 100644 src/disabled_disk_io.cpp create mode 100644 src/disk_buffer_holder.cpp create mode 100644 src/disk_buffer_pool.cpp create mode 100644 src/disk_interface.cpp create mode 100644 src/disk_io_thread_pool.cpp create mode 100644 src/disk_job_fence.cpp create mode 100644 src/disk_job_pool.cpp create mode 100644 src/ed25519/LICENSE create mode 100644 src/ed25519/add_scalar.cpp create mode 100644 src/ed25519/fe.cpp create mode 100644 src/ed25519/fe.h create mode 100644 src/ed25519/fixedint.h create mode 100644 src/ed25519/ge.cpp create mode 100644 src/ed25519/ge.h create mode 100644 src/ed25519/hasher512.cpp create mode 100644 src/ed25519/key_exchange.cpp create mode 100644 src/ed25519/keypair.cpp create mode 100644 src/ed25519/precomp_data.h create mode 100644 src/ed25519/sc.cpp create mode 100644 src/ed25519/sc.h create mode 100644 src/ed25519/sha512.cpp create mode 100644 src/ed25519/sign.cpp create mode 100644 src/ed25519/verify.cpp create mode 100644 src/entry.cpp create mode 100644 src/enum_net.cpp create mode 100644 src/error_code.cpp create mode 100644 src/escape_string.cpp create mode 100644 src/ffs.cpp create mode 100644 src/file.cpp create mode 100644 src/file_progress.cpp create mode 100644 src/file_storage.cpp create mode 100644 src/file_view_pool.cpp create mode 100644 src/fingerprint.cpp create mode 100644 src/generate_peer_id.cpp create mode 100644 src/gzip.cpp create mode 100644 src/hash_picker.cpp create mode 100644 src/hasher.cpp create mode 100644 src/hex.cpp create mode 100644 src/http_connection.cpp create mode 100644 src/http_parser.cpp create mode 100644 src/http_seed_connection.cpp create mode 100644 src/http_tracker_connection.cpp create mode 100644 src/i2p_stream.cpp create mode 100644 src/identify_client.cpp create mode 100644 src/instantiate_connection.cpp create mode 100644 src/ip_filter.cpp create mode 100644 src/ip_helpers.cpp create mode 100644 src/ip_notifier.cpp create mode 100644 src/ip_voter.cpp create mode 100644 src/kademlia/dht_settings.cpp create mode 100644 src/kademlia/dht_state.cpp create mode 100644 src/kademlia/dht_storage.cpp create mode 100644 src/kademlia/dht_tracker.cpp create mode 100644 src/kademlia/dos_blocker.cpp create mode 100644 src/kademlia/ed25519.cpp create mode 100644 src/kademlia/find_data.cpp create mode 100644 src/kademlia/get_item.cpp create mode 100644 src/kademlia/get_peers.cpp create mode 100644 src/kademlia/item.cpp create mode 100644 src/kademlia/msg.cpp create mode 100644 src/kademlia/node.cpp create mode 100644 src/kademlia/node_entry.cpp create mode 100644 src/kademlia/node_id.cpp create mode 100644 src/kademlia/put_data.cpp create mode 100644 src/kademlia/refresh.cpp create mode 100644 src/kademlia/routing_table.cpp create mode 100644 src/kademlia/rpc_manager.cpp create mode 100644 src/kademlia/sample_infohashes.cpp create mode 100644 src/kademlia/traversal_algorithm.cpp create mode 100644 src/listen_socket_handle.cpp create mode 100644 src/load_torrent.cpp create mode 100644 src/lsd.cpp create mode 100644 src/magnet_uri.cpp create mode 100644 src/merkle.cpp create mode 100644 src/merkle_tree.cpp create mode 100644 src/mmap.cpp create mode 100644 src/mmap_disk_io.cpp create mode 100644 src/mmap_disk_job.cpp create mode 100644 src/mmap_storage.cpp create mode 100644 src/natpmp.cpp create mode 100644 src/packet_buffer.cpp create mode 100644 src/parse_url.cpp create mode 100644 src/part_file.cpp create mode 100644 src/path.cpp create mode 100644 src/pe_crypto.cpp create mode 100644 src/peer_class.cpp create mode 100644 src/peer_class_set.cpp create mode 100644 src/peer_connection.cpp create mode 100644 src/peer_connection_handle.cpp create mode 100644 src/peer_info.cpp create mode 100644 src/peer_list.cpp create mode 100644 src/performance_counters.cpp create mode 100644 src/piece_picker.cpp create mode 100644 src/platform_util.cpp create mode 100644 src/posix_disk_io.cpp create mode 100644 src/posix_part_file.cpp create mode 100644 src/posix_storage.cpp create mode 100644 src/proxy_base.cpp create mode 100644 src/proxy_settings.cpp create mode 100644 src/puff.cpp create mode 100644 src/random.cpp create mode 100644 src/read_resume_data.cpp create mode 100644 src/receive_buffer.cpp create mode 100644 src/request_blocks.cpp create mode 100644 src/resolve_links.cpp create mode 100644 src/resolver.cpp create mode 100644 src/session.cpp create mode 100644 src/session_call.cpp create mode 100644 src/session_handle.cpp create mode 100644 src/session_impl.cpp create mode 100644 src/session_params.cpp create mode 100644 src/session_settings.cpp create mode 100644 src/session_stats.cpp create mode 100644 src/settings_pack.cpp create mode 100644 src/sha1.cpp create mode 100644 src/sha1_hash.cpp create mode 100644 src/sha256.cpp create mode 100644 src/smart_ban.cpp create mode 100644 src/socket_io.cpp create mode 100644 src/socket_type.cpp create mode 100644 src/socks5_stream.cpp create mode 100644 src/ssl.cpp create mode 100644 src/stack_allocator.cpp create mode 100644 src/stat.cpp create mode 100644 src/stat_cache.cpp create mode 100644 src/storage_utils.cpp create mode 100644 src/string_util.cpp create mode 100644 src/time.cpp create mode 100644 src/timestamp_history.cpp create mode 100644 src/torrent.cpp create mode 100644 src/torrent_handle.cpp create mode 100644 src/torrent_info.cpp create mode 100644 src/torrent_peer.cpp create mode 100644 src/torrent_peer_allocator.cpp create mode 100644 src/torrent_status.cpp create mode 100644 src/tracker_manager.cpp create mode 100644 src/truncate.cpp create mode 100644 src/udp_socket.cpp create mode 100644 src/udp_tracker_connection.cpp create mode 100644 src/upnp.cpp create mode 100644 src/ut_metadata.cpp create mode 100644 src/ut_pex.cpp create mode 100644 src/utf8.cpp create mode 100644 src/utp_socket_manager.cpp create mode 100644 src/utp_stream.cpp create mode 100644 src/version.cpp create mode 100644 src/web_connection_base.cpp create mode 100644 src/web_peer_connection.cpp create mode 100644 src/write_resume_data.cpp create mode 100644 src/xml_parse.cpp create mode 100644 test/CMakeLists.txt create mode 100644 test/Jamfile create mode 100644 test/bittorrent_peer.cpp create mode 100644 test/bittorrent_peer.hpp create mode 100644 test/broadcast_socket.cpp create mode 100644 test/broadcast_socket.hpp create mode 100644 test/corrupt.gz create mode 100644 test/dht_server.cpp create mode 100644 test/dht_server.hpp create mode 100644 test/enum_if.cpp create mode 100755 test/http_proxy.py create mode 100644 test/invalid1.gz create mode 100644 test/main.cpp create mode 100644 test/make_torrent.cpp create mode 100644 test/make_torrent.hpp create mode 100644 test/mutable_test_torrents/test1.torrent create mode 100644 test/mutable_test_torrents/test1_pad_files.torrent create mode 100644 test/mutable_test_torrents/test1_single.torrent create mode 100644 test/mutable_test_torrents/test1_single_padded.torrent create mode 100644 test/mutable_test_torrents/test2.torrent create mode 100644 test/mutable_test_torrents/test2_pad_files.torrent create mode 100644 test/mutable_test_torrents/test3.torrent create mode 100644 test/mutable_test_torrents/test3_pad_files.torrent create mode 100644 test/peer_server.cpp create mode 100644 test/peer_server.hpp create mode 100644 test/print_alerts.cpp create mode 100644 test/print_alerts.hpp create mode 100644 test/root1.xml create mode 100644 test/root2.xml create mode 100644 test/root3.xml create mode 100644 test/settings.cpp create mode 100644 test/settings.hpp create mode 100644 test/setup_transfer.cpp create mode 100644 test/setup_transfer.hpp create mode 100755 test/socks.py create mode 100644 test/ssl/cert_request.pem create mode 100644 test/ssl/dhparams.pem create mode 100644 test/ssl/invalid_peer_certificate.pem create mode 100644 test/ssl/invalid_peer_private_key.pem create mode 100644 test/ssl/peer_certificate.pem create mode 100644 test/ssl/peer_private_key.pem create mode 100755 test/ssl/regenerate_test_certificate.sh create mode 100644 test/ssl/root_ca_cert.pem create mode 100644 test/ssl/root_ca_private.pem create mode 100644 test/ssl/server.pem create mode 100644 test/swarm_suite.cpp create mode 100644 test/swarm_suite.hpp create mode 100644 test/test.cpp create mode 100644 test/test.hpp create mode 100644 test/test_add_torrent.cpp create mode 100644 test/test_alert_manager.cpp create mode 100644 test/test_alert_types.cpp create mode 100644 test/test_alloca.cpp create mode 100644 test/test_apply_pad.cpp create mode 100644 test/test_auto_unchoke.cpp create mode 100644 test/test_bandwidth_limiter.cpp create mode 100644 test/test_bdecode.cpp create mode 100644 test/test_bencoding.cpp create mode 100644 test/test_bitfield.cpp create mode 100644 test/test_bloom_filter.cpp create mode 100644 test/test_buffer.cpp create mode 100644 test/test_checking.cpp create mode 100644 test/test_copy_file.cpp create mode 100644 test/test_crc32.cpp create mode 100644 test/test_create_torrent.cpp create mode 100644 test/test_dht.cpp create mode 100644 test/test_dht_storage.cpp create mode 100644 test/test_direct_dht.cpp create mode 100644 test/test_dos_blocker.cpp create mode 100644 test/test_ed25519.cpp create mode 100644 test/test_enum_net.cpp create mode 100644 test/test_fast_extension.cpp create mode 100644 test/test_fence.cpp create mode 100644 test/test_ffs.cpp create mode 100644 test/test_file.cpp create mode 100644 test/test_file_progress.cpp create mode 100644 test/test_file_storage.cpp create mode 100644 test/test_flags.cpp create mode 100644 test/test_generate_peer_id.cpp create mode 100644 test/test_gzip.cpp create mode 100644 test/test_hash_picker.cpp create mode 100644 test/test_hasher.cpp create mode 100644 test/test_hasher512.cpp create mode 100644 test/test_heterogeneous_queue.cpp create mode 100644 test/test_http_connection.cpp create mode 100644 test/test_http_parser.cpp create mode 100644 test/test_identify_client.cpp create mode 100644 test/test_info_hash.cpp create mode 100644 test/test_io.cpp create mode 100644 test/test_ip_filter.cpp create mode 100644 test/test_ip_voter.cpp create mode 100644 test/test_listen_socket.cpp create mode 100644 test/test_lsd.cpp create mode 100644 test/test_magnet.cpp create mode 100644 test/test_merkle.cpp create mode 100644 test/test_merkle_tree.cpp create mode 100644 test/test_mmap.cpp create mode 100644 test/test_packet_buffer.cpp create mode 100644 test/test_part_file.cpp create mode 100644 test/test_pe_crypto.cpp create mode 100644 test/test_peer_classes.cpp create mode 100644 test/test_peer_list.cpp create mode 100644 test/test_peer_priority.cpp create mode 100644 test/test_piece_picker.cpp create mode 100644 test/test_primitives.cpp create mode 100644 test/test_priority.cpp create mode 100644 test/test_privacy.cpp create mode 100644 test/test_read_piece.cpp create mode 100644 test/test_read_resume.cpp create mode 100644 test/test_receive_buffer.cpp create mode 100644 test/test_recheck.cpp create mode 100644 test/test_remap_files.cpp create mode 100644 test/test_remove_torrent.cpp create mode 100644 test/test_resolve_links.cpp create mode 100644 test/test_resume.cpp create mode 100644 test/test_session.cpp create mode 100644 test/test_session_params.cpp create mode 100644 test/test_settings_pack.cpp create mode 100644 test/test_sha1_hash.cpp create mode 100644 test/test_similar_torrent.cpp create mode 100644 test/test_sliding_average.cpp create mode 100644 test/test_socket_io.cpp create mode 100644 test/test_span.cpp create mode 100644 test/test_ssl.cpp create mode 100644 test/test_stack_allocator.cpp create mode 100644 test/test_stat_cache.cpp create mode 100644 test/test_storage.cpp create mode 100644 test/test_store_buffer.cpp create mode 100644 test/test_string.cpp create mode 100644 test/test_tailqueue.cpp create mode 100644 test/test_threads.cpp create mode 100644 test/test_time.cpp create mode 100644 test/test_time_critical.cpp create mode 100644 test/test_timestamp_history.cpp create mode 100644 test/test_torrent.cpp create mode 100644 test/test_torrent_info.cpp create mode 100644 test/test_torrent_list.cpp create mode 100644 test/test_torrents/absolute_filename.torrent create mode 100644 test/test_torrents/backslash_path.torrent create mode 100644 test/test_torrents/bad_name.torrent create mode 100644 test/test_torrents/base.torrent create mode 100644 test/test_torrents/collection.torrent create mode 100644 test/test_torrents/collection2.torrent create mode 100644 test/test_torrents/creation_date.torrent create mode 100644 test/test_torrents/dht_nodes.torrent create mode 100644 test/test_torrents/duplicate_files.torrent create mode 100644 test/test_torrents/duplicate_web_seeds.torrent create mode 100644 test/test_torrents/empty-files-1.torrent create mode 100644 test/test_torrents/empty-files-2.torrent create mode 100644 test/test_torrents/empty-files-3.torrent create mode 100644 test/test_torrents/empty-files-4.torrent create mode 100644 test/test_torrents/empty-files-5.torrent create mode 100644 test/test_torrents/empty_httpseed.torrent create mode 100644 test/test_torrents/empty_path.torrent create mode 100644 test/test_torrents/empty_path_multi.torrent create mode 100644 test/test_torrents/hidden_parent_path.torrent create mode 100644 test/test_torrents/httpseed.torrent create mode 100644 test/test_torrents/invalid_file_size.torrent create mode 100644 test/test_torrents/invalid_filename.torrent create mode 100644 test/test_torrents/invalid_filename2.torrent create mode 100644 test/test_torrents/invalid_info.torrent create mode 100644 test/test_torrents/invalid_name.torrent create mode 100644 test/test_torrents/invalid_name2.torrent create mode 100644 test/test_torrents/invalid_name3.torrent create mode 100644 test/test_torrents/invalid_path_list.torrent create mode 100644 test/test_torrents/invalid_piece_len.torrent create mode 100644 test/test_torrents/invalid_pieces.torrent create mode 100644 test/test_torrents/invalid_symlink.torrent create mode 100644 test/test_torrents/large.torrent create mode 100644 test/test_torrents/long_name.torrent create mode 100644 test/test_torrents/many_pieces.torrent create mode 100644 test/test_torrents/missing_path_list.torrent create mode 100644 test/test_torrents/missing_piece_len.torrent create mode 100644 test/test_torrents/negative_file_size.torrent create mode 100644 test/test_torrents/negative_piece_len.torrent create mode 100644 test/test_torrents/negative_size.torrent create mode 100644 test/test_torrents/no_creation_date.torrent create mode 100644 test/test_torrents/no_files.torrent create mode 100644 test/test_torrents/no_name.torrent create mode 100644 test/test_torrents/overlapping_symlinks.torrent create mode 100644 test/test_torrents/pad_file.torrent create mode 100644 test/test_torrents/pad_file_no_path.torrent create mode 100644 test/test_torrents/parent_path.torrent create mode 100644 test/test_torrents/sample.torrent create mode 100644 test/test_torrents/similar.torrent create mode 100644 test/test_torrents/similar2.torrent create mode 100644 test/test_torrents/single_multi_file.torrent create mode 100644 test/test_torrents/slash_path.torrent create mode 100644 test/test_torrents/slash_path2.torrent create mode 100644 test/test_torrents/slash_path3.torrent create mode 100644 test/test_torrents/string.torrent create mode 100644 test/test_torrents/symlink1.torrent create mode 100755 test/test_torrents/symlink2.torrent create mode 100644 test/test_torrents/symlink_zero_size.torrent create mode 100644 test/test_torrents/unaligned_pieces.torrent create mode 100644 test/test_torrents/unordered.torrent create mode 100644 test/test_torrents/url_list.torrent create mode 100644 test/test_torrents/url_list2.torrent create mode 100644 test/test_torrents/url_list3.torrent create mode 100644 test/test_torrents/url_seed.torrent create mode 100644 test/test_torrents/url_seed_multi.torrent create mode 100644 test/test_torrents/url_seed_multi_single_file.torrent create mode 100644 test/test_torrents/url_seed_multi_space.torrent create mode 100644 test/test_torrents/url_seed_multi_space_nolist.torrent create mode 100644 test/test_torrents/v2.torrent create mode 100644 test/test_torrents/v2_bad_file_alignment.torrent create mode 100644 test/test_torrents/v2_deep_recursion.torrent create mode 100644 test/test_torrents/v2_hybrid.torrent create mode 100644 test/test_torrents/v2_incomplete_piece_layer.torrent create mode 100644 test/test_torrents/v2_invalid_file.torrent create mode 100644 test/test_torrents/v2_invalid_filename.torrent create mode 100644 test/test_torrents/v2_invalid_pad_file.torrent create mode 100644 test/test_torrents/v2_invalid_piece_layer.torrent create mode 100644 test/test_torrents/v2_invalid_piece_layer_size.torrent create mode 100755 test/test_torrents/v2_invalid_root_hash.torrent create mode 100644 test/test_torrents/v2_large_file.torrent create mode 100644 test/test_torrents/v2_large_offset.torrent create mode 100644 test/test_torrents/v2_mismatching_metadata.torrent create mode 100644 test/test_torrents/v2_missing_file_root_invalid_symlink.torrent create mode 100644 test/test_torrents/v2_multipiece_file.torrent create mode 100644 test/test_torrents/v2_multiple_files.torrent create mode 100644 test/test_torrents/v2_no_piece_layers.torrent create mode 100644 test/test_torrents/v2_no_power2_piece.torrent create mode 100644 test/test_torrents/v2_non_multiple_piece_layer.torrent create mode 100644 test/test_torrents/v2_only.torrent create mode 100644 test/test_torrents/v2_overlong_integer.torrent create mode 100644 test/test_torrents/v2_piece_layer_invalid_file_hash.torrent create mode 100644 test/test_torrents/v2_piece_size.torrent create mode 100644 test/test_torrents/v2_symlinks.torrent create mode 100644 test/test_torrents/v2_unordered_files.torrent create mode 100644 test/test_torrents/v2_zero_root.torrent create mode 100644 test/test_torrents/v2_zero_root_small.torrent create mode 100644 test/test_torrents/whitespace_url.torrent create mode 100644 test/test_torrents/zero.torrent create mode 100644 test/test_torrents/zero2.torrent create mode 100644 test/test_tracker.cpp create mode 100644 test/test_transfer.cpp create mode 100644 test/test_truncate.cpp create mode 100644 test/test_upnp.cpp create mode 100644 test/test_url_seed.cpp create mode 100644 test/test_utf8.cpp create mode 100644 test/test_utils.cpp create mode 100644 test/test_utils.hpp create mode 100644 test/test_utp.cpp create mode 100644 test/test_web_seed.cpp create mode 100644 test/test_web_seed_ban.cpp create mode 100644 test/test_web_seed_chunked.cpp create mode 100644 test/test_web_seed_http.cpp create mode 100644 test/test_web_seed_http_pw.cpp create mode 100644 test/test_web_seed_redirect.cpp create mode 100644 test/test_web_seed_socks4.cpp create mode 100644 test/test_web_seed_socks5.cpp create mode 100644 test/test_web_seed_socks5_no_peers.cpp create mode 100644 test/test_web_seed_socks5_pw.cpp create mode 100644 test/test_xml.cpp create mode 100644 test/udp_tracker.cpp create mode 100644 test/udp_tracker.hpp create mode 100644 test/utf8_test.txt create mode 100644 test/web_seed_suite.cpp create mode 100644 test/web_seed_suite.hpp create mode 100644 test/web_server.py create mode 100644 test/zeroes.gz create mode 100644 tools/CMakeLists.txt create mode 100644 tools/Jamfile create mode 100644 tools/dht_put.cpp create mode 100644 tools/dht_sample.cpp create mode 100644 tools/disk_io_stress_test.cpp create mode 100755 tools/parse_dht_log.py create mode 100755 tools/parse_dht_rtt.py create mode 100755 tools/parse_dht_stats.py create mode 100755 tools/parse_peer_log.py create mode 100755 tools/parse_sample.py create mode 100755 tools/parse_session_stats.py create mode 100755 tools/parse_utp_log.py create mode 100644 tools/session_log_alerts.cpp diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..c8a2154 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,25 @@ +Written by Arvid Norberg. Copyright (c) 2003-2021 + +Contributions by: +Andrei Kurushin +Steven Siloti +Alden Torres +Thomas Fischer +Massaroddel +Tianhao Qiu. +Shyam +Magnus Jonsson +Daniel Wallin +Cory Nelson +Stas Khirman +Ryan Norton +Andrew Resch + +Thanks to (github user) nervoir for bug reports + +Thanks to Reimond Retz for bugfixes, suggestions and testing + +Thanks to University of Umeå for providing development and test hardware. + +Project is hosted by Github + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5cb81ab --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,985 @@ +cmake_minimum_required(VERSION 3.16.0 FATAL_ERROR) # Configurable policies: <= CMP0097 + +cmake_policy(SET CMP0091 NEW) +cmake_policy(SET CMP0092 NEW) + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules) +include(LibtorrentMacros) + +read_version("${CMAKE_CURRENT_SOURCE_DIR}/include/libtorrent/version.hpp" VER_MAJOR VER_MINOR VER_TINY) + +project(libtorrent + DESCRIPTION "Bittorrent library" + VERSION ${VER_MAJOR}.${VER_MINOR}.${VER_TINY} +) +set (SOVERSION "${VER_MAJOR}.${VER_MINOR}") + +include(GNUInstallDirs) +include(GeneratePkgConfig) + +set(libtorrent_include_files + add_torrent_params.hpp + address.hpp + alert.hpp + alert_types.hpp + announce_entry.hpp + assert.hpp + bdecode.hpp + bencode.hpp + bitfield.hpp + bloom_filter.hpp + bt_peer_connection.hpp + choker.hpp + client_data.hpp + close_reason.hpp + config.hpp + copy_ptr.hpp + crc32c.hpp + create_torrent.hpp + deadline_timer.hpp + debug.hpp + disk_buffer_holder.hpp + disk_interface.hpp + disk_observer.hpp + download_priority.hpp + entry.hpp + enum_net.hpp + error.hpp + error_code.hpp + extensions.hpp + file.hpp + file_storage.hpp + fingerprint.hpp + flags.hpp + fwd.hpp + gzip.hpp + hash_picker.hpp + hasher.hpp + hex.hpp + http_connection.hpp + http_parser.hpp + http_seed_connection.hpp + http_stream.hpp + http_tracker_connection.hpp + i2p_stream.hpp + identify_client.hpp + index_range.hpp + io.hpp + io_service.hpp + ip_filter.hpp + ip_voter.hpp + libtorrent.hpp + link.hpp + lsd.hpp + magnet_uri.hpp + mmap_disk_io.hpp + mmap_storage.hpp + natpmp.hpp + netlink.hpp + operations.hpp + optional.hpp + parse_url.hpp + part_file.hpp + peer.hpp + peer_class.hpp + peer_class_set.hpp + peer_class_type_filter.hpp + peer_connection.hpp + peer_connection_handle.hpp + peer_connection_interface.hpp + peer_id.hpp + peer_info.hpp + peer_list.hpp + peer_request.hpp + performance_counters.hpp + pex_flags.hpp + piece_block.hpp + piece_block_progress.hpp + piece_picker.hpp + platform_util.hpp + portmap.hpp + proxy_base.hpp + puff.hpp + random.hpp + read_resume_data.hpp + request_blocks.hpp + resolve_links.hpp + session.hpp + session_handle.hpp + session_params.hpp + session_settings.hpp + session_stats.hpp + session_status.hpp + session_types.hpp + settings_pack.hpp + sha1.hpp + sha1_hash.hpp + sha256.hpp + sliding_average.hpp + socket.hpp + socket_io.hpp + socket_type.hpp + socks5_stream.hpp + span.hpp + ssl.hpp + ssl_stream.hpp + stack_allocator.hpp + stat.hpp + stat_cache.hpp + storage_defs.hpp + string_util.hpp + string_view.hpp + tailqueue.hpp + time.hpp + torrent.hpp + torrent_flags.hpp + torrent_handle.hpp + torrent_info.hpp + torrent_peer.hpp + torrent_peer_allocator.hpp + torrent_status.hpp + tracker_manager.hpp + truncate.hpp + udp_socket.hpp + udp_tracker_connection.hpp + union_endpoint.hpp + units.hpp + upnp.hpp + utf8.hpp + vector_utils.hpp + version.hpp + web_connection_base.hpp + web_peer_connection.hpp + write_resume_data.hpp + xml_parse.hpp +) + +set(libtorrent_kademlia_include_files + announce_flags.hpp + dht_observer.hpp + dht_settings.hpp + dht_state.hpp + dht_storage.hpp + dht_tracker.hpp + direct_request.hpp + dos_blocker.hpp + ed25519.hpp + find_data.hpp + get_item.hpp + get_peers.hpp + io.hpp + item.hpp + msg.hpp + node.hpp + node_entry.hpp + node_id.hpp + observer.hpp + put_data.hpp + refresh.hpp + routing_table.hpp + rpc_manager.hpp + sample_infohashes.hpp + traversal_algorithm.hpp + types.hpp +) + +set(libtorrent_extensions_include_files + smart_ban.hpp + ut_metadata.hpp + ut_pex.hpp +) + +set(libtorrent_aux_include_files + alert_manager.hpp + aligned_storage.hpp + aligned_union.hpp + alloca.hpp + allocating_handler.hpp + apply_pad_files.hpp + array.hpp + bandwidth_limit.hpp + bandwidth_manager.hpp + bandwidth_queue_entry.hpp + bandwidth_socket.hpp + bind_to_device.hpp + buffer.hpp + byteswap.hpp + chained_buffer.hpp + cpuid.hpp + deferred_handler.hpp + deprecated.hpp + deque.hpp + dev_random.hpp + directory.hpp + disable_warnings_pop.hpp + disable_warnings_push.hpp + disk_buffer_pool.hpp + mmap_disk_job.hpp + disk_io_thread_pool.hpp + disk_job_fence.hpp + disk_job_pool.hpp + ed25519.hpp + escape_string.hpp + export.hpp + ffs.hpp + file_progress.hpp + file_view_pool.hpp + has_block.hpp + heterogeneous_queue.hpp + instantiate_connection.hpp + invariant_check.hpp + io.hpp + ip_helpers.hpp + ip_notifier.hpp + keepalive.hpp + listen_socket_handle.hpp + lsd.hpp + merkle.hpp + merkle_tree.hpp + noexcept_movable.hpp + numeric_cast.hpp + packet_buffer.hpp + packet_pool.hpp + path.hpp + polymorphic_socket.hpp + pool.hpp + portmap.hpp + posix_part_file.hpp + proxy_settings.hpp + range.hpp + receive_buffer.hpp + resolver.hpp + resolver_interface.hpp + scope_end.hpp + session_call.hpp + session_impl.hpp + session_interface.hpp + session_settings.hpp + session_udp_sockets.hpp + set_socket_buffer.hpp + set_traffic_class.hpp + set_traffic_class.hpp + socket_type.hpp + storage_free_list.hpp + storage_utils.hpp + string_ptr.hpp + strview_less.hpp + suggest_piece.hpp + throw.hpp + time.hpp + timestamp_history.hpp + torrent_impl.hpp + torrent_list.hpp + unique_ptr.hpp + utp_socket_manager.hpp + utp_stream.hpp + vector.hpp + win_crypto_provider.hpp + win_util.hpp +) + +set(try_signal_include_files + try_signal + signal_error_code + try_signal_mingw + try_signal_msvc + try_signal_posix +) + +set(sources + add_torrent_params.cpp + alert.cpp + alert_manager.cpp + announce_entry.cpp + assert.cpp + bandwidth_limit.cpp + bandwidth_manager.cpp + bandwidth_queue_entry.cpp + bdecode.cpp + bitfield.cpp + bloom_filter.cpp + bt_peer_connection.cpp + chained_buffer.cpp + choker.cpp + close_reason.cpp + copy_file.cpp + cpuid.cpp + crc32c.cpp + create_torrent.cpp + directory.cpp + disabled_disk_io.cpp + disk_buffer_holder.cpp + disk_buffer_pool.cpp + disk_interface.cpp + disk_io_thread_pool.cpp + disk_job_fence.cpp + disk_job_pool.cpp + entry.cpp + enum_net.cpp + error_code.cpp + escape_string.cpp + ffs.cpp + file.cpp + file_progress.cpp + file_storage.cpp + file_view_pool.cpp + fingerprint.cpp + generate_peer_id.cpp + gzip.cpp + hash_picker.cpp + hasher.cpp + hex.cpp + http_connection.cpp + http_parser.cpp + http_seed_connection.cpp + http_tracker_connection.cpp + i2p_stream.cpp + identify_client.cpp + instantiate_connection.cpp + ip_filter.cpp + ip_helpers.cpp + ip_notifier.cpp + ip_voter.cpp + listen_socket_handle.cpp + load_torrent.cpp + lsd.cpp + magnet_uri.cpp + merkle.cpp + merkle_tree.cpp + mmap.cpp + mmap_disk_io.cpp + mmap_disk_job.cpp + mmap_storage.cpp + natpmp.cpp + packet_buffer.cpp + parse_url.cpp + part_file.cpp + path.cpp + peer_class.cpp + peer_class_set.cpp + peer_connection.cpp + peer_connection_handle.cpp + peer_info.cpp + peer_list.cpp + performance_counters.cpp + piece_picker.cpp + platform_util.cpp + posix_disk_io.cpp + posix_part_file.cpp + posix_storage.cpp + proxy_base.cpp + proxy_settings.cpp + puff.cpp + random.cpp + read_resume_data.cpp + receive_buffer.cpp + request_blocks.cpp + resolve_links.cpp + resolver.cpp + session.cpp + session_call.cpp + session_handle.cpp + session_impl.cpp + session_params.cpp + session_settings.cpp + session_stats.cpp + settings_pack.cpp + sha1.cpp + sha1_hash.cpp + sha256.cpp + socket_io.cpp + socket_type.cpp + socks5_stream.cpp + ssl.cpp + stack_allocator.cpp + stat.cpp + stat_cache.cpp + storage_utils.cpp + string_util.cpp + time.cpp + timestamp_history.cpp + torrent.cpp + torrent_handle.cpp + torrent_info.cpp + torrent_peer.cpp + torrent_peer_allocator.cpp + torrent_status.cpp + tracker_manager.cpp + truncate.cpp + udp_socket.cpp + udp_tracker_connection.cpp + upnp.cpp + utf8.cpp + utp_socket_manager.cpp + utp_stream.cpp + version.cpp + web_connection_base.cpp + web_peer_connection.cpp + write_resume_data.cpp + xml_parse.cpp + +# -- extensions -- + smart_ban.cpp + ut_pex.cpp + ut_metadata.cpp +) + +# -- kademlia -- +set(kademlia_sources + dht_settings.cpp + dht_state.cpp + dht_storage.cpp + dht_tracker.cpp + dos_blocker.cpp + ed25519.cpp + find_data.cpp + get_item.cpp + get_peers.cpp + item.cpp + msg.cpp + node.cpp + node_entry.cpp + node_id.cpp + put_data.cpp + refresh.cpp + routing_table.cpp + rpc_manager.cpp + sample_infohashes.cpp + traversal_algorithm.cpp +) + +# -- ed25519 -- +set(ed25519_sources + add_scalar.cpp + fe.cpp + ge.cpp + key_exchange.cpp + keypair.cpp + sc.cpp + sign.cpp + verify.cpp + sha512.cpp + hasher512.cpp +) + +set(try_signal_sources + try_signal.cpp + signal_error_code.cpp +) + +list(TRANSFORM sources PREPEND "src/") +list(TRANSFORM kademlia_sources PREPEND "src/kademlia/") +list(TRANSFORM ed25519_sources PREPEND "src/ed25519/") +list(TRANSFORM libtorrent_include_files PREPEND "include/libtorrent/") +list(TRANSFORM libtorrent_extensions_include_files PREPEND "include/libtorrent/extensions/") +list(TRANSFORM libtorrent_aux_include_files PREPEND "include/libtorrent/aux_/") +list(TRANSFORM libtorrent_kademlia_include_files PREPEND "include/libtorrent/kademlia/") +list(TRANSFORM try_signal_sources PREPEND "deps/try_signal/") + +# these options control target creation and thus have to be declared before the add_library() call +feature_option(BUILD_SHARED_LIBS "build libtorrent as a shared library" ON) +feature_option(static_runtime "build libtorrent with static runtime" OFF) + +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_public_dependency(Threads REQUIRED) + +if(CMAKE_CXX_COMPILER_ID MATCHES Clang) + add_compile_options( + -Weverything + -Wno-c++98-compat-pedantic + -Wno-c++11-compat-pedantic + -Wno-padded + -Wno-alloca + -Wno-global-constructors + -Wno-exit-time-destructors + -Wno-weak-vtables + -Wno-return-std-move-in-c++11 + -Wno-unknown-warning-option + ) +elseif(CMAKE_CXX_COMPILER_ID MATCHES GNU) + add_compile_options( + -Wall + -Wextra + -Wpedantic + -Wvla + -Wno-noexcept-type + -Wno-format-zero-length + -ftemplate-depth=512 + ) +elseif(MSVC) + add_compile_options( + /W4 + # C4251: 'identifier' : class 'type' needs to have dll-interface to be + # used by clients of class 'type2' + /wd4251 + # C4268: 'identifier' : 'const' static/global data initialized + # with compiler generated default constructor fills the object with zeros + /wd4268 + # C4275: non DLL-interface classkey 'identifier' used as base for + # DLL-interface classkey 'identifier' + /wd4275 + # C4373: virtual function overrides, previous versions of the compiler + # did not override when parameters only differed by const/volatile qualifiers + /wd4373 + # C4503: 'identifier': decorated name length exceeded, name was truncated + /wd4503 + ) +endif() + +if(static_runtime) + if (MSVC) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + else() + include(ucm_flags) + ucm_set_runtime(STATIC) + endif() + set(Boost_USE_MULTITHREADED ON) + set(Boost_USE_STATIC_RUNTIME ON) + set(OPENSSL_MSVC_STATIC_RT ON) +endif() + +if (NOT BUILD_SHARED_LIBS) + set(Boost_USE_STATIC_LIBS ON) + set(OPENSSL_USE_STATIC_LIBS ON) +endif() + +add_library(torrent-rasterbar + ${sources} + ${try_signal_sources} + ${libtorrent_include_files} + ${libtorrent_extensions_include_files} + ${libtorrent_aux_include_files} +) + +# C++ 14 support is required +target_compile_features(torrent-rasterbar + PUBLIC + cxx_std_14 + cxx_attribute_deprecated + cxx_binary_literals + cxx_contextual_conversions + cxx_decltype_auto + cxx_digit_separators + cxx_generic_lambdas + cxx_lambda_init_captures + cxx_relaxed_constexpr + cxx_variable_templates +) + +if (BUILD_SHARED_LIBS) + target_compile_definitions(torrent-rasterbar + PRIVATE TORRENT_BUILDING_SHARED + INTERFACE TORRENT_LINKING_SHARED + ) +endif() + +set_target_properties(torrent-rasterbar + PROPERTIES + CXX_VISIBILITY_PRESET "hidden" + VISIBILITY_INLINES_HIDDEN "true" + VERSION ${PROJECT_VERSION} + SOVERSION ${SOVERSION} +) + +target_include_directories(torrent-rasterbar PUBLIC + $ + $ + PRIVATE deps/try_signal +) + +target_compile_definitions(torrent-rasterbar + PUBLIC + $<$:TORRENT_USE_ASSERTS> + BOOST_ASIO_ENABLE_CANCELIO + BOOST_ASIO_NO_DEPRECATED + PRIVATE + TORRENT_BUILDING_LIBRARY + _FILE_OFFSET_BITS=64 + BOOST_EXCEPTION_DISABLE + BOOST_ASIO_HAS_STD_CHRONO +) + +target_link_libraries(torrent-rasterbar + PUBLIC + Threads::Threads +) + +# Unconditional platform-specific settings +if (WIN32) + target_link_libraries(torrent-rasterbar + PUBLIC + bcrypt mswsock ws2_32 iphlpapi + debug dbghelp crypt32 + ) + + add_definitions(-D_WIN32_WINNT=0x0600) # target Windows Vista or later + + target_compile_definitions(torrent-rasterbar + PUBLIC WIN32_LEAN_AND_MEAN # prevent winsock1 to be included + ) + + if (MSVC) + target_compile_definitions(torrent-rasterbar + PUBLIC + BOOST_ALL_NO_LIB + _SCL_SECURE_NO_DEPRECATE _CRT_SECURE_NO_DEPRECATE # disable bogus deprecation warnings on msvc8 + ) + target_compile_options(torrent-rasterbar + PRIVATE + # https://docs.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170 + /utf-8 + # https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ + /Zc:__cplusplus + /MP # for multi-core compilation + /bigobj # increase the number of sections for obj files + ) + set_target_properties(torrent-rasterbar PROPERTIES LINK_FLAGS_RELEASE "/OPT:ICF=5 /OPT:REF") + endif() +endif() + +if (ANDROID) + target_link_libraries(torrent-rasterbar PRIVATE ${CMAKE_DL_LIBS}) +endif() + +if (APPLE) + # for ip_notifier + target_link_libraries(torrent-rasterbar PRIVATE "-framework CoreFoundation" "-framework SystemConfiguration") +endif() + +# check if we need to link with libatomic (not needed on MSVC) +if (NOT Windows) + # TODO: migrate to CheckSourceCompiles in CMake >= 3.19 + include(CheckCXXSourceCompiles) + + set(ATOMICS_TEST_SOURCE [=[ + #include + #include + std::atomic x{0}; + int main() { + x.fetch_add(1, std::memory_order_relaxed); + return 0; + } + ]=]) + string(REPLACE "std::atomic" "std::atomic" ATOMICS8_TEST_SOURCE "${ATOMICS_TEST_SOURCE}") + string(REPLACE "std::atomic" "std::atomic" ATOMICS64_TEST_SOURCE "${ATOMICS_TEST_SOURCE}") + + if(APPLE) + set(CMAKE_REQUIRED_FLAGS "-std=c++11") + endif() + check_cxx_source_compiles("${ATOMICS_TEST_SOURCE}" HAVE_CXX_ATOMICS_WITHOUT_LIB) + check_cxx_source_compiles("${ATOMICS8_TEST_SOURCE}" HAVE_CXX_ATOMICS8_WITHOUT_LIB) + check_cxx_source_compiles("${ATOMICS64_TEST_SOURCE}" HAVE_CXX_ATOMICS64_WITHOUT_LIB) + if((NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) OR (NOT HAVE_CXX_ATOMICS8_WITHOUT_LIB) OR (NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB)) + set(CMAKE_REQUIRED_LIBRARIES "atomic") + check_cxx_source_compiles("${ATOMICS_TEST_SOURCE}" HAVE_CXX_ATOMICS_WITH_LIB) + check_cxx_source_compiles("${ATOMICS8_TEST_SOURCE}" HAVE_CXX_ATOMICS8_WITH_LIB) + check_cxx_source_compiles("${ATOMICS64_TEST_SOURCE}" HAVE_CXX_ATOMICS64_WITH_LIB) + if ((NOT HAVE_CXX_ATOMICS_WITH_LIB) OR (NOT HAVE_CXX_ATOMICS8_WITH_LIB) OR (NOT HAVE_CXX_ATOMICS64_WITH_LIB)) + message(FATAL_ERROR "No native support for std::atomic, or libatomic not found!") + else() + message(STATUS "Linking with libatomic for atomics support") + unset(CMAKE_REQUIRED_LIBRARIES) + target_link_libraries(torrent-rasterbar PUBLIC atomic) + endif() + endif() + if(APPLE) + unset(CMAKE_REQUIRED_FLAGS) + endif() +endif() + +feature_option(build_tests "build tests" OFF) +feature_option(build_examples "build examples" OFF) +feature_option(build_tools "build tools" OFF) +feature_option(python-bindings "build python bindings" OFF) +feature_option(python-egg-info "generate python egg info" OFF) +feature_option(python-install-system-dir "Install python bindings to the system installation directory rather than the CMake installation prefix" OFF) + +# these options require existing target +feature_option(dht "enable support for Mainline DHT" ON) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME deprecated-functions DEFAULT ON + DESCRIPTION "enable deprecated functions for backwards compatibility" DISABLED TORRENT_NO_DEPRECATE) +feature_option(encryption "Enables encryption in libtorrent" ON) +feature_option(exceptions "build with exception support" ON) +feature_option(gnutls "build using GnuTLS instead of OpenSSL" OFF) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME extensions DEFAULT ON + DESCRIPTION "Enables protocol extensions" DISABLED TORRENT_DISABLE_EXTENSIONS) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME i2p DEFAULT ON + DESCRIPTION "build with I2P support" DISABLED TORRENT_USE_I2P=0) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME logging DEFAULT ON + DESCRIPTION "build with logging" DISABLED TORRENT_DISABLE_LOGGING) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME mutable-torrents DEFAULT ON + DESCRIPTION "Enables mutable torrent support" DISABLED TORRENT_DISABLE_MUTABLE_TORRENTS) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME streaming DEFAULT ON + DESCRIPTION "Enables support for piece deadline" DISABLED TORRENT_DISABLE_STREAMING) + +if(NOT gnutls) + find_public_dependency(OpenSSL) + set_package_properties(OpenSSL + PROPERTIES + URL "https://www.openssl.org/" + DESCRIPTION "Full-strength general purpose cryptography library" + TYPE RECOMMENDED + PURPOSE "Provides HTTPS support to libtorrent" + ) + + if(TARGET OpenSSL::SSL) + # TODO: needed until https://gitlab.kitware.com/cmake/cmake/issues/19263 is fixed + if(WIN32 AND OPENSSL_USE_STATIC_LIBS) + target_link_libraries(torrent-rasterbar PRIVATE crypt32) + endif() + target_link_libraries(torrent-rasterbar PUBLIC OpenSSL::SSL) + target_compile_definitions(torrent-rasterbar + PUBLIC + TORRENT_USE_OPENSSL + TORRENT_USE_LIBCRYPTO + TORRENT_SSL_PEERS + OPENSSL_NO_SSL2) + endif() +endif() + +if(gnutls OR NOT TARGET OpenSSL::SSL) + find_public_dependency(GnuTLS) + set_package_properties(GnuTLS + PROPERTIES + URL "https://www.gnutls.org/" + DESCRIPTION "GnuTLS is a free software implementation of the TLS and DTLS protocols" + TYPE RECOMMENDED + PURPOSE "Provides HTTPS support to libtorrent" + ) + if(GNUTLS_FOUND) + target_link_libraries(torrent-rasterbar PUBLIC GnuTLS::GnuTLS) + target_compile_definitions(torrent-rasterbar + PUBLIC + TORRENT_USE_GNUTLS + TORRENT_SSL_PEERS) + target_include_directories(torrent-rasterbar PUBLIC + $ + $) + install(DIRECTORY deps/asio-gnutls/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + elseif(gnutls) + message(FATAL_ERROR "GnuTLS library not found") + endif() +endif() + +if (NOT GNUTLS_FOUND AND NOT TARGET OpenSSL::SSL) + if(TARGET OpenSSL::Crypto) + target_link_libraries(torrent-rasterbar PUBLIC OpenSSL::Crypto) + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_LIBCRYPTO) + else() + find_public_dependency(LibGcrypt) + set_package_properties(LibGcrypt + PROPERTIES + URL "https://www.gnupg.org/software/libgcrypt/index.html" + DESCRIPTION "A general purpose cryptographic library" + TYPE RECOMMENDED + PURPOSE "Use GCrypt instead of the built-in functions for RC4 and SHA1" + ) + if (LibGcrypt_FOUND) + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_LIBGCRYPT) + target_link_libraries(torrent-rasterbar PRIVATE LibGcrypt::LibGcrypt) + endif() + endif() +endif() + +if (encryption) + target_sources(torrent-rasterbar PRIVATE include/libtorrent/pe_crypto.hpp src/pe_crypto.cpp) +else() + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_DISABLE_ENCRYPTION) +endif() + +if (dht) + target_sources(torrent-rasterbar PRIVATE + ${libtorrent_kademlia_include_files} + include/libtorrent/aux_/hasher512.hpp + ${kademlia_sources} + ${ed25519_sources} + ) +else() + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_DISABLE_DHT) +endif() + +# Boost +find_public_dependency(Boost REQUIRED) +target_link_libraries(torrent-rasterbar PUBLIC Boost::headers) +if (Boost_MAJOR_VERSION LESS_EQUAL 1 AND Boost_MINOR_VERSION LESS 69) + find_package(Boost REQUIRED COMPONENTS system) + target_link_libraries(torrent-rasterbar PUBLIC Boost::system) +endif() + +if (exceptions) + if (MSVC) + target_compile_options(torrent-rasterbar PUBLIC /EHsc) + else (MSVC) + target_compile_options(torrent-rasterbar PUBLIC -fexceptions) + endif (MSVC) +else() + if (MSVC) + target_compile_definitions(torrent-rasterbar PUBLIC _HAS_EXCEPTIONS=0) + else (MSVC) + target_compile_options(torrent-rasterbar PUBLIC -fno-exceptions) + endif (MSVC) +endif() + +# developer options +option(developer-options "Activates options useful for a developer") +if(developer-options) + set(asserts "auto" CACHE STRING "use assertions") + set_property(CACHE asserts PROPERTY STRINGS auto on off production system) + if ("${asserts}" MATCHES "on|production|system") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_ASSERTS=1) + endif() + if ("${asserts}" STREQUAL "production") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_PRODUCTION_ASSERTS=1) + elseif("${asserts}" STREQUAL "system") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_SYSTEM_ASSERTS=1) + endif() + + target_optional_compile_definitions(torrent-rasterbar PUBLIC NAME asio-debugging DEFAULT OFF + ENABLED TORRENT_ASIO_DEBUGGING) + target_optional_compile_definitions(torrent-rasterbar PUBLIC NAME picker-debugging DEFAULT OFF + ENABLED TORRENT_DEBUG_REFCOUNTS) + set(invariant-checks "off" CACHE STRING "") + set_property(CACHE invariant-checks PROPERTY STRINGS off on full) + if (invariant-checks MATCHES "on|full") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_INVARIANT_CHECKS=1) + endif() + if (invariant-checks STREQUAL "full") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_EXPENSIVE_INVARIANT_CHECKS) + endif() + + target_optional_compile_definitions(torrent-rasterbar PUBLIC NAME utp-log DEFAULT OFF + ENABLED TORRENT_UTP_LOG_ENABLE) + target_optional_compile_definitions(torrent-rasterbar PUBLIC NAME simulate-slow-read DEFAULT OFF + ENABLED TORRENT_SIMULATE_SLOW_READ) + option(debug-iterators "" OFF) + if (debug-iterators) + if (MSVC) + target_compile_definitions(torrent-rasterbar PUBLIC _ITERATOR_DEBUG_LEVEL=2) + endif() + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_compile_definitions(torrent-rasterbar PUBLIC _GLIBCXX_DEBUG _GLIBCXX_DEBUG_PEDANTIC) + endif() + endif() + target_optional_compile_definitions(torrent-rasterbar PUBLIC NAME profile-calls DEFAULT OFF + ENABLED TORRENT_PROFILE_CALLS=1) +endif() + +# This is best effort attempt to propagate whether the library was built with +# C++11 or not. It affects the ABI of entry. A client building with C++14 and +# linking against a libtorrent binary built with C++11 can still define +# TORRENT_CXX11_ABI +if ("${CMAKE_CXX_STANDARD}" STREQUAL "11") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_CXX11_ABI) +endif() + +# There is little to none support for using pkg-config with MSVC and most users won't bother with it. +# However, msys is a linux-like platform on Windows that do support/prefer using pkg-config. +if (NOT MSVC) + generate_and_install_pkg_config_file(torrent-rasterbar libtorrent-rasterbar) +endif() + +include(CheckCXXCompilerFlag) + +add_subdirectory(bindings) + +if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") + file(RELATIVE_PATH CMAKE_INSTALL_LIBDIR "${CMAKE_INSTALL_PREFIX}" "${CMAKE_INSTALL_LIBDIR}") +endif() + +install(TARGETS torrent-rasterbar EXPORT LibtorrentRasterbarTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) +install(DIRECTORY include/libtorrent DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.h*") + +# === generate a CMake Config File === +include(CMakePackageConfigHelpers) +set(ConfigPackageLocation ${CMAKE_INSTALL_LIBDIR}/cmake/LibtorrentRasterbar) +string(REGEX REPLACE "([^;]+)" "find_dependency(\\1)" _find_dependency_calls "${_package_dependencies}") +string(REPLACE ";" "\n" _find_dependency_calls "${_find_dependency_calls}") + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/LibtorrentRasterbar/LibtorrentRasterbarConfigVersion.cmake" + VERSION ${libtorrent_VERSION} + COMPATIBILITY AnyNewerVersion +) + +export(EXPORT LibtorrentRasterbarTargets + FILE "${CMAKE_CURRENT_BINARY_DIR}/LibtorrentRasterbar/LibtorrentRasterbarTargets.cmake" + NAMESPACE LibtorrentRasterbar:: +) + +configure_package_config_file(LibtorrentRasterbarConfig.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/LibtorrentRasterbar/LibtorrentRasterbarConfig.cmake" + INSTALL_DESTINATION "${ConfigPackageLocation}" + NO_SET_AND_CHECK_MACRO + NO_CHECK_REQUIRED_COMPONENTS_MACRO +) + +install(EXPORT LibtorrentRasterbarTargets + NAMESPACE + LibtorrentRasterbar:: + DESTINATION + ${ConfigPackageLocation} +) +install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/LibtorrentRasterbar/LibtorrentRasterbarConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/LibtorrentRasterbar/LibtorrentRasterbarConfigVersion.cmake" + DESTINATION + ${ConfigPackageLocation} +) + +install( + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/examples/cmake/FindLibtorrentRasterbar.cmake + DESTINATION + ${CMAKE_INSTALL_DATADIR}/cmake/Modules +) + +if (MSVC) + set_target_properties(torrent-rasterbar + PROPERTIES + PDB_NAME torrent-rasterbar + PDB_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} + COMPILE_PDB_NAME torrent-rasterbar + COMPILE_PDB_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} + ) + + if (static_runtime) + set(PDB_INSTALL_DIR lib) + else() + set(PDB_INSTALL_DIR bin) + endif() + + install( + FILES + ${CMAKE_BINARY_DIR}/torrent-rasterbar.pdb + DESTINATION + ${PDB_INSTALL_DIR} + CONFIGURATIONS + Debug RelWithDebInfo + OPTIONAL + ) +endif() + +# === build tools === +if (build_tools) + add_subdirectory(tools) +endif() + +# === build examples === +if (build_examples) + add_subdirectory(examples) +endif() + +# === build tests === +if(build_tests) + enable_testing() + # this will make some internal functions available in the DLL interface + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_EXPORT_EXTRA) + add_subdirectory(test) +endif() + +feature_summary(DEFAULT_DESCRIPTION WHAT ALL) diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..e747efb --- /dev/null +++ b/COPYING @@ -0,0 +1,28 @@ +Copyright (c) 2003-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of Rasterbar Software nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..6cb5672 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,2133 @@ +* 2.0.7 released + + * fix issue in use of copy_file_range() on linux + * avoid open-file race in the file_view_pool + * fix issue where stop-when-ready would not close files + * fix issue with duplicate hybrid torrent via separate v1 and v2 magnet links + * added new function to load torrent files, load_torrent_*() + * support sync_file_range() on linux + * fix issue in write_torrent_file() when file size is exactly piece size + * fix file_num_blocks() and file_num_pieces() for empty files + * add new overload to make_magnet_uri() + * add missing protocol version to tracker_reply_alert and tracker_error_alert + * fix privilege issue with SetFileValidData() + * add asynchronous overload of torrent_handle::add_piece() + * default to a single hashing thread, for full checks + * Fix bug when checking files and the first piece is invalid + +* 2.0.6 released + + * fix issue creating a v2 torrent from torrent_info containing an empty file + * make recheck files also update which files use partfile + * add write_through disk_io_write_mode, which flushes pieces to disk immediately + * improve copy file function to preserve sparse regions (when supported) + * add function to truncate over-sized files part of a torrent + * fix directory creation on windows shared folders + * add flag to make add_files() not record file attributes + * deprecate (unused) allow_partial_disk_writes settings + * fix disk-full error reporting in mmap_disk_io + * fixed similar-torrents feature for v2 torrents + * fix potential unbounded recursion in add_completed_job, in disk I/O + * deprecated (unused) volatile_read_cache setting + * fix part files being marked as hidden on windows + +* 2.0.5 released + + * on windows, explicitly flush memory mapped files periodically + * fix build with WolfSSL + * fix issue where incoming uTP connections were not accepted over SOCKS5 + * fix several issues in handling of checking files of v2 torrents, esp. from magnet links + * make the token limit when parsing metadata from magnet files configurable + * fix issue with stalled pieces on disk full errors + * fix missing python binding for file_progress_flags + * fix torrent_file_with_hashes() to fail when we don't have the piece layers + * restore path character encoding conversion for non UTF-8 locales on linux + * fix use-after-free bug in make_magnet_uri + * add write_torrent_file() to produce a .torrent file from add_torrent_params + * allow loading v2 .torrent files without piece layer + * fix issue with adding v2 torrents with invalid file root hash + +* 2.0.4 released + + * fix piece picker bug causing double-picks with prefer-contiguous enabled + * expose session_params in python bindings + * fix (deprecated) use of add_torrent_params::info_hash + * fix issue creating and loading v2 torrents with empty files. Improves + conformance to BEP52 reference implementation + +* 2.0.3 released + + * add new torrent_file_with_hashes() which includes piece layers for + creating .torrent files + * add file_prio_alert, posted when file priorities are updated + * fix issue where set_piece_hashes() would not propagate file errors + * add missing python binding for event_t + * add work-around for systems without fseeko() (such as Android) + * add convenience header libtorrent/libtorrent.hpp + * increase default max_allowed_in_request_queue + * fix loading non-ascii filenames on windows with torrent_info constructor (2.0 regression) + * add std::hash<> specialization for info_hash_t + * fix integer overflow in hash_picker and properly restrict max file sizes in torrents + * strengthen SSRF mitigation for web seeds + +* 2.0.2 released + + * add v1() and v2() functions to torrent_info + * fix piece_layers() to work for single-piece files + * fix python binding regression in session constructor flags + * fix unaligned piece requests in mmap_storage + * improve client_data_t ergonomics + * fix issue with concurrent access to part files + +* 2.0.1 released + + * fix attribute in single-file v2 torrent creation + * fix padding for empty files in v2 torrent creation + * add function to ask a file_storage whether it's v2 or not + * fix mtime field when creating single-file v2 torrents + * fix performance regression in checking files + * disable use of SetFileValidData() by default (windows). A new setting + allows enabling it + +2.0 released + + * dropped depenency on iconv + * deprecate set_file_hash() in torrent creator, as it's superceded by v2 torrents + * deprecate mutable access to info_section in torrent_info + * removed deprecated lazy_entry/lazy_bdecode + * stats_alert deprecated + * remove bittyrant choking algorithm + * update userdata in add_torrent_params to be type-safe and add to torrent_handle + * add ip_filter to session_params + * added support for wolfSSL for SHA-1 hash and HTTPS (no Torrents over SSL) + * requires OpenSSL minimum version 1.0.0 with SNI support + * deprecated save_state() and load_state() on session in favour of new + write_session_params() and read_session_params() + * added support for BitTorrent v2 (see docs/upgrade_to_2.0.html) + * create_torrent() pad_file_limit parameter removed + * create_torrent() merkle- and optimize-alignment flags removed + * merkle_tree removed from add_torrent_params + * announce_entry expose information per v1 and v2 info-hash announces + * deprecated sha1_hash info_hash members on torrent_removed_alert, + torrent_deleted_alert, torrent_delete_failed_alert and add_torrent_params + * undeprecate error_file_metadata for torrent errors related to its metadata + * remove support for adding a torrent under a UUID (used for previous RSS support) + * remove deprecated feature to add torrents by file:// URL + * remove deprecated feature to download .torrent file from URL + * requires boost >= 1.66 to build + * update networking API to networking TS compatible boost.asio + * overhauled disk I/O subsystem to use memory mapped files (where available) + * libtorrent now requires C++14 to build + * added support for GnuTLS for HTTPS and torrents over SSL + +1.2.17 released + + * fixed tracker connections spinning when hostname lookups stall + * fixed error in pkg-config file generation in Jamfile + * improve backwards compatibility with loading magnet link resume files + * fix bind-to-device for tracker announces and UPnP + * rename peer_tos setting to peer_dscp + * fix bdecode support for large strings (>= 100 MB) + +1.2.16 released + + * send User-Agent field in anonymous mode + * fix python binding for settings_pack conversion + * fix DHT announce timer issue + * use DSCP_TRAFFIC_TYPE socket option on windows + * update default ToS setting according to RFC 8622 + * keep trying to announce to trackers even when all fail + * don't disable announcing from local endpoints because of temporary failures + * fix issue in parsing UPnP XML response with multiple forwarding services + +1.2.15 released + + * cache DNS lookups for SOCKS5 proxy + * fix stalled pieces on disk-full errors + * fix build configuration issue on NetBSD, OpenBSD and DragonFly + * make UTF-8 sanitization a bit stricter. This will re-write invalid UTF-8 + code points encoding surrogate pairs + * fix restoring last_seen_complete from resume data + * fix issue on MacOS where the DHT was not restarted on a network-up notification + * make remove_torrent flags be treated as flags (instead of an enum) + +1.2.14 released + + * improve handling of seed flag in PEX messages + * fix issue of accruing unlimited DHT node candidates when DHT is disabled + * fix bug in parsing chunked encoding + * fix incorrect reporting of active_duration when entering graceful-pause + * fix python binding for functions taking string_view + * fix python binding for torrent_info constructor overloads + * issue python deprecation warnings for some deprecated functions in the python bindings + * fix python binding for torrent_info::add_url_seed, add_tracker and add_http_seed + +1.2.13 released + + * Use /etc/ssl/cert.pem to validate HTTPS connections on MacOS + * allow no-interest timeouts of peer connections before all connections slots are full + * fix issue where a DHT message would count as an incoming connection + * fix issue when failing to parse outgoing_interfaces setting + * fix super-seeding issue that could cause a segfault + * fix data race in python binding of session::get_torrent_status() + * fix need_save_resume_data() for renaming files, share-mode, upload-mode, + disable- pex, lsd, and dht. + * fix incoming TCP connections when using tracker-only proxy + * fix issue with paths starting with ./ + * fix integer overflow when setting a high DHT upload rate limit + * improve Path MTU discovery logic in uTP + * fix overflow issue when rlimit_nofile is set to infinity + * fix issue in python binding interpreting int settings > INT_MAX + * Fix cxxflags and linkflags injection via environment variables + +1.2.12 released + + * fix loading of DHT node ID from previous session on startup + * use getrandom(), when available, and fall back to /dev/urandom + * fix python binding for "value" in dht put alerts + * fix bug in python binding for dht_put_mutable_item + * fix uTP issue acking FIN packets + * validate HTTPS certificates by default (trackers and web seeds) + * load SSL certificates from windows system certificate store, to authenticate trackers + * introduce mitigation for Server Side Request Forgery in tracker and web seed URLs + * fix error handling for pool allocation failure + +1.2.11 released + + * fix issue with moving the session object + * deprecate torrent_status::allocating. This state is no longer used + * fix bug creating torrents with symbolic links + * remove special case to save metadata in resume data unconditionally when added throught magnet link + * fix bugs in mutable-torrent support (reusing identical files from different torrents) + * fix incorrectly inlined move-assignment of file_storage + * add session::paused flag, and the ability to construct a session in paused mode + * fix session-pause causing tracker announces to fail + * fix peer-exchange flags bug + * allow saving resume data before metadata has been downloaded (for magnet links) + * record blocks in the disk queue as downloaded in the resume data + * fix bug in set_piece_deadline() when set in a zero-priority piece + * fix issue in URL parser, causing issues with certain tracker URLs + * use a different error code than host-unreachable, when skipping tracker announces + +1.2.10 released + + * fix regression in python binding for move_storage() + * improve stat_file() performance on Windows + * fix issue with loading invalid torrents with only 0-sized files + * fix to avoid large stack allocations + +1.2.9 released + + * add macro TORRENT_CXX11_ABI for clients building with C++14 against + libtorrent build with C++11 + * refreshed m4 scripts for autotools + * removed deprecated wstring overloads on non-windows systems + * drop dependency on Unicode's ConvertUTF code (which had a license + incompatible with Debian) + * fix bugs exposed on big-endian systems + * fix detection of hard-links not being supported by filesystem + * fixed resume data regression for seeds with prio 0 files + +1.2.8 released + + * validate UTF-8 encoding of client version strings from peers + * don't time out tracker announces as eagerly while resolving hostnames + * fix NAT-PMP shutdown issue + * improve hostname lookup by merging identical lookups + * fix network route enumeration for large routing tables + * fixed issue where pop_alerts() could return old, invalid alerts + * fix issue when receiving have-all message before the metadata + * don't leave lingering part files handles open + * disallow calling add_piece() during checking + * fix incorrect filename truncation at multi-byte character + * always announce listen port 1 when using a proxy + +1.2.7 released + + * add set_alert_fd in python binding, to supersede set_alert_notify + * fix bug in part files > 2 GiB + * add function to clear the peer list for a torrent + * fix resume data functions to save/restore more torrent flags + * limit number of concurrent HTTP announces + * fix queue position for force_rechecking a torrent that is not auto-managed + * improve rate-based choker documentation, and minor tweak + * undeprecate upnp_ignore_nonrouters (but refering to devices on our subnet) + * increase default tracker timeout + * retry failed socks5 server connections + * allow UPnP lease duration to be changed after device discovery + * fix IPv6 address change detection on Windows + +1.2.6 released + + * fix peer timeout logic + * simplify proxy handling. A proxy now overrides listen_interfaces + * fix issues when configured to use a non-default choking algorithm + * fix issue in reading resume data + * revert NXDOMAIN change from 1.2.4 + * don't open any listen sockets if listen_interfaces is empty or misconfigured + * fix bug in auto disk cache size logic + * fix issue with outgoing_interfaces setting, where bind() would be called twice + * add build option to disable share-mode + * support validation of HTTPS trackers + * deprecate strict super seeding mode + * make UPnP port-mapping lease duration configurable + * deprecate the bittyrant choking algorithm + * add build option to disable streaming + +1.2.5 release + + * announce port=1 instead of port=0, when there is no listen port + * fix LSD over IPv6 + * support TCP_NOTSENT_LOWAT on Linux + * fix correct interface binding of local service discovery multicast + * fix issue with knowing which interfaces to announce to trackers and DHT + * undeprecate settings_pack::dht_upload_rate_limit + +1.2.4 release + + * fix binding TCP and UDP sockets to the same port, when specifying port 0 + * fix announce_to_all_trackers and announce_to_all_tiers behavior + * fix suggest_read_cache setting + * back-off tracker hostname looksups resulting in NXDOMAIN + * lower SOCKS5 UDP keepalive timeout + * fix external IP voting for multi-homed DHT nodes + * deprecate broadcast_lsd setting. Just use multicast + * deprecate upnp_ignore_nonrouters setting + * don't attempt sending event=stopped if event=start never succeeded + * make sure &key= stays consistent between different source IPs (as mandated by BEP7) + * fix binding sockets to outgoing interface + * add new socks5_alert to trouble shoot SOCKS5 proxies + +1.2.3 release + + * fix erroneous event=completed tracker announce when checking files + * promote errors in parsing listen_interfaces to post listen_failed_alert + * fix bug in protocol encryption/obfuscation + * fix buffer overflow in SOCKS5 UDP logic + * fix issue of rapid calls to file_priority() clobbering each other + * clear tracker errors on success + * optimize setting with unlimited unchoke slots + * fixed restoring of trackers, comment, creation date and created-by in resume data + * fix handling of torrents with too large pieces + * fixed division by zero in anti-leech choker + * fixed bug in torrent_info::swap + +1.2.2 release + + * fix cases where the disable_hash_checks setting was not honored + * fix updating of is_finished torrent status, when changing piece priorities + * fix regression in &left= reporting when adding a seeding torrent + * fix integer overflow in http parser + * improve sanitation of symlinks, to support more complex link targets + * add DHT routing table affinity for BEP 42 nodes + * add torrent_info constructor overloads to control torrent file limits + * feature to disable DHT, PEX and LSD per torrent + * fix issue where trackers from magnet links were not included in create_torrent() + * make peer_info::client a byte array in python binding + * pick contiguous pieces from peers with high download rate + * fix error handling of moving storage to a drive letter that isn't mounted + * fix HTTP Host header when using proxy + +1.2.1 release + + * add dht_pkt_alert and alerts_dropped_alert to python bindings + * fix python bindins for block_uploaded_alert + * optimize resolving duplicate filenames in loading torrent files + * fix python binding of dht_settings + * tighten up various input validation checks + * fix create_torrent python binding + * update symlinks to conform to BEP 47 + * fix python bindings for peer_info + * support creating symlinks, for torrents with symlinks in them + * fix error in seed_mode flag + * support magnet link parameters with number siffixes + * consistently use "lt" namespace in examples and documentation + * fix Mingw build to use native cryptoAPI + * uPnP/NAT-PMP errors no longer set the client's advertised listen port to zero + +1.2 release + + * requires boost >= 1.58 to build + * tweak heuristic of how to interpret url seeds in multi-file torrents + * support &ipv4= tracker argument for private torrents + * renamed debug_notification to connect_notification + * when updating listen sockets, only post alerts for new ones + * deprecate anonymous_mode_alert + * deprecated force_proxy setting (when set, the proxy is always used) + * add support for Port Control Protocol (PCP) + * deliver notification of alerts being dropped via alerts_dropped_alert + * deprecated alert::progress_notification alert category, split into + finer grained categories + * update plugin interface functions for improved type-safety + * implemented support magnet URI extension, select specific file indices + for download, BEP53 + * make tracker keys multi-homed. remove set_key() function on session. + * add flags()/set_flags()/unset_flags() to torrent_handle, deprecate individual functions + * added alert for block being sent to the send buffer + * drop support for windows compilers without std::wstring + * implemented support for DHT info hash indexing, BEP51 + * removed deprecated support for file_base in file_storage + * added support for running separate DHT nodes on each network interface + * added support for establishing UTP connections on any network interface + * added support for sending tracker announces on every network interface + * introduce "lt" namespace alias + * need_save_resume_data() will no longer return true every 15 minutes + * make the file_status interface explicitly public types + * added resolver_cache_timeout setting for internal host name resolver + * make parse_magnet_uri take a string_view instead of std::string + * deprecate add_torrent_params::url field. use parse_magnet_uri instead + * optimize download queue management + * deprecated (undocumented) file:// urls + * add limit for number of web seed connections + * added support for retrieval of DHT live nodes + * complete UNC path support + * add packets pool allocator + * remove disk buffer pool allocator + * fix last_upload and last_download overflow after 9 hours in past + * python binding add more add_torrent_params fields and an invalid key check + * introduce introduce distinct types for peer_class_t, piece_index_t and + file_index_t. + * fix crash caused by empty bitfield + * removed disk-access-log build configuration + * removed mmap_cache feature + * strengthened type safety in handling of piece and file indices + * deprecate identify_client() and fingerprint type + * make sequence number for mutable DHT items backed by std::int64_t + * tweaked storage_interface to have stronger type safety + * deprecate relative times in torrent_status, replaced by std::chrono::time_point + * refactor in alert types to use more const fields and more clear API + * changed session_stats_alert counters type to signed (std::int64_t) + * remove torrent eviction/ghost torrent feature + * include target in DHT lookups, when queried from the session + * improve support for HTTP redirects for web seeds + * use string_view in entry interface + * deprecate "send_stats" property on trackers (since lt_tracker extension has + been removed) + * remove deprecate session_settings API (use settings_pack instead) + * improve file layout optimization when creating torrents with padfiles + * remove remote_dl_rate feature + * source code migration from boost::shared_ptr to std::shared_ptr + * storage_interface API changed to use span and references + * changes in public API to work with std::shared_ptr + * extensions API changed to use span and std::shared_ptr + * plugin API changed to handle DHT requests using string_view + * removed support for lt_trackers and metadata_transfer extensions + (pre-dating ut_metadata) + * support windows' CryptoAPI for SHA-1 + * separated ssl and crypto options in build + * remove lazy-bitfield feature + * simplified suggest-read-cache feature to not depend on disk threads + * removed option to disable contiguous receive buffers + * deprecated public to_hex() and from_hex() functions + * separated address and port fields in listen alerts + * added support for parsing new x.pe parameter from BEP 9 + * peer_blocked_alert now derives from peer_alert + * transitioned exception types to system_error + * made alerts move-only + * move files one-by-one when moving storage for a torrent + * removed RSS support + * removed feature to resolve country for peers + * added support for BEP 32, "IPv6 extension for DHT" + * overhauled listen socket and UDP socket handling, improving multi-home + support and bind-to-device + * resume data is now communicated via add_torrent_params objects + * added new read_resume_data()/write_resume_data functions to write bencoded, + backwards compatible resume files + * removed deprecated fields from add_torrent_params + * deprecate "resume_data" field in add_torrent_params + * improved support for bind-to-device + * deprecated ssl_listen, SSL sockets are specified in listen_interfaces now + * improved support for listening on multiple sockets and interfaces + * resume data no longer has timestamps of files + * require C++11 to build libtorrent + + * replace use of boost-endian with boost-predef + +1.1.12 release + + * uTP performance fixes + +1.1.11 release + + * fix move_storage with save_path with a trailing slash + * fix tracker announce issue, advertising port 0 in secondary IPv6 announce + * fix missing boost/noncopyable.hpp includes + * fix python binding for torrent_info::creation_date() + +1.1.10 release + + * fix issue in udp_socket with unusual socket failure + * split progress_notification alert category into file-, piece- and block progress + * utp close-reason fix + * exposed default add_torrent_params flags to python bindings + * fix redundant flushes of partfile metadata + * add option to ignore min-interval from trackers on force-reannounce + * raise default setting for active_limit + * fall back to copy+remove if rename_file fails + * improve handling of filesystems not supporting fallocate() + * force-proxy no longer disables DHT + * improve connect-boost feature, to make new torrents quickly connect peers + +1.1.9 release + + * save both file and piece priorities in resume file + * added missing stats_metric python binding + * uTP connections are no longer exempt from rate limits by default + * fix exporting files from partfile while seeding + * fix potential deadlock on Windows, caused by performing restricted + tasks from within DllMain + * fix issue when subsequent file priority updates cause torrent to stop + +1.1.8 release + + * coalesce reads and writes by default on windows + * fixed disk I/O performance of checking hashes and creating torrents + * fix race condition in part_file + * fix part_file open mode compatibility test + * fixed race condition in random number generator + * fix race condition in stat_cache (disk storage) + * improve error handling of failing to change file priority + The API for custom storage implementations was altered + * set the hidden attribute when creating the part file + * fix tracker announces reporting more data downloaded than the size of the torrent + * fix recent regression with force_proxy setting + +1.1.7 release + + * don't perform DNS lookups for the DHT bootstrap unless DHT is enabled + * fix issue where setting file/piece priority would stop checking + * expose post_dht_stats() to python binding + * fix backwards compatibility to downloads without partfiles + * improve part-file related error messages + * fix reporting &redundant= in tracker announces + * fix tie-break in duplicate peer connection disconnect logic + * fix issue with SSL tracker connections left in CLOSE_WAIT state + * defer truncating existing files until the first time we write to them + * fix issue when receiving a torrent with 0-sized padfiles as magnet link + * fix issue resuming 1.0.x downloads with a file priority 0 + * fix torrent_status::next_announce + * fix pad-file scalability issue + * made coalesce_reads/coalesce_writes settings take effect on linux and windows + * use unique peer_ids per connection + * fix iOS build on recent SDK + * fix tracker connection bind issue for IPv6 trackers + * fix error handling of some merkle torrents + * fix error handling of unsupported hard-links + +1.1.6 release + + * deprecate save_encryption_settings (they are part of the normal settings) + * add getters for peer_class_filter and peer_class_type_filter + * make torrent_handler::set_priority() to use peer_classes + * fix support for boost-1.66 (requires C++11) + * fix i2p support + * fix loading resume data when in seed mode + * fix part-file creation race condition + * fix issue with initializing settings on session construction + * fix issue with receiving interested before metadata + * fix IPv6 tracker announce issue + * restore path sanitization behavior of ":" + * fix listen socket issue when disabling "force_proxy" mode + * fix full allocation failure on APFS + +1.1.5 release + + * fix infinite loop when parsing certain invalid magnet links + * fix parsing of torrents with certain invalid filenames + * fix leak of torrent_peer objecs (entries in peer_list) + * fix leak of peer_class objects (when setting per-torrent rate limits) + * expose peer_class API to python binding + * fix integer overflow in whole_pieces_threshold logic + * fix uTP path MTU discovery issue on windows (DF bit was not set correctly) + * fix python binding for torrent_handle, to be hashable + * fix IPv6 tracker support by performing the second announce in more cases + * fix utf-8 encoding check in torrent parser + * fix infinite loop when parsing maliciously crafted torrents + * fix invalid read in parse_int in bdecoder (CVE-2017-9847) + * fix issue with very long tracker- and web seed URLs + * don't attempt to create empty files on startup, if they already exist + * fix force-recheck issue (new files would not be picked up) + * fix inconsistency in file_priorities and override_resume_data behavior + * fix paused torrents not generating a state update when their ul/dl rate + transitions to zero + +1.1.4 release + + * corrected missing const qualifiers on bdecode_node + * fix changing queue position of paused torrents (1.1.3 regression) + * fix re-check issue after move_storage + * handle invalid arguments to set_piece_deadline() + * move_storage did not work for torrents without metadata + * improve shutdown time by only announcing to trackers whose IP we know + * fix python3 portability issue in python binding + * delay 5 seconds before reconnecting socks5 proxy for UDP ASSOCIATE + * fix NAT-PMP crash when removing a mapping at the wrong time + * improve path sanitization (filter unicode text direction characters) + * deprecate partial_piece_info::piece_state + * bind upnp requests to correct local address + * save resume data when removing web seeds + * fix proxying of https connections + * fix race condition in disk I/O storage class + * fix http connection timeout on multi-homed hosts + * removed depdendency on boost::uintptr_t for better compatibility + * fix memory leak in the disk cache + * fix double free in disk cache + * forward declaring libtorrent types is discouraged. a new fwd.hpp header is provided + +1.1.3 release + + * removed (broken) support for incoming connections over socks5 + * restore announce_entry's timestamp fields to posix time in python binding + * deprecate torrent_added_alert (in favor of add_torrent_alert) + * fix python binding for parse_magnet_uri + * fix minor robustness issue in DHT bootstrap logic + * fix issue where torrent_status::num_seeds could be negative + * document deprecation of dynamic loading/unloading of torrents + * include user-agent in tracker announces in anonymous_mode for private torrents + * add support for IPv6 peers from udp trackers + * correctly URL encode the IPv6 argument to trackers + * fix default file pool size on windows + * fix bug where settings_pack::file_pool_size setting was not being honored + * add feature to periodically close files (to make windows clear disk cache) + * fix bug in torrent_handle::file_status + * fix issue with peers not updated on metadata from magnet links + +1.1.2 release + + * default TOS marking to 0x20 + * fix invalid access when leaving seed-mode with outstanding hash jobs + * fix ABI compatibility issue introduced with preformatted entry type + * add web_seed_name_lookup_retry to session_settings + * slightly improve proxy settings backwards compatibility + * add function to get default settings + * updating super seeding would include the torrent in state_update_alert + * fix issue where num_seeds could be greater than num_peers in torrent_status + * finished non-seed torrents can also be in super-seeding mode + * fix issue related to unloading torrents + * fixed finished-time calculation + * add missing min_memory_usage() and high_performance_seed() settings presets to python + * fix stat cache issue that sometimes would produce incorrect resume data + * storage optimization to peer classes + * fix torrent name in alerts of builds with deprecated functions + * make torrent_info::is_valid() return false if torrent failed to load + * fix per-torrent rate limits for >256 peer classes + * don't load user_agent and peer_fingerprint from session_state + * fix file rename issue with name prefix matching torrent name + * fix division by zero when setting tick_interval > 1000 + * fix move_storage() to its own directory (would delete the files) + * fix socks5 support for UDP + * add setting urlseed_max_request_bytes to handle large web seed requests + * fix python build with CC/CXX environment + * add trackers from add_torrent_params/magnet links to separate tiers + * fix resumedata check issue with files with priority 0 + * deprecated mmap_cache feature + * add utility function for generating peer ID fingerprint + * fix bug in last-seen-complete + * remove file size limit in torrent_info filename constructor + * fix tail-padding for last file in create_torrent + * don't send user-agent in metadata http downloads or UPnP requests when + in anonymous mode + * fix internal resolve links lookup for mutable torrents + * hint DHT bootstrap nodes of actual bootstrap request + +1.1.1 release + + * update puff.c for gzip inflation (CVE-2016-7164) + * add dht_bootstrap_node a setting in settings_pack (and add default) + * make pad-file and symlink support conform to BEP47 + * fix piece picker bug that could result in division by zero + * fix value of current_tracker when all tracker failed + * deprecate lt_trackers extension + * remove load_asnum_db and load_country_db from python bindings + * fix crash in session::get_ip_filter when not having set one + * fix filename escaping when repairing torrents with broken web seeds + * fix bug where file_completed_alert would not be posted unless file_progress + had been queries by the client + * move files one-by-one when moving storage for a torrent + * fix bug in enum_net() for BSD and Mac + * fix bug in python binding of announce_entry + * fixed bug related to flag_merge_resume_http_seeds flag in add_torrent_params + * fixed inverted priority of incoming piece suggestions + * optimize allow-fast logic + * fix issue where FAST extension messages were not used during handshake + * fixed crash on invalid input in http_parser + * upgraded to libtommath 1.0 + * fixed parsing of IPv6 endpoint with invalid port character separator + * added limited support for new x.pe parameter from BEP 9 + * fixed dht stats counters that weren't being updated + * make sure add_torrent_alert is always posted before other alerts for + the torrent + * fixed peer-class leak when settings per-torrent rate limits + * added a new "preformatted" type to bencode entry variant type + * improved Socks5 support and test coverage + * fix set_settings in python binding + * Added missing alert categories in python binding + * Added dht_get_peers_reply_alert alert in python binding + * fixed updating the node id reported to peers after changing IPs + +1.1.0 release + + * improve robustness and performance of uTP PMTU discovery + * fix duplicate ACK issue in uTP + * support filtering which parts of session state are loaded by load_state() + * deprecate support for adding torrents by HTTP URL + * allow specifying which tracker to scrape in scrape_tracker + * tracker response alerts from user initiated announces/scrapes are now + posted regardless of alert mask + * improve DHT performance when changing external IP (primarily affects + bootstrapping). + * add feature to stop torrents immediately after checking files is done + * make all non-auto managed torrents exempt from queuing logic, including + checking torrents. + * add option to not proxy tracker connections through proxy + * removed sparse-regions feature + * support using 0 disk threads (to perform disk I/O in network thread) + * removed deprecated handle_alert template + * enable logging build config by default (but alert mask disabled by default) + * deprecated RSS API + * experimental support for BEP 38, "mutable torrents" + * replaced lazy_bdecode with a new bdecoder that's a lot more efficient + * deprecate time functions, expose typedefs of boost::chrono in the + libtorrent namespace instead + * deprecate file_base feature in file_storage/torrent_info + * changed default piece and file priority to 4 (previously 1) + * improve piece picker support for reverse picking (used for snubbed peers) + to not cause priority inversion for regular peers + * improve piece picker to better support torrents with very large pieces + and web seeds. (request large contiguous ranges, but not necessarily a + whole piece). + * deprecated session_status and session::status() in favor of performance + counters. + * improve support for HTTP where one direction of the socket is shut down. + * remove internal fields from web_seed_entry + * separate crypto library configuration and whether to support + bittorrent protocol encryption + * simplify bittorrent protocol encryption by just using internal RC4 + implementation. + * optimize copying torrent_info and file_storage objects + * cancel non-critical DNS lookups when shutting down, to cut down on + shutdown delay. + * greatly simplify the debug logging infrastructure. logs are now delivered + as alerts, and log level is controlled by the alert mask. + * removed auto_expand_choker. use rate_based_choker instead + * optimize UDP tracker packet handling + * support SSL over uTP connections + * support web seeds that resolve to multiple IPs + * added auto-sequential feature. download well-seeded torrents in-order + * removed built-in GeoIP support (this functionality is orthogonal to + libtorrent) + * deprecate proxy settings in favor of regular settings + * deprecate separate settings for peer protocol encryption + * support specifying listen interfaces and outgoing interfaces as device + names (eth0, en2, tun0 etc.) + * support for using purgrable memory as disk cache on Mac OS. + * be more aggressive in corking sockets, to coalesce messages into larger + packets. + * pre-emptively unchoke peers to save one round-trip at connection start-up. + * add session constructor overload that takes a settings_pack + * torrent_info is no longer an intrusive_ptr type. It is held by shared_ptr. + This is a non-backwards compatible change + * move listen interface and port to the settings + * move use_interfaces() to be a setting + * extend storage interface to allow deferred flushing and flush the part-file + metadata periodically + * make statistics propagate instantly rather than on the second tick + * support for partfiles, where partial pieces belonging to skipped files are + put + * support using multiple threads for socket operations (especially useful for + high performance SSL connections) + * allow setting rate limits for arbitrary peer groups. Generalizes + per-torrent rate limits, and local peer limits + * improved disk cache complexity O(1) instead of O(log(n)) + * add feature to allow storing disk cache blocks in an mmapped file + (presumably on an SSD) + * optimize peer connection distribution logic across torrents to scale + better with many torrents + * replaced std::map with boost::unordered_map for torrent list, to scale + better with many torrents + * optimized piece picker + * optimized disk cache + * optimized .torrent file parsing + * optimized initialization of storage when adding a torrent + * added support for adding torrents asynchronously (for improved startup + performance) + * added support for asynchronous disk I/O + * almost completely changed the storage interface (for custom storage) + * added support for hashing pieces in multiple threads + + * fix padfile issue + * fix PMTUd bug + * update puff to fix gzip crash + +1.0.10 release + + * fixed inverted priority of incoming piece suggestions + * fixed crash on invalid input in http_parser + * added a new "preformatted" type to bencode entry variant type + * fix division by zero in super-seeding logic + +1.0.9 release + + * fix issue in checking outgoing interfaces (when that option is enabled) + * python binding fix for boost-1.60.0 + * optimize enumeration of network interfaces on windows + * improve reliability of binding listen sockets + * support SNI in https web seeds and trackers + * fix unhandled exception in DHT when receiving a DHT packet over IPv6 + +1.0.8 release + + * fix bug where web seeds were not used for torrents added by URL + * fix support for symlinks on windows + * fix long filename issue (on unixes) + * fixed performance bug in DHT torrent eviction + * fixed win64 build (GetFileAttributesEx) + * fixed bug when deleting files for magnet links before they had metadata + +1.0.7 release + + * fix bug where loading settings via load_state() would not trigger all + appropriate actions + * fix bug where 32 bit builds could use more disk cache than the virtual + address space (when set to automatic) + * fix support for torrents with > 500'000 pieces + * fix ip filter bug when banning peers + * fix IPv6 IP address resolution in URLs + * introduce run-time check for torrent info-sections being too large + * fix web seed bug when using proxy and proxy-peer-connections=false + * fix bug in magnet link parser + * introduce add_torrent_params flags to merge web seeds with resume data + (similar to trackers) + * fix bug where dont_count_slow_torrents could not be disabled + * fix fallocate hack on linux (fixes corruption on some architectures) + * fix auto-manage bug with announce to tracker/lsd/dht limits + * improve DHT routing table to not create an unbalanced tree + * fix bug in uTP that would cause any connection taking more than one second + to connect be timed out (introduced in the vulnerability path) + * fixed falling back to sending UDP packets direct when socks proxy fails + * fixed total_wanted bug (when setting file priorities in add_torrent_params) + * fix python3 compatibility with sha1_hash + +1.0.6 release + + * fixed uTP vulnerability + * make utf8 conversions more lenient + * fix loading of piece priorities from resume data + * improved seed-mode handling (seed-mode will now automatically be left when + performing operations implying it's not a seed) + * fixed issue with file priorities and override resume data + * fix request queue size performance issue + * slightly improve UDP tracker performance + * fix http scrape + * add missing port mapping functions to python binding + * fix bound-checking issue in bdecoder + * expose missing dht_settings fields to python + * add function to query the DHT settings + * fix bug in 'dont_count_slow_torrents' feature, which would start too many + torrents + +1.0.5 release + + * improve ip_voter to avoid flapping + * fixed bug when max_peerlist_size was set to 0 + * fix issues with missing exported symbols when building dll + * fix division by zero bug in edge case while connecting peers + +1.0.4 release + + * fix bug in python binding for file_progress on torrents with no metadata + * fix assert when removing a connected web seed + * fix bug in tracker timeout logic + * switch UPnP post back to HTTP 1.1 + * support conditional DHT get + * OpenSSL build fixes + * fix DHT scrape bug + +1.0.3 release + + * python binding build fix for boost-1.57.0 + * add --enable-export-all option to configure script, to export all symbols + from libtorrent + * fix if_nametoindex build error on windows + * handle overlong utf-8 sequences + * fix link order bug in makefile for python binding + * fix bug in interest calculation, causing premature disconnects + * tweak flag_override_resume_data semantics to make more sense (breaks + backwards compatibility of edge-cases) + * improve DHT bootstrapping and periodic refresh + * improve DHT maintanence performance (by pinging instead of full lookups) + * fix bug in DHT routing table node-id prefix optimization + * fix incorrect behavior of flag_use_resume_save_path + * fix protocol race-condition in super seeding mode + * support read-only DHT nodes + * remove unused partial hash DHT lookups + * remove potentially privacy leaking extension (non-anonymous mode) + * peer-id connection ordering fix in anonymous mode + * mingw fixes + +1.0.2 release + + * added missing force_proxy to python binding + * anonymous_mode defaults to false + * make DHT DOS detection more forgiving to bursts + * support IPv6 multicast in local service discovery + * simplify CAS function in DHT put + * support IPv6 traffic class (via the TOS setting) + * made uTP re-enter slow-start after time-out + * fixed uTP upload performance issue + * fix missing support for DHT put salt + +1.0.1 release + + * fix alignment issue in bitfield + * improved error handling of gzip + * fixed crash when web seeds redirect + * fix compiler warnings + +1.0 release + + * fix bugs in convert_to/from_native() on windows + * fix support for web servers not supporting keepalive + * support storing save_path in resume data + * don't use full allocation on network drives (on windows) + * added clear_piece_deadlines() to remove all piece deadlines + * improve queuing logic of inactive torrents (dont_count_slow_torrents) + * expose optimistic unchoke logic to plugins + * fix issue with large UDP packets on windows + * remove set_ratio() feature + * improve piece_deadline/streaming + * honor pieces with priority 7 in sequential download mode + * simplified building python bindings + * make ignore_non_routers more forgiving in the case there are no UPnP + devices at a known router. Should improve UPnP compatibility. + * include reason in peer_blocked_alert + * support magnet links wrapped in .torrent files + * rate limiter optimization + * rate limiter overflow fix (for very high limits) + * non-auto-managed torrents no longer count against the torrent limits + * handle DHT error responses correctly + * allow force_announce to only affect a single tracker + * add moving_storage field to torrent_status + * expose UPnP and NAT-PMP mapping in session object + * DHT refactoring and support for storing arbitrary data with put and get + * support building on android + * improved support for web seeds that don't support keep-alive + * improve DHT routing table to return better nodes (lower RTT and closer + to target) + * don't use pointers to resume_data and file_priorities in + add_torrent_params + * allow moving files to absolute paths, out of the download directory + * make move_storage more generic to allow both overwriting files as well + as taking existing ones + * fix choking issue at high upload rates + * optimized rate limiter + * make disk cache pool allocator configurable + * fix library ABI to not depend on logging being enabled + * use hex encoding instead of base32 in create_magnet_uri + * include name, save_path and torrent_file in torrent_status, for + improved performance + * separate anonymous mode and force-proxy mode, and tighten it up a bit + * add per-tracker scrape information to announce_entry + * report errors in read_piece_alert + * DHT memory optimization + * improve DHT lookup speed + * improve support for windows XP and earlier + * introduce global connection priority for improved swarm performance + * make files deleted alert non-discardable + * make built-in sha functions not conflict with libcrypto + * improve web seed hash failure case + * improve DHT lookup times + * uTP path MTU discovery improvements + * optimized the torrent creator optimizer to scale significantly better + with more files + * fix uTP edge case where udp socket buffer fills up + * fix nagle implementation in uTP + + * fix bug in error handling in protocol encryption + +0.16.18 release + + * fix uninitialized values in DHT DOS mitigation + * fix error handling in file::phys_offset + * fix bug in HTTP scrape response parsing + * enable TCP keepalive for socks5 connection for UDP associate + * fix python3 support + * fix bug in lt_donthave extension + * expose i2p_alert to python. cleaning up of i2p connection code + * fixed overflow and download performance issue when downloading at high rates + * fixed bug in add_torrent_alert::message for magnet links + * disable optimistic disconnects when connection limit is low + * improved error handling of session::listen_on + * suppress initial 'completed' announce to trackers added with replace_trackers + after becoming a seed + * SOCKS4 fix for trying to connect over IPv6 + * fix saving resume data when removing all trackers + * fix bug in udp_socket when changing socks5 proxy quickly + +0.16.17 release + + * don't fall back on wildcard port in UPnP + * fix local service discovery for magnet links + * fix bitfield issue in file_storage + * added work-around for MingW issue in file I/O + * fixed sparse file detection on windows + * fixed bug in gunzip + * fix to use proxy settings when adding .torrent file from URL + * fix resume file issue related to daylight savings time on windows + * improve error checking in lazy_bdecode + +0.16.16 release + + * add missing add_files overload to the python bindings + * improve error handling in http gunzip + * fix debug logging for banning web seeds + * improve support for de-selected files in full allocation mode + * fix dht_bootstrap_alert being posted + * SetFileValidData fix on windows (prevents zero-fill) + * fix minor lock_files issue on unix + +0.16.15 release + + * fix mingw time_t 64 bit issue + * fix use of SetFileValidData on windows + * fix crash when using full allocation storage mode + * improve error_code and error_category support in python bindings + * fix python binding for external_ip_alert + +0.16.14 release + + * make lt_tex more robust against bugs and malicious behavior + * HTTP chunked encoding fix + * expose file_granularity flag to python bindings + * fix DHT memory error + * change semantics of storage allocation to allocate on first write rather + than on startup (behaves better with changing file priorities) + * fix resend logic in response to uTP SACK messages + * only act on uTP RST packets with correct ack_nr + * make uTP errors log in normal log mode (not require verbose) + * deduplicate web seed entries from torrent files + * improve error reporting from lazy_decode() + +0.16.13 release + + * fix auto-manage issue when pausing session + * fix bug in non-sparse mode on windows, causing incorrect file errors to + be generated + * fix set_name() on file_storage actually affecting save paths + * fix large file support issue on mingw + * add some error handling to set_piece_hashes() + * fix completed-on timestamp to not be clobbered on each startup + * fix deadlock caused by some UDP tracker failures + * fix potential integer overflow issue in timers on windows + * minor fix to peer_proportional mixed_mode algorithm (TCP limit could go + too low) + * graceful pause fix + * i2p fixes + * fix issue when loading certain malformed .torrent files + * pass along host header with http proxy requests and possible + http_connection shutdown hang + +0.16.12 release + + * fix building with C++11 + * fix IPv6 support in UDP socket (uTP) + * fix mingw build issues + * increase max allowed outstanding piece requests from peers + * uTP performance improvement. only fast retransmit one packet at a time + * improve error message for 'file too short' + * fix piece-picker stat bug when only selecting some files for download + * fix bug in async_add_torrent when settings file_priorities + * fix boost-1.42 support for python bindings + * fix memory allocation issue (virtual addres space waste) on windows + +0.16.11 release + + * fix web seed URL double escape issue + * fix string encoding issue in alert messages + * fix SSL authentication issue + * deprecate std::wstring overloads. long live utf-8 + * improve time-critical pieces feature (streaming) + * introduce bandwidth exhaustion attack-mitigation in allowed-fast pieces + * python binding fix issue where torrent_info objects where destructing when + their torrents were deleted + * added missing field to scrape_failed_alert in python bindings + * GCC 4.8 fix + * fix proxy failure semantics with regards to anonymous mode + * fix round-robin seed-unchoke algorithm + * add bootstrap.sh to generage configure script and run configure + * fix bug in SOCK5 UDP support + * fix issue where torrents added by URL would not be started immediately + +0.16.10 release + + * fix encryption level handle invalid values + * add a number of missing functions to the python binding + * fix typo in Jamfile for building shared libraries + * prevent tracker exchange for magnet links before metadata is received + * fix crash in make_magnet_uri when generating links longer than 1024 + characters + * fix hanging issue when closing files on windows (completing a download) + * fix piece picking edge case that could cause torrents to get stuck at + hash failure + * try unencrypted connections first, and fall back to encryption if it + fails (performance improvement) + * add missing functions to python binding (flush_cache(), remap_files() + and orig_files()) + * improve handling of filenames that are invalid on windows + * support 'implied_port' in DHT announce_peer + * don't use pool allocator for disk blocks (cache may now return pages + to the kernel) + +0.16.9 release + + * fix long filename truncation on windows + * distinguish file open mode when checking files and downloading/seeding + with bittorrent. updates storage interface + * improve file_storage::map_file when dealing with invalid input + * improve handling of invalid utf-8 sequences in strings in torrent files + * handle more cases of broken .torrent files + * fix bug filename collision resolver + * fix bug in filename utf-8 verification + * make need_save_resume() a bit more robust + * fixed sparse flag manipulation on windows + * fixed streaming piece picking issue + +0.16.8 release + + * make rename_file create missing directories for new filename + * added missing python function: parse_magnet_uri + * fix alerts.all_categories in python binding + * fix torrent-abort issue which would cancel name lookups of other torrents + * make torrent file parser reject invalid path elements earlier + * fixed piece picker bug when using pad-files + * fix read-piece response for cancelled deadline-pieces + * fixed file priority vector-overrun + * fix potential packet allocation alignment issue in utp + * make 'close_redudnant_connections' cover more cases + * set_piece_deadline() also unfilters the piece (if its priority is 0) + * add work-around for bug in windows vista and earlier in + GetOverlappedResult + * fix traversal algorithm leak in DHT + * fix string encoding conversions on windows + * take torrent_handle::query_pieces into account in torrent_handle::statue() + * honor trackers responding with 410 + * fixed merkle tree torrent creation bug + * fixed crash with empty url-lists in torrent files + * added missing max_connections() function to python bindings + +0.16.7 release + + * fix string encoding in error messages + * handle error in read_piece and set_piece_deadline when torrent is removed + * DHT performance improvement + * attempt to handle ERROR_CANT_WAIT disk error on windows + * improve peers exchanged over PEX + * fixed rare crash in ut_metadata extension + * fixed files checking issue + * added missing pop_alerts() to python bindings + * fixed typos in configure script, inversing some feature-enable/disable flags + * added missing flag_update_subscribe to python bindings + * active_dht_limit, active_tracker_limit and active_lsd_limit now + interpret -1 as infinite + +0.16.6 release + + * fixed verbose log error for NAT holepunching + * fix a bunch of typos in python bindings + * make get_settings available in the python binding regardless of + deprecated functions + * fix typo in python settings binding + * fix possible dangling pointer use in peer list + * fix support for storing arbitrary data in the DHT + * fixed bug in uTP packet circle buffer + * fix potential crash when using torrent_handle::add_piece + * added missing add_torrent_alert to python binding + +0.16.5 release + + * udp socket refcounter fix + * added missing async_add_torrent to python bindings + * raised the limit for bottled http downloads to 2 MiB + * add support for magnet links and URLs in python example client + * fixed typo in python bindings' add_torrent_params + * introduce a way to add built-in plugins from python + * consistently disconnect the same peer when two peers simultaneously connect + * fix local endpoint queries for uTP connections + * small optimization to local peer discovery to ignore our own broadcasts + * try harder to bind the udp socket (uTP, DHT, UDP-trackers, LSD) to the + same port as TCP + * relax file timestamp requirements for accepting resume data + * fix performance issue in web seed downloader (coalescing of blocks + sometimes wouldn't work) + * web seed fixes (better support for torrents without trailing / in + web seeds) + * fix some issues with SSL over uTP connections + * fix UDP trackers trying all endpoints behind the hostname + +0.16.4 release + + * raise the default number of torrents allowed to announce to trackers + to 1600 + * improve uTP slow start behavior + * fixed UDP socket error causing it to fail on Win7 + * update use of boost.system to not use deprecated functions + * fix GIL issue in python bindings. Deprecated extension support in python + * fixed bug where setting upload slots to -1 would not mean infinite + * extend the UDP tracker protocol to include the request string from the + tracker URL + * fix mingw build for linux crosscompiler + +0.16.3 release + + * fix python binding backwards compatibility in replace_trackers + * fix possible starvation in metadata extension + * fix crash when creating torrents and optimizing file order with pad files + * disable support for large MTUs in uTP until it is more reliable + * expose post_torrent_updates and state_update_alert to python bindings + * fix incorrect SSL error messages + * fix windows build of shared library with openssl + * fix race condition causing shutdown hang + +0.16.2 release + + * fix permissions issue on linux with noatime enabled for non-owned files + * use random peer IDs in anonymous mode + * fix move_storage bugs + * fix unnecessary dependency on boost.date_time when building boost.asio as separate compilation + * always use SO_REUSEADDR and deprecate the flag to turn it on + * add python bindings for SSL support + * minor uTP tweaks + * fix end-game mode issue when some files are selected to not be downloaded + * improve uTP slow start + * make uTP less aggressive resetting cwnd when idle + +0.16.1 release + + * fixed crash when providing corrupt resume data + * fixed support for boost-1.44 + * fixed reversed semantics of queue_up() and queue_down() + * added missing functions to python bindings (file_priority(), set_dht_settings()) + * fixed low_prio_disk support on linux + * fixed time critical piece accounting in the request queue + * fixed semantics of rate_limit_utp to also ignore per-torrent limits + * fixed piece sorting bug of deadline pieces + * fixed python binding build on Mac OS and BSD + * fixed UNC path normalization (on windows, unless UNC paths are disabled) + * fixed possible crash when enabling multiple connections per IP + * fixed typo in win vista specific code, breaking the build + * change default of rate_limit_utp to true + * fixed DLL export issue on windows (when building a shared library linking statically against boost) + * fixed FreeBSD build + * fixed web seed performance issue with pieces > 1 MiB + * fixed unchoke logic when using web seeds + * fixed compatibility with older versions of boost (down to boost 1.40) + +0.16 release + + * support torrents with more than 262000 pieces + * make tracker back-off configurable + * don't restart the swarm after downloading metadata from magnet links + * lower the default tracker retry intervals + * support banning web seeds sending corrupt data + * don't let hung outgoing connection attempts block incoming connections + * improve SSL torrent support by using SNI and a single SSL listen socket + * improved peer exchange performance by sharing incoming connections which advertize listen port + * deprecate set_ratio(), and per-peer rate limits + * add web seed support for torrents with pad files + * introduced a more scalable API for torrent status updates (post_torrent_updates()) and updated client_test to use it + * updated the API to add_torrent_params turning all bools into flags of a flags field + * added async_add_torrent() function to significantly improve performance when + adding many torrents + * change peer_states to be a bitmask (bw_limit, bw_network, bw_disk) + * changed semantics of send_buffer_watermark_factor to be specified as a percentage + * add incoming_connection_alert for logging all successful incoming connections + * feature to encrypt peer connections with a secret AES-256 key stored in .torrent file + * deprecated compact storage allocation + * close files in separate thread on systems where close() may block (Mac OS X for instance) + * don't create all directories up front when adding torrents + * support DHT scrape + * added support for fadvise/F_RDADVISE for improved disk read performance + * introduced pop_alerts() which pops the entire alert queue in a single call + * support saving metadata in resume file, enable it by default for magnet links + * support for receiving multi announce messages for local peer discovery + * added session::listen_no_system_port flag to prevent libtorrent from ever binding the listen socket to port 0 + * added option to not recheck on missing or incomplete resume data + * extended stats logging with statistics=on builds + * added new session functions to more efficiently query torrent status + * added alerts for added and removed torrents + * expanded plugin interface to support session wide states + * made the metadata block requesting algorithm more robust against hash check failures + * support a separate option to use proxies for peers or not + * pausing the session now also pauses checking torrents + * moved alert queue size limit into session_settings + * added support for DHT rss feeds (storing only) + * added support for RSS feeds + * fixed up some edge cases in DHT routing table and improved unit test of it + * added error category and error codes for HTTP errors + * made the DHT implementation slightly more robust against routing table poisoning and node ID spoofing + * support chunked encoding in http downloads (http_connection) + * support adding torrents by url to the .torrent file + * support CDATA tags in xml parser + * use a python python dictionary for settings instead of session_settings object (in python bindings) + * optimized metadata transfer (magnet link) startup time (shaved off about 1 second) + * optimized swarm startup time (shaved off about 1 second) + * support DHT name lookup + * optimized memory usage of torrent_info and file_storage, forcing some API changes + around file_storage and file_entry + * support trackerid tracker extension + * graceful peer disconnect mode which finishes transactions before disconnecting peers + * support chunked encoding for web seeds + * uTP protocol support + * resistance towards certain flood attacks + * support chunked encoding for web seeds (only for BEP 19, web seeds) + * optimized session startup time + * support SSL for web seeds, through all proxies + * support extending web seeds with custom authorization and extra headers + * settings that are not changed from the default values are not saved + in the session state + * made seeding choking algorithm configurable + * deprecated setters for max connections, max half-open, upload and download + rates and unchoke slots. These are now set through session_settings + * added functions to query an individual peer's upload and download limit + * full support for BEP 21 (event=paused) + * added share-mode feature for improving share ratios + * merged all proxy settings into a single one + * improved SOCKS5 support by proxying hostname lookups + * improved support for multi-homed clients + * added feature to not count downloaded bytes from web seeds in stats + * added alert for incoming local service discovery messages + * added option to set file priorities when adding torrents + * removed the session mutex for improved performance + * added upload and download activity timer stats for torrents + * made the reuse-address flag configurable on the listen socket + * moved UDP trackers over to use a single socket + * added feature to make asserts log to a file instead of breaking the process + (production asserts) + * optimized disk I/O cache clearing + * added feature to ask a torrent if it needs to save its resume data or not + * added setting to ignore file modification time when loading resume files + * support more fine-grained torrent states between which peer sources it + announces to + * supports calculating sha1 file-hashes when creating torrents + * made the send_buffer_watermark performance warning more meaningful + * supports complete_ago extension + * dropped zlib as a dependency and builds using puff.c instead + * made the default cache size depend on available physical RAM + * added flags to torrent::status() that can filter which values are calculated + * support 'explicit read cache' which keeps a specific set of pieces + in the read cache, without implicitly caching other pieces + * support sending suggest messages based on what's in the read cache + * clear sparse flag on files that complete on windows + * support retry-after header for web seeds + * replaced boost.filesystem with custom functions + * replaced dependency on boost.thread by asio's internal thread primitives + * added support for i2p torrents + * cleaned up usage of MAX_PATH and related macros + * made it possible to build libtorrent without RTTI support + * added support to build with libgcrypt and a shipped version of libtommath + * optimized DHT routing table memory usage + * optimized disk cache to work with large caches + * support variable number of optimistic unchoke slots and to dynamically + adjust based on the total number of unchoke slots + * support for BitTyrant choker algorithm + * support for automatically start torrents when they receive an + incoming connection + * added more detailed instrumentation of the disk I/O thread + +0.15.11 release + + * fixed web seed bug, sometimes causing infinite loops + * fixed race condition when setting session_settings immediately after creating session + * give up immediately when failing to open a listen socket (report the actual error) + * restored ABI compatibility with 0.15.9 + * added missing python bindings for create_torrent and torrent_info + +0.15.10 release + + * fix 'parameter incorrect' issue when using unbuffered IO on windows + * fixed UDP socket error handling on windows + * fixed peer_tos (type of service) setting + * fixed crash when loading resume file with more files than the torrent in it + * fix invalid-parameter error on windows when disabling filesystem disk cache + * fix connection queue issue causing shutdown delays + * fixed mingw build + * fix overflow bug in progress_ppm field + * don't filter local peers received from a non-local tracker + * fix python deadlock when using python extensions + * fixed small memory leak in DHT + +0.15.9 release + + * added some functions missing from the python binding + * fixed rare piece picker bug + * fixed invalid torrent_status::finished_time + * fixed bugs in dont-have and upload-only extension messages + * don't open files in random-access mode (speeds up hashing) + +0.15.8 release + + * allow NULL to be passed to create_torrent::set_comment and create_torrent::set_creator + * fix UPnP issue for routers with multiple PPPoE connections + * fix issue where event=stopped announces wouldn't be sent when closing session + * fix possible hang in file::readv() on windows + * fix CPU busy loop issue in tracker announce logic + * honor IOV_MAX when using writev and readv + * don't post 'operation aborted' UDP errors when changing listen port + * fix tracker retry logic, where in some configurations the next tier would not be tried + * fixed bug in http seeding logic (introduced in 0.15.7) + * add support for dont-have extension message + * fix for set_piece_deadline + * add reset_piece_deadline function + * fix merkle tree torrent assert + +0.15.7 release + + * exposed set_peer_id to python binding + * improve support for merkle tree torrent creation + * exposed comparison operators on torrent_handle to python + * exposed alert error_codes to python + * fixed bug in announce_entry::next_announce_in and min_announce_in + * fixed sign issue in set_alert_mask signature + * fixed unaligned disk access for unbuffered I/O in windows + * support torrents whose name is empty + * fixed connection limit to take web seeds into account as well + * fixed bug when receiving a have message before having the metadata + * fixed python bindings build with disabled DHT support + * fixed BSD file allocation issue + * fixed bug in session::delete_files option to remove_torrent + +0.15.6 release + + * fixed crash in udp trackers when using SOCKS5 proxy + * fixed reconnect delay when leaving upload only mode + * fixed default values being set incorrectly in add_torrent_params through add_magnet_uri in python bindings + * implemented unaligned write (for unbuffered I/O) + * fixed broadcast_lsd option + * fixed udp-socket race condition when using a proxy + * end-game mode optimizations + * fixed bug in udp_socket causing it to issue two simultaneous async. read operations + * fixed mingw build + * fixed minor bug in metadata block requester (for magnet links) + * fixed race condition in iconv string converter + * fixed error handling in torrent_info constructor + * fixed bug in torrent_info::remap_files + * fix python binding for wait_for_alert + * only apply privileged port filter to DHT-only peers + +0.15.5 release + + * support DHT extension to report external IPs + * fixed rare crash in http_connection's error handling + * avoid connecting to peers listening on ports < 1024 + * optimized piece picking to not cause busy loops in some end-game modes + * fixed python bindings for tcp::endpoint + * fixed edge case of pad file support + * limit number of torrents tracked by DHT + * fixed bug when allow_multiple_connections_per_ip was enabled + * potential WOW64 fix for unbuffered I/O (windows) + * expose set_alert_queue_size_limit to python binding + * support dht nodes in magnet links + * support 100 Continue HTTP responses + * changed default choker behavior to use 8 unchoke slots (instead of being rate based) + * fixed error reporting issue in disk I/O thread + * fixed file allocation issues on linux + * fixed filename encoding and decoding issue on platforms using iconv + * reports redundant downloads to tracker, fixed downloaded calculation to + be more stable when not including redundant. Improved redundant data accounting + to be more accurate + * fixed bugs in http seed connection and added unit test for it + * fixed error reporting when fallocate fails + * deprecate support for separate proxies for separate kinds of connections + +0.15.4 release + + * fixed piece picker issue triggered by hash failure and timed out requests to the piece + * fixed optimistic unchoke issue when setting per torrent unchoke limits + * fixed UPnP shutdown issue + * fixed UPnP DeletePortmapping issue + * fixed NAT-PMP issue when adding the same mapping multiple times + * no peers from tracker when stopping is no longer an error + * improved web seed retry behavior + * fixed announce issue + +0.15.3 release + + * fixed announce bug where event=completed would not be sent if it violated the + min-announce of the tracker + * fixed limitation in rate limiter + * fixed build error with boost 1.44 + +0.15.2 release + + * updated compiler to msvc 2008 for python binding + * restored default fail_limit to unlimited on all trackers + * fixed rate limit bug for DHT + * fixed SOCKS5 bug for routing UDP packets + * fixed bug on windows when verifying resume data for a torrent where + one of its directories had been removed + * fixed race condition in peer-list with DHT + * fix force-reannounce and tracker retry issue + +0.15.1 release + + * fixed rare crash when purging the peer list + * fixed race condition around m_abort in session_impl + * fixed bug in web_peer_connection which could cause a hang when downloading + from web servers + * fixed bug in metadata extensions combined with encryption + * refactored socket reading code to not use async. operations unnecessarily + * some timer optimizations + * removed the reuse-address flag on the listen socket + * fixed bug where local peer discovery and DHT wouldn't be announced to without trackers + * fixed bug in bdecoder when decoding invalid messages + * added build warning when building with UNICODE but the standard library + doesn't provide std::wstring + * fixed add_node python binding + * fixed issue where trackers wouldn't tried immediately when the previous one failed + * fixed synchronization issue between download queue and piece picker + * fixed bug in udp tracker scrape response parsing + * fixed bug in the disk thread that could get triggered under heavy load + * fixed bug in add_piece() that would trigger asserts + * fixed vs 2010 build + * recognizes more clients in identify_client() + * fixed bug where trackers wouldn't be retried if they failed + * slight performance fix in disk elevator algorithm + * fixed potential issue where a piece could be checked twice + * fixed build issue on windows related to GetCompressedSize() + * fixed deadlock when starting torrents with certain invalid tracker URLs + * fixed iterator bug in disk I/O thread + * fixed FIEMAP support on linux + * fixed strict aliasing warning on gcc + * fixed inconsistency when creating torrents with symlinks + * properly detect windows version to initialize half-open connection limit + * fixed bug in url encoder where $ would not be encoded + +0.15 release + + * introduced a session state save mechanism. load_state() and save_state(). + this saves all session settings and state (except torrents) + * deprecated dht_state functions and merged it with the session state + * added support for multiple trackers in magnet links + * added support for explicitly flushing the disk cache + * added torrent priority to affect bandwidth allocation for its peers + * reduced the number of floating point operations (to better support + systems without FPU) + * added new alert when individual files complete + * added support for storing symbolic links in .torrent files + * added support for uTorrent interpretation of multi-tracker torrents + * handle torrents with duplicate filenames + * piece timeouts are adjusted to download rate limits + * encodes urls in torrent files that needs to be encoded + * fixed not passing &supportcrypto=1 when encryption is disabled + * introduced an upload mode, which torrents are switched into when + it hits a disk write error, instead of stopping the torrent. + this lets libtorrent keep uploading the parts it has when it + encounters a disk-full error for instance + * improved disk error handling and expanded use of error_code in + error reporting. added a bandwidth state, bw_disk, when waiting + for the disk io thread to catch up writing buffers + * improved read cache memory efficiency + * added another cache flush algorithm to write the largest + contiguous blocks instead of the least recently used + * introduced a mechanism to be lighter on the disk when checking torrents + * applied temporary memory storage optimization to when checking + a torrent as well + * removed hash_for_slot() from storage_interface. It is now implemented + by using the readv() function from the storage implementation + * improved IPv6 support by announcing twice when necessary + * added feature to set a separate global rate limit for local peers + * added preset settings for low memory environments and seed machines + min_memory_usage() and high_performance_seeder() + * optimized overall memory usage for DHT nodes and requests, peer + entries and disk buffers + * change in API for block_info in partial_piece_info, instead of + accessing 'peer', call 'peer()' + * added support for fully automatic unchoker (no need to specify + number of upload slots). This is on by default + * added support for changing socket buffer sizes through + session_settings + * added support for merkle hash tree torrents (.merkle.torrent) + * added 'seed mode', which assumes that all files are complete + and checks hashes lazily, as blocks are requested + * added new extension for file attributes (executable and hidden) + * added support for unbuffered I/O for aligned files + * added workaround for sparse file issue on Windows Vista + * added new lt_trackers extension to exchange trackers between + peers + * added support for BEP 17 http seeds + * added read_piece() to read pieces from torrent storage + * added option for udp tracker preference + * added super seeding + * added add_piece() function to inject data from external sources + * add_tracker() function added to torrent_handle + * if there is no working tracker, current_tracker is the + tracker that is currently being tried + * torrents that are checking can now be paused, which will + pause the checking + * introduced another torrent state, checking_resume_data, which + the torrent is in when it's first added, and is comparing + the files on disk with the resume data + * DHT bandwidth usage optimizations + * rate limited DHT send socket + * tracker connections are now also subject to IP filtering + * improved optimistic unchoke logic + * added monitoring of the DHT lookups + * added bandwidth reports for estimated TCP/IP overhead and DHT + * includes DHT traffic in the rate limiter + * added support for bitcomet padding files + * improved support for sparse files on windows + * added ability to give seeding torrents preference to active slots + * added torrent_status::finished_time + * automatically caps files and connections by default to rlimit + * added session::is_dht_running() function + * added torrent_handle::force_dht_announce() + * added torrent_info::remap_files() + * support min_interval tracker extension + * added session saving and loading functions + * added support for min-interval in tracker responses + * only keeps one outstanding duplicate request per peer + reduces waste download, specifically when streaming + * added support for storing per-peer rate limits across reconnects + * improved fallocate support + * fixed magnet link issue when using resume data + * support disk I/O priority settings + * added info_hash to torrent_deleted_alert + * improved LSD performance and made the interval configurable + * improved UDP tracker support by caching connect tokens + * fast piece optimization + +release 0.14.10 + + * fixed udp tracker race condition + * added support for torrents with odd piece sizes + * fixed issue with disk read cache not being cleared when removing torrents + * made the DHT socket bind to the same interface as the session + * fixed issue where an http proxy would not be used on redirects + * Solaris build fixes + * disabled buggy disconnect_peers feature + +release 0.14.9 + + * disabled feature to drop requests after having been skipped too many times + * fixed range request bug for files larger than 2 GB in web seeds + * don't crash when trying to create torrents with 0 files + * fixed big_number __init__ in python bindings + * fixed optimistic unchoke timer + * fixed bug where torrents with incorrectly formatted web seed URLs would be + connected multiple times + * fixed MinGW support + * fixed DHT bootstrapping issue + * fixed UDP over SOCKS5 issue + * added support for "corrupt" tracker announce + * made end-game mode less aggressive + +release 0.14.8 + + * ignore unkown metadata messages + * fixed typo that would sometimes prevent queued torrents to be checked + * fixed bug in auto-manager where active_downloads and active_seeds would + sometimes be used incorrectly + * force_recheck() no longer crashes on torrents with no metadata + * fixed broadcast socket regression from 0.14.7 + * fixed hang in NATPMP when shut down while waiting for a response + * fixed some more error handling in bdecode + +release 0.14.7 + + * fixed deadlock in natpmp + * resume data alerts are always posted, regardless of alert mask + * added wait_for_alert to python binding + * improved invalid filename character replacement + * improved forward compatibility in DHT + * added set_piece_hashes that takes a callback to the python binding + * fixed division by zero in get_peer_info() + * fixed bug where pieces may have been requested before the metadata + was received + * fixed incorrect error when deleting files from a torrent where + not all files have been created + * announces torrents immediately to the DHT when it's started + * fixed bug in add_files that would fail to recurse if the path + ended with a / + * fixed bug in error handling when parsing torrent files + * fixed file checking bug when renaming a file before checking the torrent + * fixed race conditon when receiving metadata from swarm + * fixed assert in ut_metadata plugin + * back-ported some fixes for building with no exceptions + * fixed create_torrent when passing in a path ending with / + * fixed move_storage when source doesn't exist + * fixed DHT state save bug for node-id + * fixed typo in python binding session_status struct + * broadcast sockets now join every network interface (used for UPnP and + local peer discovery) + +release 0.14.6 + + * various missing include fixes to be buildable with boost 1.40 + * added missing functions to python binding related to torrent creation + * fixed to add filename on web seed urls that lack it + * fixed BOOST_ASIO_HASH_MAP_BUCKETS define for boost 1.39 + * fixed checking of fast and suggest messages when used with magnet links + * fixed bug where web seeds would not disconnect if being resolved when + the torrent was paused + * fixed download piece performance bug in piece picker + * fixed bug in connect candidate counter + * replaces invalid filename characters with . + * added --with-libgeoip option to configure script to allow building and + linking against system wide library + * fixed potential pure virtual function call in extensions on shutdown + * fixed disk buffer leak in smart_ban extension + +release 0.14.5 + + * fixed bug when handling malformed webseed urls and an http proxy + * fixed bug when setting unlimited upload or download rates for torrents + * fix to make torrent_status::list_peers more accurate. + * fixed memory leak in disk io thread when not using the cache + * fixed bug in connect candidate counter + * allow 0 upload slots + * fixed bug in rename_file(). The new name would not always be saved in + the resume data + * fixed resume data compatibility with 0.13 + * fixed rare piece-picker bug + * fixed bug where one allowed-fast message would be sent even when + disabled + * fixed race condition in UPnP which could lead to crash + * fixed inversed seed_time ratio logic + * added get_ip_filter() to session + +release 0.14.4 + + * connect candidate calculation fix + * tightened up disk cache memory usage + * fixed magnet link parser to accept hex-encoded info-hashes + * fixed inverted logic when picking which peers to connect to + (should mean a slight performance improvement) + * fixed a bug where a failed rename_file() would leave the storage + in an error state which would pause the torrent + * fixed case when move_storage() would fail. Added a new alert + to be posted when it does + * fixed crash bug when shutting down while checking a torrent + * fixed handling of web seed urls that didn't end with a + slash for multi-file torrents + * lowered the default connection speed to 10 connection attempts + per second + * optimized memory usage when checking files fails + * fixed bug when checking a torrent twice + * improved handling of out-of-memory conditions in disk I/O thread + * fixed bug when force-checking a torrent with partial pieces + * fixed memory leak in disk cache + * fixed torrent file path vulnerability + * fixed upnp + * fixed bug when dealing with clients that drop requests (i.e. BitComet) + fixes assert as well + +release 0.14.3 + + * added python binding for create_torrent + * fixed boost-1.38 build + * fixed bug where web seeds would be connected before the files + were checked + * fixed filename bug when using wide characters + * fixed rare crash in peer banning code + * fixed potential HTTP compatibility issue + * fixed UPnP crash + * fixed UPnP issue where the control url contained the base url + * fixed a replace_trackers bug + * fixed bug where the DHT port mapping would not be removed when + changing DHT port + * fixed move_storage bug when files were renamed to be moved out + of the root directory + * added error handling for set_piece_hashes + * fixed missing include in enum_if.cpp + * fixed dual IP stack issue + * fixed issue where renamed files were sometimes not saved in resume data + * accepts tracker responses with no 'peers' field, as long as 'peers6' + is present + * fixed CIDR-distance calculation in the precense of IPv6 peers + * save partial resume data for torrents that are queued for checking + or checking, to maintain stats and renamed files + * Don't try IPv6 on windows if it's not installed + * move_storage fix + * fixed potential crash on shutdown + * fixed leaking exception from bdecode on malformed input + * fixed bug where connection would hang when receiving a keepalive + * fixed bug where an asio exception could be thrown when resolving + peer countries + * fixed crash when shutting down while checking a torrent + * fixed potential crash in connection_queue when a peer_connection + fail to open its socket + +release 0.14.2 + + * added missing functions to the python bindings torrent_info::map_file, + torrent_info::map_block and torrent_info::file_at_offset. + * removed support for boost-1.33 and earlier (probably didn't work) + * fixed potential freezes issues at shutdown + * improved error message for python setup script + * fixed bug when torrent file included announce-list, but no valid + tracker urls + * fixed bug where the files requested from web seeds would be the + renamed file names instead of the original file names in the torrent. + * documentation fix of queing section + * fixed potential issue in udp_socket (affected udp tracker support) + * made name, comment and created by also be subject to utf-8 error + correction (filenames already were) + * fixed dead-lock when settings DHT proxy + * added missing export directives to lazy_entry + * fixed disk cache expiry settings bug (if changed, it would be set + to the cache size) + * fixed bug in http_connection when binding to a particular IP + * fixed typo in python binding (torrent_handle::piece_prioritize should + be torrent_handle::piece_priorities) + * fixed race condition when saving DHT state + * fixed bugs related to lexical_cast being locale dependent + * added support for SunPro C++ compiler + * fixed bug where messeges sometimes could be encrypted in the + wrong order, for encrypted connections. + * fixed race condition where torrents could get stuck waiting to + get checked + * fixed mapped files bug where it wouldn't be properly restored + from resume data properly + * removed locale dependency in xml parser (caused asserts on windows) + * fixed bug when talking to https 1.0 servers + * fixed UPnP bug that could cause stack overflow + +release 0.14.1 + + * added converter for python unicode strings to utf-8 paths + * fixed bug in http downloader where the host field did not + include the port number + * fixed headers to not depend on NDEBUG, which would prohibit + linking a release build of libtorrent against a debug application + * fixed bug in disk I/O thread that would make the thread + sometimes quit when an error occurred + * fixed DHT bug + * fixed potential shutdown crash in disk_io_thread + * fixed usage of deprecated boost.filsystem functions + * fixed http_connection unit test + * fixed bug in DHT when a DHT state was loaded + * made rate limiter change in 0.14 optional (to take estimated + TCP/IP overhead into account) + * made the python plugin buildable through the makefile + * fixed UPnP bug when url base ended with a slash and + path started with a slash + * fixed various potentially leaking exceptions + * fixed problem with removing torrents that are checking + * fixed documentation bug regarding save_resume_data() + * added missing documentation on torrent creation + * fixed bugs in python client examples + * fixed missing dependency in package-config file + * fixed shared geoip linking in Jamfile + * fixed python bindings build on windows and made it possible + to generate a windows installer + * fixed bug in NAT-PMP implementation + +release 0.14 + + * deprecated add_torrent() in favor of a new add_torrent() + that takes a struct with parameters instead. Torrents + are paused and auto managed by default. + * removed 'connecting_to_tracker' torrent state. This changes + the enum values for the other states. + * Improved seeding and choking behavior. + * Fixed rare buffer overrun bug when calling get_download_queue + * Fixed rare bug where torrent could be put back into downloading + state even though it was finished, after checking files. + * Fixed rename_file to work before the file on disk has been + created. + * Fixed bug in tracker connections in case of errors caused + in the connection constructor. + * Updated alert system to be filtered by category instead of + severity level. Alerts can generate a message through + alert::message(). + * Session constructor will now start dht, upnp, natpmp, lsd by + default. Flags can be passed in to the constructor to not + do this, if these features are to be enabled and disabled + at a later point. + * Removed 'connecting_to_tracker' torrent state + * Fix bug where FAST pieces were cancelled on choke + * Fixed problems with restoring piece states when hash failed. + * Minimum peer reconnect time fix. Peers with no failures would + reconnect immediately. + * Improved web seed error handling + * DHT announce fixes and off-by-one loop fix + * Fixed UPnP xml parse bug where it would ignore the port number + for the control url. + * Fixed bug in torrent writer where the private flag was added + outside of the info dictionary + * Made the torrent file parser less strict of what goes in the + announce-list entry + * Fixed type overflow bug where some statistics was incorrectly + reported for file larger than 2 GB + * boost-1.35 support + * Fixed bug in statistics from web server peers where it sometimes + could report too many bytes downloaded. + * Fixed bug where statistics from the last second was lost when + disconnecting a peer. + * receive buffer optimizations (memcpy savings and memory savings) + * Support for specifying the TOS byte for peer traffic. + * Basic support for queueing of torrents. + * Better bias to give connections to downloading torrents + with fewer peers. + * Optimized resource usage (removed the checking thread) + * Support to bind outgoing connections to specific ports + * Disk cache support. + * New, more memory efficient, piece picker with sequential download + support (instead of the more complicated sequential download threshold). + * Auto Upload slots. Automtically opens up more slots if + upload limit is not met. + * Improved NAT-PMP support by querying the default gateway + * Improved UPnP support by ignoring routers not on the clients subnet. + +release 0.13 + + * Added scrape support + * Added add_extension() to torrent_handle. Can instantiate + extensions for torrents while downloading + * Added support for remove_torrent to delete the files as well + * Fixed issue with failing async_accept on windows + * DHT improvements, proper error messages are now returned when + nodes sends bad packets + * Optimized the country table used to resolve country of peers + * Copying optimization for sending data. Data is no longer copied from + the disk I/O buffer to the send buffer. + * Buffer optimization to use a raw buffer instead of std::vector + * Improved file storage to use sparse files + * Updated python bindings + * Added more clients to the identifiable clients list. + * Torrents can now be started in paused state (to better support queuing) + * Improved IPv6 support (support for IPv6 extension to trackers and + listens on both IPv6 and IPv4 interfaces). + * Improved asserts used. Generates a stacktrace on linux + * Piece picker optimizations and improvements + * Improved unchoker, connection limit and rate limiter + * Support for FAST extension + * Fixed invalid calculation in DHT node distance + * Fixed bug in URL parser that failed to parse IPv6 addresses + * added peer download rate approximation + * added port filter for outgoing connection (to prevent + triggering firewalls) + * made most parameters configurable via session_settings + * added encryption support + * added parole mode for peers whose data fails the hash check. + * optimized heap usage in piece-picker and web seed downloader. + * fixed bug in DHT where older write tokens weren't accepted. + * added support for sparse files. + * introduced speed categories for peers and pieces, to separate + slow and fast peers. + * added a half-open tcp connection limit that takes all connections + in to account, not just peer connections. + * added alerts for filtered IPs. + * added support for SOCKS4 and 5 proxies and HTTP CONNECT proxies. + * fixed proper distributed copies calculation. + * added option to use openssl for sha-1 calculations. + * optimized the piece picker in the case where a peer is a seed. + * added support for local peer discovery + * removed the dependency on the compiled boost.date_time library + * deprecated torrent_info::print() + * added UPnP support + * fixed problem where peer interested flags were not updated correctly + when pieces were filtered + * improvements to ut_pex messages, including support for seed flag + * prioritizes upload bandwidth to peers that might send back data + * the following functions have been deprecated: + void torrent_handle::filter_piece(int index, bool filter) const; + void torrent_handle::filter_pieces(std::vector const& pieces) const; + bool torrent_handle::is_piece_filtered(int index) const; + std::vector torrent_handle::filtered_pieces() const; + void torrent_handle::filter_files(std::vector const& files) const; + + instead, use the piece_priority functions. + + * added support for NAT-PMP + * added support for piece priorities. Piece filtering is now set as + a priority + * Fixed crash when last piece was smaller than one block and reading + fastresume data for that piece + * Makefiles should do a better job detecting boost + * Fixed crash when all tracker urls are removed + * Log files can now be created at user supplied path + * Log files failing to create is no longer fatal + * Fixed dead-lock in torrent_handle + * Made it build with boost 1.34 on windows + * Fixed bug in URL parser that failed to parse IPv6 addresses + * Fixed bug in DHT, related to IPv6 nodes + * DHT accepts transaction IDs that have garbage appended to them + * DHT logs messages that it fails to decode + +release 0.12 + + * fixes to make the DHT more compatible + * http seed improvements including error reporting and url encoding issues. + * fixed bug where directories would be left behind when moving storage + in some cases. + * fixed crashing bug when restarting or stopping the DHT. + * added python binding, using boost.python + * improved character conversion on windows when strings are not utf-8. + * metadata extension now respects the private flag in the torrent. + * made the DHT to only be used as a fallback to trackers by default. + * added support for HTTP redirection support for web seeds. + * fixed race condition when accessing a torrent that was checking its + fast resume data. + * fixed a bug in the DHT which could be triggered if the network was + dropped or extremely rare cases. + * if the download rate is limited, web seeds will now only use left-over + bandwidth after all bt peers have used up as much bandwidth as they can. + * added the possibility to have libtorrent resolve the countries of + the peers in torrents. + * improved the bandwidth limiter (it now implements a leaky bucket/node bucket). + * improved the HTTP seed downloader to report accurate progress. + * added more client peer-id signatures to be recognized. + * added support for HTTP servers that skip the CR before the NL at line breaks. + * fixed bug in the HTTP code that only accepted headers case sensitive. + * fixed bug where one of the session constructors didn't initialize boost.filesystem. + * fixed bug when the initial checking of a torrent fails with an exception. + * fixed bug in DHT code which would send incorrect announce messages. + * fixed bug where the http header parser was case sensitive to the header + names. + * Implemented an optmization which frees the piece_picker once a torrent + turns into a seed. + * Added support for uT peer exchange extension, implemented by Massaroddel. + * Modified the quota management to offer better bandwidth balancing + between peers. + * logging now supports multiple sessions (different sessions now log + to different directories). + * fixed random number generator seed problem, generating the same + peer-id for sessions constructed the same second. + * added an option to accept multiple connections from the same IP. + * improved tracker logging. + * moved the file_pool into session. The number of open files is now + limited per session. + * fixed uninitialized private flag in torrent_info + * fixed long standing issue with file.cpp on windows. Replaced the low level + io functions used on windows. + * made it possible to associate a name with torrents without metadata. + * improved http-downloading performance by requesting entire pieces via + http. + * added plugin interface for extensions. And changed the interface for + enabling extensions. + +release 0.11 + + * added support for incorrectly encoded paths in torrent files + (assumes Latin-1 encoding and converts to UTF-8). + * added support for destructing session objects asynchronously. + * fixed bug with file_progress() with files = 0 bytes + * fixed a race condition bug in udp_tracker_connection that could + cause a crash. + * fixed bug occuring when increasing the sequenced download threshold + with max availability lower than previous threshold. + * fixed an integer overflow bug occuring when built with gcc 4.1.x + * fixed crasing bug when closing while checking a torrent + * fixed bug causing a crash with a torrent with piece length 0 + * added an extension to the DHT network protocol to support the + exchange of nodes with IPv6 addresses. + * modified the ip_filter api slightly to support IPv6 + * modified the api slightly to make sequenced download threshold + a per torrent-setting. + * changed the address type to support IPv6 + * fixed bug in piece picker which would not behave as + expected with regard to sequenced download threshold. + * fixed bug with file_progress() with files > 2 GB. + * added --enable-examples option to configure script. + * fixed problem with the resource distribution algorithm + (controlling e.g upload/download rates). + * fixed incorrect asserts in storage related to torrents with + zero-sized files. + * added support for trackerless torrents (with kademlia DHT). + * support for torrents with the private flag set. + * support for torrents containing bootstrap nodes for the + DHT network. + * fixed problem with the configure script on FreeBSD. + * limits the pipelining used on url-seeds. + * fixed problem where the shutdown always would delay for + session_settings::stop_tracker_timeout seconds. + * session::listen_on() won't reopen the socket in case the port and + interface is the same as the one currently in use. + * added http proxy support for web seeds. + * fixed problem where upload and download stats could become incorrect + in case of high cpu load. + * added more clients to the identifiable list. + * fixed fingerprint parser to cope with latest Mainline versions. + +release 0.10 + + * fixed a bug where the requested number of peers in a tracker request could + be too big. + * fixed a bug where empty files were not created in full allocation mode. + * fixed a bug in storage that would, in rare cases, fail to do a + complete check. + * exposed more settings for tweaking parameters in the piece-picker, + downloader and uploader (http_settings replaced by session_settings). + * tweaked default settings to improve high bandwidth transfers. + * improved the piece picker performance and made it possible to download + popular pieces in sequence to improve disk performance. + * added the possibility to control upload and download limits per peer. + * fixed problem with re-requesting skipped pieces when peer was sending pieces + out of fifo-order. + * added support for http seeding (the GetRight protocol) + * renamed identifiers called 'id' in the public interface to support linking + with Objective.C++ + * changed the extensions protocol to use the new one, which is also + implemented by uTorrent. + * factorized the peer_connection and added web_peer_connection which is + able to download from http-sources. + * converted the network code to use asio (resulted in slight api changes + dealing with network addresses). + * made libtorrent build in vc7 (patches from Allen Zhao) + * fixed bug caused when binding outgoing connections to a non-local interface. + * add_torrent() will now throw if called while the session object is + being closed. + * added the ability to limit the number of simultaneous half-open + TCP connections. Flags in peer_info has been added. + +release 0.9.1 + + * made the session disable file name checks within the boost.filsystem library + * fixed race condition in the sockets + * strings that are invalid utf-8 strings are now decoded with the + local codepage on windows + * added the ability to build libtorrent both as a shared library + * client_test can now monitor a directory for torrent files and automatically + start and stop downloads while running + * fixed problem with file_size() when building on windows with unicode support + * added a new torrent state, allocating + * added a new alert, metadata_failed_alert + * changed the interface to session::add_torrent for some speed optimizations. + * greatly improved the command line control of the example client_test. + * fixed bug where upload rate limit was not being applied. + * files that are being checked will no longer stall files that don't need + checking. + * changed the way libtorrent identifies support for its excentions + to look for 'ext' at the end of the peer-id. + * improved performance by adding a circle buffer for the send buffer. + * fixed bugs in the http tracker connection when using an http proxy. + * fixed problem with storage's file pool when creating torrents and then + starting to seed them. + * hard limit on remote request queue and timeout on requests (a timeout + triggers rerequests). This makes libtorrent work much better with + "broken" clients like BitComet which may ignore requests. + +Initial release 0.9 + + * multitracker support + * serves multiple torrents on a single port and a single thread + * supports http proxies and proxy authentication + * gzipped tracker-responses + * block level piece picker + * queues torrents for file check, instead of checking all of them in parallel + * uses separate threads for checking files and for main downloader + * upload and download rate limits + * piece-wise, unordered, incremental file allocation + * fast resume support + * supports files > 2 gigabytes + * supports the no_peer_id=1 extension + * support for udp-tracker protocol + * number of connections limit + * delays sending have messages + * can resume pieces downloaded in any order + * adjusts the length of the request queue depending on download rate + * supports compact=1 + * selective downloading + * ip filter + diff --git a/Jamfile b/Jamfile new file mode 100644 index 0000000..4792eb1 --- /dev/null +++ b/Jamfile @@ -0,0 +1,1119 @@ +# This Jamfile requires boost-build v2 to build. +# The version shipped with boost 1.34.0 + +import modules ; +import path ; +import os ; +import errors ; +import feature : feature ; +import package ; +import virtual-target ; +import cast ; + +# we need version numbers in the form X.Y.Z in order to trigger the built-in +# support for generating symlinks to the installed library +VERSION = 2.0.7 ; + +BOOST_ROOT = [ modules.peek : BOOST_ROOT ] ; +CXXFLAGS = [ modules.peek : CXXFLAGS ] ; +LDFLAGS = [ modules.peek : LDFLAGS ] ; + +ECHO "CXXFLAGS =" $(CXXFLAGS) ; +ECHO "LDFLAGS =" $(LDFLAGS) ; +ECHO "OS =" [ os.name ] ; + +if $(BOOST_ROOT) +{ + ECHO "building boost from source directory: " $(BOOST_ROOT) ; + + use-project /boost : $(BOOST_ROOT) ; + alias boost_system : /boost/system//boost_system : : : $(BOOST_ROOT) ; +} +else +{ + local boost-lib-search-path = + /usr/local/opt/boost/lib + /opt/homebrew/lib + ; + + local boost-include-path = + /usr/local/opt/boost/include + /opt/homebrew/include + ; + + # the names are decorated in MacPorts. + lib boost_system : : darwin boost_system-mt $(boost-lib-search-path) + : : $(boost-include-path) ; + + lib boost_system : : boost_system ; +} + +use-project /try_signal : ./deps/try_signal ; + +rule linking ( properties * ) +{ + local result ; + if on in $(properties) + { + result += /libsimulator//simulator ; + } + + if windows in $(properties) + && ( on in $(properties) + || production in $(properties) + || on in $(properties) ) + { + result += dbghelp ; + } + + # gcrypt libraries, if enabled + if gcrypt in $(properties) + { + result += gcrypt ; + } + else if openssl in $(properties) + { + result += ssl ; + result += crypto ; + if linux in $(properties) + { + result += dl ; + } + } + else if gnutls in $(properties) + { + result += ./deps/asio-gnutls//asio-gnutls ; + result += gnutls/shared ; + } + else if libcrypto in $(properties) + { + result += crypto ; + if linux in $(properties) + { + result += dl ; + } + } + else if wolfssl in $(properties) + { + result += wolfssl ; + } + + if windows in $(properties) + || cygwin in $(properties) + { + # socket functions on windows require winsock libraries + result += ws2_32 + wsock32 + iphlpapi + WIN32_LEAN_AND_MEAN + __USE_W32_SOCKETS + WIN32 + _WIN32 + ; + + # when DHT is enabled, we need ed25519 which in turn + # needs entropy + if ! off in $(properties) + { + result += advapi32 ; + } + + # windows xp has no CNG + if ! xp in $(properties) + { + result += bcrypt ; + } + } + + if android in $(properties) + { + result += dl ; + } + + if beos in $(properties) + { + result += netkit gcc ; + } + + if haiku in $(properties) + { + result += libnetwork gcc ; + } + + + if solaris in $(properties) + { + result += libsocket libnsl ; + } + + if darwin in $(properties) + || iphone in $(properties) + { + # for ip_notifier + result += CoreFoundation SystemConfiguration ; + } + + if gcc in $(properties) + && linux in $(properties) + && ( on in $(properties) + || production in $(properties) + || on in $(properties) ) + { + # for backtraces in assertion failures + # which only works on ELF targets with gcc + result += -Wl,--export-dynamic -rdynamic ; + } + else + { + # backtraces don't work with visibility=hidden, so we only add that in + # the else-block + result += hidden ; + } + + if static in $(properties) + { + if shared in $(properties) + { + # if libtorrent is being built as a shared library + # but we're linking against boost statically, we still + # need to make boost think it's being built as a shared + # library, so that it properly exports its symbols + result += BOOST_ALL_DYN_LINK ; + result += boost_system/static/BOOST_ALL_DYN_LINK ; + } + else + { + result += boost_system/static ; + } + + if gcc in $(properties) + && ! windows in $(properties) + && shared in $(properties) + { + result += on ; + } + + } + else if shared in $(properties) + { + result += boost_system/shared ; + } + else + { + result += boost_system ; + } + + result += BOOST_ALL_NO_LIB + BOOST_MULTI_INDEX_DISABLE_SERIALIZATION + BOOST_SYSTEM_NO_DEPRECATED + ; + + if shared in $(properties) + { + result += /try_signal//try_signal/static/on ; + } + else + { + result += /try_signal//try_signal/static ; + } + + return $(result) ; +} + +rule warnings ( properties * ) +{ + local result ; + + if off in $(properties) + { + return $(result) ; + } + + if clang in $(properties) + || darwin in $(properties) + { + result += -Weverything ; + result += -Wno-documentation ; + result += -Wno-c++98-compat-pedantic ; + result += -Wno-c++11-compat-pedantic ; + result += -Wno-padded ; + result += -Wno-alloca ; + result += -Wno-global-constructors ; + result += -Wno-poison-system-directories ; +# this warns on any global static object, which are used for error_category +# objects + result += -Wno-exit-time-destructors ; + +# enable these warnings again, once the other ones are dealt with + result += -Wno-weak-vtables ; + + result += -Wno-return-std-move-in-c++11 ; + result += -Wno-unknown-warning-option ; + +# libtorrent uses alloca() carefully + result += -Wno-alloca ; + } + + if gcc in $(properties) + { + result += -Wall ; + result += -Wextra ; + result += -Wpedantic ; + result += -Wvla ; + result += -Wno-format-zero-length ; + result += -Wno-noexcept-type ; + } + + if msvc in $(properties) + { + # on msvc this resolves to /W4 + result += all ; + +# enable these warnings again, once the other ones are dealt with + +# disable warning C4251: 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2' + result += /wd4251 ; +# disable warning C4275: non DLL-interface classkey 'identifier' used as base for DLL-interface classkey 'identifier' + result += /wd4275 ; +# disable warning C4373: virtual function overrides, previous versions of the compiler did not override when parameters only differed by const/volatile qualifiers + result += /wd4373 ; + # C4268: 'identifier' : 'const' static/global data initialized + # with compiler generated default constructor fills the object with zeros + result += /wd4268 ; + # C4503: 'identifier': decorated name length exceeded, name was truncated + result += /wd4503 ; + } + + return $(result) ; +} + +# rule for adding the right source files +# depending on target-os and features +rule building ( properties * ) +{ + local result ; + + if ( off in $(properties) && + ! off in $(properties) ) + { + ECHO "'invariant-check' requires enabled 'asserts' mode. (e.g. specify build params: invariant-check=on asserts=on)" ; + result += no ; + } + + local VERSION = [ feature.get-values : $(properties) ] ; + if ! $(VERSION) || $(VERSION) < 14 + { + ECHO "libtorrent requires at least C++14. Specify cxxstd=14 or higher" ; + result += no ; + } + + if msvc in $(properties) || intel-win in $(properties) + { + # allow larger .obj files (with more sections) + result += /bigobj ; + # https://docs.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170 + result += /utf-8 ; + } + + if gcc in $(properties) && windows in $(properties) + { + # allow larger .obj files (with more sections) + result += -Wa,-mbig-obj ; + } + + if ( production in $(properties) + || on in $(properties) ) + { + result += src/assert.cpp ; + } + + if on in $(properties) + { + result += src/pe_crypto.cpp ; + } + + if ( darwin in $(properties) + || gcc in $(properties) + || clang in $(properties) + || clang-darwin in $(properties) ) + # on GCC, enabling debugging in libstdc++ + # breaks the ABI and its ability to appear + # in shared object interfaces, so when it's + # enabled, just export everything (since we're) + # probably not a production build anyway + && ! on in $(properties) + { + if ( gcc in $(properties) ) + { + result += -Wl,-Bsymbolic ; + } + } + + return $(result) ; +} + +rule tag ( name : type ? : property-set ) +{ + # we only care about the names of our output static- or shared library, not + # other targets like object files + if $(type) != SHARED_LIB && $(type) != STATIC_LIB + { + return [ virtual-target.add-prefix-and-suffix $(name) : $(type) : $(property-set) ] ; + } + + # static libraries are not versioned + if $(type) = STATIC_LIB + { + return [ virtual-target.add-prefix-and-suffix $(name)-rasterbar : $(type) : $(property-set) ] ; + } + + # shared libraries have the version number before the filename extension on + # windows + if [ $(property-set).get ] in windows cygwin + { + # TODO: add version on windows too + # return [ virtual-target.add-prefix-and-suffix $(name)-rasterbar-$(VERSION) : $(type) : $(property-set) ] ; + return [ virtual-target.add-prefix-and-suffix $(name)-rasterbar : $(type) : $(property-set) ] ; + } + else + { + local name = [ virtual-target.add-prefix-and-suffix $(name)-rasterbar : $(type) : $(property-set) ] ; + return $(name).$(VERSION) ; + } +} + +# the search path to pick up the openssl libraries from. This is the +# property of those libraries +rule openssl-lib-path ( properties * ) +{ + local OPENSSL_LIB = [ feature.get-values : $(properties) ] ; + + if darwin in $(properties) && $(OPENSSL_LIB) = "" + { + # on macOS, default to pick up openssl from the homebrew installation + # brew install openssl + # homebrew on M1 Macs install to /opt/homebrew + OPENSSL_LIB = /opt/homebrew/opt/openssl/lib /usr/local/opt/openssl/lib ; + } + else if windows in $(properties) && $(OPENSSL_LIB) = "" + { + # the de-facto windows installer is https://slproweb.com/products/Win32OpenSSL.html, which installs to c:\Program Files\OpenSSL-Win{32,64}. + # chocolatey appears to use this installer. + local address_model = [ feature.get-values : $(properties) ] ; + OPENSSL_LIB += "C:/Program Files/OpenSSL-Win$(address_model)/lib" ; + OPENSSL_LIB += "C:/Program Files (x86)/OpenSSL-Win$(address_model)/lib" ; + } + + local result ; + result += $(OPENSSL_LIB) ; + return $(result) ; +} + +# the include path to pick up openssl headers from. This is the +# usage-requirement for the openssl-related libraries +rule openssl-include-path ( properties * ) +{ + local OPENSSL_INCLUDE = [ feature.get-values : $(properties) ] ; + + if darwin in $(properties) && $(OPENSSL_INCLUDE) = "" + { + # on macOS, default to pick up openssl from the homebrew installation + # brew install openssl + # homebrew on M1 Macs install to /opt/homebrew + OPENSSL_INCLUDE = /opt/homebrew/opt/openssl/include /usr/local/opt/openssl/include ; + } + else if windows in $(properties) && $(OPENSSL_INCLUDE) = "" + { + # the de-facto windows installer is https://slproweb.com/products/Win32OpenSSL.html, which installs to c:\Program Files\OpenSSL-Win{32,64}. + # chocolatey appears to use this installer. + local address_model = [ feature.get-values : $(properties) ] ; + OPENSSL_INCLUDE += "C:/Program Files/OpenSSL-Win$(address_model)/include" ; + OPENSSL_INCLUDE += "C:/Program Files (x86)/OpenSSL-Win$(address_model)/include" ; + } + + local result ; + result += $(OPENSSL_INCLUDE) ; + return $(result) ; +} + +# the search path to pick up the gnutls libraries from. This is the +# property of those libraries +rule gnutls-lib-path ( properties * ) +{ + local GNUTLS_LIB = [ feature.get-values : $(properties) ] ; + + if darwin in $(properties) && $(GNUTLS_LIB) = "" + { + # on macOS, default to pick up openssl from the homebrew installation + # brew install openssl + # homebrew on M1 Macs install to /opt/homebrew + GNUTLS_LIB = /opt/homebrew/opt/gnutls/lib /usr/local/opt/gnutls/lib ; + } + + local result ; + result += $(GNUTLS_LIB) ; + return $(result) ; +} + +# the include path to pick up gnutls headers from. This is the +# usage-requirement for the gnutls-related libraries +rule gnutls-include-path ( properties * ) +{ + local GNUTLS_INCLUDE = [ feature.get-values : $(properties) ] ; + + if darwin in $(properties) && $(GNUTLS_INCLUDE) = "" + { + # on macOS, default to pick up openssl from the homebrew installation + # brew install openssl + # homebrew on M1 Macs install to /opt/homebrew + GNUTLS_INCLUDE = /opt/homebrew/opt/gnutls/include /usr/local/opt/gnutls/include ; + } + + local result ; + result += $(GNUTLS_INCLUDE) ; + return $(result) ; +} + +# the search path to pick up the wolfssl libraries from. This is the +# property of those libraries +rule wolfssl-lib-path ( properties * ) +{ + local WOLFSSL_LIB = [ feature.get-values : $(properties) ] ; + + if linux in $(properties) && $(WOLFSSL_LIB) = "" + { + # on linux, default ./configure install path + WOLFSSL_LIB = /usr/local/lib ; + } + + local result ; + result += $(WOLFSSL_LIB) ; + return $(result) ; +} + +# the include path to pick up wolfssl headers from. This is the +# usage-requirement for the wolfssl-related libraries +rule wolfssl-include-path ( properties * ) +{ + local WOLFSSL_INCLUDE = [ feature.get-values : $(properties) ] ; + + if linux in $(properties) && $(WOLFSSL_INCLUDE) = "" + { + # on linux, default ./configure install path + WOLFSSL_INCLUDE = /usr/local/include ; + } + + local result ; + result += $(WOLFSSL_INCLUDE) ; + result += $(WOLFSSL_INCLUDE)/wolfssl ; + return $(result) ; +} + +path-constant blacklist-file : tools/sanitizer-blacklist.txt ; + +feature openssl-lib : : free path ; +feature openssl-include : : free path ; + +feature gnutls-lib : : free path ; +feature gnutls-include : : free path ; + +feature wolfssl-lib : : free path ; +feature wolfssl-include : : free path ; + +feature test-coverage : off on : composite propagated link-incompatible ; +feature.compose on : --coverage --coverage ; + +feature predictive-pieces : on off : composite propagated ; +feature.compose off : TORRENT_DISABLE_PREDICTIVE_PIECES ; + +feature share-mode : on off : composite propagated ; +feature.compose off : TORRENT_DISABLE_SHARE_MODE ; + +feature streaming : on off : composite propagated ; +feature.compose off : TORRENT_DISABLE_STREAMING ; + +feature super-seeding : on off : composite propagated ; +feature.compose off : TORRENT_DISABLE_SUPERSEEDING ; + +feature i2p : on off : composite propagated ; +feature.compose on : TORRENT_USE_I2P=1 ; +feature.compose off : TORRENT_USE_I2P=0 ; + +feature asserts : off on production system : composite propagated ; +feature.compose on : TORRENT_USE_ASSERTS=1 ; +feature.compose production : TORRENT_USE_ASSERTS=1 TORRENT_PRODUCTION_ASSERTS=1 ; +feature.compose system : TORRENT_USE_ASSERTS=1 TORRENT_USE_SYSTEM_ASSERTS=1 ; + +feature windows-version : vista win7 win10 xp : composite propagated ; +feature.compose vista : _WIN32_WINNT=0x0600 ; +feature.compose win7 : _WIN32_WINNT=0x0601 ; +feature.compose win10 : _WIN32_WINNT=0x0A00 ; +feature.compose xp : _WIN32_WINNT=0x0501 ; + +feature extensions : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_EXTENSIONS ; + +feature asio-debugging : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_ASIO_DEBUGGING ; + +feature picker-debugging : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_DEBUG_REFCOUNTS ; + +feature mmap-disk-io : on off : composite propagated ; +feature.compose off : TORRENT_HAVE_MMAP=0 TORRENT_HAVE_MAP_VIEW_OF_FILE=0 ; + +feature simulator : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_BUILD_SIMULATOR ; + +feature invariant-checks : off on full : composite propagated link-incompatible ; +feature.compose on : TORRENT_USE_INVARIANT_CHECKS=1 ; +feature.compose full : TORRENT_USE_INVARIANT_CHECKS=1 TORRENT_EXPENSIVE_INVARIANT_CHECKS ; + +feature utp-log : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_UTP_LOG_ENABLE ; + +feature simulate-slow-read : off on : composite propagated ; +feature.compose on : TORRENT_SIMULATE_SLOW_READ ; + +feature logging : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_LOGGING ; + +feature alert-msg : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_ALERT_MSG ; + +feature dht : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_DHT ; + +feature encryption : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_ENCRYPTION ; + +feature mutable-torrents : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_MUTABLE_TORRENTS ; + +feature crypto : openssl built-in wolfssl gnutls libcrypto gcrypt : composite propagated ; +feature.compose openssl + : TORRENT_USE_LIBCRYPTO + TORRENT_USE_OPENSSL + TORRENT_SSL_PEERS + OPENSSL_NO_SSL2 ; +feature.compose wolfssl + : TORRENT_USE_WOLFSSL + TORRENT_USE_LIBCRYPTO + TORRENT_USE_OPENSSL + OPENSSL_NO_SSL2 + BOOST_ASIO_USE_WOLFSSL + OPENSSL_ALL + WOLFSSL_SHA512 + WOLFSSL_NGINX + WC_NO_HARDEN ; +feature.compose gnutls + : TORRENT_USE_GNUTLS + TORRENT_SSL_PEERS ; +feature.compose libcrypto : TORRENT_USE_LIBCRYPTO ; +feature.compose gcrypt : TORRENT_USE_LIBGCRYPT ; + +feature openssl-version : 1.1 pre1.1 : composite propagated ; + +feature deprecated-functions : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_NO_DEPRECATE ; + +feature boost-link : default static shared : propagated composite ; + +# msvc enables debug iterators by default in debug builds whereas GCC and +# clang do not, that's why "default" is there. msvc has incorrect noexcept +# constructors on some containers when enabling debug iterators, so it's +# possible to turn them off +feature debug-iterators : default off on : composite propagated link-incompatible ; +feature.compose on : _GLIBCXX_DEBUG _GLIBCXX_DEBUG_PEDANTIC ; +feature.compose off : _ITERATOR_DEBUG_LEVEL=0 ; + +feature fpic : off on : composite propagated link-incompatible ; +feature.compose on : -fPIC ; + +feature profile-calls : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_PROFILE_CALLS=1 ; + +# controls whether or not to export some internal +# libtorrent functions. Used for unit testing +feature export-extra : off on : composite propagated ; +# export some internal libtorrent functions +# in order to me able to unit test them. +# this is off by default to keep the export +# symbol table reasonably small +feature.compose on : TORRENT_EXPORT_EXTRA ; + +lib advapi32 : : advapi32 ; +lib user32 : : user32 ; +lib shell32 : : shell32 ; +lib gdi32 : : gdi32 ; +lib bcrypt : : bcrypt ; +lib crypt32 : : crypt32 ; +lib z : : shared z ; + +# openssl libraries on windows +# technically, crypt32 is not an OpenSSL dependency, but libtorrent needs it on +# windows to access the system certificate store, for authenticating trackers +alias ssl-deps : advapi32 user32 shell32 gdi32 crypt32 ; + +# pre OpenSSL 1.1 windows +lib crypto : ssl-deps : windows pre1.1 libeay32 + @openssl-lib-path : : @openssl-include-path ; +lib ssl : ssl-deps : windows pre1.1 ssleay32 + crypto @openssl-lib-path : : @openssl-include-path ; + +# OpenSSL 1.1+ windows +lib crypto : ssl-deps : windows 1.1 libcrypto + @openssl-lib-path : : @openssl-include-path ; +lib ssl : ssl-deps : windows 1.1 libssl crypto + @openssl-lib-path : : @openssl-include-path ; + +# generic OpenSSL +lib crypto : : crypto z @openssl-lib-path : : + @openssl-include-path ; +lib ssl : : ssl crypto @openssl-lib-path : : + @openssl-include-path ; + +lib gnutls : : gnutls @gnutls-lib-path : : + @gnutls-include-path ; + +lib wolfssl : : wolfssl @wolfssl-lib-path : : + @wolfssl-include-path ; + +lib dbghelp : : dbghelp ; + +# required for networking on beos +lib netkit : : net /boot/system/lib shared ; +lib gcc : : gcc static ; + +# gcrypt on linux/bsd etc. +lib gcrypt : : gcrypt shared /opt/local/lib ; +lib dl : : shared dl ; + +lib libsocket : : libnsl socket shared /usr/sfw/lib shared ; +lib libnsl : : nsl shared /usr/sfw/lib shared ; +lib libnetwork : : network shared ; + +# socket libraries on windows +lib wsock32 : : wsock32 shared ; +lib ws2_32 : : ws2_32 shared ; +lib iphlpapi : : iphlpapi shared ; + +SOURCES = + alert + alert_manager + announce_entry + assert + bandwidth_limit + bandwidth_manager + bandwidth_queue_entry + bdecode + bitfield + bloom_filter + chained_buffer + choker + close_reason + copy_file + cpuid + crc32c + create_torrent + directory + disk_buffer_holder + disk_buffer_pool + disk_interface + disk_io_thread_pool + disabled_disk_io + disk_job_fence + disk_job_pool + entry + error_code + file_storage + escape_string + string_util + file + path + fingerprint + gzip + hasher + hash_picker + hex + http_connection + http_parser + identify_client + ip_filter + ip_helpers + ip_notifier + ip_voter + listen_socket_handle + merkle + merkle_tree + peer_connection + platform_util + bt_peer_connection + web_connection_base + web_peer_connection + http_seed_connection + peer_connection_handle + i2p_stream + instantiate_connection + natpmp + packet_buffer + piece_picker + peer_list + proxy_base + puff + random + read_resume_data + write_resume_data + receive_buffer + resolve_links + session + session_params + session_handle + session_impl + session_call + settings_pack + sha1 + sha1_hash + sha256 + socket_io + socket_type + socks5_stream + stat + storage_utils + torrent + torrent_handle + torrent_info + torrent_peer + torrent_peer_allocator + torrent_status + time + tracker_manager + http_tracker_connection + udp_tracker_connection + timestamp_history + udp_socket + upnp + utf8 + utp_socket_manager + utp_stream + file_view_pool + lsd + enum_net + magnet_uri + parse_url + xml_parse + version + peer_class + peer_class_set + part_file + stat_cache + request_blocks + session_stats + performance_counters + resolver + session_settings + proxy_settings + file_progress + ffs + add_torrent_params + peer_info + stack_allocator + generate_peer_id + mmap + mmap_disk_io + mmap_disk_job + mmap_storage + posix_disk_io + posix_part_file + posix_storage + ssl + truncate + load_torrent + +# -- extensions -- + ut_pex + ut_metadata + smart_ban + ; + +KADEMLIA_SOURCES = + dht_state + dht_storage + dht_tracker + msg + node + node_entry + refresh + rpc_manager + find_data + node_id + routing_table + traversal_algorithm + dos_blocker + get_peers + item + get_item + put_data + ed25519 + sample_infohashes + dht_settings + ; + +ED25519_SOURCES = + add_scalar + fe + ge + key_exchange + keypair + sc + sign + verify + hasher512 + sha512 + ; + +local usage-requirements = + ./include + ./include/libtorrent + release:NDEBUG + _FILE_OFFSET_BITS=64 +# enable cancel support in asio + BOOST_ASIO_ENABLE_CANCELIO +# make sure asio uses std::chrono + BOOST_ASIO_HAS_STD_CHRONO + BOOST_ASIO_NO_DEPRECATED + @linking +# msvc optimizations + msvc,release:"/OPT:ICF=5" + msvc,release:"/OPT:REF" + + # disable bogus deprecation warnings on msvc8 + windows:_SCL_SECURE_NO_DEPRECATE + windows:_CRT_SECURE_NO_DEPRECATE + + "$(CXXFLAGS:J= )" + ; + +project torrent ; + +lib torrent + + : # sources + src/$(SOURCES).cpp + + : # requirements + multi + TORRENT_BUILDING_LIBRARY + shared:TORRENT_BUILDING_SHARED + BOOST_NO_DEPRECATED + shared:BOOST_SYSTEM_SOURCE + + on:src/kademlia/$(KADEMLIA_SOURCES).cpp + on:src/ed25519/$(ED25519_SOURCES).cpp + + @building + @warnings + + @tag + + $(usage-requirements) + "$(LDFLAGS:J= )" + + : # default build + multi + 14 + 512 + + : # usage requirements + $(usage-requirements) + shared:TORRENT_LINKING_SHARED + + ; + + +# install rules + +# return libdir and includedir +rule install-paths ( properties * ) +{ + import version ; + + # package.paths was introduced in boost-1.70 (2018.02) + # however, boost build's versioning scheme changed in boost-1.71 to version + # 4.0 + local boost-build-version = [ SPLIT_BY_CHARACTERS [ version.boost-build ] : "-" ] ; + if [ version.version-less [ SPLIT_BY_CHARACTERS $(boost-build-version[1]) : "." ] : 2018 03 ] + { + import option ; + import property ; + local prefix = [ option.get prefix : [ property.select : $(properties) ] ] ; + prefix = $(prefix:G=) ; + # Or some likely defaults if neither is given. + if ! $(prefix) + { + if [ modules.peek : NT ] { prefix = C:\\$(package-name) ; } + else if [ modules.peek : UNIX ] { prefix = /usr/local ; } + } + + return $(prefix)/lib $(prefix)/include ; + } + else + { + local p = [ package.paths libtorrent : $(properties) ] ; + return [ $(p).libdir ] [ $(p).includedir ] ; + } +} + +rule generate-pkg-config ( properties * ) +{ + import property-set ; + import project ; + + local l = [ project.target [ project.module-name "." ] ] ; + + # this is the libtorrent library target + local t = [ $(l).find torrent : . ] ; + + # these are the properties we're using to build it with + local props = [ $(t).generate [ property-set.create $(properties) ] ] ; + local libname = [ $(props[2]).name ] ; + props = $(props[1]) ; + + p = [ install-paths $(properties) ] ; + + local libdir = $(p[1]) ; + local includes = $(p[2]) ; + + local defines ; + local shared_deps ; + local private_deps ; + for d in [ feature.expand $(properties) ] [ $(props).raw ] { + switch $(d) + { + case \TORRENT_* : { + d = [ SPLIT_BY_CHARACTERS $(d) : ">" ] ; + defines += $(d[2]) ; + } + case \BOOST_* : { + d = [ SPLIT_BY_CHARACTERS $(d) : ">" ] ; + defines += $(d[2]) ; + } + case \* : { + d = [ SPLIT_BY_CHARACTERS $(d) : ">" ] ; + d = $(d[2]) ; + if ( [ path.is-rooted $(d) ] ) + { + includes += $(d) ; + } + } + case \* : { + d = [ SPLIT_BY_CHARACTERS $(d) : ">" ] ; + # this is the target + local t = $(d[2]) ; + if [ $(t).type ] = SHARED_LIB + { + local path = [ $(t).path ] ; + if $(path) != "" + { + libdir += $(path) ; + } + shared_deps += [ $(t).name ] ; + } + else if [ $(t).type ] = SEARCHED_LIB + { + local path = [ $(t).search ] ; + if $(path) != "" + { + libdir += $(path) ; + } + shared_deps += [ $(t).name ] ; + } + else if ( [ $(t).type ] = STATIC_LIB ) + { + private_deps += [ $(t).name ] ; + } + } + } + } + + # TODO: use $(libname) in future versions + local config = "Name: libtorrent-rasterbar" + "\nDescription: libtorrent is an open source C++ library implementing the BitTorrent protocol" + "\nURL: https://libtorrent.org" + "\nVersion: $(VERSION)" + "\nLibs:" + " -L\"$(libdir)\"" + " -ltorrent-rasterbar" + " -l$(shared_deps)" + "\nLibs.private:" + " -L\"$(libdir)\"" + " -l$(private_deps)" + "\nCflags:" + " -D$(defines)" + " -I\"$(includes)\"" + "\n" + ; + + local dummy = @("libtorrent-rasterbar.pc":E=$(config)) ; +} + +rule install-pkg-config ( target-name : data * : requirements * ) +{ + import stage ; + local p = [ install-paths $(requirements) ] ; + local libdir = $(p[0]) ; + + stage.install $(target-name) + : $(data) + : $(requirements) $(libdir)/pkgconfig + ; + + import project ; + local c = [ project.current ] ; + local project-module = [ $(c).project-module ] ; + module $(project-module) + { + explicit $(1) ; + } +} + +headers = [ path.glob-tree include/libtorrent : *.hpp ] ; + +package.install install-torrent-lib + : libtorrent + : + : torrent + : $(headers) + ; + +package.install-data install-cmake-module + : cmake/Modules + : examples/cmake/FindLibtorrentRasterbar.cmake + ; + +install-pkg-config pkg-config-target : libtorrent-rasterbar.pc : @generate-pkg-config ; + +alias install : install-torrent-lib install-cmake-module pkg-config-target ; + +explicit install ; + + +# testing headers targets + +local header_targets ; +for local target in $(headers) +{ + if ! [ path.basename $(target) ] in storage.hpp windows.hpp win_util.hpp win_crypto_provider.hpp torrent_impl.hpp io_service.hpp + { + # this cast tells boost build that the header files really *are* cpp files + # otherwise the object rule doesn't know which language to interpret them as + obj header-build/$(target).o : [ cast.cast _ cpp : $(target) ] + : torrent -fsyntax-only + : 14 ; + explicit header-build/$(target).o ; + header_targets += $(target) ; + } +} + +alias check-headers : header-build/$(header_targets).o ; +explicit check-headers ; diff --git a/Jamroot.jam b/Jamroot.jam new file mode 100644 index 0000000..e69de29 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..05e9627 --- /dev/null +++ b/LICENSE @@ -0,0 +1,183 @@ +Copyright (c) 2003-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------ + +puff.c +Copyright (C) 2002, 2003 Mark Adler +For conditions of distribution and use, see copyright notice in puff.h +version 1.7, 3 Mar 2003 + +puff.c is a simple inflate written to be an unambiguous way to specify the +deflate format. It is not written for speed but rather simplicity. As a +side benefit, this code might actually be useful when small code is more +important than speed, such as bootstrap applications. For typical deflate +data, zlib's inflate() is about four times as fast as puff(). zlib's +inflate compiles to around 20K on my machine, whereas puff.c compiles to +around 4K on my machine (a PowerPC using GNU cc). If the faster decode() +function here is used, then puff() is only twice as slow as zlib's +inflate(). + +All dynamically allocated memory comes from the stack. The stack required +is less than 2K bytes. This code is compatible with 16-bit int's and +assumes that long's are at least 32 bits. puff.c uses the short data type, +assumed to be 16 bits, for arrays in order to conserve memory. The code +works whether integers are stored big endian or little endian. + +In the comments below are "Format notes" that describe the inflate process +and document some of the less obvious aspects of the format. This source +code is meant to supplement RFC 1951, which formally describes the deflate +format: + + http://www.zlib.org/rfc-deflate.html + +------------------------------------------------------------------------------ + +bindings/python/src/ + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------ + +ed25519 implementation based on: + +Copyright (c) 2015 Orson Peters + +This software is provided 'as-is', without any express or implied warranty. In no event will the +authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial +applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the + original software. If you use this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be misrepresented as + being the original software. + +3. This notice may not be removed or altered from any source distribution. + +------------------------------------------------------------------------------ + +src/sha1.cpp include/libtorrent/sha1.hpp + +SHA-1 in C +By Steve Reid +100% Public Domain + +------------------------------------------------------------------------------ + +include/libtorrent/_aux/route.h + + * Copyright (c) 2000-2008 Apple Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * Copyright (c) 1980, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)route.h 8.3 (Berkeley) 4/19/94 + * $FreeBSD: src/sys/net/route.h,v 1.36.2.1 2000/08/16 06:14:23 jayanth Exp $ + +------------------------------------------------------------------------------ + +src/sha256.cpp + +SHA-256. Adapted from LibTomCrypt. This code is Public Domain + diff --git a/LibtorrentRasterbarConfig.cmake.in b/LibtorrentRasterbarConfig.cmake.in new file mode 100644 index 0000000..69aeca1 --- /dev/null +++ b/LibtorrentRasterbarConfig.cmake.in @@ -0,0 +1,9 @@ +# - Config file for the @PROJECT_NAME@ package +# It defines the LibtorrentRasterbar::torrent-rasterbar target to link against + +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +@_find_dependency_calls@ + +include("${CMAKE_CURRENT_LIST_DIR}/LibtorrentRasterbarTargets.cmake") diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..382050e --- /dev/null +++ b/Makefile @@ -0,0 +1,1138 @@ +VERSION=2.0.7 + +BUILD_CONFIG=release link=shared crypto=openssl warnings=off address-model=64 + +ifeq (${PREFIX},) +PREFIX=/usr/local/ +endif + +ALL: FORCE + BOOST_ROOT="" b2 ${BUILD_CONFIG} + +python-binding: FORCE + (cd bindings/python; BOOST_ROOT="" b2 ${BUILD_CONFIG} stage_module stage_dependencies) + +examples: FORCE + (cd examples; BOOST_ROOT="" b2 ${BUILD_CONFIG} stage_client_test stage_connection_tester) + +tools: FORCE + (cd tools; BOOST_ROOT="" b2 ${BUILD_CONFIG}) + +install: FORCE + BOOST_ROOT="" b2 ${BUILD_CONFIG} install --prefix=${PREFIX} + +sim: FORCE + (cd simulation; BOOST_ROOT="" b2 $(filter-out crypto=openssl,${BUILD_CONFIG}) crypto=built-in) + +check: FORCE + (cd test; BOOST_ROOT="" b2 crypto=openssl warnings=off) + +clean: FORCE + rm -rf \ + bin \ + examples/bin \ + tools/bin \ + bindings/python/bin \ + test/bin \ + simulation/bin \ + simulator/libsimulator/bin + +DOCS_IMAGES = \ + docs/img/screenshot.png \ + docs/img/screenshot_thumb.png \ + docs/img/cwnd.png \ + docs/img/cwnd_thumb.png \ + docs/img/delays.png \ + docs/img/delays_thumb.png \ + docs/img/our_delay_base.png \ + docs/img/our_delay_base_thumb.png \ + docs/img/read_disk_buffers.png \ + docs/img/read_disk_buffers.diagram \ + docs/img/storage.png \ + docs/img/write_disk_buffers.png \ + docs/img/write_disk_buffers.diagram \ + docs/img/ip_id_v4.png \ + docs/img/ip_id_v6.png \ + docs/img/hash_distribution.png \ + docs/img/complete_bit_prefixes.png \ + docs/img/troubleshooting.dot \ + docs/img/troubleshooting.png \ + docs/img/troubleshooting_thumb.png \ + docs/img/hacking.diagram \ + docs/img/hacking.png \ + docs/img/utp_stack.diagram \ + docs/img/utp_stack.png \ + docs/img/bitcoin.png \ + docs/img/logo-color-text.png \ + docs/img/pp-acceptance-medium.png \ + docs/style.css + +DOCS_PAGES = \ + docs/building.html \ + docs/client_test.html \ + docs/contributing.html \ + docs/dht_extensions.html \ + docs/dht_rss.html \ + docs/dht_sec.html \ + docs/dht_store.html \ + docs/examples.html \ + docs/extension_protocol.html \ + docs/features-ref.html \ + docs/index.html \ + docs/manual-ref.html \ + docs/projects.html \ + docs/python_binding.html \ + docs/tuning-ref.html \ + docs/settings.rst \ + docs/stats_counters.rst \ + docs/troubleshooting.html \ + docs/udp_tracker_protocol.html \ + docs/utp.html \ + docs/streaming.html \ + docs/building.rst \ + docs/client_test.rst \ + docs/contributing.rst \ + docs/dht_extensions.rst \ + docs/dht_rss.rst \ + docs/dht_sec.rst \ + docs/dht_store.rst \ + docs/examples.rst \ + docs/extension_protocol.rst \ + docs/features.rst \ + docs/index.rst \ + docs/manual.rst \ + docs/manual-ref.rst \ + docs/projects.rst \ + docs/python_binding.rst \ + docs/tuning.rst \ + docs/troubleshooting.rst \ + docs/udp_tracker_protocol.rst \ + docs/utp.rst \ + docs/streaming.rst \ + docs/tutorial.rst \ + docs/tutorial-ref.rst \ + docs/header.rst \ + docs/hacking.rst \ + docs/hacking.html \ + docs/todo.html \ + docs/tutorial-ref.html \ + docs/upgrade_to_1.2-ref.html \ + docs/upgrade_to_2.0-ref.html \ + docs/security-audit.html \ + docs/reference.html \ + docs/reference-Core.html \ + docs/reference-DHT.html \ + docs/reference-Session.html \ + docs/reference-Torrent_Handle.html \ + docs/reference-Torrent_Info.html \ + docs/reference-Trackers.html \ + docs/reference-PeerClass.html \ + docs/reference-Torrent_Status.html \ + docs/reference-Stats.html \ + docs/reference-Resume_Data.html \ + docs/reference-Add_Torrent.html \ + docs/reference-Plugins.html \ + docs/reference-Create_Torrents.html \ + docs/reference-Error_Codes.html \ + docs/reference-Storage.html \ + docs/reference-Custom_Storage.html \ + docs/reference-Utility.html \ + docs/reference-Bencoding.html \ + docs/reference-Alerts.html \ + docs/reference-Filter.html \ + docs/reference-Settings.html \ + docs/reference-Bdecoding.html \ + docs/reference-ed25519.html \ + docs/single-page-ref.html + +ED25519_SOURCE = \ + fe.h \ + fixedint.h \ + ge.h \ + precomp_data.h \ + sc.h \ + add_scalar.cpp \ + fe.cpp \ + ge.cpp \ + key_exchange.cpp \ + keypair.cpp \ + sc.cpp \ + sign.cpp \ + verify.cpp \ + sha512.cpp \ + hasher512.cpp \ + +EXTRA_DIST = \ + Jamfile \ + Jamroot.jam \ + project-config.jam \ + Makefile \ + CMakeLists.txt \ + cmake/Modules/FindLibGcrypt.cmake \ + cmake/Modules/GeneratePkgConfig.cmake \ + cmake/Modules/ucm_flags.cmake \ + cmake/Modules/LibtorrentMacros.cmake \ + cmake/Modules/GeneratePkgConfig/generate-pkg-config.cmake.in \ + cmake/Modules/GeneratePkgConfig/pkg-config.cmake.in \ + cmake/Modules/GeneratePkgConfig/target-compile-settings.cmake.in \ + LibtorrentRasterbarConfig.cmake.in \ + bindings/CMakeLists.txt \ + setup.py \ + LICENSE \ + src/ed25519/LICENSE \ + COPYING \ + AUTHORS \ + NEWS \ + README.rst \ + ChangeLog \ + $(DOCS_PAGES) \ + $(DOCS_IMAGES) + +PYTHON_FILES= \ + CMakeLists.txt \ + Jamfile \ + client.py \ + make_torrent.py \ + setup.py \ + setup.py.cmake.in \ + simple_client.py \ + src/alert.cpp \ + src/boost_python.hpp \ + src/bytes.hpp \ + src/converters.cpp \ + src/create_torrent.cpp \ + src/datetime.cpp \ + src/entry.cpp \ + src/error_code.cpp \ + src/fingerprint.cpp \ + src/gil.hpp \ + src/ip_filter.cpp \ + src/load_torrent.cpp \ + src/magnet_uri.cpp \ + src/module.cpp \ + src/optional.hpp \ + src/peer_info.cpp \ + src/session.cpp \ + src/session_settings.cpp \ + src/sha1_hash.cpp \ + src/sha256_hash.cpp \ + src/info_hash.cpp \ + src/string.cpp \ + src/torrent_handle.cpp \ + src/torrent_info.cpp \ + src/torrent_status.cpp \ + src/utility.cpp \ + src/version.cpp + +EXAMPLE_FILES= \ + CMakeLists.txt \ + Jamfile \ + bt-get.cpp \ + bt-get2.cpp \ + bt-get3.cpp \ + check_files.cpp \ + client_test.cpp \ + cmake/FindLibtorrentRasterbar.cmake \ + connection_tester.cpp \ + dump_torrent.cpp \ + dump_bdecode.cpp \ + magnet2torrent.cpp \ + make_torrent.cpp \ + print.cpp \ + print.hpp \ + session_view.cpp \ + session_view.hpp \ + simple_client.cpp \ + custom_storage.cpp \ + stats_counters.cpp \ + torrent2magnet.cpp \ + torrent_view.cpp \ + torrent_view.hpp \ + upnp_test.cpp + +TOOLS_FILES= \ + CMakeLists.txt \ + Jamfile \ + dht_put.cpp \ + dht_sample.cpp \ + disk_io_stress_test.cpp\ + parse_dht_log.py \ + parse_dht_rtt.py \ + parse_dht_stats.py \ + parse_peer_log.py \ + parse_sample.py \ + parse_session_stats.py \ + parse_utp_log.py \ + session_log_alerts.cpp + +KADEMLIA_SOURCES = \ + dht_settings.cpp \ + dht_state.cpp \ + dht_storage.cpp \ + dht_tracker.cpp \ + dos_blocker.cpp \ + ed25519.cpp \ + find_data.cpp \ + get_item.cpp \ + get_peers.cpp \ + item.cpp \ + msg.cpp \ + node.cpp \ + node_entry.cpp \ + node_id.cpp \ + put_data.cpp \ + refresh.cpp \ + routing_table.cpp \ + rpc_manager.cpp \ + sample_infohashes.cpp \ + traversal_algorithm.cpp + +SOURCES = \ + add_torrent_params.cpp \ + alert.cpp \ + alert_manager.cpp \ + announce_entry.cpp \ + assert.cpp \ + bandwidth_limit.cpp \ + bandwidth_manager.cpp \ + bandwidth_queue_entry.cpp \ + bdecode.cpp \ + bitfield.cpp \ + bloom_filter.cpp \ + bt_peer_connection.cpp \ + chained_buffer.cpp \ + choker.cpp \ + close_reason.cpp \ + copy_file.cpp \ + cpuid.cpp \ + crc32c.cpp \ + create_torrent.cpp \ + directory.cpp \ + disabled_disk_io.cpp \ + disk_buffer_holder.cpp \ + disk_buffer_pool.cpp \ + disk_interface.cpp \ + disk_io_thread_pool.cpp \ + disk_job_fence.cpp \ + disk_job_pool.cpp \ + entry.cpp \ + enum_net.cpp \ + error_code.cpp \ + escape_string.cpp \ + ffs.cpp \ + file.cpp \ + file_progress.cpp \ + file_storage.cpp \ + file_view_pool.cpp \ + fingerprint.cpp \ + generate_peer_id.cpp \ + gzip.cpp \ + hash_picker.cpp \ + hasher.cpp \ + hex.cpp \ + http_connection.cpp \ + http_parser.cpp \ + http_seed_connection.cpp \ + http_tracker_connection.cpp \ + i2p_stream.cpp \ + identify_client.cpp \ + instantiate_connection.cpp \ + ip_filter.cpp \ + ip_helpers.cpp \ + ip_notifier.cpp \ + ip_voter.cpp \ + listen_socket_handle.cpp \ + load_torrent.cpp \ + lsd.cpp \ + magnet_uri.cpp \ + merkle.cpp \ + merkle_tree.cpp \ + mmap.cpp \ + mmap_disk_io.cpp \ + mmap_disk_job.cpp \ + mmap_storage.cpp \ + natpmp.cpp \ + packet_buffer.cpp \ + parse_url.cpp \ + part_file.cpp \ + path.cpp \ + pe_crypto.cpp \ + peer_class.cpp \ + peer_class_set.cpp \ + peer_connection.cpp \ + peer_connection_handle.cpp \ + peer_info.cpp \ + peer_list.cpp \ + performance_counters.cpp \ + piece_picker.cpp \ + platform_util.cpp \ + posix_disk_io.cpp \ + posix_part_file.cpp \ + posix_storage.cpp \ + proxy_base.cpp \ + proxy_settings.cpp \ + puff.cpp \ + random.cpp \ + read_resume_data.cpp \ + receive_buffer.cpp \ + request_blocks.cpp \ + resolve_links.cpp \ + resolver.cpp \ + session.cpp \ + session_call.cpp \ + session_handle.cpp \ + session_impl.cpp \ + session_params.cpp \ + session_settings.cpp \ + session_stats.cpp \ + settings_pack.cpp \ + sha1.cpp \ + sha1_hash.cpp \ + sha256.cpp \ + smart_ban.cpp \ + socket_io.cpp \ + socket_type.cpp \ + socks5_stream.cpp \ + ssl.cpp \ + stack_allocator.cpp \ + stat.cpp \ + stat_cache.cpp \ + storage_utils.cpp \ + string_util.cpp \ + time.cpp \ + timestamp_history.cpp \ + torrent.cpp \ + torrent_handle.cpp \ + torrent_info.cpp \ + torrent_peer.cpp \ + torrent_peer_allocator.cpp \ + torrent_status.cpp \ + tracker_manager.cpp \ + truncate.cpp \ + udp_socket.cpp \ + udp_tracker_connection.cpp \ + upnp.cpp \ + ut_metadata.cpp \ + ut_pex.cpp \ + utf8.cpp \ + utp_socket_manager.cpp \ + utp_stream.cpp \ + version.cpp \ + web_connection_base.cpp \ + web_peer_connection.cpp \ + write_resume_data.cpp \ + xml_parse.cpp + +HEADERS = \ + add_torrent_params.hpp \ + address.hpp \ + alert.hpp \ + alert_types.hpp \ + announce_entry.hpp \ + assert.hpp \ + bdecode.hpp \ + bencode.hpp \ + bitfield.hpp \ + bloom_filter.hpp \ + bt_peer_connection.hpp \ + choker.hpp \ + client_data.hpp \ + close_reason.hpp \ + config.hpp \ + copy_ptr.hpp \ + crc32c.hpp \ + create_torrent.hpp \ + deadline_timer.hpp \ + debug.hpp \ + disabled_disk_io.hpp \ + disk_buffer_holder.hpp \ + disk_interface.hpp \ + disk_observer.hpp \ + download_priority.hpp \ + entry.hpp \ + enum_net.hpp \ + error.hpp \ + error_code.hpp \ + extensions.hpp \ + file.hpp \ + file_storage.hpp \ + fingerprint.hpp \ + flags.hpp \ + fwd.hpp \ + gzip.hpp \ + hash_picker.hpp \ + hasher.hpp \ + hex.hpp \ + http_connection.hpp \ + http_parser.hpp \ + http_seed_connection.hpp \ + http_stream.hpp \ + http_tracker_connection.hpp \ + i2p_stream.hpp \ + identify_client.hpp \ + index_range.hpp \ + info_hash.hpp \ + io.hpp \ + io_context.hpp \ + io_service.hpp \ + ip_filter.hpp \ + ip_voter.hpp \ + libtorrent.hpp \ + link.hpp \ + load_torrent.hpp \ + lsd.hpp \ + magnet_uri.hpp \ + mmap_disk_io.hpp \ + mmap_storage.hpp \ + natpmp.hpp \ + netlink.hpp \ + operations.hpp \ + optional.hpp \ + parse_url.hpp \ + part_file.hpp \ + pe_crypto.hpp \ + peer.hpp \ + peer_class.hpp \ + peer_class_set.hpp \ + peer_class_type_filter.hpp \ + peer_connection.hpp \ + peer_connection_handle.hpp \ + peer_connection_interface.hpp \ + peer_id.hpp \ + peer_info.hpp \ + peer_list.hpp \ + peer_request.hpp \ + performance_counters.hpp \ + pex_flags.hpp \ + piece_block.hpp \ + piece_block_progress.hpp \ + piece_picker.hpp \ + platform_util.hpp \ + portmap.hpp \ + posix_disk_io.hpp \ + proxy_base.hpp \ + puff.hpp \ + random.hpp \ + read_resume_data.hpp \ + request_blocks.hpp \ + resolve_links.hpp \ + session.hpp \ + session_handle.hpp \ + session_params.hpp \ + session_settings.hpp \ + session_stats.hpp \ + session_status.hpp \ + session_types.hpp \ + settings_pack.hpp \ + sha1.hpp \ + sha1_hash.hpp \ + sha256.hpp \ + sliding_average.hpp \ + socket.hpp \ + socket_io.hpp \ + socket_type.hpp \ + socks5_stream.hpp \ + span.hpp \ + ssl.hpp \ + ssl_stream.hpp \ + stack_allocator.hpp \ + stat.hpp \ + stat_cache.hpp \ + storage.hpp \ + storage_defs.hpp \ + string_util.hpp \ + string_view.hpp \ + tailqueue.hpp \ + time.hpp \ + torrent.hpp \ + torrent_flags.hpp \ + torrent_handle.hpp \ + torrent_info.hpp \ + torrent_peer.hpp \ + torrent_peer_allocator.hpp \ + torrent_status.hpp \ + tracker_manager.hpp \ + truncate.hpp \ + udp_socket.hpp \ + udp_tracker_connection.hpp \ + union_endpoint.hpp \ + units.hpp \ + upnp.hpp \ + utf8.hpp \ + vector_utils.hpp \ + version.hpp \ + web_connection_base.hpp \ + web_peer_connection.hpp \ + write_resume_data.hpp \ + xml_parse.hpp \ + \ + aux_/alert_manager.hpp \ + aux_/aligned_storage.hpp \ + aux_/aligned_union.hpp \ + aux_/alloca.hpp \ + aux_/allocating_handler.hpp \ + aux_/announce_entry.hpp \ + aux_/apply_pad_files.hpp \ + aux_/array.hpp \ + aux_/bandwidth_limit.hpp \ + aux_/bandwidth_manager.hpp \ + aux_/bandwidth_queue_entry.hpp \ + aux_/bandwidth_socket.hpp \ + aux_/bind_to_device.hpp \ + aux_/buffer.hpp \ + aux_/byteswap.hpp \ + aux_/container_wrapper.hpp \ + aux_/chained_buffer.hpp \ + aux_/cpuid.hpp \ + aux_/deferred_handler.hpp \ + aux_/deprecated.hpp \ + aux_/deque.hpp \ + aux_/dev_random.hpp \ + aux_/directory.hpp \ + aux_/disable_deprecation_warnings_push.hpp \ + aux_/disable_warnings_pop.hpp \ + aux_/disable_warnings_push.hpp \ + aux_/disk_buffer_pool.hpp \ + aux_/disk_io_thread_pool.hpp \ + aux_/disk_job_fence.hpp \ + aux_/disk_job_pool.hpp \ + aux_/ed25519.hpp \ + aux_/escape_string.hpp \ + aux_/export.hpp \ + aux_/ffs.hpp \ + aux_/file_pointer.hpp \ + aux_/file_progress.hpp \ + aux_/file_view_pool.hpp \ + aux_/generate_peer_id.hpp \ + aux_/has_block.hpp \ + aux_/hasher512.hpp \ + aux_/heterogeneous_queue.hpp \ + aux_/instantiate_connection.hpp \ + aux_/invariant_check.hpp \ + aux_/io.hpp \ + aux_/ip_helpers.hpp \ + aux_/ip_notifier.hpp \ + aux_/keepalive.hpp \ + aux_/listen_socket_handle.hpp \ + aux_/lsd.hpp \ + aux_/merkle.hpp \ + aux_/merkle_tree.hpp \ + aux_/mmap.hpp \ + aux_/mmap_disk_job.hpp \ + aux_/noexcept_movable.hpp \ + aux_/numeric_cast.hpp \ + aux_/open_mode.hpp \ + aux_/packet_buffer.hpp \ + aux_/packet_pool.hpp \ + aux_/path.hpp \ + aux_/polymorphic_socket.hpp \ + aux_/pool.hpp \ + aux_/portmap.hpp \ + aux_/posix_part_file.hpp \ + aux_/posix_storage.hpp \ + aux_/proxy_settings.hpp \ + aux_/range.hpp \ + aux_/receive_buffer.hpp \ + aux_/resolver.hpp \ + aux_/resolver_interface.hpp \ + aux_/route.h \ + aux_/scope_end.hpp \ + aux_/session_call.hpp \ + aux_/session_impl.hpp \ + aux_/session_interface.hpp \ + aux_/session_settings.hpp \ + aux_/session_udp_sockets.hpp \ + aux_/set_socket_buffer.hpp \ + aux_/set_traffic_class.hpp \ + aux_/sha512.hpp \ + aux_/socket_type.hpp \ + aux_/storage_free_list.hpp \ + aux_/storage_utils.hpp \ + aux_/store_buffer.hpp \ + aux_/string_ptr.hpp \ + aux_/strview_less.hpp \ + aux_/suggest_piece.hpp \ + aux_/throw.hpp \ + aux_/time.hpp \ + aux_/timestamp_history.hpp \ + aux_/torrent_impl.hpp \ + aux_/torrent_list.hpp \ + aux_/unique_ptr.hpp \ + aux_/utp_socket_manager.hpp \ + aux_/utp_stream.hpp \ + aux_/vector.hpp \ + aux_/windows.hpp \ + aux_/win_cng.hpp \ + aux_/win_crypto_provider.hpp \ + aux_/win_util.hpp \ + \ + extensions/smart_ban.hpp \ + extensions/ut_metadata.hpp \ + extensions/ut_pex.hpp \ + \ + kademlia/announce_flags.hpp \ + kademlia/dht_observer.hpp \ + kademlia/dht_settings.hpp \ + kademlia/dht_state.hpp \ + kademlia/dht_storage.hpp \ + kademlia/dht_tracker.hpp \ + kademlia/direct_request.hpp \ + kademlia/dos_blocker.hpp \ + kademlia/ed25519.hpp \ + kademlia/find_data.hpp \ + kademlia/get_item.hpp \ + kademlia/get_peers.hpp \ + kademlia/io.hpp \ + kademlia/item.hpp \ + kademlia/msg.hpp \ + kademlia/node.hpp \ + kademlia/node_entry.hpp \ + kademlia/node_id.hpp \ + kademlia/observer.hpp \ + kademlia/put_data.hpp \ + kademlia/refresh.hpp \ + kademlia/routing_table.hpp \ + kademlia/rpc_manager.hpp \ + kademlia/sample_infohashes.hpp \ + kademlia/traversal_algorithm.hpp \ + kademlia/types.hpp + +TRY_SIGNAL = \ + signal_error_code.cpp \ + signal_error_code.hpp \ + try_signal.cpp \ + try_signal.hpp \ + try_signal_mingw.hpp \ + try_signal_msvc.hpp \ + try_signal_posix.hpp \ + LICENSE \ + README.rst \ + Jamfile \ + CMakeLists.txt + +ASIO_GNUTLS = \ + LICENSE_1_0.txt \ + Jamfile \ + include/boost/asio/gnutls.hpp \ + include/boost/asio/gnutls \ + include/boost/asio/gnutls/rfc2818_verification.hpp \ + include/boost/asio/gnutls/stream_base.hpp \ + include/boost/asio/gnutls/error.hpp \ + include/boost/asio/gnutls/host_name_verification.hpp \ + include/boost/asio/gnutls/stream.hpp \ + include/boost/asio/gnutls/context_base.hpp \ + include/boost/asio/gnutls/verify_context.hpp \ + include/boost/asio/gnutls/context.hpp \ + README.md \ + test/unit_test.hpp \ + test/gnutls/context_base.cpp \ + test/gnutls/stream_base.cpp \ + test/gnutls/host_name_verification.cpp \ + test/gnutls/error.cpp \ + test/gnutls/Jamfile.v2 \ + test/gnutls/context.cpp \ + test/gnutls/rfc2818_verification.cpp \ + test/gnutls/stream.cpp + +SIM_SOURCES = \ + Jamfile \ + create_torrent.cpp \ + create_torrent.hpp \ + fake_peer.hpp \ + disk_io.hpp \ + disk_io.cpp \ + make_proxy_settings.hpp \ + setup_dht.cpp \ + setup_dht.hpp \ + setup_swarm.cpp \ + setup_swarm.hpp \ + test_auto_manage.cpp \ + test_checking.cpp \ + test_dht.cpp \ + test_dht_bootstrap.cpp \ + test_dht_rate_limit.cpp \ + test_dht_storage.cpp \ + test_error_handling.cpp \ + test_fast_extensions.cpp \ + test_http_connection.cpp \ + test_ip_filter.cpp \ + test_metadata_extension.cpp \ + test_optimistic_unchoke.cpp \ + test_pause.cpp \ + test_pe_crypto.cpp \ + test_peer_connection.cpp \ + test_save_resume.cpp \ + test_session.cpp \ + test_socks5.cpp \ + test_super_seeding.cpp \ + test_swarm.cpp \ + test_thread_pool.cpp \ + test_torrent_status.cpp \ + test_tracker.cpp \ + test_transfer.cpp \ + test_transfer_matrix.cpp \ + test_utp.cpp \ + test_web_seed.cpp \ + transfer_sim.hpp \ + transfer_sim.cpp \ + utils.cpp \ + utils.hpp + +LIBSIM_SOURCES = \ + acceptor.cpp \ + default_config.cpp \ + high_resolution_clock.cpp \ + high_resolution_timer.cpp \ + http_proxy.cpp \ + http_server.cpp \ + io_service.cpp \ + pcap.cpp \ + queue.cpp \ + resolver.cpp \ + simulation.cpp \ + simulator.cpp \ + sink_forwarder.cpp \ + socks_server.cpp \ + tcp_socket.cpp \ + udp_socket.cpp + +LIBSIM_HEADERS = \ + chrono.hpp \ + config.hpp \ + function.hpp \ + handler_allocator.hpp \ + http_proxy.hpp \ + http_server.hpp \ + noexcept_movable.hpp \ + packet.hpp \ + pcap.hpp \ + pop_warnings.hpp \ + push_warnings.hpp \ + queue.hpp \ + simulator.hpp \ + sink.hpp \ + sink_forwarder.hpp \ + socks_server.hpp \ + utils.hpp + +LIBSIM_EXTRA = \ + CMakeLists.txt \ + Jamfile \ + Jamroot.jam \ + LICENSE \ + README.rst + +LIBSIM_TESTS = \ + acceptor.cpp \ + main.cpp \ + multi_accept.cpp \ + multi_homed.cpp \ + null_buffers.cpp \ + parse_request.cpp \ + resolver.cpp \ + timer.cpp \ + udp_socket.cpp \ + catch.hpp + +TEST_SOURCES = \ + enum_if.cpp \ + test_add_torrent.cpp \ + test_alert_manager.cpp \ + test_alert_types.cpp \ + test_alloca.cpp \ + test_apply_pad.cpp \ + test_auto_unchoke.cpp \ + test_bandwidth_limiter.cpp \ + test_bdecode.cpp \ + test_bencoding.cpp \ + test_bitfield.cpp \ + test_bloom_filter.cpp \ + test_buffer.cpp \ + test_checking.cpp \ + test_copy_file.cpp \ + test_crc32.cpp \ + test_create_torrent.cpp \ + test_dht.cpp \ + test_dht_storage.cpp \ + test_direct_dht.cpp \ + test_dos_blocker.cpp \ + test_ed25519.cpp \ + test_enum_net.cpp \ + test_fast_extension.cpp \ + test_fence.cpp \ + test_ffs.cpp \ + test_file.cpp \ + test_file_progress.cpp \ + test_file_storage.cpp \ + test_flags.cpp \ + test_generate_peer_id.cpp \ + test_gzip.cpp \ + test_hash_picker.cpp \ + test_hasher.cpp \ + test_hasher512.cpp \ + test_heterogeneous_queue.cpp \ + test_http_connection.cpp \ + test_http_parser.cpp \ + test_identify_client.cpp \ + test_info_hash.cpp \ + test_io.cpp \ + test_ip_filter.cpp \ + test_ip_voter.cpp \ + test_listen_socket.cpp \ + test_lsd.cpp \ + test_magnet.cpp \ + test_merkle.cpp \ + test_merkle_tree.cpp \ + test_mmap.cpp \ + test_packet_buffer.cpp \ + test_part_file.cpp \ + test_pe_crypto.cpp \ + test_peer_classes.cpp \ + test_peer_list.cpp \ + test_peer_priority.cpp \ + test_piece_picker.cpp \ + test_primitives.cpp \ + test_priority.cpp \ + test_privacy.cpp \ + test_read_piece.cpp \ + test_read_resume.cpp \ + test_receive_buffer.cpp \ + test_recheck.cpp \ + test_remap_files.cpp \ + test_remove_torrent.cpp \ + test_resolve_links.cpp \ + test_resume.cpp \ + test_session.cpp \ + test_session_params.cpp \ + test_settings_pack.cpp \ + test_sha1_hash.cpp \ + test_similar_torrent.cpp \ + test_sliding_average.cpp \ + test_socket_io.cpp \ + test_span.cpp \ + test_ssl.cpp \ + test_stack_allocator.cpp \ + test_stat_cache.cpp \ + test_storage.cpp \ + test_store_buffer.cpp \ + test_string.cpp \ + test_tailqueue.cpp \ + test_threads.cpp \ + test_time.cpp \ + test_time_critical.cpp \ + test_timestamp_history.cpp \ + test_torrent.cpp \ + test_torrent_info.cpp \ + test_torrent_list.cpp \ + test_tracker.cpp \ + test_truncate.cpp \ + test_transfer.cpp \ + test_upnp.cpp \ + test_url_seed.cpp \ + test_utf8.cpp \ + test_utp.cpp \ + test_web_seed.cpp \ + test_web_seed_ban.cpp \ + test_web_seed_chunked.cpp \ + test_web_seed_http.cpp \ + test_web_seed_http_pw.cpp \ + test_web_seed_redirect.cpp \ + test_web_seed_socks4.cpp \ + test_web_seed_socks5.cpp \ + test_web_seed_socks5_no_peers.cpp \ + test_web_seed_socks5_pw.cpp \ + test_xml.cpp \ + \ + main.cpp \ + broadcast_socket.cpp \ + broadcast_socket.hpp \ + test.cpp \ + setup_transfer.cpp \ + dht_server.cpp \ + udp_tracker.cpp \ + peer_server.cpp \ + bittorrent_peer.cpp \ + make_torrent.cpp \ + web_seed_suite.cpp \ + swarm_suite.cpp \ + test_utils.cpp \ + settings.cpp \ + print_alerts.cpp \ + test.hpp \ + setup_transfer.hpp \ + dht_server.hpp \ + peer_server.hpp \ + udp_tracker.hpp \ + web_seed_suite.hpp \ + swarm_suite.hpp \ + test_utils.hpp \ + settings.hpp \ + make_torrent.hpp \ + bittorrent_peer.hpp \ + print_alerts.hpp + +TEST_TORRENTS = \ + absolute_filename.torrent \ + backslash_path.torrent \ + bad_name.torrent \ + base.torrent \ + collection.torrent \ + collection2.torrent \ + creation_date.torrent \ + dht_nodes.torrent \ + duplicate_files.torrent \ + duplicate_web_seeds.torrent \ + empty_httpseed.torrent \ + empty_path.torrent \ + empty_path_multi.torrent \ + empty-files-1.torrent \ + empty-files-2.torrent \ + empty-files-3.torrent \ + empty-files-4.torrent \ + empty-files-5.torrent \ + hidden_parent_path.torrent \ + httpseed.torrent \ + invalid_file_size.torrent \ + invalid_filename.torrent \ + invalid_filename2.torrent \ + invalid_info.torrent \ + invalid_name.torrent \ + invalid_name2.torrent \ + invalid_name3.torrent \ + invalid_path_list.torrent \ + invalid_piece_len.torrent \ + invalid_pieces.torrent \ + invalid_symlink.torrent \ + large.torrent \ + long_name.torrent \ + many_pieces.torrent \ + missing_path_list.torrent \ + missing_piece_len.torrent \ + negative_file_size.torrent \ + negative_piece_len.torrent \ + negative_size.torrent \ + no_creation_date.torrent \ + no_files.torrent \ + no_name.torrent \ + overlapping_symlinks.torrent \ + pad_file.torrent \ + pad_file_no_path.torrent \ + parent_path.torrent \ + sample.torrent \ + similar.torrent \ + similar2.torrent \ + single_multi_file.torrent \ + slash_path.torrent \ + slash_path2.torrent \ + slash_path3.torrent \ + string.torrent \ + symlink1.torrent \ + symlink2.torrent \ + symlink_zero_size.torrent \ + unaligned_pieces.torrent \ + unordered.torrent \ + url_list.torrent \ + url_list2.torrent \ + url_list3.torrent \ + url_seed.torrent \ + url_seed_multi.torrent \ + url_seed_multi_single_file.torrent \ + url_seed_multi_space.torrent \ + url_seed_multi_space_nolist.torrent \ + whitespace_url.torrent \ + v2.torrent \ + v2_multipiece_file.torrent \ + v2_only.torrent \ + v2_invalid_filename.torrent \ + v2_mismatching_metadata.torrent \ + v2_no_power2_piece.torrent \ + v2_invalid_file.torrent \ + v2_deep_recursion.torrent \ + v2_non_multiple_piece_layer.torrent \ + v2_piece_layer_invalid_file_hash.torrent \ + v2_incomplete_piece_layer.torrent \ + v2_invalid_pad_file.torrent \ + v2_invalid_piece_layer.torrent \ + v2_invalid_piece_layer_size.torrent \ + v2_multiple_files.torrent \ + v2_bad_file_alignment.torrent \ + v2_unordered_files.torrent \ + v2_overlong_integer.torrent \ + v2_missing_file_root_invalid_symlink.torrent \ + v2_symlinks.torrent \ + v2_no_piece_layers.torrent \ + v2_large_file.torrent \ + v2_large_offset.torrent \ + v2_piece_size.torrent \ + v2_zero_root.torrent \ + v2_zero_root_small.torrent \ + v2_hybrid.torrent \ + v2_invalid_root_hash.torrent \ + zero.torrent \ + zero2.torrent + +MUTABLE_TEST_TORRENTS = \ + test1.torrent \ + test1_pad_files.torrent \ + test1_single.torrent \ + test1_single_padded.torrent \ + test2.torrent \ + test2_pad_files.torrent \ + test3.torrent \ + test3_pad_files.torrent + +TEST_EXTRA = Jamfile \ + Jamfile \ + CMakeLists.txt \ + $(addprefix test_torrents/,${TEST_TORRENTS}) \ + $(addprefix mutable_test_torrents/,${MUTABLE_TEST_TORRENTS}) \ + root1.xml \ + root2.xml \ + root3.xml \ + ssl/regenerate_test_certificate.sh \ + ssl/cert_request.pem \ + ssl/dhparams.pem \ + ssl/invalid_peer_certificate.pem \ + ssl/invalid_peer_private_key.pem \ + ssl/peer_certificate.pem \ + ssl/peer_private_key.pem \ + ssl/root_ca_cert.pem \ + ssl/root_ca_private.pem \ + ssl/server.pem \ + zeroes.gz \ + corrupt.gz \ + invalid1.gz \ + utf8_test.txt \ + web_server.py \ + socks.py \ + http_proxy.py \ + root1.xml \ + root2.xml \ + root3.xml + +dist: FORCE + (cd docs; make) + rm -rf libtorrent-rasterbar-${VERSION} libtorrent-rasterbar-${VERSION}.tar.gz + mkdir libtorrent-rasterbar-${VERSION} + rsync -R ${EXTRA_DIST} \ + $(addprefix src/,${SOURCES}) \ + $(addprefix src/kademlia/,${KADEMLIA_SOURCES}) \ + $(addprefix include/libtorrent/,${HEADERS}) \ + $(addprefix examples/,${EXAMPLE_FILES}) \ + $(addprefix tools/,${TOOLS_FILES}) \ + $(addprefix bindings/python/,${PYTHON_FILES}) \ + $(addprefix test/,${TEST_SOURCES}) \ + $(addprefix test/,${TEST_EXTRA}) \ + $(addprefix simulation/,${SIM_SOURCES}) \ + $(addprefix deps/try_signal/,${TRY_SIGNAL}) \ + $(addprefix deps/asio-gnutls/,${ASIO_GNUTLS}) \ + $(addprefix simulation/libsimulator/,${LIBSIM_EXTRA}) \ + $(addprefix simulation/libsimulator/test,${LIBSIM_TEST}) \ + $(addprefix simulation/libsimulator/include/simulator/,${LIBSIM_HEADERS}) \ + $(addprefix simulation/libsimulator/src/,${LIBSIM_SOURCES}) \ + $(addprefix src/ed25519/,$(ED25519_SOURCE)) \ + libtorrent-rasterbar-${VERSION} + tar -czf libtorrent-rasterbar-${VERSION}.tar.gz libtorrent-rasterbar-${VERSION} + +FORCE: + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..58378c1 --- /dev/null +++ b/NEWS @@ -0,0 +1 @@ +See ChangeLog diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..b45fb29 --- /dev/null +++ b/README.rst @@ -0,0 +1,70 @@ +.. image:: docs/img/logo-color-text.png + +.. image:: https://github.com/arvidn/libtorrent/actions/workflows/windows.yml/badge.svg + :target: https://github.com/arvidn/libtorrent/actions/workflows/windows.yml + +.. image:: https://github.com/arvidn/libtorrent/actions/workflows/macos.yml/badge.svg + :target: https://github.com/arvidn/libtorrent/actions/workflows/macos.yml + +.. image:: https://github.com/arvidn/libtorrent/actions/workflows/linux.yml/badge.svg + :target: https://github.com/arvidn/libtorrent/actions/workflows/linux.yml + +.. image:: https://github.com/arvidn/libtorrent/actions/workflows/python.yml/badge.svg + :target: https://github.com/arvidn/libtorrent/actions/workflows/python.yml + +.. image:: https://ci.appveyor.com/api/projects/status/w7teauvub5813mew/branch/RC_2_0?svg=true + :target: https://ci.appveyor.com/project/arvidn/libtorrent/branch/RC_2_0 + +.. image:: https://api.cirrus-ci.com/github/arvidn/libtorrent.svg?branch=RC_2_0 + :target: https://cirrus-ci.com/github/arvidn/libtorrent + +.. image:: https://img.shields.io/lgtm/alerts/g/arvidn/libtorrent.svg?logo=lgtm&logoWidth=18 + :target: https://lgtm.com/projects/g/arvidn/libtorrent/alerts/ + +.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/libtorrent.svg + :target: https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&q=proj%3Alibtorrent&can=1 + +.. image:: https://codecov.io/github/arvidn/libtorrent/coverage.svg?branch=RC_2_0 + :target: https://codecov.io/github/arvidn/libtorrent?branch=RC_2_0&view=all#sort=missing&dir=desc + +.. image:: https://img.shields.io/lgtm/grade/cpp/g/arvidn/libtorrent.svg?logo=lgtm&logoWidth=18 + :target: https://lgtm.com/projects/g/arvidn/libtorrent/context:cpp + +.. image:: https://www.openhub.net/p/rasterbar-libtorrent/widgets/project_thin_badge.gif + :target: https://www.openhub.net/p/rasterbar-libtorrent + +.. image:: https://bestpractices.coreinfrastructure.org/projects/3020/badge + :target: https://bestpractices.coreinfrastructure.org/en/projects/3020 + +libtorrent is an open source C++ library implementing the BitTorrent protocol, +along with most popular extensions, making it suitable for real world +deployment. It is configurable to be able to fit both servers and embedded +devices. + +The main goals of libtorrent are to be efficient and easy to use. + +See `libtorrent.org`__ for more detailed build and usage instructions. + +.. __: https://libtorrent.org + +To build with boost-build, make sure boost and boost-build is installed and run: + + b2 + +In the libtorrent root. To build the examples, run ``b2`` in the ``examples`` +directory. + +See `building.html`__ for more details on how to build and which configuration +options are available. For python bindings, see `the python docs`__. + +libtorrent `ABI report`_. + +.. _`ABI report`: https://abi-laboratory.pro/index.php?view=timeline&l=libtorrent + +libtorrent package versions in linux distributions, on repology_. + +.. _repology: https://repology.org/project/libtorrent-rasterbar/versions + +.. __: docs/building.rst +.. __: docs/python_binding.rst + diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt new file mode 100644 index 0000000..5dd618f --- /dev/null +++ b/bindings/CMakeLists.txt @@ -0,0 +1,3 @@ +if (python-bindings) + add_subdirectory(python) +endif() diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt new file mode 100644 index 0000000..827977b --- /dev/null +++ b/bindings/python/CMakeLists.txt @@ -0,0 +1,128 @@ +cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR) # Configurable policies: <= CMP0102 + +# To build python bindings we need a python executable and boost python module. Unfortunately, +# their names might not be interlinked and we can not implement a general solution. +# The code below assumes default boost installation, when the module for python 3 is named 'python3'. +# To customize that one can provide a name for the Boost::python module via +# 'boost-python-module-name' variable when invoking cmake. +# E.g. on Gentoo with python 3.6 and Boost::python library name 'libboost_python-3.6.so' +# the parameter would be -Dboost-python-module-name="python-3.6". + +# The extension module and the cpython executable have to use the same C runtime library. On Windows +# Python is compiled with MSVC and we will test MSVC version to make sure that it is the same for +# the given Python version and our extension module. See https://wiki.python.org/moin/WindowsCompilers +# for details. We provide a flag to skip this test for whatever reason (pass -Dskip-python-runtime-test=True) + +# Sets _ret to a list of python versions (major.minor) that use the same MSVC runtime as this build does +# assumes MSVC was detected already +# See https://en.wikipedia.org/wiki/Microsoft_Visual_C++#Internal_version_numbering +# See https://devguide.python.org/#status-of-python-branches for supported python versions +function(_get_compatible_python_versions _ret) + if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19 AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 20) + list(APPEND _tmp 3.6 3.7 3.8 3.9) + endif() + set(${_ret} ${_tmp} PARENT_SCOPE) +endfunction() + + +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND NOT skip-python-runtime-test) + _get_compatible_python_versions(Python_ADDITIONAL_VERSIONS) +endif() + +find_package(Python3 COMPONENTS Interpreter Development REQUIRED) +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND NOT skip-python-runtime-test) + message(STATUS "Testing found python version. Requested: ${Python_ADDITIONAL_VERSIONS}, found: ${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}") + if (NOT "${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}" IN_LIST Python_ADDITIONAL_VERSIONS) + message(FATAL_ERROR "Incompatible Python and C runtime: MSVC ${CMAKE_CXX_COMPILER_VERSION} and Python ${Python3_VERSION}") + endif() +endif() + +set(boost-python-module-name "python${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}" CACHE STRING "Boost::python module name, e.g. 'python-3.6'") + +find_package(Boost REQUIRED COMPONENTS ${boost-python-module-name}) + +Python3_add_library(python-libtorrent MODULE WITH_SOABI + src/alert.cpp + src/converters.cpp + src/create_torrent.cpp + src/datetime.cpp + src/entry.cpp + src/error_code.cpp + src/fingerprint.cpp + src/info_hash.cpp + src/ip_filter.cpp + src/load_torrent.cpp + src/magnet_uri.cpp + src/module.cpp + src/peer_info.cpp + src/session.cpp + src/session_settings.cpp + src/sha1_hash.cpp + src/sha256_hash.cpp + src/string.cpp + src/torrent_handle.cpp + src/torrent_info.cpp + src/torrent_status.cpp + src/utility.cpp + src/version.cpp +) + +set_target_properties(python-libtorrent + PROPERTIES + OUTPUT_NAME libtorrent +) + +if (MSVC) + target_compile_options(python-libtorrent PRIVATE /bigobj) +endif() + +target_link_libraries(python-libtorrent + PRIVATE + torrent-rasterbar + "Boost::${boost-python-module-name}" +) + +# Bindings module uses deprecated libtorrent features, thus we disable these warnings +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + check_cxx_compiler_flag("-Wno-deprecated-declarations" _WNO_DEPRECATED_DECLARATIONS) + if (_WNO_DEPRECATED_DECLARATIONS) + target_compile_options(python-libtorrent PRIVATE -Wno-deprecated-declarations) + endif() +endif() + +if (python-install-system-dir) + set(_PYTHON3_SITE_ARCH "${Python3_SITEARCH}") +else() + execute_process( + COMMAND "${Python3_EXECUTABLE}" -c [=[ +import distutils.sysconfig +print(distutils.sysconfig.get_python_lib(prefix='', plat_specific=True)) +]=] + OUTPUT_VARIABLE _PYTHON3_SITE_ARCH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) +endif() + +message(STATUS "Python 3 site packages: ${_PYTHON3_SITE_ARCH}") +message(STATUS "Python 3 extension suffix: ${Python3_SOABI}") + +install(TARGETS python-libtorrent DESTINATION "${_PYTHON3_SITE_ARCH}") + +if (python-egg-info) + set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.cmake.in") + set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py") + set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/timestamp") + set(DEPS python-libtorrent "${SETUP_PY}") + + configure_file(${SETUP_PY_IN} ${SETUP_PY} @ONLY) + + add_custom_command(OUTPUT ${OUTPUT} + COMMAND ${Python3_EXECUTABLE} ${SETUP_PY} egg_info + COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT} + DEPENDS ${DEPS} + ) + + add_custom_target(python_bindings ALL DEPENDS ${OUTPUT}) + + install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/libtorrent.egg-info" DESTINATION "${_PYTHON3_SITE_ARCH}") +endif() diff --git a/bindings/python/Jamfile b/bindings/python/Jamfile new file mode 100644 index 0000000..0fcf61c --- /dev/null +++ b/bindings/python/Jamfile @@ -0,0 +1,363 @@ +import python ; +import feature ; +import feature : feature ; +import project ; +import targets ; +import "class" : new ; +import modules ; + +use-project /torrent : ../.. ; + +BOOST_ROOT = [ modules.peek : BOOST_ROOT ] ; + +CXXFLAGS = [ modules.peek : CXXFLAGS ] ; +LDFLAGS = [ modules.peek : LDFLAGS ] ; + +ECHO "CXXFLAGS =" $(CXXFLAGS) ; +ECHO "LDFLAGS =" $(LDFLAGS) ; + +# this is used to make bjam use the same version of python which is executing setup.py + +feature libtorrent-link : shared static prebuilt : composite propagated ; + +feature libtorrent-python-pic : off on : composite propagated link-incompatible ; +feature.compose on : -fPIC ; + +# when invoking the install_module target, this feature can be specified to +# install the python module to a specific directory +feature python-install-path : : free path ; + +# when not specifying a custom install path, this controls whether to install +# the python module in the system directory or user-specifc directory +feature python-install-scope : user system : ; + +# copied from boost 1.63's boost python jamfile +rule find-py3-version +{ + local BOOST_VERSION_TAG = [ modules.peek boostcpp : BOOST_VERSION_TAG ] ; + if $(BOOST_VERSION_TAG) >= 1_67 + { + # starting with boost 1.67.0 boost python no longer define a separate + # target for python3 (boost_python3) so then we just use the regular + # boost_python target + return ; + } + local versions = [ feature.values python ] ; + local py3ver ; + for local v in $(versions) + { + if $(v) >= 3.0 + { + py3ver = $(v) ; + } + } + return $(py3ver) ; +} + +if $(BOOST_ROOT) +{ + use-project /boost : $(BOOST_ROOT) ; + alias boost_python : /boost/python//boost_python : : : $(BOOST_ROOT) ; + if [ find-py3-version ] + { + alias boost_python3 : /boost/python//boost_python3 : : : $(BOOST_ROOT) ; + } + else + { + alias boost_python3 : boost_python ; + } +} +else +{ + local boost-lib-search-path = + /opt/local/lib + /usr/lib + /usr/local/lib + /sw/lib + /usr/g++/lib + ; + + local boost-include-path = + /opt/local/include + /usr/local/include + /usr/sfw/include + ; + + import version ; + + rule boost_python_version ( name : type ? : properties * ) + { + # examples of names for the boost_python library: + # ubuntu bionic (1.65.1): libboost_python3-py36.so.1.65.1 + # ubuntu bionic (1.65): libboost_python-py36.so + # libboost_python3-py36.so + # libboost_python3.so + # ubuntu bionic (1.62.0): libboost_python-py36.so.1.62.0 + # ubuntu focal (1.67.0): libboost_python38.so.1.67.0 + # ubuntu focal (1.71.0): libboost_python38.so + # ubuntu groovy (1.71.0): libboost_python38.so + # ubuntu hirsute(1.71.0): libboost_python39.so + # debian buster (1.67.0): libboost_python37.so + # libboost_python3.so + # libboost_python3-py37.so + # debian sid (1.74.0): libboost_python39.so + # debian sid (1.71.0): libboost_python39.so + # debian bullseye (1.71): libboost_python39.so + # devian buster-backports (1.71): libboost_python37.so + # debian buster (1.67): libboost_python37.so.1.67.0 + # debian stretch (1.62.0): libboost_python-py35.so.1.62.0 + # debian stretch (1.62): libboost_python-py35.so + # debian jessie (1.55.0): libboost_python-py34.so.1.55.0 + # boost Jamfile: libboost_python38.so.1.73.0 + + local py-version-str = [ $(properties).get ] ; + local py-version = "" ; + local infix = "" ; + if $(py-version-str) { + py-version = [ SPLIT_BY_CHARACTERS $(py-version-str) : "." ] ; + + if [ version.version-less $(py-version) : 3 7 ] && [ $(properties).get ] = linux + { + infix = "-py" ; + } + } + local boost-python-lib = "boost_python" $(infix) $(py-version) ; + + if $(type) in SEARCHED_LIB + { + return $(boost-python-lib:J) ; + } + return ; + } + + lib boost_python : : @boost_python_version : : $(boost-include-path) ; + alias boost_python3 : boost_python ; +} + +lib prebuilt_libtorrent : : torrent-rasterbar : : ../../include ; +lib prebuilt_libtorrent : : windows torrent : : ../../include ; + +rule libtorrent_linking ( properties * ) +{ + local result ; + + # allow larger .obj files (with more sections) + if msvc in $(properties) || intel-win in $(properties) + { + # allow larger .obj files (with more sections) + result += /bigobj ; + } + + if gcc in $(properties) && windows in $(properties) + { + # allow larger .obj files (with more sections) + result += -Wa,-mbig-obj ; + } + + if ! windows in $(properties) + && gcc in $(properties) + && static in $(properties) + { + result += on ; + } + + if gcc in $(properties) + || darwin in $(properties) + || clang in $(properties) + || clang-darwin in $(properties) + { + # hide non-external symbols + result += -fvisibility=hidden ; + result += -fvisibility-inlines-hidden ; + + if ( gcc in $(properties) ) + { + result += -Wl,-Bsymbolic ; + } + } + + if static in $(properties) + { + ECHO "WARNING: you probably want to specify libtorrent-link=static rather than link=static" ; + } + + local BOOST_VERSION_TAG = [ modules.peek boostcpp : BOOST_VERSION_TAG ] ; + if static in $(properties) && $(BOOST_VERSION_TAG) < 1_74 && linux in $(properties) + { + ECHO "WARNING: you cannot link statically against boost-python on linux before version 1.74.0, because it links against pthread statically in that case, which is not allowed" ; + } + + local boost_python_lib ; + + for local prop in $(properties) + { + switch $(prop) + { + case 2.* : boost_python_lib = boost_python ; + case 3.* : boost_python_lib = boost_python3 ; + } + } + + if ! $(boost_python_lib) + { + ECHO "WARNING: unknown python version" ; + boost_python_lib = boost_python ; + } + + # linux must link dynamically against boost python because it pulls + # in libpthread, which must be linked dynamically since we're building a .so + # (the static build of libpthread is not position independent) + if shared in $(properties) || ( linux in $(properties) && $(BOOST_VERSION_TAG) < 1_74 ) + { + result += $(boost_python_lib)/shared/off ; + } + else + { + result += $(boost_python_lib)/static/off ; + } + + if shared in $(properties) + { + result += /torrent//torrent/shared ; + } + else if static in $(properties) + { + result += /torrent//torrent/static ; + } + else + { + result += prebuilt_libtorrent ; + } + + return $(result) ; +} + +# this is a copy of the rule from boost-build's python-extension, but without +# specifying no as a mandatory property. That property +# would otherwise cause build failures because it suppresses linking against the +# runtime library and kernel32 on windows + +rule my-python-extension ( name : sources * : requirements * : default-build * : + usage-requirements * ) +{ + requirements += /python//python_for_extensions ; + + local project = [ project.current ] ; + + targets.main-target-alternative + [ new typed-target $(name) : $(project) : PYTHON_EXTENSION + : [ targets.main-target-sources $(sources) : $(name) ] + : [ targets.main-target-requirements $(requirements) : $(project) ] + : [ targets.main-target-default-build $(default-build) : $(project) ] + ] ; +} + +my-python-extension libtorrent + : # sources + src/module.cpp + src/sha1_hash.cpp + src/sha256_hash.cpp + src/info_hash.cpp + src/converters.cpp + src/create_torrent.cpp + src/fingerprint.cpp + src/utility.cpp + src/session.cpp + src/entry.cpp + src/torrent_info.cpp + src/string.cpp + src/torrent_handle.cpp + src/torrent_status.cpp + src/session_settings.cpp + src/version.cpp + src/alert.cpp + src/datetime.cpp + src/peer_info.cpp + src/ip_filter.cpp + src/magnet_uri.cpp + src/error_code.cpp + src/load_torrent.cpp + : # requirements + src + gcc:-Wno-deprecated-declarations + darwin:-Wno-deprecated-declarations + darwin:-Wno-unused-command-line-argument + @libtorrent_linking + openssl:/torrent//ssl + openssl:/torrent//crypto + "$(CXXFLAGS:J= )" + "$(LDFLAGS:J= )" + # C4268: 'identifier' : 'const' static/global data initialized + # with compiler generated default constructor fills the object with zeros + msvc:/wd4268 + : # default-build + all + 14 + : # usage-requirements + false + ; + +rule python-install-dir ( properties * ) +{ + local install-dir = [ feature.get-values python-install-path : $(properties) ] ; + if ( $(install-dir) != "" ) + { + # if the user has provided an install location, use that one + return $(install-dir) ; + } + + local python-interpreter = [ feature.get-values python.interpreter : $(properties) ] ; + if ( $(python-interpreter) = "" ) + { + return . ; + } + + # sys.path are defined differently between python2 and python3 + + local python-path ; + if system in $(properties) + { + python-path = [ SHELL "$(python-interpreter) -c \"import distutils.sysconfig; import sys; sys.stdout.write(distutils.sysconfig.get_python_lib())\"" ] ; + } + else + { + python-path = [ SHELL "$(python-interpreter) -c \"import site; import sys; sys.stdout.write(site.USER_SITE)\"" ] ; + } + + if $(python-path) = "" + { + return . ; + } + + ECHO "python install directory:" $(python-path) ; + return $(python-path) ; +} + +install install_module + : libtorrent + : @python-install-dir + PYTHON_EXTENSION + ; + +explicit install_module ; + +install stage_module + : libtorrent + : . + PYTHON_EXTENSION + : 14 + ; + +install stage_dependencies + : /torrent//torrent + boost_python + boost_python3 + : dependencies + on + SHARED_LIB + : 14 + ; + +explicit stage_module ; +explicit stage_dependencies ; + diff --git a/bindings/python/client.py b/bindings/python/client.py new file mode 100755 index 0000000..300572f --- /dev/null +++ b/bindings/python/client.py @@ -0,0 +1,381 @@ +#!/usr/bin/env python3 + +# Copyright Daniel Wallin 2006. Use, modification and distribution is +# subject to the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + +import sys +import atexit +import libtorrent as lt +import time +import os.path + + +class WindowsConsole: + def __init__(self): + self.console = Console.getconsole() + + def clear(self): + self.console.page() + + def write(self, str): + self.console.write(str) + + def sleep_and_input(self, seconds): + time.sleep(seconds) + if msvcrt.kbhit(): + return msvcrt.getch() + return None + + +class UnixConsole: + def __init__(self): + self.fd = sys.stdin + self.old = termios.tcgetattr(self.fd.fileno()) + new = termios.tcgetattr(self.fd.fileno()) + new[3] = new[3] & ~termios.ICANON + new[6][termios.VTIME] = 0 + new[6][termios.VMIN] = 1 + termios.tcsetattr(self.fd.fileno(), termios.TCSADRAIN, new) + + atexit.register(self._onexit) + + def _onexit(self): + termios.tcsetattr(self.fd.fileno(), termios.TCSADRAIN, self.old) + + def clear(self): + sys.stdout.write('\033[2J\033[0;0H') + sys.stdout.flush() + + def write(self, str): + sys.stdout.write(str) + sys.stdout.flush() + + def sleep_and_input(self, seconds): + read, __, __ = select.select( + [self.fd.fileno()], [], [], seconds) + if len(read) > 0: + return self.fd.read(1) + return None + + +if os.name == 'nt': + import Console + import msvcrt +else: + import termios + import select + + +def write_line(console, line): + console.write(line) + + +def add_suffix(val): + prefix = ['B', 'kB', 'MB', 'GB', 'TB'] + for i in range(len(prefix)): + if abs(val) < 1000: + if i == 0: + return '%5.3g%s' % (val, prefix[i]) + else: + return '%4.3g%s' % (val, prefix[i]) + val /= 1000 + + return '%6.3gPB' % val + + +def progress_bar(progress, width): + assert(progress <= 1) + progress_chars = int(progress * width + 0.5) + return progress_chars * '#' + (width - progress_chars) * '-' + + +def print_peer_info(console, peers): + + out = (' down (total ) up (total )' + ' q r flags block progress client\n') + + for p in peers: + + out += '%s/s ' % add_suffix(p.down_speed) + out += '(%s) ' % add_suffix(p.total_download) + out += '%s/s ' % add_suffix(p.up_speed) + out += '(%s) ' % add_suffix(p.total_upload) + out += '%2d ' % p.download_queue_length + out += '%2d ' % p.upload_queue_length + + out += 'I' if p.flags & lt.peer_info.interesting else '.' + out += 'C' if p.flags & lt.peer_info.choked else '.' + out += 'i' if p.flags & lt.peer_info.remote_interested else '.' + out += 'c' if p.flags & lt.peer_info.remote_choked else '.' + out += 'e' if p.flags & lt.peer_info.supports_extensions else '.' + out += 'l' if p.flags & lt.peer_info.local_connection else 'r' + out += ' ' + + if p.downloading_piece_index >= 0: + assert(p.downloading_progress <= p.downloading_total) + out += progress_bar(float(p.downloading_progress) / + p.downloading_total, 15) + else: + out += progress_bar(0, 15) + out += ' ' + + if p.flags & lt.peer_info.handshake: + id = 'waiting for handshake' + elif p.flags & lt.peer_info.connecting: + id = 'connecting to peer' + else: + id = p.client + + out += '%s\n' % id[:10] + + write_line(console, out) + + +def print_download_queue(console, download_queue): + + out = "" + + for e in download_queue: + out += '%4d: [' % e['piece_index'] + for b in e['blocks']: + s = b['state'] + if s == 3: + out += '#' + elif s == 2: + out += '=' + elif s == 1: + out += '-' + else: + out += ' ' + out += ']\n' + + write_line(console, out) + + +def add_torrent(ses, filename, options): + atp = lt.add_torrent_params() + if filename.startswith('magnet:'): + atp = lt.parse_magnet_uri(filename) + else: + ti = lt.torrent_info(filename) + resume_file = os.path.join(options.save_path, ti.name() + '.fastresume') + try: + atp = lt.read_resume_data(open(resume_file, 'rb').read()) + except Exception as e: + print('failed to open resume file "%s": %s' % (resume_file, e)) + atp.ti = ti + + atp.save_path = options.save_path + atp.storage_mode = lt.storage_mode_t.storage_mode_sparse + atp.flags |= lt.torrent_flags.duplicate_is_error \ + | lt.torrent_flags.auto_managed \ + | lt.torrent_flags.duplicate_is_error + ses.async_add_torrent(atp) + + +def main(): + from optparse import OptionParser + + parser = OptionParser() + + parser.add_option('-p', '--port', type='int', help='set listening port') + + parser.add_option( + '-i', '--listen-interface', type='string', + help='set interface for incoming connections', ) + + parser.add_option( + '-o', '--outgoing-interface', type='string', + help='set interface for outgoing connections') + + parser.add_option( + '-d', '--max-download-rate', type='float', + help='the maximum download rate given in kB/s. 0 means infinite.') + + parser.add_option( + '-u', '--max-upload-rate', type='float', + help='the maximum upload rate given in kB/s. 0 means infinite.') + + parser.add_option( + '-s', '--save-path', type='string', + help='the path where the downloaded file/folder should be placed.') + + parser.add_option( + '-r', '--proxy-host', type='string', + help='sets HTTP proxy host and port (separated by \':\')') + + parser.set_defaults( + port=6881, + listen_interface='0.0.0.0', + outgoing_interface='', + max_download_rate=0, + max_upload_rate=0, + save_path='.', + proxy_host='' + ) + + (options, args) = parser.parse_args() + + if options.port < 0 or options.port > 65525: + options.port = 6881 + + options.max_upload_rate *= 1000 + options.max_download_rate *= 1000 + + if options.max_upload_rate <= 0: + options.max_upload_rate = -1 + if options.max_download_rate <= 0: + options.max_download_rate = -1 + + settings = { + 'user_agent': 'python_client/' + lt.__version__, + 'listen_interfaces': '%s:%d' % (options.listen_interface, options.port), + 'download_rate_limit': int(options.max_download_rate), + 'upload_rate_limit': int(options.max_upload_rate), + 'alert_mask': lt.alert.category_t.all_categories, + 'outgoing_interfaces': options.outgoing_interface, + } + + if options.proxy_host != '': + settings['proxy_hostname'] = options.proxy_host.split(':')[0] + settings['proxy_type'] = lt.proxy_type_t.http + settings['proxy_port'] = options.proxy_host.split(':')[1] + + ses = lt.session(settings) + + # map torrent_handle to torrent_status + torrents = {} + alerts_log = [] + + for f in args: + add_torrent(ses, f, options) + + if os.name == 'nt': + console = WindowsConsole() + else: + console = UnixConsole() + + alive = True + while alive: + console.clear() + + out = '' + + for h, t in torrents.items(): + out += 'name: %-40s\n' % t.name[:40] + + if t.state != lt.torrent_status.seeding: + state_str = ['queued', 'checking', 'downloading metadata', + 'downloading', 'finished', 'seeding', + '', 'checking fastresume'] + out += state_str[t.state] + ' ' + + out += '%5.4f%% ' % (t.progress * 100) + out += progress_bar(t.progress, 49) + out += '\n' + + out += 'total downloaded: %d Bytes\n' % t.total_done + out += 'peers: %d seeds: %d distributed copies: %d\n' % \ + (t.num_peers, t.num_seeds, t.distributed_copies) + out += '\n' + + out += 'download: %s/s (%s) ' \ + % (add_suffix(t.download_rate), add_suffix(t.total_download)) + out += 'upload: %s/s (%s) ' \ + % (add_suffix(t.upload_rate), add_suffix(t.total_upload)) + + if t.state != lt.torrent_status.seeding: + out += 'info-hash: %s\n' % t.info_hashes + out += 'next announce: %s\n' % t.next_announce + out += 'tracker: %s\n' % t.current_tracker + + write_line(console, out) + + print_peer_info(console, t.handle.get_peer_info()) + print_download_queue(console, t.handle.get_download_queue()) + + if t.state != lt.torrent_status.seeding: + try: + out = '\n' + fp = h.file_progress() + ti = t.torrent_file + for idx, p in enumerate(fp): + out += progress_bar(p / float(ti.files().file_size(idx)), 20) + out += ' ' + ti.files().file_path(idx) + '\n' + write_line(console, out) + except Exception: + pass + + write_line(console, 76 * '-' + '\n') + write_line(console, '(q)uit), (p)ause), (u)npause), (r)eannounce\n') + write_line(console, 76 * '-' + '\n') + + alerts = ses.pop_alerts() + for a in alerts: + alerts_log.append(a.message()) + + # add new torrents to our list of torrent_status + if isinstance(a, lt.add_torrent_alert): + h = a.handle + h.set_max_connections(60) + h.set_max_uploads(-1) + torrents[h] = h.status() + + # update our torrent_status array for torrents that have + # changed some of their state + if isinstance(a, lt.state_update_alert): + for s in a.status: + torrents[s.handle] = s + + if len(alerts_log) > 20: + alerts_log = alerts_log[-20:] + + for a in alerts_log: + write_line(console, a + '\n') + + c = console.sleep_and_input(0.5) + + ses.post_torrent_updates() + if not c: + continue + + if c == 'r': + for h in torrents: + h.force_reannounce() + elif c == 'q': + alive = False + elif c == 'p': + for h in torrents: + h.pause() + elif c == 'u': + for h in torrents: + h.resume() + + ses.pause() + for h, t in torrents.items(): + if not h.is_valid() or not t.has_metadata: + continue + h.save_resume_data() + + while len(torrents) > 0: + alerts = ses.pop_alerts() + for a in alerts: + if isinstance(a, lt.save_resume_data_alert): + print(a) + data = lt.write_resume_data_buf(a.params) + h = a.handle + if h in torrents: + open(os.path.join(options.save_path, torrents[h].name + '.fastresume'), 'wb').write(data) + del torrents[h] + + if isinstance(a, lt.save_resume_data_failed_alert): + h = a.handle + if h in torrents: + print('failed to save resume data for ', torrents[h].name) + del torrents[h] + time.sleep(0.5) + + +main() diff --git a/bindings/python/make_torrent.py b/bindings/python/make_torrent.py new file mode 100755 index 0000000..85a2229 --- /dev/null +++ b/bindings/python/make_torrent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + + +import sys +import os +import libtorrent + +if len(sys.argv) < 3: + print('usage make_torrent.py file tracker-url') + sys.exit(1) + +input = os.path.abspath(sys.argv[1]) + +fs = libtorrent.file_storage() + +# def predicate(f): +# print f +# return True +# libtorrent.add_files(fs, input, predicate) + +parent_input = os.path.split(input)[0] + +# if we have a single file, use it because os.walk does not work on a single files +if os.path.isfile(input): + size = os.path.getsize(input) + fs.add_file(input, size) + +for root, dirs, files in os.walk(input): + # skip directories starting with . + if os.path.split(root)[1][0] == '.': + continue + + for f in files: + # skip files starting with . + if f[0] == '.': + continue + + # skip thumbs.db on windows + if f == 'Thumbs.db': + continue + + fname = os.path.join(root[len(parent_input) + 1:], f) + size = os.path.getsize(os.path.join(parent_input, fname)) + print('%10d kiB %s' % (size / 1024, fname)) + fs.add_file(fname, size) + +if fs.num_files() == 0: + print('no files added') + sys.exit(1) + +t = libtorrent.create_torrent(fs, 0, 4 * 1024 * 1024) + +t.add_tracker(sys.argv[2]) +t.set_creator('libtorrent %s' % libtorrent.__version__) + +libtorrent.set_piece_hashes(t, parent_input, lambda x: sys.stdout.write('.')) +sys.stdout.write('\n') + +f = open('out.torrent', 'wb+') +f.write(libtorrent.bencode(t.generate())) +f.close() diff --git a/bindings/python/setup.py b/bindings/python/setup.py new file mode 100644 index 0000000..5c2dbeb --- /dev/null +++ b/bindings/python/setup.py @@ -0,0 +1,463 @@ +#!/usr/bin/env python3 + +from distutils import log +import distutils.debug +import distutils.sysconfig +import distutils.util +import os +import pathlib +import sys +import sysconfig +import tempfile +import subprocess +import contextlib +import warnings +import re +import shlex + +import setuptools +import setuptools.command.build_ext as _build_ext_lib + + +def b2_bool(value): + if value: + return "on" + return "off" + + +# Frustratingly, the "bdist_*" unconditionally (re-)run "build" without +# args, even ignoring "build_*" earlier on the same command line. This +# means "build_*" must be a no-op if some build output exists, even if that +# output might have been generated with different args (like +# "--define=FOO"). b2 does not know how to be "naively idempotent" like +# this; it will only generate outputs that exactly match the build request. +# +# It doesn't work to short-circuit initialize_options() / finalize_options(), +# as this doesn't play well with the way options are externally manipulated by +# distutils. +# +# It DOES work to short-circuit Distribution.reinitialize_command(), so we do +# that here. + + +class B2Distribution(setuptools.Distribution): + def reinitialize_command(self, command, reinit_subcommands=0): + if command == "build_ext": + return self.get_command_obj("build_ext") + return super().reinitialize_command( + command, reinit_subcommands=reinit_subcommands + ) + + +# Various setuptools logic expects us to provide Extension instances for each +# extension in the distro. +class StubExtension(setuptools.Extension): + def __init__(self, name): + # An empty sources list ensures the base build_ext command won't build + # anything + super().__init__(name, sources=[]) + + +def b2_escape(value): + value = value.replace("\\", "\\\\") + value = value.replace('"', '\\"') + return f'"{value}"' + + +def write_b2_python_config(config): + write = config.write + # b2 keys python environments by X.Y version, breaking ties by matching + # a property list, called the "condition" of the environment. To ensure + # b2 always picks the environment we define here, we define a special + # feature for the condition and include that in the build request. + + # Note that we might try to reuse a property we know will be set, like + # TORRENT_FOO. But python.jam actually modifies the build request + # in this case, so that TORRENT_FOO becomes something like + # TORRENT_FOO,3.7,linux:... which causes chaos. + # We should always define a custom feature for the condition. + write("import feature ;\n") + write("feature.feature libtorrent-python : on ;\n") + + # python.jam tries to determine correct include and library paths. Per + # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=691378 , include + # detection is broken, but debian's fix is also broken (invokes a global + # pythonX.Y instead of the passed interpreter) + paths = sysconfig.get_paths() + includes = [paths["include"], paths["platinclude"]] + + write("using python") + write(f" : {sysconfig.get_python_version()}") + write(f" : {b2_escape(sys.executable)}") + write(" : ") + write(" ".join(b2_escape(path) for path in includes)) + write(" :") # libraries + write(" : on") + + # Note that all else being equal, we'd like to exactly control the output + # filename, so distutils can find it. However: + # 1. We can only control part of the filename; the prefix is controlled by + # our Jamfile and the final suffix is controlled by python.jam. + # 2. Debian patched python.jam to disregard the configured ext_suffix + # anyway; they always override it with the same sysconfig var we use, + # found by invoking the executable. + + # So instead of applying an arbitrary name, we just try to guarantee that + # b2 produces a name that distutils would expect, on all platforms. In + # other words we apply debian's override everywhere, and hope no other + # overrides ever disagree with us. + + # Note that sysconfig and distutils.sysconfig disagree here, especially on + # windows. + ext_suffix = distutils.sysconfig.get_config_var("EXT_SUFFIX") + + # python.jam appends the platform-specific final suffix on its own. I can't + # find a consistent value from sysconfig or distutils.sysconfig for this. + for plat_suffix in (".pyd", ".dll", ".so", ".sl"): + if ext_suffix.endswith(plat_suffix): + ext_suffix = ext_suffix[: -len(plat_suffix)] + break + write(f" : {b2_escape(ext_suffix)}") + write(" ;\n") + + +BuildExtBase = _build_ext_lib.build_ext + + +class LibtorrentBuildExt(BuildExtBase): + + CONFIG_MODE_DISTUTILS = "distutils" + CONFIG_MODE_B2 = "b2" + CONFIG_MODES = (CONFIG_MODE_DISTUTILS, CONFIG_MODE_B2) + + user_options = BuildExtBase.user_options + [ + ( + "config-mode=", + None, + "'b2' or 'distutils' (default). " + "In b2 mode, setup.py will just invoke b2 using --b2-args. " + "It will not attempt to auto-configure b2 or override any " + "args. " + "In distutils mode, setup.py will attempt to configure " + "and invoke b2 to match the expectations and behavior of " + "distutils (libtorrent will be built against the invoking " + "python, etc; note not all behaviors are currently supported). " + "The feature set will match the version found on pypi. " + "You can selectively override the auto-configuration in " + "this mode with --no-autoconf or --b2-args. For example " + "--b2-args=python=x.y or --no-autoconf=python will prevent " + "python from being auto-configured. " + "Note that --b2-args doesn't currently understand implicit features. " + "Be sure to include their names, e.g. --b2-args=variant=debug", + ), + ( + "b2-args=", + None, + "The full argument string to pass to b2. This is parsed with shlex, to " + "support arguments with spaces. For example: --b2-args 'variant=debug " + '"my-feature=a value with spaces"\'', + ), + ( + "no-autoconf=", + None, + "Space-separated list of b2 arguments that should not be " + "auto-configured in distutils mode.", + ), + ( + "libtorrent-link=", + None, + "(DEPRECATED; use --b2-args=libtorrent-link=...) ", + ), + ( + "boost-link=", + None, + "(DEPRECATED; use --b2-args=boost-link=...) " + ), + ("toolset=", None, "(DEPRECATED; use --b2-args=toolset=...) b2 toolset"), + ( + "pic", + None, + "(DEPRECATED; use --b2-args=libtorrent-python-pic=on) " + "whether to compile with -fPIC", + ), + ( + "optimization=", + None, + "(DEPRECATED; use --b2-args=optimization=...) " "b2 optimization mode", + ), + ( + "hash", + None, + "(DEPRECATED; use --b2-args=--hash) " + "use a property hash for the build directory, rather than " + "property subdirectories", + ), + ( + "cxxstd=", + None, + "(DEPRECATED; use --b2-args=cxxstd=...) " + "boost cxxstd value (14, 17, 20, etc.)", + ), + ] + + boolean_options = BuildExtBase.boolean_options + ["pic", "hash"] + + def initialize_options(self): + + self.config_mode = self.CONFIG_MODE_DISTUTILS + self.b2_args = "" + self.no_autoconf = "" + + self.cxxflags = None + self.linkflags = None + + # TODO: this is for backwards compatibility + # loading these files will be removed in libtorrent-2.0 + try: + with open('compile_flags') as f: + opts = f.read() + if '-std=c++' in opts: + self.cxxflags = ['-std=c++' + opts.split('-std=c++')[-1].split()[0]] + except OSError: + pass + + # TODO: this is for backwards compatibility + # loading these files will be removed in libtorrent-2.0 + try: + with open('link_flags') as f: + opts = f.read().split(' ') + opts = [x for x in opts if x.startswith('-L')] + if len(opts): + self.linkflags = opts + except OSError: + pass + + self.toolset = None + self.libtorrent_link = None + self.boost_link = None + self.pic = None + self.optimization = None + self.hash = None + self.cxxstd = None + + self._b2_args_split = [] + self._b2_args_configured = set() + + return super().initialize_options() + + def finalize_options(self): + super().finalize_options() + + if self.config_mode not in self.CONFIG_MODES: + raise distutils.errors.DistutilsOptionError( + f"--config-mode must be one of {self.CONFIG_MODES}" + ) + + # shlex the args here to warn early on bad config + self._b2_args_split = shlex.split(self.b2_args or "") + self._b2_args_configured.update(shlex.split(self.no_autoconf or "")) + + # In b2's arg system only single-character args can consume the next + # arg, but it may also be concatenated. So we may have "-x", + # "-x value", or "-xvalue". All --long args which take a value must + # appear as "--long=value" + i = 0 + while i < len(self._b2_args_split): + arg = self._b2_args_split[i] + m = re.match(r"(-[dfjlmopst])(.*)", arg) + if m: + name = m.group(1) + # An arg that takes a value but wasn't concatenated. Treat the + # next option as the value + if not m.group(2): + i += 1 + else: + name = arg.split("=", 1)[0] + self._b2_args_configured.add(name) + i += 1 + + # Add deprecated args + if self.libtorrent_link: + warnings.warn( + "--libtorrent-link is deprecated; use --b2-args=libtorrent-link=..." + ) + self._maybe_add_arg(f"libtorrent-link={self.libtorrent_link}") + self._b2_args_configured.add("libtorrent-link") + if self.boost_link: + warnings.warn("--boost-link is deprecated; use --b2-args=boost-link=...") + self._maybe_add_arg(f"boost-link={self.boost_link}") + self._b2_args_configured.add("boost-link") + if self.toolset: + warnings.warn("--toolset is deprecated; use --b2-args=toolset=...") + self._maybe_add_arg(f"toolset={self.toolset}") + self._b2_args_configured.add("toolset") + if self.pic: + warnings.warn("--pic is deprecated; use --b2-args=libtorrent-python-pic=on") + self._maybe_add_arg("libtorrent-python-pic=on") + self._b2_args_configured.add("libtorrent-python-pic") + if self.optimization: + warnings.warn( + "--optimization is deprecated; use --b2-args=optimization=..." + ) + self._maybe_add_arg(f"optimization={self.optimization}") + self._b2_args_configured.add("optimization") + if self.hash: + warnings.warn("--hash is deprecated; use --b2-args=--hash") + self._maybe_add_arg("--hash") + if self.cxxstd: + warnings.warn("--cxxstd is deprecated; use --b2-args=cxxstd=...") + self._maybe_add_arg(f"cxxstd={self.cxxstd}") + self._b2_args_configured.add("cxxstd") + + def _should_add_arg(self, arg): + m = re.match(r"(-\w).*", arg) + if m: + name = m.group(1) + else: + name = arg.split("=", 1)[0] + return name not in self._b2_args_configured + + def _maybe_add_arg(self, arg): + if self._should_add_arg(arg): + self._b2_args_split.append(arg) + return True + return False + + def run(self): + # The current jamfile layout just supports one extension + self._build_extension_with_b2() + return super().run() + + def _build_extension_with_b2(self): + python_binding_dir = pathlib.Path(__file__).parent.absolute() + with self._configure_b2(): + if self.linkflags: + for lf in self.linkflags: + # since b2 may be running with a different directory as cwd, + # relative + # paths need to be converted to absolute + if lf[2] != '/': + lf = '-L' + str(pathlib.Path(lf[2:]).absolute()) + self._b2_args_split.append("linkflags=" + lf) + if self.cxxflags: + for f in self.cxxflags: + self._b2_args_split.append("cxxflags=" + f) + command = ["b2"] + self._b2_args_split + log.info(" ".join(command)) + subprocess.run(command, cwd=python_binding_dir, check=True) + + @contextlib.contextmanager + def _configure_b2(self): + if self.config_mode == self.CONFIG_MODE_DISTUTILS: + # If we're using distutils mode, we'll auto-configure a lot of args + # and write temporary config. + yield from self._configure_b2_with_distutils() + else: + # If we're using b2 mode, no configuration needed + yield + + def _configure_b2_with_distutils(self): + if os.name == "nt": + self._maybe_add_arg("--abbreviate-paths") + + self._maybe_add_arg("boost-link=static") + self._maybe_add_arg("libtorrent-link=static") + + self._maybe_add_arg("crypto=openssl") + + if distutils.debug.DEBUG: + self._maybe_add_arg("--debug-configuration") + self._maybe_add_arg("--debug-building") + self._maybe_add_arg("--debug-generators") + + # Default feature configuration + self._maybe_add_arg("deprecated-functions=on") + + variant = "debug" if self.debug else "release" + self._maybe_add_arg(f"variant={variant}") + bits = 64 if sys.maxsize > 2 ** 32 else 32 + self._maybe_add_arg(f"address-model={bits}") + + # Cross-compiling logic: tricky, because autodetection is usually + # better than our matching + if sys.platform == "darwin": + # macOS uses multi-arch binaries. Attempt to match the + # configuration of the running python by translating distutils + # platform modes to b2 architecture modes + machine = distutils.util.get_platform().split("-")[-1] + if machine == "arm64": + self._maybe_add_arg("architecture=arm") + elif machine in ("ppc", "ppc64"): + self._maybe_add_arg("architecture=power") + elif machine in ("i386", "x86_64", "intel"): + self._maybe_add_arg("architecture=x86") + elif machine in ("universal", "fat", "fat3", "fat64"): + self._maybe_add_arg("architecture=combined") + # NB: as of boost 1.75.0, b2 doesn't have a straightforward way to + # build a "universal2" (arm64 + x86_64) binary + + if self.parallel: + self._maybe_add_arg(f"-j{self.parallel}") + + # We use a "project-config.jam" to instantiate a python environment + # to exactly match the running one. + override_project_config = False + if self._should_add_arg("--project-config"): + if self._maybe_add_arg(f"python={sysconfig.get_python_version()}"): + + override_project_config = True + + # Jamfile hacks to ensure we select the python environment defined in + # our project-config.jam + self._maybe_add_arg("libtorrent-python=on") + + # Our goal is to produce an artifact at this path. If we do this, the + # distutils build system will skip trying to build it. + target = pathlib.Path(self.get_ext_fullpath("libtorrent")).absolute() + self.announce(f"target: {target}") + + # b2 doesn't provide a way to signal the name or paths of its outputs. + # We try to convince python.jam to name its output file like our target + # and copy it to our target directory. See comments in + # write_b2_python_config for limitations on controlling the filename. + + # Jamfile hack to copy the module to our target directory + self._maybe_add_arg(f"python-install-path={target.parent}") + self._maybe_add_arg("install_module") + + # We use a "project-config.jam" to instantiate a python environment + # to exactly match the running one. + if override_project_config: + config = tempfile.NamedTemporaryFile(mode="w+", delete=False) + try: + write_b2_python_config(config) + config.seek(0) + log.info("project-config.jam contents:") + log.info(config.read()) + config.close() + self._b2_args_split.append(f"--project-config={config.name}") + yield + finally: + # If we errored while writing config, windows may complain about + # unlinking a file "in use" + config.close() + os.unlink(config.name) + else: + yield + + +setuptools.setup( + name="libtorrent", + version="2.0.7", + author="Arvid Norberg", + author_email="arvid@libtorrent.org", + description="Python bindings for libtorrent-rasterbar", + long_description="Python bindings for libtorrent-rasterbar", + url="http://libtorrent.org", + license="BSD", + ext_modules=[StubExtension("libtorrent")], + cmdclass={ + "build_ext": LibtorrentBuildExt, + }, + distclass=B2Distribution, +) diff --git a/bindings/python/setup.py.cmake.in b/bindings/python/setup.py.cmake.in new file mode 100644 index 0000000..7fdcfcf --- /dev/null +++ b/bindings/python/setup.py.cmake.in @@ -0,0 +1,15 @@ +from setuptools import setup +import platform + +setup( + name='libtorrent', + version='@libtorrent_VERSION@', + author='Arvid Norberg', + author_email='arvid@libtorrent.org', + description='Python bindings for libtorrent-rasterbar', + long_description='Python bindings for libtorrent-rasterbar', + url='http://libtorrent.org', + platforms=[platform.system() + '-' + platform.machine()], + license='BSD', + package_dir = {'': '@CMAKE_CURRENT_BINARY_DIR@'} +) diff --git a/bindings/python/simple_client.py b/bindings/python/simple_client.py new file mode 100755 index 0000000..df32c9a --- /dev/null +++ b/bindings/python/simple_client.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# Copyright Arvid Norberg 2008. Use, modification and distribution is +# subject to the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +from __future__ import print_function + +import libtorrent as lt +import time +import sys + +ses = lt.session({'listen_interfaces': '0.0.0.0:6881'}) + +info = lt.torrent_info(sys.argv[1]) +h = ses.add_torrent({'ti': info, 'save_path': '.'}) +s = h.status() +print('starting', s.name) + +while (not s.is_seeding): + s = h.status() + + print('\r%.2f%% complete (down: %.1f kB/s up: %.1f kB/s peers: %d) %s' % ( + s.progress * 100, s.download_rate / 1000, s.upload_rate / 1000, + s.num_peers, s.state), end=' ') + + alerts = ses.pop_alerts() + for a in alerts: + if a.category() & lt.alert.category_t.error_notification: + print(a) + + sys.stdout.flush() + + time.sleep(1) + +print(h.status().name, 'complete') diff --git a/bindings/python/src/alert.cpp b/bindings/python/src/alert.cpp new file mode 100644 index 0000000..c51635c --- /dev/null +++ b/bindings/python/src/alert.cpp @@ -0,0 +1,1160 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include // for piece_block +#include +#include +#include +#include "bytes.hpp" +#include "gil.hpp" + +#include + +using namespace boost::python; +using namespace lt; + +#ifdef _MSC_VER +#pragma warning(push) +// warning c4996: x: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +bytes get_buffer(read_piece_alert const& rpa) +{ + return rpa.buffer ? bytes(rpa.buffer.get(), rpa.size) + : bytes(); +} + +#if TORRENT_ABI_VERSION <= 2 +list stats_alert_transferred(stats_alert const& alert) +{ + list result; + for (int i = 0; i < alert.num_channels; ++i) { + result.append(alert.transferred[i]); + } + return result; +} +#endif + +list get_status_from_update_alert(state_update_alert const& alert) +{ + list result; + + for (std::vector::const_iterator i = alert.status.begin(); i != alert.status.end(); ++i) + { + result.append(*i); + } + return result; +} + +list dht_stats_active_requests(dht_stats_alert const& a) +{ + list result; + + for (std::vector::const_iterator i = a.active_requests.begin(); + i != a.active_requests.end(); ++i) + { + dict d; + + d["type"] = i->type; + d["outstanding_requests"] = i->outstanding_requests; + d["timeouts"] = i->timeouts; + d["responses"] = i->responses; + d["branch_factor"] = i->branch_factor; + d["nodes_left"] = i->nodes_left; + d["last_sent"] = i->last_sent; + d["first_timeout"] = i->first_timeout; + + result.append(d); + } + return result; +} + +list dht_stats_routing_table(dht_stats_alert const& a) +{ + list result; + + for (std::vector::const_iterator i = a.routing_table.begin(); + i != a.routing_table.end(); ++i) + { + dict d; + + d["num_nodes"] = i->num_nodes; + d["num_replacements"] = i->num_replacements; + + result.append(d); + } + return result; +} + +dict dht_immutable_item(dht_immutable_item_alert const& alert) +{ + dict d; + d["key"] = alert.target; + d["value"] = bytes(alert.item.string()); + return d; +} + +dict dht_mutable_item(dht_mutable_item_alert const& alert) +{ + dict d; + d["key"] = bytes(alert.key.data(), alert.key.size()); + d["value"] = bytes(alert.item.string()); + d["signature"] = bytes(alert.signature.data(), alert.signature.size()); + d["seq"] = alert.seq; + d["salt"] = bytes(alert.salt); + d["authoritative"] = alert.authoritative; + return d; +} + +dict dht_put_item(dht_put_alert const& alert) +{ + dict d; + if (alert.target.is_all_zeros()) { + d["public_key"] = bytes(alert.public_key.data(), alert.public_key.size()); + d["signature"] = bytes(alert.signature.data(), alert.signature.size()); + d["seq"] = alert.seq; + d["salt"] = bytes(alert.salt); + } else { + d["target"] = alert.target; + } + return d; +} + +dict session_stats_values(session_stats_alert const& alert) +{ + std::vector map = session_stats_metrics(); + dict d; + auto counters = alert.counters(); + + for (stats_metric const& m : map) + { + d[m.name] = counters[m.value_index]; + } + return d; +} + +list dht_live_nodes_nodes(dht_live_nodes_alert const& alert) +{ + list result; + std::vector> const nodes = alert.nodes(); + for (std::pair const& node : nodes) + { + dict d; + d["nid"] = node.first; + d["endpoint"] = node.second; + result.append(d); + } + return result; +} + +list dht_sample_infohashes_nodes(dht_sample_infohashes_alert const& alert) +{ + list result; + std::vector> const nodes = alert.nodes(); + for (std::pair const& node : nodes) + { + dict d; + d["nid"] = node.first; + d["endpoint"] = node.second; + result.append(d); + } + return result; +} + +#if TORRENT_ABI_VERSION == 1 +entry const& get_resume_data_entry(save_resume_data_alert const& self) +{ + python_deprecated("resume_data is deprecated"); + return *self.resume_data; +} +#endif + +namespace boost +{ + // some older compilers (like msvc-12.0) end up using + // boost::is_polymorphic inside boost.python applied + // to alert types. This is problematic, since it appears + // to be implemented by deriving from the type, which + // yields a compiler error since most alerts are final. + // this just short-cuts the query to say that all these + // types are indeed polymorphic, no need to derive from + // them. +#define POLY(x) template<> \ + struct is_polymorphic : boost::mpl::true_ {}; + + POLY(torrent_alert) + POLY(tracker_alert) + POLY(torrent_removed_alert) + POLY(read_piece_alert) + POLY(peer_alert) + POLY(tracker_error_alert) + POLY(tracker_warning_alert) + POLY(tracker_reply_alert) + POLY(tracker_announce_alert) + POLY(hash_failed_alert) + POLY(peer_ban_alert) + POLY(peer_error_alert) + POLY(invalid_request_alert) + POLY(torrent_error_alert) + POLY(torrent_finished_alert) + POLY(piece_finished_alert) + POLY(block_finished_alert) + POLY(block_downloading_alert) + POLY(storage_moved_alert) + POLY(storage_moved_failed_alert) + POLY(torrent_deleted_alert) + POLY(torrent_paused_alert) + POLY(torrent_checked_alert) + POLY(url_seed_alert) + POLY(file_error_alert) + POLY(metadata_failed_alert) + POLY(metadata_received_alert) + POLY(listen_failed_alert) + POLY(listen_succeeded_alert) + POLY(portmap_error_alert) + POLY(portmap_alert) + POLY(fastresume_rejected_alert) + POLY(peer_blocked_alert) + POLY(scrape_reply_alert) + POLY(scrape_failed_alert) + POLY(udp_error_alert) + POLY(external_ip_alert) + POLY(save_resume_data_alert) + POLY(file_completed_alert) + POLY(file_renamed_alert) + POLY(file_rename_failed_alert) + POLY(torrent_resumed_alert) + POLY(state_changed_alert) + POLY(state_update_alert) + POLY(i2p_alert) + POLY(dht_immutable_item_alert) + POLY(dht_mutable_item_alert) + POLY(dht_put_alert) + POLY(dht_reply_alert) + POLY(dht_announce_alert) + POLY(dht_get_peers_alert) + POLY(peer_unsnubbed_alert) + POLY(peer_snubbed_alert) + POLY(peer_connect_alert) + POLY(peer_disconnected_alert) + POLY(request_dropped_alert) + POLY(block_timeout_alert) + POLY(unwanted_block_alert) + POLY(torrent_delete_failed_alert) + POLY(save_resume_data_failed_alert) + POLY(performance_alert) +#if TORRENT_ABI_VERSION <= 2 + POLY(stats_alert) +#endif + POLY(cache_flushed_alert) + POLY(incoming_connection_alert) + POLY(torrent_need_cert_alert) + POLY(add_torrent_alert) + POLY(dht_outgoing_get_peers_alert) + POLY(lsd_error_alert) + POLY(dht_stats_alert) + POLY(incoming_request_alert) + POLY(dht_log_alert) + POLY(dht_pkt_alert) + POLY(dht_get_peers_reply_alert) + POLY(dht_direct_response_alert) + POLY(session_error_alert) + POLY(dht_live_nodes_alert) + POLY(session_stats_header_alert) + POLY(dht_sample_infohashes_alert) + POLY(block_uploaded_alert) + POLY(alerts_dropped_alert) + POLY(session_stats_alert) + POLY(socks5_alert) + POLY(file_prio_alert) + POLY(oversized_file_alert) + POLY(torrent_conflict_alert) + +#if TORRENT_ABI_VERSION == 1 + POLY(anonymous_mode_alert) + POLY(torrent_added_alert) +#endif + +#ifndef TORRENT_DISABLE_LOGGING + POLY(portmap_log_alert) + POLY(log_alert) + POLY(torrent_log_alert) + POLY(peer_log_alert) + POLY(picker_log_alert) +#endif // TORRENT_DISABLE_LOGGING + +#undef POLY +} + +struct dummy3 {}; +struct dummy12 {}; + +bytes get_pkt_buf(dht_pkt_alert const& alert) +{ + return {alert.pkt_buf().data(), static_cast(alert.pkt_buf().size())}; +} + +list get_dropped_alerts(alerts_dropped_alert const& alert) +{ + list ret; + for (int i = 0; i < int(alert.dropped_alerts.size()); ++i) + ret.append(bool(alert.dropped_alerts[i])); + return ret; +} + +void bind_alert() +{ + using boost::noncopyable; + + using by_value = return_value_policy; + + { + scope alert_scope = class_("alert", no_init) + .def("message", &alert::message) + .def("what", &alert::what) + .def("category", &alert::category) + .def("__str__", &alert::message) + ; + + scope s = class_("category_t"); + s.attr("error_notification") = alert::error_notification; + s.attr("peer_notification") = alert::peer_notification; + s.attr("port_mapping_notification") = alert::port_mapping_notification; + s.attr("storage_notification") = alert::storage_notification; + s.attr("tracker_notification") = alert::tracker_notification; + s.attr("connect_notification") = alert::connect_notification; + s.attr("status_notification") = alert::status_notification; +#if TORRENT_ABI_VERSION == 1 + s.attr("debug_notification") = alert::debug_notification; + s.attr("progress_notification") = alert::progress_notification; +#endif + s.attr("ip_block_notification") = alert::ip_block_notification; + s.attr("performance_warning") = alert::performance_warning; + s.attr("dht_notification") = alert::dht_notification; +#if TORRENT_ABI_VERSION <= 2 + s.attr("stats_notification") = alert::stats_notification; +#endif + s.attr("session_log_notification") = alert::session_log_notification; + s.attr("torrent_log_notification") = alert::torrent_log_notification; + s.attr("peer_log_notification") = alert::peer_log_notification; + s.attr("incoming_request_notification") = alert::incoming_request_notification; + s.attr("dht_log_notification") = alert::dht_log_notification; + s.attr("dht_operation_notification") = alert::dht_operation_notification; + s.attr("port_mapping_log_notification") = alert::port_mapping_log_notification; + s.attr("picker_log_notification") = alert::picker_log_notification; + s.attr("file_progress_notification") = alert::file_progress_notification; + s.attr("piece_progress_notification") = alert::piece_progress_notification; + s.attr("upload_notification") = alert::upload_notification; + s.attr("block_progress_notification") = alert::block_progress_notification; + s.attr("all_categories") = alert::all_categories; + } + + { + scope s = class_("alert_category"); + s.attr("error") = alert_category::error; + s.attr("peer") = alert_category::peer; + s.attr("port_mapping") = alert_category::port_mapping; + s.attr("storage") = alert_category::storage; + s.attr("tracker") = alert_category::tracker; + s.attr("connect") = alert_category::connect; + s.attr("status") = alert_category::status; + s.attr("ip_block") = alert_category::ip_block; + s.attr("performance_warning") = alert_category::performance_warning; + s.attr("dht") = alert_category::dht; + s.attr("stats") = alert_category::stats; + s.attr("session_log") = alert_category::session_log; + s.attr("torrent_log") = alert_category::torrent_log; + s.attr("peer_log") = alert_category::peer_log; + s.attr("incoming_request") = alert_category::incoming_request; + s.attr("dht_log") = alert_category::dht_log; + s.attr("dht_operation") = alert_category::dht_operation; + s.attr("port_mapping_log") = alert_category::port_mapping_log; + s.attr("picker_log") = alert_category::picker_log; + s.attr("file_progress") = alert_category::file_progress; + s.attr("piece_progress") = alert_category::piece_progress; + s.attr("upload") = alert_category::upload; + s.attr("block_progress") = alert_category::block_progress; + s.attr("all") = alert_category::all; + } + + enum_("operation_t") + .value("unknown", operation_t::unknown) + .value("bittorrent", operation_t::bittorrent) + .value("iocontrol", operation_t::iocontrol) + .value("getpeername", operation_t::getpeername) + .value("getname", operation_t::getname) + .value("alloc_recvbuf", operation_t::alloc_recvbuf) + .value("alloc_sndbuf", operation_t::alloc_sndbuf) + .value("file_write", operation_t::file_write) + .value("file_read", operation_t::file_read) + .value("file", operation_t::file) + .value("sock_write", operation_t::sock_write) + .value("sock_read", operation_t::sock_read) + .value("sock_open", operation_t::sock_open) + .value("sock_bind", operation_t::sock_bind) + .value("available", operation_t::available) + .value("encryption", operation_t::encryption) + .value("connect", operation_t::connect) + .value("ssl_handshake", operation_t::ssl_handshake) + .value("get_interface", operation_t::get_interface) + .value("sock_listen", operation_t::sock_listen) + .value("sock_bind_to_device", operation_t::sock_bind_to_device) + .value("sock_accept", operation_t::sock_accept) + .value("parse_address", operation_t::parse_address) + .value("enum_if", operation_t::enum_if) + .value("file_stat", operation_t::file_stat) + .value("file_copy", operation_t::file_copy) + .value("file_fallocate", operation_t::file_fallocate) + .value("file_hard_link", operation_t::file_hard_link) + .value("file_remove", operation_t::file_remove) + .value("file_rename", operation_t::file_rename) + .value("file_open", operation_t::file_open) + .value("mkdir", operation_t::mkdir) + .value("check_resume", operation_t::check_resume) + .value("exception", operation_t::exception) + .value("alloc_cache_piece", operation_t::alloc_cache_piece) + .value("partfile_move", operation_t::partfile_move) + .value("partfile_read", operation_t::partfile_read) + .value("partfile_write", operation_t::partfile_write) + .value("hostname_lookup", operation_t::hostname_lookup) + .value("symlink", operation_t::symlink) + .value("handshake", operation_t::handshake) + .value("sock_option", operation_t::sock_option) + ; + + def("operation_name", static_cast(<::operation_name)); + + class_, noncopyable>( + "torrent_alert", no_init) + .add_property("handle", make_getter(&torrent_alert::handle, by_value())) + .add_property("torrent_name", &torrent_alert::torrent_name) + ; + + class_, noncopyable>( + "tracker_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("url", &tracker_alert::url) +#endif + .add_property("local_endpoint", make_getter(&tracker_alert::local_endpoint, by_value())) + .def("tracker_url", &tracker_alert::tracker_url) + ; + +#if TORRENT_ABI_VERSION == 1 + class_, noncopyable>( + "torrent_added_alert", no_init) + ; +#endif + + class_, noncopyable>( + "torrent_removed_alert", no_init) +#if TORRENT_ABI_VERSION < 3 + .def_readonly("info_hash", &torrent_removed_alert::info_hash) +#endif + .def_readonly("info_hashes", &torrent_removed_alert::info_hashes) + ; + + class_, noncopyable>( + "read_piece_alert", nullptr, no_init) + .def_readonly("error", &read_piece_alert::error) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("ec", &read_piece_alert::ec) +#endif + .add_property("buffer", get_buffer) + .add_property("piece", make_getter(&read_piece_alert::piece, by_value())) + .def_readonly("size", &read_piece_alert::size) + ; + + class_, noncopyable>( + "peer_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .add_property("ip", make_getter(&peer_alert::ip, by_value())) +#endif + .add_property("endpoint", make_getter(&peer_alert::endpoint, by_value())) + .def_readonly("pid", &peer_alert::pid) + ; + class_, noncopyable>( + "tracker_error_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("msg", &tracker_error_alert::msg) + .def_readonly("status_code", &tracker_error_alert::status_code) +#endif + .def("error_message", &tracker_error_alert::error_message) + .def("failure_reason", &tracker_error_alert::failure_reason) + .def_readonly("times_in_row", &tracker_error_alert::times_in_row) + .def_readonly("error", &tracker_error_alert::error) + // TODO: move this to tracker_alert + .def_readonly("version", &tracker_error_alert::version) + ; + + class_, noncopyable>( + "tracker_warning_alert", no_init) + // TODO: move this to tracker_alert + .def_readonly("version", &tracker_warning_alert::version) + ; + + class_, noncopyable>( + "tracker_reply_alert", no_init) + .def_readonly("num_peers", &tracker_reply_alert::num_peers) + // TODO: move this to tracker_alert + .def_readonly("version", &tracker_reply_alert::version) + ; + + class_, noncopyable>( + "tracker_announce_alert", no_init) + .def_readonly("event", &tracker_announce_alert::event) + // TODO: move this to tracker_alert + .def_readonly("version", &tracker_announce_alert::version) + ; + + class_, noncopyable>( + "hash_failed_alert", no_init) + .add_property("piece_index", make_getter(&hash_failed_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "peer_ban_alert", no_init); + + class_, noncopyable>( + "peer_error_alert", no_init) + .def_readonly("error", &peer_error_alert::error) + .def_readonly("op", &peer_error_alert::op) + ; + + class_, noncopyable>( + "invalid_request_alert", no_init) + .def_readonly("request", &invalid_request_alert::request) + ; + + class_("peer_request") + .add_property("piece", make_getter(&peer_request::piece, by_value())) + .def_readonly("start", &peer_request::start) + .def_readonly("length", &peer_request::length) + .def(self == self) + ; + + class_, noncopyable>( + "torrent_error_alert", no_init) + .def_readonly("error", &torrent_error_alert::error) + ; + + class_, noncopyable>( + "torrent_finished_alert", no_init); + + class_, noncopyable>( + "piece_finished_alert", no_init) + .add_property("piece_index", make_getter(&piece_finished_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "block_finished_alert", no_init) + .add_property("block_index", make_getter(&block_finished_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&block_finished_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "block_downloading_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("peer_speedmsg", &block_downloading_alert::peer_speedmsg) +#endif + .add_property("block_index", make_getter(&block_downloading_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&block_downloading_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "storage_moved_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("path", &storage_moved_alert::path) +#endif + .def("storage_path", &storage_moved_alert::storage_path) + .def("old_path", &storage_moved_alert::old_path) + ; + + class_, noncopyable>( + "storage_moved_failed_alert", no_init) + .def_readonly("error", &storage_moved_failed_alert::error) + .def("file_path", &storage_moved_failed_alert::file_path) + .def_readonly("op", &storage_moved_failed_alert::op) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("operation", &storage_moved_failed_alert::operation) +#endif + ; + + class_, noncopyable>( + "torrent_deleted_alert", no_init) +#if TORRENT_ABI_VERSION < 3 + .def_readonly("info_hash", &torrent_deleted_alert::info_hash) +#endif + .def_readonly("info_hashes", &torrent_deleted_alert::info_hashes) + ; + + class_, noncopyable>( + "torrent_paused_alert", no_init); + + class_, noncopyable>( + "torrent_checked_alert", no_init); + + class_, noncopyable>( + "url_seed_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("url", &url_seed_alert::url) + .def_readonly("msg", &url_seed_alert::msg) +#endif + .def_readonly("error", &url_seed_alert::error) + .def("server_url", &url_seed_alert::server_url) + .def("error_message", &url_seed_alert::error_message) + ; + + class_, noncopyable>( + "file_error_alert", no_init) + .def_readonly("error", &file_error_alert::error) + .def("filename", &file_error_alert::filename) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("file", &file_error_alert::file) + .def_readonly("msg", &file_error_alert::msg) +#endif + ; + + class_, noncopyable>( + "metadata_failed_alert", no_init) + .def_readonly("error", &metadata_failed_alert::error) + ; + + class_, noncopyable>( + "metadata_received_alert", no_init); + + class_, noncopyable>( + "listen_failed_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .add_property("endpoint", make_getter(&listen_failed_alert::endpoint, by_value())) +#endif + .add_property("address", make_getter(&listen_failed_alert::address, by_value())) + .def_readonly("port", &listen_failed_alert::port) + .def("listen_interface", &listen_failed_alert::listen_interface) + .def_readonly("error", &listen_failed_alert::error) + .def_readonly("op", &listen_failed_alert::op) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("operation", &listen_failed_alert::operation) + .def_readonly("sock_type", &listen_failed_alert::sock_type) +#endif + .def_readonly("socket_type", &listen_failed_alert::socket_type) + ; + + class_, noncopyable>( + "listen_succeeded_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .add_property("endpoint", make_getter(&listen_succeeded_alert::endpoint, by_value())) +#endif + .add_property("address", make_getter(&listen_succeeded_alert::address, by_value())) + .def_readonly("port", &listen_succeeded_alert::port) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("sock_type", &listen_succeeded_alert::sock_type) +#endif + .def_readonly("socket_type", &listen_succeeded_alert::socket_type) + ; + +#if TORRENT_ABI_VERSION == 1 + enum_("listen_succeded_alert_socket_type_t") + .value("tcp", listen_succeeded_alert::socket_type_t::tcp) + .value("tcp_ssl", listen_succeeded_alert::socket_type_t::tcp_ssl) + .value("udp", listen_succeeded_alert::socket_type_t::udp) + .value("i2p", listen_succeeded_alert::socket_type_t::i2p) + .value("socks5", listen_succeeded_alert::socket_type_t::socks5) + .value("utp_ssl", listen_succeeded_alert::socket_type_t::utp_ssl) + ; + + enum_("listen_failed_alert_socket_type_t") + .value("tcp", listen_failed_alert::socket_type_t::tcp) + .value("tcp_ssl", listen_failed_alert::socket_type_t::tcp_ssl) + .value("udp", listen_failed_alert::socket_type_t::udp) + .value("i2p", listen_failed_alert::socket_type_t::i2p) + .value("socks5", listen_failed_alert::socket_type_t::socks5) + .value("utp_ssl", listen_failed_alert::socket_type_t::utp_ssl) + ; +#endif + + enum_("socket_type_t") + .value("tcp", socket_type_t::tcp) + .value("socks5", socket_type_t::socks5) + .value("http", socket_type_t::http) + .value("utp", socket_type_t::utp) +#if TORRENT_ABI_VERSION <= 2 + .value("udp", socket_type_t::udp) +#endif + .value("i2p", socket_type_t::i2p) + .value("tcp_ssl", socket_type_t::tcp_ssl) + .value("socks5_ssl", socket_type_t::socks5_ssl) + .value("http_ssl", socket_type_t::http_ssl) + .value("utp_ssl", socket_type_t::utp_ssl) + ; + + class_, noncopyable>( + "portmap_error_alert", no_init) + .add_property("mapping", make_getter(&portmap_error_alert::mapping, by_value())) + .def_readonly("error", &portmap_error_alert::error) + .def_readonly("map_transport", &portmap_error_alert::map_transport) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("map_type", &portmap_error_alert::map_type) + .def_readonly("type", &portmap_error_alert::map_type) + .def_readonly("msg", &portmap_error_alert::msg) +#endif + ; + + class_, noncopyable>("portmap_alert", no_init) + .add_property("mapping", make_getter(&portmap_alert::mapping, by_value())) + .def_readonly("external_port", &portmap_alert::external_port) + .def_readonly("map_protocol", &portmap_alert::map_protocol) + .def_readonly("map_transport", &portmap_alert::map_transport) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("type", &portmap_alert::map_type) + .def_readonly("map_type", &portmap_alert::map_type) +#endif + ; + +#ifndef TORRENT_DISABLE_LOGGING + + class_, noncopyable>("portmap_log_alert", no_init) + .def_readonly("map_transport", &portmap_log_alert::map_transport) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("type", &portmap_log_alert::map_type) + .def_readonly("msg", &portmap_log_alert::msg) + .def_readonly("map_type", &portmap_log_alert::map_type) +#endif + ; + +#endif // TORRENT_DISABLE_LOGGING + + class_, noncopyable>( + "fastresume_rejected_alert", no_init) + .def_readonly("error", &fastresume_rejected_alert::error) + .def("file_path", &fastresume_rejected_alert::file_path) + .def_readonly("op", &fastresume_rejected_alert::op) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("operation", &fastresume_rejected_alert::operation) + .def_readonly("msg", &fastresume_rejected_alert::msg) +#endif + ; + + class_, noncopyable>( + "peer_blocked_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .add_property("ip", make_getter(&peer_blocked_alert::ip, by_value())) +#endif + .add_property("reason", &peer_blocked_alert::reason) + ; + + enum_("reason_t") + .value("ip_filter", peer_blocked_alert::reason_t::ip_filter) + .value("port_filter", peer_blocked_alert::reason_t::port_filter) + .value("i2p_mixed", peer_blocked_alert::reason_t::i2p_mixed) + .value("privileged_ports", peer_blocked_alert::reason_t::privileged_ports) + .value("utp_disabled", peer_blocked_alert::reason_t::utp_disabled) + .value("tcp_disabled", peer_blocked_alert::reason_t::tcp_disabled) + .value("invalid_local_interface", peer_blocked_alert::reason_t::invalid_local_interface) + ; + + class_, noncopyable>( + "scrape_reply_alert", no_init) + .def_readonly("incomplete", &scrape_reply_alert::incomplete) + .def_readonly("complete", &scrape_reply_alert::complete) + ; + + class_, noncopyable>( + "scrape_failed_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("msg", &scrape_failed_alert::msg) +#endif + .def("error_message", &scrape_failed_alert::error_message) + .def_readonly("error", &scrape_failed_alert::error) + ; + + class_, noncopyable>( + "udp_error_alert", no_init) + .add_property("endpoint", make_getter(&udp_error_alert::endpoint, by_value())) + .def_readonly("error", &udp_error_alert::error) + ; + + class_, noncopyable>( + "external_ip_alert", no_init) + .add_property("external_address", make_getter(&external_ip_alert::external_address, by_value())) + ; + + class_, noncopyable>( + "save_resume_data_alert", no_init) + .def_readonly("params", &save_resume_data_alert::params) +#if TORRENT_ABI_VERSION == 1 + .add_property("resume_data", make_function(get_resume_data_entry, by_value())) +#endif + ; + + class_, noncopyable>( + "file_completed_alert", no_init) + .add_property("index", make_getter(&file_completed_alert::index, by_value())) + ; + + class_, noncopyable>( + "file_renamed_alert", no_init) + .add_property("index", make_getter(&file_renamed_alert::index, by_value())) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("name", &file_renamed_alert::name) +#endif + .def("new_name", &file_renamed_alert::new_name) + .def("old_name", &file_renamed_alert::old_name) + ; + + class_, noncopyable>( + "file_rename_failed_alert", no_init) + .add_property("index", make_getter(&file_rename_failed_alert::index, by_value())) + .def_readonly("error", &file_rename_failed_alert::error) + ; + + class_, noncopyable>( + "torrent_resumed_alert", no_init + ); + + class_, noncopyable>( + "state_changed_alert", no_init) + .def_readonly("state", &state_changed_alert::state) + .def_readonly("prev_state", &state_changed_alert::prev_state) + ; + + class_, noncopyable>( + "state_update_alert", no_init) + .add_property("status", &get_status_from_update_alert) + ; + + class_, noncopyable>( + "i2p_alert", no_init) + .add_property("error", &i2p_alert::error) + ; + + class_, noncopyable>( + "dht_reply_alert", no_init) + .def_readonly("num_peers", &dht_reply_alert::num_peers) + ; + + class_, noncopyable>( + "dht_announce_alert", no_init) + .add_property("ip", make_getter(&dht_announce_alert::ip, by_value())) + .def_readonly("port", &dht_announce_alert::port) + .def_readonly("info_hash", &dht_announce_alert::info_hash) + ; + + class_, noncopyable>( + "dht_get_peers_alert", no_init + ) + .def_readonly("info_hash", &dht_get_peers_alert::info_hash) + ; + + class_, noncopyable>( + "peer_unsnubbed_alert", no_init + ); + + class_, noncopyable>( + "peer_snubbed_alert", no_init + ); + + class_, noncopyable>( + "peer_connect_alert", no_init + ); + + class_, noncopyable>( + "peer_disconnected_alert", no_init) + .def_readonly("socket_type", &peer_disconnected_alert::socket_type) + .def_readonly("op", &peer_disconnected_alert::op) + .def_readonly("error", &peer_disconnected_alert::error) + .def_readonly("reason", &peer_disconnected_alert::reason) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("msg", &peer_disconnected_alert::msg) +#endif + ; + + class_, noncopyable>( + "request_dropped_alert", no_init) + .add_property("block_index", make_getter(&request_dropped_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&request_dropped_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "block_timeout_alert", no_init) + .add_property("block_index", make_getter(&block_timeout_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&block_timeout_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "unwanted_block_alert", no_init) + .add_property("block_index", make_getter(&unwanted_block_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&unwanted_block_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "torrent_delete_failed_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("msg", &torrent_delete_failed_alert::msg) +#endif + .def_readonly("error", &torrent_delete_failed_alert::error) +#if TORRENT_ABI_VERSION < 3 + .def_readonly("info_hash", &torrent_delete_failed_alert::info_hash) +#endif + .def_readonly("info_hashes", &torrent_delete_failed_alert::info_hashes) + ; + + class_, noncopyable>( + "save_resume_data_failed_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("msg", &save_resume_data_failed_alert::msg) +#endif + .def_readonly("error", &save_resume_data_failed_alert::error) + ; + + class_, noncopyable>( + "performance_alert", no_init) + .def_readonly("warning_code", &performance_alert::warning_code) + ; + enum_("performance_warning_t") + .value("outstanding_disk_buffer_limit_reached", performance_alert::outstanding_disk_buffer_limit_reached) + .value("outstanding_request_limit_reached", performance_alert::outstanding_request_limit_reached) + .value("upload_limit_too_low", performance_alert::upload_limit_too_low) + .value("download_limit_too_low", performance_alert::download_limit_too_low) + .value("send_buffer_watermark_too_low", performance_alert::send_buffer_watermark_too_low) + .value("too_many_optimistic_unchoke_slots", performance_alert::too_many_optimistic_unchoke_slots) +#if TORRENT_ABI_VERSION == 1 + .value("bittyrant_with_no_uplimit", performance_alert::bittyrant_with_no_uplimit) +#endif + .value("too_high_disk_queue_limit", performance_alert::too_high_disk_queue_limit) + .value("too_few_outgoing_ports", performance_alert::too_few_outgoing_ports) + .value("too_few_file_descriptors", performance_alert::too_few_file_descriptors) + ; + +#if TORRENT_ABI_VERSION <= 2 + class_, noncopyable>( + "stats_alert", no_init) + .add_property("transferred", &stats_alert_transferred) + .def_readonly("interval", &stats_alert::interval) + ; + + enum_("stats_channel") + .value("upload_payload", stats_alert::upload_payload) + .value("upload_protocol", stats_alert::upload_protocol) + .value("upload_ip_protocol", stats_alert::upload_ip_protocol) +#if TORRENT_ABI_VERSION == 1 + .value("upload_dht_protocol", stats_alert::upload_dht_protocol) + .value("upload_tracker_protocol", stats_alert::upload_tracker_protocol) +#endif + .value("download_payload", stats_alert::download_payload) + .value("download_protocol", stats_alert::download_protocol) + .value("download_ip_protocol", stats_alert::download_ip_protocol) +#if TORRENT_ABI_VERSION == 1 + .value("download_dht_protocol", stats_alert::download_dht_protocol) + .value("download_tracker_protocol", stats_alert::download_tracker_protocol) +#endif + ; +#endif // TORRENT_ABI_VERSION + + class_, noncopyable>( + "cache_flushed_alert", no_init) + ; + +#if TORRENT_ABI_VERSION == 1 + class_, noncopyable>( + "anonymous_mode_alert", no_init) + .def_readonly("kind", &anonymous_mode_alert::kind) + .def_readonly("str", &anonymous_mode_alert::str) + ; + + enum_("kind") + .value("tracker_no_anonymous", anonymous_mode_alert::tracker_not_anonymous) + ; +#endif // TORRENT_ABI_VERSION + + class_, noncopyable>( + "incoming_connection_alert", no_init) + .def_readonly("socket_type", &incoming_connection_alert::socket_type) +#if TORRENT_ABI_VERSION == 1 + .add_property("ip", make_getter(&incoming_connection_alert::ip, by_value())) +#endif + .add_property("endpoint", make_getter(&incoming_connection_alert::endpoint, by_value())) + ; + class_, noncopyable>( + "torrent_need_cert_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("error", &torrent_need_cert_alert::error) +#endif + ; + + class_, noncopyable>( + "add_torrent_alert", no_init) + .def_readonly("error", &add_torrent_alert::error) + .add_property("params", &add_torrent_alert::params) + ; + + class_, noncopyable>( + "dht_outgoing_get_peers_alert", no_init) + .def_readonly("info_hash", &dht_outgoing_get_peers_alert::info_hash) + .def_readonly("obfuscated_info_hash", &dht_outgoing_get_peers_alert::obfuscated_info_hash) +#if TORRENT_ABI_VERSION == 1 + .add_property("ip", make_getter(&dht_outgoing_get_peers_alert::ip, by_value())) +#endif + .add_property("endpoint", make_getter(&dht_outgoing_get_peers_alert::endpoint, by_value())) + ; + + class_, noncopyable>( + "log_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def("msg", depr(&log_alert::msg)) +#endif + .def("log_message", &log_alert::log_message) + ; + + class_, noncopyable>( + "torrent_log_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def("msg", depr(&torrent_log_alert::msg)) +#endif + .def("log_message", &torrent_log_alert::log_message) + ; + + class_, noncopyable>( + "peer_log_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def("msg", depr(&peer_log_alert::msg)) +#endif + .def("log_message", &peer_log_alert::log_message) + ; + + class_, noncopyable>( + "picker_log_alert", no_init) + .add_property("picker_flags", &picker_log_alert::picker_flags) + .def("blocks", &picker_log_alert::blocks) + ; + + class_, noncopyable>( + "lsd_error_alert", no_init) + .def_readonly("error", &lsd_error_alert::error) + ; + + class_, noncopyable>( + "dht_stats_alert", no_init) + .add_property("active_requests", &dht_stats_active_requests) + .add_property("routing_table", &dht_stats_routing_table) + ; + + class_, noncopyable>("dht_log_alert", no_init) + .add_property("module", make_getter(&dht_log_alert::module, by_value())) + .def("log_message", &dht_log_alert::log_message) + ; + + class_, noncopyable>( + "dht_pkt_alert", no_init) + .add_property("pkt_buf", &get_pkt_buf) + ; + + class_, noncopyable>( + "dht_immutable_item_alert", no_init) + .add_property("target", make_getter(&dht_immutable_item_alert::target, by_value())) + .add_property("item", &dht_immutable_item) + ; + + class_, noncopyable>( + "dht_mutable_item_alert", no_init) + .add_property("key", make_getter(&dht_mutable_item_alert::key, by_value())) + .add_property("signature", make_getter(&dht_mutable_item_alert::signature, by_value())) + .def_readonly("seq", &dht_mutable_item_alert::seq) + .def_readonly("salt", &dht_mutable_item_alert::salt) + .add_property("item", &dht_mutable_item) + .def_readonly("authoritative", &dht_mutable_item_alert::authoritative) + ; + + class_, noncopyable>( + "dht_put_alert", no_init) + .add_property("target", make_getter(&dht_put_alert::target, by_value())) + .add_property("public_key", make_getter(&dht_put_alert::public_key, by_value())) + .add_property("signature", make_getter(&dht_put_alert::signature, by_value())) + .def_readonly("salt", &dht_put_alert::salt) + .def_readonly("seq", &dht_put_alert::seq) + .def_readonly("num_success", &dht_put_alert::num_success) + ; + + class_, noncopyable>( + "session_stats_alert", no_init) + .add_property("values", &session_stats_values) + ; + + class_, noncopyable>( + "session_stats_header_alert", no_init) + ; + + std::vector (dht_get_peers_reply_alert::*peers)() const = &dht_get_peers_reply_alert::peers; + + class_, noncopyable>( + "dht_get_peers_reply_alert", no_init) + .def_readonly("info_hash", &dht_get_peers_reply_alert::info_hash) + .def("num_peers", &dht_get_peers_reply_alert::num_peers) + .def("peers", peers) + ; + + class_, noncopyable>( + "block_uploaded_alert", no_init) + .add_property("block_index", &block_uploaded_alert::block_index) + .add_property("piece_index", make_getter(&block_uploaded_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "alerts_dropped_alert", no_init) + .add_property("dropped_alerts", &get_dropped_alerts) + ; + + class_, noncopyable>( + "socks5_alert", no_init) + .def_readonly("error", &socks5_alert::error) + .def_readonly("op", &socks5_alert::op) + .add_property("ip", make_getter(&socks5_alert::ip, by_value())) + ; + + class_, noncopyable>( + "file_prio_alert", no_init) + ; + + class_, noncopyable>( + "dht_live_nodes_alert", no_init) + .add_property("node_id", &dht_live_nodes_alert::node_id) + .add_property("num_nodes", &dht_live_nodes_alert::num_nodes) + .add_property("nodes", &dht_live_nodes_nodes) + ; + + std::vector (dht_sample_infohashes_alert::*samples)() const = &dht_sample_infohashes_alert::samples; + + class_, noncopyable>( + "dht_sample_infohashes_alert", no_init) + .add_property("endpoint", make_getter(&dht_sample_infohashes_alert::endpoint, by_value())) + .add_property("interval", make_getter(&dht_sample_infohashes_alert::interval, by_value())) + .add_property("num_infohashes", &dht_sample_infohashes_alert::num_infohashes) + .add_property("num_samples", &dht_sample_infohashes_alert::num_samples) + .add_property("samples", samples) + .add_property("num_nodes", &dht_sample_infohashes_alert::num_nodes) + .add_property("nodes", &dht_sample_infohashes_nodes) + ; + + class_, noncopyable>( + "dht_bootstrap_alert", no_init) + ; + + class_, noncopyable>( + "oversized_file_alert", no_init) + ; + + class_, noncopyable>( + "torrent_conflict_alert", no_init) + .add_property("conflicting_torrent", make_getter(&torrent_conflict_alert::conflicting_torrent, by_value())) + .add_property("metadata", make_getter(&torrent_conflict_alert::metadata, by_value())) + ; + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/bindings/python/src/boost_python.hpp b/bindings/python/src/boost_python.hpp new file mode 100644 index 0000000..ed636e4 --- /dev/null +++ b/bindings/python/src/boost_python.hpp @@ -0,0 +1,43 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_PYTHON_HPP +#define BOOST_PYTHON_HPP + +#include +#include +#include + +#include + +// in boost 1.60, placeholders moved into a namespace, just like std +#if BOOST_VERSION >= 106000 +using namespace boost::placeholders; +#endif + +#include +#include + +#include + +#include + +// something in here creates a define for this, presumably to make older +// versions of msvc appear to support snprintf +#ifdef snprintf +#undef snprintf +#endif + +#ifdef vsnprintf +#undef vsnprintf +#endif + +inline void python_deprecated(char const* msg) +{ + if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1) == -1) + boost::python::throw_error_already_set(); +} + +#endif + diff --git a/bindings/python/src/bytes.hpp b/bindings/python/src/bytes.hpp new file mode 100644 index 0000000..27611c0 --- /dev/null +++ b/bindings/python/src/bytes.hpp @@ -0,0 +1,22 @@ +// Copyright Arvid Norberg 2006-2013. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BYTES_HPP +#define BYTES_HPP + +#include + +struct bytes +{ + bytes(char const* s, std::size_t len): arr(s, len) {} + bytes(std::string const& s): arr(s) {} + bytes(std::string&& s): arr(std::move(s)) {} + bytes(bytes const&) = default; + bytes(bytes&&) noexcept = default; + bytes& operator=(bytes&&) & noexcept = default; + bytes() {} + std::string arr; +}; + +#endif diff --git a/bindings/python/src/converters.cpp b/bindings/python/src/converters.cpp new file mode 100644 index 0000000..9c7c2b2 --- /dev/null +++ b/bindings/python/src/converters.cpp @@ -0,0 +1,556 @@ +// Copyright Andrew Resch 2009. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/session_stats.hpp" // for stats_metric +#include "libtorrent/time.hpp" +#include "libtorrent/torrent_flags.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/disk_interface.hpp" // for open_file_state +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/alert_types.hpp" // for picker_flags_t +#include "libtorrent/session_types.hpp" // for save_state_flags_t +#include "libtorrent/file_storage.hpp" // for file_flags_t +#include "libtorrent/alert.hpp" +#include "libtorrent/create_torrent.hpp" // for create_flags_t +#include "libtorrent/portmap.hpp" // for port_mapping_t +#include "libtorrent/peer_class.hpp" +#include "libtorrent/pex_flags.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/write_resume_data.hpp" +#include +#include + +using namespace boost::python; +namespace bp = boost::python; + +template +struct endpoint_to_tuple +{ + static PyObject* convert(T const& ep) + { + return incref(bp::make_tuple(ep.address().to_string(), ep.port()).ptr()); + } +}; + +template +struct tuple_to_endpoint +{ + tuple_to_endpoint() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + if (!PyTuple_Check(x)) return nullptr; + if (PyTuple_Size(x) != 2) return nullptr; + extract ip(object(borrowed(PyTuple_GetItem(x, 0)))); + if (!ip.check()) return nullptr; + extract port(object(borrowed(PyTuple_GetItem(x, 1)))); + if (!port.check()) return nullptr; + lt::error_code ec; + lt::make_address(ip, ec); + if (ec) return nullptr; + return x; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data) + ->storage.bytes; + + object o(borrowed(x)); + data->convertible = new (storage) T(lt::make_address( + static_cast(extract(o[0]))), extract(o[1])); + } +}; + +template +struct pair_to_tuple +{ + static PyObject* convert(const std::pair& p) + { + return incref(bp::make_tuple(p.first, p.second).ptr()); + } +}; + +template +struct address_to_tuple +{ + static PyObject* convert(Addr const& addr) + { + return incref(bp::object(addr.to_string()).ptr()); + } +}; + +template +struct tuple_to_pair +{ + tuple_to_pair() + { + converter::registry::push_back( + &convertible, &construct, type_id>() + ); + } + + static void* convertible(PyObject* x) + { + return (PyTuple_Check(x) && PyTuple_Size(x) == 2) ? x: nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + std::pair>*)data)->storage.bytes; + + object o(borrowed(x)); + std::pair p; + p.first = extract(o[0]); + p.second = extract(o[1]); + data->convertible = new (storage) std::pair(p); + } +}; + +struct from_string_view +{ + static PyObject* convert(lt::string_view v) + { + str ret(v.data(), v.size()); + return incref(ret.ptr()); + } +}; + +struct to_string_view +{ + to_string_view() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return +#if PY_VERSION_HEX < 0x03020000 + PyString_Check(x) ? x : +#endif + PyUnicode_Check(x) ? x : nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + lt::string_view>*)data)->storage.bytes; + +#if PY_VERSION_HEX < 0x03020000 + if (PyString_Check(x)) + { + data->convertible = new (storage) lt::string_view( + PyString_AsString(x), PyString_Size(x)); + } + else +#endif + { + Py_ssize_t size = 0; + char const* unicode = PyUnicode_AsUTF8AndSize(x, &size); + data->convertible = new (storage) lt::string_view(unicode, size); + } + } +}; + +template +struct map_to_dict +{ + static PyObject* convert(Map const& m) + { + dict ret; + for (auto const& e : m) + ret[e.first] = e.second; + return incref(ret.ptr()); + } +}; + +template> +struct dict_to_map +{ + dict_to_map() + { + converter::registry::push_back(&convertible, &construct, type_id()); + } + + static void* convertible(PyObject* x) + { + return PyDict_Check(x) ? x: nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + + dict o(borrowed(x)); + Map m; + + stl_input_iterator i(o.keys()), end; + for (; i != end; ++i) + { + T1 const& key = *i; + m[key] = extract(o[key]); + } + data->convertible = new (storage) Map(m); + } +}; + +template +struct vector_to_list +{ + static PyObject* convert(T const& v) + { + list l; + for (int i = 0; i < int(v.size()); ++i) + { + l.append(v[i]); + } + return incref(l.ptr()); + } +}; + +template +struct list_to_vector +{ + list_to_vector() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyList_Check(x) ? x: nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + T>*)data)->storage.bytes; + + T p; + int const size = int(PyList_Size(x)); + p.reserve(size); + for (int i = 0; i < size; ++i) + { + object o(borrowed(PyList_GetItem(x, i))); + p.push_back(extract(o)); + } + data->convertible = new (storage) T(std::move(p)); + } +}; + +template +struct list_to_bitfield +{ + list_to_bitfield() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyList_Check(x) ? x : nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + T>*)data)->storage.bytes; + + T p; + int const size = int(PyList_Size(x)); + p.resize(size); + for (int i = 0; i < size; ++i) + { + object o(borrowed(PyList_GetItem(x, i))); + if (extract(o)) p.set_bit(IndexType{i}); + else p.clear_bit(IndexType{i}); + } + data->convertible = new (storage) T(std::move(p)); + } +}; + +template +struct bitfield_to_list +{ + static PyObject* convert(T const& v) + { + list ret; + for (auto const i : v) + ret.append(i); + return incref(ret.ptr()); + } +}; + +template +struct from_strong_typedef +{ + using underlying_type = typename T::underlying_type; + + static PyObject* convert(const T& v) + { + object o(static_cast(v)); + return incref(o.ptr()); + } +}; + +template +struct to_strong_typedef +{ + using underlying_type = typename T::underlying_type; + + to_strong_typedef() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyNumber_Check(x) ? x : nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + data->convertible = new (storage) T(extract(object(borrowed(x)))); + } +}; + +template +struct to_enum_class +{ + using underlying_type = typename std::underlying_type::type; + + to_enum_class() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyNumber_Check(x) ? x : nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + data->convertible = new (storage) T(static_cast(static_cast(extract(object(borrowed(x)))))); + } +}; + +template +struct from_bitfield_flag +{ + using underlying_type = typename T::underlying_type; + + static PyObject* convert(T const v) + { + // this is because python uses "long int" to represent integral values + // internally, it cannot hold large unsigned values + auto const val = static_cast(v) + & static_cast(std::numeric_limits::max()); + object o(val); + return incref(o.ptr()); + } +}; + +template +struct to_bitfield_flag +{ + using underlying_type = typename T::underlying_type; + + to_bitfield_flag() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyNumber_Check(x) ? x : nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + data->convertible = new (storage) T(extract(object(borrowed(x)))); + } +}; + +void bind_converters() +{ + // C++ -> python conversions + to_python_converter, pair_to_tuple>(); + to_python_converter, pair_to_tuple>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter, pair_to_tuple>(); + to_python_converter, pair_to_tuple>(); + + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + + to_python_converter, bitfield_to_list>>(); + to_python_converter>(); + + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter(); + + // work-around types + to_python_converter, address_to_tuple< + lt::aux::noexcept_movable>>(); + to_python_converter, endpoint_to_tuple< + lt::aux::noexcept_movable>>(); + to_python_converter, endpoint_to_tuple< + lt::aux::noexcept_movable>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>>, vector_to_list>>>>(); + to_python_converter>, map_to_dict>>>(); + to_python_converter>, map_to_dict>>>(); + to_python_converter, map_to_dict>>(); + to_python_converter>(); + +#if TORRENT_ABI_VERSION == 1 + to_python_converter>, vector_to_list>>>(); + list_to_vector>>(); + +#ifndef TORRENT_DISABLE_DHT + to_python_converter, vector_to_list>>(); +#endif +#endif + + // python -> C++ conversions + tuple_to_pair(); + tuple_to_pair(); + tuple_to_endpoint(); + tuple_to_endpoint(); + tuple_to_pair(); + dict_to_map(); + list_to_vector>(); + list_to_vector>(); + list_to_vector>(); + list_to_vector>(); + list_to_vector>(); + list_to_vector>>(); + list_to_vector>>(); + + // work-around types + list_to_vector>>(); + list_to_vector>>(); + list_to_vector>>(); + list_to_vector>>(); + list_to_vector>>(); + list_to_vector>>>(); + list_to_vector>>(); + dict_to_map>>(); + dict_to_map>>(); + + // bitfield types + list_to_bitfield, lt::piece_index_t>(); + list_to_bitfield(); + + bitfield_to_list>(); + bitfield_to_list(); + + to_strong_typedef(); + to_strong_typedef(); + to_strong_typedef(); + to_strong_typedef(); + to_strong_typedef(); + to_strong_typedef(); + to_enum_class(); +#if TORRENT_ABI_VERSION <= 2 + to_enum_class(); +#endif + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_string_view(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); +} diff --git a/bindings/python/src/create_torrent.cpp b/bindings/python/src/create_torrent.cpp new file mode 100644 index 0000000..65d82be --- /dev/null +++ b/bindings/python/src/create_torrent.cpp @@ -0,0 +1,277 @@ +// Copyright Daniel Wallin & Arvid Norberg 2009. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include "libtorrent/torrent_info.hpp" +#include +#include "bytes.hpp" +#include "gil.hpp" + +using namespace boost::python; +using namespace lt; + +#ifdef _MSC_VER +#pragma warning(push) +// warning c4996: x: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +namespace +{ + void set_hash(create_torrent& c, piece_index_t p, bytes const& b) + { + c.set_hash(p, sha1_hash(b.arr)); + } + +#if TORRENT_ABI_VERSION < 3 + void set_file_hash(create_torrent& c, file_index_t f, bytes const& b) + { + c.set_file_hash(f, sha1_hash(b.arr)); + } +#endif + +#ifndef BOOST_NO_EXCEPTIONS + void set_piece_hashes_callback(create_torrent& c, std::string const& p + , boost::python::object cb) + { + set_piece_hashes(c, p, std::function( + [&](piece_index_t const i) { cb(i); })); + } +#else + void set_piece_hashes_callback(create_torrent& c, std::string const& p + , boost::python::object cb) + { + error_code ec; + set_piece_hashes(c, p, [&](piece_index_t const i) { cb(i); }, ec); + } + + void set_piece_hashes0(create_torrent& c, std::string const & s) + { + error_code ec; + set_piece_hashes(c, s, ec); + } +#endif + + void add_node(create_torrent& ct, std::string const& addr, int port) + { + ct.add_node(std::make_pair(addr, port)); + } + +#if TORRENT_ABI_VERSION == 1 + void add_file_deprecated(file_storage& ct, file_entry const& fe) + { + python_deprecated("this overload of add_file() is deprecated"); + ct.add_file(fe); + } + + struct FileIter + { + using value_type = lt::file_entry; + using reference = lt::file_entry; + using pointer = lt::file_entry*; + using difference_type = int; + using iterator_category = std::forward_iterator_tag; + + FileIter(file_storage const& fs, file_index_t i) : m_fs(&fs), m_i(i) {} + FileIter(FileIter const&) = default; + FileIter() : m_fs(nullptr), m_i(0) {} + lt::file_entry operator*() const + { return m_fs->at(m_i); } + + FileIter operator++() { m_i++; return *this; } + FileIter operator++(int) { return FileIter(*m_fs, m_i++); } + + bool operator==(FileIter const& rhs) const + { return m_fs == rhs.m_fs && m_i == rhs.m_i; } + + int operator-(FileIter const& rhs) const + { + assert(rhs.m_fs == m_fs); + return m_i - rhs.m_i; + } + + FileIter& operator=(FileIter const&) = default; + + file_storage const* m_fs; + file_index_t m_i; + }; + + FileIter begin_files(file_storage const& self) + { + python_deprecated("__iter__ is deprecated"); + return FileIter(self, file_index_t(0)); + } + + FileIter end_files(file_storage const& self) + { return FileIter(self, self.end_file()); } +#endif // TORRENT_ABI_VERSION + + void add_files_callback(file_storage& fs, std::string const& file + , boost::python::object cb, create_flags_t const flags) + { + add_files(fs, file, [&](std::string const& i) { return cb(i); }, flags); + } + + void add_file(file_storage& fs, std::string const& file, std::int64_t size + , file_flags_t const flags, std::time_t md, std::string link) + { + fs.add_file(file, size, flags, md, link); + } + + void add_tracker(create_torrent& ct, std::string url, int tier) + { + ct.add_tracker(url, tier); + } + + struct dummy13 {}; + struct dummy14 {}; +} + +void bind_create_torrent() +{ + void (file_storage::*set_name0)(std::string const&) = &file_storage::set_name; + void (file_storage::*rename_file0)(file_index_t, std::string const&) = &file_storage::rename_file; + +#ifndef BOOST_NO_EXCEPTIONS + void (*set_piece_hashes0)(create_torrent&, std::string const&) = &set_piece_hashes; +#endif + void (*add_files0)(file_storage&, std::string const&, create_flags_t) = add_files; + + std::string (file_storage::*file_storage_symlink)(file_index_t) const = &file_storage::symlink; + sha1_hash (file_storage::*file_storage_hash)(file_index_t) const = &file_storage::hash; + std::string (file_storage::*file_storage_file_path)(file_index_t, std::string const&) const = &file_storage::file_path; + string_view (file_storage::*file_storage_file_name)(file_index_t) const = &file_storage::file_name; + std::int64_t (file_storage::*file_storage_file_size)(file_index_t) const = &file_storage::file_size; + std::int64_t (file_storage::*file_storage_file_offset)(file_index_t) const = &file_storage::file_offset; + file_flags_t (file_storage::*file_storage_file_flags)(file_index_t) const = &file_storage::file_flags; + +#if TORRENT_ABI_VERSION == 1 + file_entry (file_storage::*at)(int) const = &file_storage::at; +#endif + + // TODO: 3 move this to its own file + { + scope s = class_("file_storage") + .def("is_valid", &file_storage::is_valid) + .def("add_file", add_file, (arg("path"), arg("size"), arg("flags") = 0, arg("mtime") = 0, arg("linkpath") = "")) + .def("num_files", &file_storage::num_files) +#if TORRENT_ABI_VERSION == 1 + .def("at", depr(at)) + .def("add_file", add_file_deprecated, arg("entry")) + .def("__iter__", boost::python::range(&begin_files, &end_files)) + .def("__len__", depr(&file_storage::num_files)) +#endif // TORRENT_ABI_VERSION + .def("hash", file_storage_hash) + .def("symlink", file_storage_symlink) + .def("file_path", file_storage_file_path, (arg("idx"), arg("save_path") = "")) + .def("file_name", file_storage_file_name) + .def("file_size", file_storage_file_size) + .def("root", &file_storage::root) + .def("file_offset", file_storage_file_offset) + .def("file_flags", file_storage_file_flags) + + .def("file_index_for_root", &file_storage::file_index_for_root) + .def("piece_index_at_file", &file_storage::piece_index_at_file) + .def("file_index_at_piece", &file_storage::file_index_at_piece) + .def("file_index_at_offset", &file_storage::file_index_at_offset) + .def("file_absolute_path", &file_storage::file_absolute_path) + + .def("v2", &file_storage::v2) + + .def("total_size", &file_storage::total_size) + .def("set_num_pieces", &file_storage::set_num_pieces) + .def("num_pieces", &file_storage::num_pieces) + .def("set_piece_length", &file_storage::set_piece_length) + .def("piece_length", &file_storage::piece_length) + .def("piece_size", &file_storage::piece_size) + .def("set_name", set_name0) + .def("rename_file", rename_file0) + .def("name", &file_storage::name, return_value_policy()) + ; + + s.attr("flag_pad_file") = file_storage::flag_pad_file; + s.attr("flag_hidden") = file_storage::flag_hidden; + s.attr("flag_executable") = file_storage::flag_executable; + s.attr("flag_symlink") = file_storage::flag_symlink; + } + + { + scope s = class_("file_flags_t"); + s.attr("flag_pad_file") = file_storage::flag_pad_file; + s.attr("flag_hidden") = file_storage::flag_hidden; + s.attr("flag_executable") = file_storage::flag_executable; + s.attr("flag_symlink") = file_storage::flag_symlink; + } + + { + scope s = class_("create_torrent", no_init) + .def(init()) + .def(init(arg("ti"))) + .def(init((arg("storage"), arg("piece_size") = 0 + , arg("flags") = create_flags_t{}))) + + .def("generate", &create_torrent::generate) + + .def("files", &create_torrent::files, return_internal_reference<>()) + .def("set_comment", &create_torrent::set_comment) + .def("set_creator", &create_torrent::set_creator) + .def("set_hash", &set_hash) +#if TORRENT_ABI_VERSION < 3 + .def("set_file_hash", &set_file_hash) +#endif + .def("add_url_seed", &create_torrent::add_url_seed) + .def("add_http_seed", &create_torrent::add_http_seed) + .def("add_node", &add_node) + .def("add_tracker", add_tracker, (arg("announce_url"), arg("tier") = 0)) + .def("set_priv", &create_torrent::set_priv) + .def("num_pieces", &create_torrent::num_pieces) + .def("piece_length", &create_torrent::piece_length) + .def("piece_size", &create_torrent::piece_size) + .def("priv", &create_torrent::priv) + .def("set_root_cert", &create_torrent::set_root_cert, (arg("pem"))) + .def("add_collection", &create_torrent::add_collection) + .def("add_similar_torrent", &create_torrent::add_similar_torrent) + + ; + +#if TORRENT_ABI_VERSION <= 2 + s.attr("optimize_alignment") = create_torrent::optimize_alignment; + s.attr("merkle") = create_torrent::merkle; +#endif + s.attr("v2_only") = create_torrent::v2_only; + s.attr("v1_only") = create_torrent::v1_only; + s.attr("canonical_files") = create_torrent::canonical_files; + s.attr("modification_time") = create_torrent::modification_time; + s.attr("symlinks") = create_torrent::symlinks; + s.attr("no_attributes") = create_torrent::no_attributes; + } + + { + scope s = class_("create_torrent_flags_t"); +#if TORRENT_ABI_VERSION == 1 + s.attr("optimize") = create_torrent::optimize; +#endif +#if TORRENT_ABI_VERSION <= 2 + s.attr("optimize_alignment") = create_torrent::optimize_alignment; + s.attr("merkle") = create_torrent::merkle; +#endif + s.attr("v2_only") = create_torrent::v2_only; + s.attr("modification_time") = create_torrent::modification_time; + s.attr("symlinks") = create_torrent::symlinks; + } + + def("add_files", add_files0, (arg("fs"), arg("path"), arg("flags") = 0)); + def("add_files", add_files_callback, (arg("fs"), arg("path") + , arg("predicate"), arg("flags") = 0)); + def("set_piece_hashes", set_piece_hashes0); + def("set_piece_hashes", set_piece_hashes_callback); + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + diff --git a/bindings/python/src/datetime.cpp b/bindings/python/src/datetime.cpp new file mode 100644 index 0000000..454b22b --- /dev/null +++ b/bindings/python/src/datetime.cpp @@ -0,0 +1,144 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include "optional.hpp" +#include +#include "libtorrent/time.hpp" +#include + +using namespace boost::python; + +object datetime_timedelta; +object datetime_datetime; + +template +struct chrono_duration_to_python +{ + static PyObject* convert(Duration const& d) + { + std::int64_t const us = lt::total_microseconds(d); + object result = datetime_timedelta( + 0 // days + , us / 1000000 // seconds + , us % 1000000 // microseconds + ); + + return incref(result.ptr()); + } +}; + +struct time_duration_to_python +{ + static PyObject* convert(boost::posix_time::time_duration const& d) + { + object result = datetime_timedelta( + 0 // days + , 0 // seconds + , d.total_microseconds() + ); + + return incref(result.ptr()); + } +}; + +template struct tag {}; + +lt::time_point now(::tag) +{ return lt::clock_type::now(); } + +lt::time_point32 now(::tag) +{ return lt::time_point_cast(lt::clock_type::now()); } + +template +struct time_point_to_python +{ + static PyObject* convert(T const pt) + { + using std::chrono::system_clock; + using std::chrono::duration_cast; + object result; + if (pt > T()) + { + time_t const tm = system_clock::to_time_t(system_clock::now() + + duration_cast(pt - now(::tag()))); + +#ifdef TORRENT_WINDOWS + std::tm const* date = localtime(&tm); +#else + std::tm buf; + std::tm const* date = localtime_r(&tm, &buf); +#endif + + result = datetime_datetime( + (int)1900 + date->tm_year + // tm use 0-11 and we need 1-12 + , (int)date->tm_mon + 1 + , (int)date->tm_mday + , date->tm_hour + , date->tm_min + , date->tm_sec + ); + } + else + { + result = object(); + } + return incref(result.ptr()); + } +}; + +struct ptime_to_python +{ + static PyObject* convert(boost::posix_time::ptime const& pt) + { + boost::gregorian::date date = pt.date(); + boost::posix_time::time_duration td = pt.time_of_day(); + + object result = datetime_datetime( + (int)date.year() + , (int)date.month() + , (int)date.day() + , td.hours() + , td.minutes() + , td.seconds() + ); + + return incref(result.ptr()); + } +}; + +void bind_datetime() +{ + object datetime = import("datetime").attr("__dict__"); + + datetime_timedelta = datetime["timedelta"]; + datetime_datetime = datetime["datetime"]; + + to_python_converter(); + + to_python_converter(); + + to_python_converter>(); + + to_python_converter>(); + + to_python_converter>(); + + to_python_converter>(); + + to_python_converter>(); + + optional_to_python(); + optional_to_python(); +} + diff --git a/bindings/python/src/entry.cpp b/bindings/python/src/entry.cpp new file mode 100644 index 0000000..6e0ee74 --- /dev/null +++ b/bindings/python/src/entry.cpp @@ -0,0 +1,185 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include "bytes.hpp" + +using namespace boost::python; +using namespace lt; + +struct entry_to_python +{ + static object convert(entry::list_type const& l) + { + list result; + + for (entry::list_type::const_iterator i(l.begin()), e(l.end()); i != e; ++i) + { + result.append(*i); + } + + return std::move(result); + } + + static object convert(entry::dictionary_type const& d) + { + dict result; + + for (entry::dictionary_type::const_iterator i(d.begin()), e(d.end()); i != e; ++i) + result[bytes(i->first)] = i->second; + + return std::move(result); + } + + static object convert0(entry const& e) + { + switch (e.type()) + { + case entry::int_t: + return object(e.integer()); + case entry::string_t: + return object(bytes(e.string())); + case entry::list_t: + return convert(e.list()); + case entry::dictionary_t: + return convert(e.dict()); + case entry::preformatted_t: + { + std::vector const& pre = e.preformatted(); + list l; + for (std::vector::const_iterator i = pre.begin() + , end(pre.end()); i != end; ++i) + l.append(int(*i)); + return tuple(l); + } + default: + return object(); + } + } + + static PyObject* convert(std::shared_ptr const& e) + { + if (!e) + return incref(Py_None); + return convert(*e); + } + + static PyObject* convert(entry const& e) + { + return incref(convert0(e).ptr()); + } +}; + +struct entry_from_python +{ + entry_from_python() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* e) + { + return e; + } + + static entry construct0(object e) + { + if (extract(e).check()) + { + dict d = extract(e); + list items(d.items()); + std::size_t length = extract(items.attr("__len__")()); + entry result(entry::dictionary_t); + + for (std::size_t i = 0; i < length; ++i) + { + if (extract(items[i][0]).check()) + { + result.dict().insert( + std::make_pair( + extract(items[i][0])().arr, + construct0(items[i][1]) + ) + ); + } + else + { + result.dict().insert( + std::make_pair( + extract(items[i][0])(), + construct0(items[i][1]) + ) + ); + } + } + + return result; + } + else if (extract(e).check()) + { + list l = extract(e); + + std::size_t length = extract(l.attr("__len__")()); + entry result(entry::list_t); + + for (std::size_t i = 0; i < length; ++i) + { + result.list().push_back(construct0(l[i])); + } + + return result; + } + else if (extract(e).check()) + { + return entry(extract(e)().arr); + } + else if (extract(e).check()) + { + return entry(extract(e)()); + } + else if (extract(e).check()) + { + return entry(extract(e)()); + } + else if (extract(e).check()) + { + tuple t = extract(e); + + std::size_t const length = extract(t.attr("__len__")()); + std::vector preformatted(length); + for (std::size_t i = 0; i < length; ++i) + { + preformatted[i] = char(extract(t[i])); + } + + return entry(preformatted); + } + else + { + // TODO: Throw a TypeError here in the future + python_deprecated("constructing a bencode entry from anything but " + "int, dict, list, string, bytes and int-tuple is deprecated"); + } + + return entry(); + } + + static void construct(PyObject* e, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + new (storage) entry(construct0(object(borrowed(e)))); + data->convertible = storage; + } +}; + +void bind_entry() +{ + to_python_converter, entry_to_python>(); + to_python_converter(); + entry_from_python(); +} + diff --git a/bindings/python/src/error_code.cpp b/bindings/python/src/error_code.cpp new file mode 100644 index 0000000..b947119 --- /dev/null +++ b/bindings/python/src/error_code.cpp @@ -0,0 +1,240 @@ +/* + +Copyright (c) 2011, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "boost_python.hpp" +#include +#include +#include +#include + +namespace boost +{ + // this fixe mysterious link error on msvc + template <> + inline boost::system::error_category const volatile* + get_pointer(class boost::system::error_category const volatile* p) + { + return p; + } +} + +#include +#if TORRENT_USE_SSL +#include +#include +#endif +#if TORRENT_USE_I2P +#include +#endif + +using namespace boost::python; +using namespace lt; +using boost::system::error_category; + +namespace { + + struct ec_pickle_suite : boost::python::pickle_suite + { + static boost::python::tuple + getinitargs(error_code const&) + { + return boost::python::tuple(); + } + + static boost::python::tuple + getstate(error_code const& ec) + { + return boost::python::make_tuple(ec.value(), ec.category().name()); + } + + static void + setstate(error_code& ec, boost::python::tuple state) + { + using namespace boost::python; + if (len(state) != 2) + { + PyErr_SetObject(PyExc_ValueError, + ("expected 2-item tuple in call to __setstate__; got %s" + % state).ptr()); + throw_error_already_set(); + } + + int const value = extract(state[0]); + std::string const category = extract(state[1]); + if (category == "system") + ec.assign(value, lt::system_category()); + else if (category == "generic") + ec.assign(value, lt::generic_category()); + else if (category == "libtorrent") + ec.assign(value, lt::libtorrent_category()); + else if (category == "http error") + ec.assign(value, lt::http_category()); + else if (category == "UPnP error") + ec.assign(value, lt::upnp_category()); + else if (category == "bdecode error") + ec.assign(value, lt::bdecode_category()); + else if (category == "asio.netdb") + ec.assign(value, boost::asio::error::get_netdb_category()); + else if (category == "asio.addinfo") + ec.assign(value, boost::asio::error::get_addrinfo_category()); + else if (category == "asio.misc") + ec.assign(value, boost::asio::error::get_misc_category()); +#if TORRENT_USE_SSL + else if (category == "asio.ssl") + ec.assign(value, boost::asio::error::get_ssl_category()); +#endif + else + { + PyErr_SetObject(PyExc_ValueError, + ("unexpected error_category passed to __setstate__; got '%s'" + % object(category)).ptr()); + throw_error_already_set(); + } + } + }; +} + +struct category_holder +{ + category_holder(boost::system::error_category const& cat) : m_cat(&cat) {} + char const* name() const { return m_cat->name(); } + std::string message(int const v) const { return m_cat->message(v); } + + friend bool operator==(category_holder const lhs, category_holder const rhs) + { return *lhs.m_cat == *rhs.m_cat; } + + friend bool operator!=(category_holder const lhs, category_holder const rhs) + { return *lhs.m_cat != *rhs.m_cat; } + + friend bool operator<(category_holder const lhs, category_holder const rhs) + { return *lhs.m_cat < *rhs.m_cat; } + + boost::system::error_category const& ref() const { return *m_cat; } + operator boost::system::error_category const&() const { return *m_cat; } +private: + boost::system::error_category const* m_cat; +}; + +void error_code_assign(boost::system::error_code& me, int const v, category_holder const cat) +{ + me.assign(v, cat.ref()); +} + +category_holder error_code_category(boost::system::error_code const& me) +{ + return category_holder(me.category()); +} + +#define WRAP_CAT(name) \ + category_holder wrap_ ##name## _category() { return category_holder(name## _category()); } + +WRAP_CAT(libtorrent) +WRAP_CAT(upnp) +WRAP_CAT(http) +WRAP_CAT(socks) +WRAP_CAT(bdecode) +#if TORRENT_USE_I2P +WRAP_CAT(i2p) +#endif +WRAP_CAT(generic) +WRAP_CAT(system) + +#undef WRAP_CAT + +#if TORRENT_ABI_VERSION == 1 + +#define WRAP_DEPR_CAT(name) \ + category_holder wrap_ ##name## _category_deprecated() { \ + python_deprecated(#name " is deprecated"); \ + return category_holder(name## _category()); \ + } + +WRAP_DEPR_CAT(libtorrent) +WRAP_DEPR_CAT(upnp) +WRAP_DEPR_CAT(http) +WRAP_DEPR_CAT(socks) +WRAP_DEPR_CAT(bdecode) +#if TORRENT_USE_I2P +WRAP_DEPR_CAT(i2p) +#endif +WRAP_DEPR_CAT(generic) +WRAP_DEPR_CAT(system) + +#undef WRAP_DEPR_CAT +#endif + +void bind_error_code() +{ + class_("error_category", no_init) + .def("name", &category_holder::name) + .def("message", &category_holder::message) + .def(self == self) + .def(self < self) + .def(self != self) + ; + + class_("error_code") + .def(init<>()) + .def(init()) + .def("message", static_cast(&error_code::message)) + .def("value", &error_code::value) + .def("clear", &error_code::clear) + .def("category", &error_code_category) + .def("assign", &error_code_assign) + .def_pickle(ec_pickle_suite()) + ; + + def("libtorrent_category", &wrap_libtorrent_category); + def("upnp_category", &wrap_upnp_category); + def("http_category", &wrap_http_category); + def("socks_category", &wrap_socks_category); + def("bdecode_category", &wrap_bdecode_category); +#if TORRENT_USE_I2P + def("i2p_category", &wrap_i2p_category); +#endif + +#if TORRENT_ABI_VERSION == 1 + def("get_libtorrent_category", &wrap_libtorrent_category_deprecated); + def("get_upnp_category", &wrap_upnp_category_deprecated); + def("get_http_category", &wrap_http_category_deprecated); + def("get_socks_category", &wrap_socks_category_deprecated); + def("get_bdecode_category", &wrap_bdecode_category_deprecated); +#if TORRENT_USE_I2P + def("get_i2p_category", &wrap_i2p_category_deprecated); +#endif +#endif // TORRENT_ABI_VERSION + + def("generic_category", &wrap_generic_category); + + def("system_category", &wrap_system_category); +} + diff --git a/bindings/python/src/fingerprint.cpp b/bindings/python/src/fingerprint.cpp new file mode 100644 index 0000000..dfe0707 --- /dev/null +++ b/bindings/python/src/fingerprint.cpp @@ -0,0 +1,34 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include "gil.hpp" +#include + +void bind_fingerprint() +{ + using namespace boost::python; + using namespace lt; + + def("generate_fingerprint", &generate_fingerprint); + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + class_("fingerprint", no_init) + .def( + init( + (arg("id"), "major", "minor", "revision", "tag") + ) + ) + .def("__str__", depr(&fingerprint::to_string)) + .def_readonly("major_version", depr(&fingerprint::major_version)) + .def_readonly("minor_version", depr(&fingerprint::minor_version)) + .def_readonly("revision_version", depr(&fingerprint::revision_version)) + .def_readonly("tag_version", depr(&fingerprint::tag_version)) + ; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // TORRENT_ABI_VERSION +} diff --git a/bindings/python/src/gil.hpp b/bindings/python/src/gil.hpp new file mode 100644 index 0000000..59f2f74 --- /dev/null +++ b/bindings/python/src/gil.hpp @@ -0,0 +1,186 @@ +// Copyright Daniel Wallin 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef GIL_070107_HPP +# define GIL_070107_HPP + +#include + +# include +# include +# include +# include +#include + +#include + +//namespace libtorrent { namespace python { + +// RAII helper to release GIL. +struct allow_threading_guard +{ + allow_threading_guard() : save(PyEval_SaveThread()) {} + ~allow_threading_guard() { PyEval_RestoreThread(save); } + PyThreadState* save; +}; + +struct lock_gil +{ + lock_gil() : state(PyGILState_Ensure()) {} + ~lock_gil() { PyGILState_Release(state); } + PyGILState_STATE state; +}; + +template +struct allow_threading +{ + allow_threading(F fn) : fn(fn) {} + template + R operator()(Self&& s, Args&&... args) + { + allow_threading_guard guard; + return (std::forward(s).*fn)(std::forward(args)...); + } + F fn; +}; + +template +struct visitor : boost::python::def_visitor> +{ + visitor(F fn) : fn(std::move(fn)) {} + + template + void visit_aux( + Class& cl, char const* name + , Options const& options, Signature const& signature) const + { + typedef typename boost::mpl::at_c::type return_type; + + cl.def( + name + , boost::python::make_function( + allow_threading(fn) + , options.policies() + , options.keywords() + , signature + ) + ); + } + + template + void visit(Class& cl, char const* name, Options const& options) const + { + this->visit_aux( + cl, name, options + , boost::python::detail::get_signature(fn, (typename Class::wrapped_type*)0) + ); + } + + F fn; +}; + +// Member function adaptor that releases and aqcuires the GIL +// around the function call. +template +visitor allow_threads(F fn) +{ + return visitor(fn); +} + +template::type>::value, int>::type = 0> +auto invoke(Fn&& fn, Self&& s, Args&&... args) -> +#if TORRENT_AUTO_RETURN_TYPES + decltype(auto) +#else + decltype((std::forward(s).*std::forward(fn))(std::forward(args)...)) +#endif +{ + return (std::forward(s).*std::forward(fn))(std::forward(args)...); +} + +template::type>::value, int>::type = 0> +auto invoke(Fn&& fn, Self&& s) -> +#if TORRENT_AUTO_RETURN_TYPES + decltype(auto) +#else + decltype((std::forward(s).*std::forward)(fn)) +#endif +{ + return (std::forward(s).*std::forward)(fn); +} + +template::type>::value, int>::type = 0> +auto invoke(Fn&& fn, Args&&... args) -> +#if TORRENT_AUTO_RETURN_TYPES + decltype(auto) +#else + decltype(std::forward(fn)(std::forward(args)...)) +#endif +{ + return std::forward(fn)(std::forward(args)...); +} + +template +struct deprecated_fun +{ + deprecated_fun(F fn, char const* name) : fn(fn), fn_name(name) {} + template + R operator()(Args&&... args) + { + std::string const msg = std::string(fn_name) + "() is deprecated"; + python_deprecated(msg.c_str()); + // TODO: in C++17 use std::invoke + return ::invoke(fn, std::forward(args)...); + } + F fn; + char const* fn_name; +}; + +template +struct deprecate_visitor : boost::python::def_visitor> +{ + deprecate_visitor(F fn) : fn(std::move(fn)) {} + + template + void visit_aux( + Class& cl, char const* name + , Options const& options, Signature const& signature) const + { + using return_type = typename boost::mpl::at_c::type; + + cl.def( + name + , boost::python::make_function( + deprecated_fun(fn, name) + , options.policies() + , options.keywords() + , signature + ) + ); + } + + template + void visit(Class& cl, char const* name, Options const& options) const + { + this->visit_aux( + cl, name, options + , boost::python::detail::get_signature(fn, (typename Class::wrapped_type*)0) + ); + } + + F fn; +}; + +template +deprecate_visitor depr(F fn) +{ + return deprecate_visitor(std::move(fn)); +} + +//}} // namespace libtorrent::python + +#endif // GIL_070107_HPP diff --git a/bindings/python/src/info_hash.cpp b/bindings/python/src/info_hash.cpp new file mode 100644 index 0000000..6ea4772 --- /dev/null +++ b/bindings/python/src/info_hash.cpp @@ -0,0 +1,41 @@ +// Copyright Arvid Norberg 2020. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include + +namespace { + +using namespace lt; + +long get_hash(info_hash_t const& ih) +{ + return std::hash{}(ih); +} + +} + +void bind_info_hash() +{ + using namespace boost::python; + using namespace lt; + + class_("info_hash_t") + .def(init(arg("sha1_hash"))) + .def(init(arg("sha256_hash"))) + .def(init((arg("sha1_hash"), arg("sha256_hash")))) + .def("__hash__", get_hash) + .def("has_v1", &info_hash_t::has_v1) + .def("has_v2", &info_hash_t::has_v2) + .def("has", &info_hash_t::has) + .def("get", &info_hash_t::get) + .def("get_best", &info_hash_t::get_best) + .add_property("v1", &info_hash_t::v1) + .add_property("v2", &info_hash_t::v2) + .def(self == self) + .def(self != self) + .def(self < self) + ; +} + diff --git a/bindings/python/src/ip_filter.cpp b/bindings/python/src/ip_filter.cpp new file mode 100644 index 0000000..766f844 --- /dev/null +++ b/bindings/python/src/ip_filter.cpp @@ -0,0 +1,49 @@ +// Copyright Andrew Resch 2008. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include "gil.hpp" + +using namespace boost::python; +using namespace lt; + +namespace +{ + void add_rule(ip_filter& filter, std::string start, std::string end, int flags) + { + return filter.add_rule(make_address(start), make_address(end), flags); + } + + int access0(ip_filter& filter, std::string addr) + { + return filter.access(make_address(addr)); + } + + template + list convert_range_list(std::vector> const& l) + { + list ret; + for (auto const& r : l) + ret.append(boost::python::make_tuple(r.first.to_string(), r.last.to_string())); + return ret; + } + + tuple export_filter(ip_filter const& f) + { + auto ret = f.export_filter(); + list ipv4 = convert_range_list(std::get<0>(ret)); + list ipv6 = convert_range_list(std::get<1>(ret)); + return boost::python::make_tuple(ipv4, ipv6); + } +} + +void bind_ip_filter() +{ + class_("ip_filter") + .def("add_rule", &add_rule) + .def("access", &access0) + .def("export_filter", &export_filter) + ; +} diff --git a/bindings/python/src/load_torrent.cpp b/bindings/python/src/load_torrent.cpp new file mode 100644 index 0000000..167e63a --- /dev/null +++ b/bindings/python/src/load_torrent.cpp @@ -0,0 +1,55 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +#include "boost_python.hpp" +#include "bytes.hpp" +#include "libtorrent/load_torrent.hpp" +#include "libtorrent/torrent_info.hpp" // for load_torrent_limits +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/bdecode.hpp" + +using namespace boost::python; + +// defined in torrent_info.cpp +lt::load_torrent_limits dict_to_limits(dict limits); + +namespace { + + lt::add_torrent_params load_torrent_file1(std::string filename, dict cfg) +{ + return lt::load_torrent_file(filename, dict_to_limits(cfg)); +} + +lt::add_torrent_params load_torrent_buffer0(bytes b) +{ + return lt::load_torrent_buffer(b.arr); +} + +lt::add_torrent_params load_torrent_buffer1(bytes b, dict cfg) +{ + return lt::load_torrent_buffer(b.arr, dict_to_limits(cfg)); +} + + +lt::add_torrent_params load_torrent_parsed1(lt::bdecode_node const& n, dict cfg) +{ + return lt::load_torrent_parsed(n, dict_to_limits(cfg)); +} + +} + +void bind_load_torrent() +{ + lt::add_torrent_params (*load_torrent_file0)(std::string const&) = <::load_torrent_file; + lt::add_torrent_params (*load_torrent_parsed0)(lt::bdecode_node const&) = <::load_torrent_parsed; + + def("load_torrent_file", load_torrent_file0); + def("load_torrent_file", load_torrent_file1); + def("load_torrent_buffer", load_torrent_buffer0); + def("load_torrent_buffer", load_torrent_buffer1); + def("load_torrent_parsed", load_torrent_parsed0); + def("load_torrent_parsed", load_torrent_parsed1); +} diff --git a/bindings/python/src/magnet_uri.cpp b/bindings/python/src/magnet_uri.cpp new file mode 100644 index 0000000..1cae4d5 --- /dev/null +++ b/bindings/python/src/magnet_uri.cpp @@ -0,0 +1,101 @@ +// Copyright Andrew Resch 2008. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt + +#include "boost_python.hpp" +#include "bytes.hpp" +#include +#include +#include +#include "gil.hpp" +#include "bytes.hpp" + +using namespace boost::python; +using namespace lt; + +extern void dict_to_add_torrent_params(dict params, add_torrent_params& p); + +namespace { + +#if TORRENT_ABI_VERSION == 1 + torrent_handle _add_magnet_uri(lt::session& s, std::string uri, dict params) + { + python_deprecated("add_magnet_uri() is deprecated"); + add_torrent_params p; + + dict_to_add_torrent_params(params, p); + + allow_threading_guard guard; + + p.url = uri; + +#ifndef BOOST_NO_EXCEPTIONS + return s.add_torrent(p); +#else + error_code ec; + return s.add_torrent(p, ec); +#endif + } +#endif + + dict parse_magnet_uri_dict(std::string const& uri) + { + error_code ec; + add_torrent_params p = parse_magnet_uri(uri, ec); + + if (ec) throw system_error(ec); + + dict ret; + + if (p.ti) ret["ti"] = p.ti; + list tracker_list; + for (std::vector::const_iterator i = p.trackers.begin() + , end(p.trackers.end()); i != end; ++i) + tracker_list.append(*i); + ret["trackers"] = tracker_list; + + list nodes_list; + for (auto const& i : p.dht_nodes) + nodes_list.append(boost::python::make_tuple(i.first, i.second)); + ret["dht_nodes"] = nodes_list; + if (p.info_hashes.has_v2()) + ret["info_hashes"] = bytes(p.info_hashes.v2.to_string()); + else + ret["info_hashes"] = bytes(p.info_hashes.v1.to_string()); +#if TORRENT_ABI_VERSION < 3 + ret["info_hash"] = bytes(p.info_hashes.get_best().to_string()); +#endif + ret["name"] = p.name; + ret["save_path"] = p.save_path; + ret["storage_mode"] = p.storage_mode; +#if TORRENT_ABI_VERSION == 1 + ret["url"] = p.url; +#endif + ret["flags"] = p.flags; + return ret; + } + + add_torrent_params parse_magnet_uri_wrap(std::string const& uri) + { + error_code ec; + add_torrent_params p = parse_magnet_uri(uri, ec); + if (ec) throw system_error(ec); + return p; + } + + std::string (*make_magnet_uri0)(torrent_handle const&) = make_magnet_uri; + std::string (*make_magnet_uri1)(torrent_info const&) = make_magnet_uri; + std::string (*make_magnet_uri2)(add_torrent_params const&) = make_magnet_uri; +} + +void bind_magnet_uri() +{ +#if TORRENT_ABI_VERSION == 1 + def("add_magnet_uri", &_add_magnet_uri); +#endif + def("make_magnet_uri", make_magnet_uri0); + def("make_magnet_uri", make_magnet_uri1); + def("make_magnet_uri", make_magnet_uri2); + def("parse_magnet_uri", parse_magnet_uri_wrap); + def("parse_magnet_uri_dict", parse_magnet_uri_dict); +} diff --git a/bindings/python/src/module.cpp b/bindings/python/src/module.cpp new file mode 100644 index 0000000..e0d3351 --- /dev/null +++ b/bindings/python/src/module.cpp @@ -0,0 +1,62 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifdef __GNUC__ +#define BOOST_PYTHON_USE_GCC_SYMBOL_VISIBILITY 1 +#endif + +#include +#include "libtorrent/config.hpp" + +void bind_utility(); +void bind_fingerprint(); +void bind_sha1_hash(); +void bind_sha256_hash(); +void bind_info_hash(); +void bind_session(); +void bind_entry(); +void bind_torrent_info(); +void bind_unicode_string_conversion(); +void bind_torrent_handle(); +void bind_torrent_status(); +void bind_session_settings(); +void bind_version(); +void bind_alert(); +void bind_datetime(); +void bind_peer_info(); +void bind_ip_filter(); +void bind_magnet_uri(); +void bind_converters(); +void bind_create_torrent(); +void bind_error_code(); +void bind_load_torrent(); + +BOOST_PYTHON_MODULE(libtorrent) +{ + Py_Initialize(); + PyEval_InitThreads(); + + bind_converters(); + bind_unicode_string_conversion(); + bind_error_code(); + bind_utility(); + bind_fingerprint(); + bind_sha1_hash(); + bind_sha256_hash(); + bind_info_hash(); + bind_entry(); + bind_torrent_handle(); + bind_session(); + bind_torrent_info(); + bind_torrent_status(); + bind_session_settings(); + bind_version(); + bind_alert(); + bind_datetime(); + bind_peer_info(); + bind_ip_filter(); + bind_magnet_uri(); + bind_create_torrent(); + bind_load_torrent(); +} diff --git a/bindings/python/src/optional.hpp b/bindings/python/src/optional.hpp new file mode 100644 index 0000000..2477924 --- /dev/null +++ b/bindings/python/src/optional.hpp @@ -0,0 +1,31 @@ +// Copyright Daniel Wallin 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef OPTIONAL_070108_HPP +# define OPTIONAL_070108_HPP + +# include "boost_python.hpp" +# include + +template +struct optional_to_python +{ + optional_to_python() + { + boost::python::to_python_converter< + boost::optional, optional_to_python + >(); + } + + static PyObject* convert(boost::optional const& x) + { + if (!x) + return boost::python::incref(Py_None); + + return boost::python::incref(boost::python::object(*x).ptr()); + } +}; + +#endif // OPTIONAL_070108_HPP + diff --git a/bindings/python/src/peer_info.cpp b/bindings/python/src/peer_info.cpp new file mode 100644 index 0000000..0792395 --- /dev/null +++ b/bindings/python/src/peer_info.cpp @@ -0,0 +1,160 @@ +// Copyright Daniel Wallin 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include "bytes.hpp" +#include +#include +#include + +using namespace boost::python; +using namespace lt; + +std::int64_t get_last_active(peer_info const& pi) +{ + return total_seconds(pi.last_active); +} + +std::int64_t get_last_request(peer_info const& pi) +{ + return total_seconds(pi.last_request); +} + +std::int64_t get_download_queue_time(peer_info const& pi) +{ + return total_seconds(pi.download_queue_time); +} + +tuple get_local_endpoint(peer_info const& pi) +{ + return boost::python::make_tuple(pi.local_endpoint.address().to_string(), pi.local_endpoint.port()); +} + +tuple get_ip(peer_info const& pi) +{ + return boost::python::make_tuple(pi.ip.address().to_string(), pi.ip.port()); +} + +list get_pieces(peer_info const& pi) +{ + list ret; + + for (bitfield::const_iterator i = pi.pieces.begin() + , end(pi.pieces.end()); i != end; ++i) + { + ret.append(*i); + } + return ret; +} + +bytes get_peer_info_client(peer_info const& pi) +{ + return pi.client; +} + +using by_value = return_value_policy; +void bind_peer_info() +{ + scope pi = class_("peer_info") + .add_property("flags", make_getter(&peer_info::flags, by_value())) + .add_property("source", make_getter(&peer_info::source, by_value())) + .add_property("read_state", make_getter(&peer_info::read_state, by_value())) + .add_property("write_state", make_getter(&peer_info::write_state, by_value())) + .add_property("ip", get_ip) + .def_readonly("up_speed", &peer_info::up_speed) + .def_readonly("down_speed", &peer_info::down_speed) + .def_readonly("payload_up_speed", &peer_info::payload_up_speed) + .def_readonly("payload_down_speed", &peer_info::payload_down_speed) + .def_readonly("total_download", &peer_info::total_download) + .def_readonly("total_upload", &peer_info::total_upload) + .def_readonly("pid", &peer_info::pid) + .add_property("pieces", get_pieces) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("upload_limit", &peer_info::upload_limit) + .def_readonly("download_limit", &peer_info::download_limit) + .def_readonly("load_balancing", &peer_info::load_balancing) + .def_readonly("remote_dl_rate", &peer_info::remote_dl_rate) +#endif + .add_property("last_request", get_last_request) + .add_property("last_active", get_last_active) + .add_property("download_queue_time", get_download_queue_time) + .def_readonly("queue_bytes", &peer_info::queue_bytes) + .def_readonly("request_timeout", &peer_info::request_timeout) + .def_readonly("send_buffer_size", &peer_info::send_buffer_size) + .def_readonly("used_send_buffer", &peer_info::used_send_buffer) + .def_readonly("receive_buffer_size", &peer_info::receive_buffer_size) + .def_readonly("used_receive_buffer", &peer_info::used_receive_buffer) + .def_readonly("num_hashfails", &peer_info::num_hashfails) + .def_readonly("download_queue_length", &peer_info::download_queue_length) + .def_readonly("upload_queue_length", &peer_info::upload_queue_length) + .def_readonly("failcount", &peer_info::failcount) + .add_property("downloading_piece_index", make_getter(&peer_info::downloading_piece_index, by_value())) + .add_property("downloading_block_index", make_getter(&peer_info::downloading_block_index, by_value())) + .def_readonly("downloading_progress", &peer_info::downloading_progress) + .def_readonly("downloading_total", &peer_info::downloading_total) + .add_property("client", get_peer_info_client) + .add_property("connection_type", make_getter(&peer_info::connection_type, by_value())) + .def_readonly("pending_disk_bytes", &peer_info::pending_disk_bytes) + .def_readonly("send_quota", &peer_info::send_quota) + .def_readonly("receive_quota", &peer_info::receive_quota) + .def_readonly("rtt", &peer_info::rtt) + .def_readonly("num_pieces", &peer_info::num_pieces) + .def_readonly("download_rate_peak", &peer_info::download_rate_peak) + .def_readonly("upload_rate_peak", &peer_info::upload_rate_peak) + .def_readonly("progress", &peer_info::progress) + .def_readonly("progress_ppm", &peer_info::progress_ppm) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("estimated_reciprocation_rate", &peer_info::estimated_reciprocation_rate) +#endif + .add_property("local_endpoint", get_local_endpoint) + ; + + // flags + pi.attr("interesting") = peer_info::interesting; + pi.attr("choked") = peer_info::choked; + pi.attr("remote_interested") = peer_info::remote_interested; + pi.attr("remote_choked") = peer_info::remote_choked; + pi.attr("supports_extensions") = peer_info::supports_extensions; + pi.attr("local_connection") = peer_info::local_connection; + pi.attr("outgoing_connection") = peer_info::outgoing_connection; + pi.attr("handshake") = peer_info::handshake; + pi.attr("connecting") = peer_info::connecting; +#if TORRENT_ABI_VERSION == 1 + pi.attr("queued") = peer_info::queued; +#endif + pi.attr("on_parole") = peer_info::on_parole; + pi.attr("seed") = peer_info::seed; + pi.attr("optimistic_unchoke") = peer_info::optimistic_unchoke; + pi.attr("snubbed") = peer_info::snubbed; + pi.attr("upload_only") = peer_info::upload_only; + pi.attr("endgame_mode") = peer_info::endgame_mode; + pi.attr("holepunched") = peer_info::holepunched; +#ifndef TORRENT_DISABLE_ENCRYPTION + pi.attr("rc4_encrypted") = peer_info::rc4_encrypted; + pi.attr("plaintext_encrypted") = peer_info::plaintext_encrypted; +#endif + + // connection_type + pi.attr("standard_bittorrent") = peer_info::standard_bittorrent; + pi.attr("web_seed") = peer_info::web_seed; + pi.attr("http_seed") = peer_info::http_seed; + + // source + pi.attr("tracker") = peer_info::tracker; + pi.attr("dht") = peer_info::dht; + pi.attr("pex") = peer_info::pex; + pi.attr("lsd") = peer_info::lsd; + pi.attr("resume_data") = peer_info::resume_data; + + // read/write state + pi.attr("bw_idle") = peer_info::bw_idle; +#if TORRENT_ABI_VERSION == 1 + pi.attr("bw_torrent") = peer_info::bw_torrent; + pi.attr("bw_global") = peer_info::bw_global; +#endif + pi.attr("bw_limit") = peer_info::bw_limit; + pi.attr("bw_network") = peer_info::bw_network; + pi.attr("bw_disk") = peer_info::bw_disk; +} + diff --git a/bindings/python/src/session.cpp b/bindings/python/src/session.cpp new file mode 100644 index 0000000..f2a43f5 --- /dev/null +++ b/bindings/python/src/session.cpp @@ -0,0 +1,1356 @@ +// Copyright Daniel Wallin, Arvid Norberg 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for sign_mutable_item +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace boost +{ + // this fixes mysterious link error on msvc + template <> + inline lt::alert const volatile* + get_pointer(lt::alert const volatile* p) + { + return p; + } +} + +#include "gil.hpp" +#include "bytes.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +using namespace boost::python; +using namespace lt; + +// defined in torrent_info.cpp +load_torrent_limits dict_to_limits(dict limits); + +namespace +{ +#if TORRENT_ABI_VERSION == 1 + struct dummy {}; + + void listen_on(lt::session& s, int min_, int max_, char const* interface, int flags) + { + allow_threading_guard guard; + error_code ec; + s.listen_on(std::make_pair(min_, max_), ec, interface, flags); +#ifndef BOOST_NO_EXCEPTIONS + if (ec) throw system_error(ec); +#endif + } + + void outgoing_ports(lt::session& s, int _min, int _max) + { + allow_threading_guard guard; + settings_pack p; + p.set_int(settings_pack::outgoing_port, _min); + p.set_int(settings_pack::num_outgoing_ports, _max - _min); + s.apply_settings(p); + return; + } +#endif // TORRENT_ABI_VERSION + +#ifndef TORRENT_DISABLE_DHT + void add_dht_node(lt::session& s, tuple n) + { + std::string ip = extract(n[0]); + int port = extract(n[1]); + allow_threading_guard guard; + s.add_dht_node(std::make_pair(ip, port)); + } + +#if TORRENT_ABI_VERSION == 1 + void add_dht_router(lt::session& s, std::string router_, int port_) + { + allow_threading_guard guard; + return s.add_dht_router(std::make_pair(router_, port_)); + } +#endif + +#endif // TORRENT_DISABLE_DHT + + void add_extension(lt::session& s, object const& e) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + if (!extract(e).check()) return; + + std::string name = extract(e); + if (name == "ut_metadata") + s.add_extension(create_ut_metadata_plugin); + else if (name == "ut_pex") + s.add_extension(create_ut_pex_plugin); + else if (name == "smart_ban") + s.add_extension(create_smart_ban_plugin); + +#endif // TORRENT_DISABLE_EXTENSIONS + } + + void make_settings_pack(lt::settings_pack& p, dict const& sett_dict) + { + stl_input_iterator i(sett_dict.keys()), end; + for (; i != end; ++i) + { + std::string const key = *i; + + int sett = setting_by_name(key); + if (sett < 0) + { + PyErr_SetString(PyExc_KeyError, ("unknown name in settings_pack: " + key).c_str()); + throw_error_already_set(); + } + + TORRENT_TRY + { + // if the dictionary doesn't contain "key", it will throw, hence + // the try-catch here + object const value = sett_dict[key]; + switch (sett & settings_pack::type_mask) + { + case settings_pack::string_type_base: + p.set_str(sett, extract(value)); + break; + case settings_pack::int_type_base: + { + std::int64_t const val = extract(value); + // deliberately truncate and sign-convert here. If we + // extract an int directly, unsigned ints may throw + // an exception otherwise, if it doesn't fit. Notably for a + // flag-type with all bits set. + p.set_int(sett, static_cast(val)); + break; + } + case settings_pack::bool_type_base: + p.set_bool(sett, extract(value)); + break; + } + } + TORRENT_CATCH(...) {} + } + } + + dict make_dict(lt::settings_pack const& sett) + { + dict ret; + for (int i = settings_pack::string_type_base; + i < settings_pack::max_string_setting_internal; ++i) + { + // deprecated settings are still here, they just have empty names + char const* name = name_for_setting(i); + if (name[0] != '\0' && sett.has_val(i)) ret[name] = sett.get_str(i); + } + + for (int i = settings_pack::int_type_base; + i < settings_pack::max_int_setting_internal; ++i) + { + char const* name = name_for_setting(i); + if (name[0] != '\0' && sett.has_val(i)) ret[name] = sett.get_int(i); + } + + for (int i = settings_pack::bool_type_base; + i < settings_pack::max_bool_setting_internal; ++i) + { + char const* name = name_for_setting(i); + if (name[0] != '\0' && sett.has_val(i)) ret[name] = sett.get_bool(i); + } + return ret; + } + + std::shared_ptr make_session(boost::python::dict sett + , session_flags_t const flags) + { + settings_pack p; + make_settings_pack(p, sett); +#if TORRENT_ABI_VERSION <= 2 + if (flags & lt::session::add_default_plugins) + { + // TODO: this can't really be removed until there is a way to + // control plugins by exposing session_params. + // The simplest solution would probably be to make the default + // plugins not be plugins, but just bake in support for them +// python_deprecated("add_default_plugins flag is deprecated"); +#endif + session_params params(std::move(p)); + return std::make_shared(std::move(params), flags); +#if TORRENT_ABI_VERSION <= 2 + } + else + { + session_params params(std::move(p), {}); + return std::make_shared(std::move(params), flags); + } +#endif + } + + void session_apply_settings(lt::session& ses, dict const& sett_dict) + { + settings_pack p; + make_settings_pack(p, sett_dict); + allow_threading_guard guard; + ses.apply_settings(p); + } + + dict session_get_settings(lt::session const& ses) + { + settings_pack sett; + { + allow_threading_guard guard; + sett = ses.get_settings(); + } + return make_dict(sett); + } + + dict min_memory_usage_wrapper() + { + settings_pack ret = min_memory_usage(); + return make_dict(ret); + } + + dict default_settings_wrapper() + { + return make_dict(default_settings()); + } + + dict high_performance_seed_wrapper() + { + settings_pack ret = high_performance_seed(); + return make_dict(ret); + } + +#ifndef BOOST_NO_EXCEPTIONS +#if TORRENT_ABI_VERSION == 1 + torrent_handle add_torrent_depr(lt::session& s, torrent_info const& ti + , std::string const& save, entry const& resume + , storage_mode_t storage_mode, bool paused) + { + allow_threading_guard guard; + return s.add_torrent(ti, save, resume, storage_mode, paused); + } +#endif +#endif +} + + void dict_to_add_torrent_params(dict params, add_torrent_params& p) + { + list items = params.items(); + int const len = int(boost::python::len(items)); + for (int i = 0; i < len; i++) + { + boost::python::api::object_item item = items[i]; + std::string const key = extract(item[0]); + object const value = item[1]; + // torrent_info objects are always held by a shared_ptr in the + // python binding, skip it if it is a object + if (key == "ti" && value != boost::python::object()) + { + // make a copy here. We don't want to end up holding a python-owned + // object inside libtorrent. If the last reference goes out of scope + // on the C++ side, it will end up freeing the python object + // without holding the GIL and likely crash. + // https://mail.python.org/pipermail/cplusplus-sig/2007-June/012130.html + p.ti = std::make_shared( + extract(value)); + continue; + } +#if TORRENT_ABI_VERSION < 3 + else if (key == "info_hash") + { + if (boost::python::len(value) == sha1_hash::size()) + { + p.info_hash = sha1_hash(bytes(extract(value)).arr.data()); + } + } +#endif + else if (key == "info_hashes") + { + if (boost::python::len(value) == sha1_hash::size()) + { + p.info_hashes = info_hash_t(sha1_hash( + bytes(extract(value)).arr.data())); + } + else if (boost::python::len(value) == sha256_hash::size()) + { + p.info_hashes = info_hash_t(sha256_hash( + bytes(extract(value)).arr.data())); + } + else + { + p.info_hashes = boost::python::extract(value); + } + continue; + } + else if(key == "name") + { + p.name = extract(value); + continue; + } + else if(key == "save_path") + { + p.save_path = extract(value); + continue; + } +#if TORRENT_ABI_VERSION == 1 + else if(key == "resume_data") + { + python_deprecated("the resume_data member is deprecated"); + std::string resume = extract(value); + p.resume_data.assign(resume.begin(), resume.end()); + continue; + } +#endif + else if(key == "storage_mode") + { + p.storage_mode = extract(value); + continue; + } + else if(key == "trackers") + { + p.trackers = extract>(value); + continue; + } + else if(key == "url_seeds") + { + p.url_seeds = extract>(value); + continue; + } + else if(key == "http_seeds") + { + p.http_seeds = + extract(value); + continue; + } + else if(key == "dht_nodes") + { + p.dht_nodes = + extract>>(value); + continue; + } + else if(key == "banned_peers") + { + p.banned_peers = extract>(value); + continue; + } + else if(key == "peers") + { + p.peers = extract>(value); + continue; + } + else if(key == "flags") + { + p.flags = extract(value); + continue; + } + else if(key == "trackerid") + { + p.trackerid = extract(value); + continue; + } +#if TORRENT_ABI_VERSION == 1 + else if(key == "url") + { + python_deprecated("the url member is deprecated"); + p.url = extract(value); + continue; + } +#endif + else if(key == "renamed_files") + { + p.renamed_files = + extract>(value); + } + else if(key == "file_priorities") + { + p.file_priorities = extract>(value); + } + else + { + PyErr_SetString(PyExc_KeyError, + ("unknown name in torrent params: " + key).c_str()); + throw_error_already_set(); + } + } + } + +namespace +{ + + torrent_handle add_torrent(lt::session& s, dict params) + { + add_torrent_params p; + dict_to_add_torrent_params(params, p); + + allow_threading_guard guard; + +#ifndef BOOST_NO_EXCEPTIONS + return s.add_torrent(std::move(p)); +#else + error_code ec; + return s.add_torrent(std::move(p), ec); +#endif + } + + void async_add_torrent(lt::session& s, dict params) + { + add_torrent_params p; + dict_to_add_torrent_params(params, p); + + allow_threading_guard guard; + + s.async_add_torrent(std::move(p)); + } + + torrent_handle wrap_add_torrent(lt::session& s, lt::add_torrent_params const& p) + { + add_torrent_params atp = p; + if (p.ti) + atp.ti = std::make_shared(*p.ti); + + allow_threading_guard guard; + +#ifndef BOOST_NO_EXCEPTIONS + return s.add_torrent(std::move(p)); +#else + error_code ec; + return s.add_torrent(std::move(p), ec); +#endif + } + + void wrap_async_add_torrent(lt::session& s, lt::add_torrent_params const& p) + { + add_torrent_params atp = p; + if (p.ti) + atp.ti = std::make_shared(*p.ti); + + allow_threading_guard guard; + + s.async_add_torrent(std::move(p)); + } + +#if TORRENT_ABI_VERSION == 1 + void start_natpmp(lt::session& s) + { + allow_threading_guard guard; + s.start_natpmp(); + } + + void start_upnp(lt::session& s) + { + allow_threading_guard guard; + s.start_upnp(); + } +#endif // TORRENT_ABI_VERSION + + void alert_notify(object cb) try + { + lock_gil lock; + if (cb) + { + cb(); + } + } + catch (boost::python::error_already_set const&) + { + // this callback isn't supposed to throw an error. + // just swallow and ignore the exception + TORRENT_ASSERT_FAIL_VAL("python notify callback threw exception"); + } + + void set_alert_notify(lt::session& s, object cb) + { + s.set_alert_notify(std::bind(&alert_notify, cb)); + } + +#ifdef TORRENT_WINDOWS + void alert_socket_notify(SOCKET const fd) + { + std::uint8_t dummy = 0; + ::send(fd, reinterpret_cast(&dummy), 1, 0); + } +#endif + + void alert_fd_notify(int const fd) + { + std::uint8_t dummy = 0; + while (::write(fd, &dummy, 1) < 0 && errno == EINTR); + } + + void set_alert_fd(lt::session& s, std::intptr_t const fd) + { +#ifdef TORRENT_WINDOWS + auto const sock = static_cast(fd); + int res; + int res_size = sizeof(res); + if (sock != INVALID_SOCKET + && ::getsockopt(sock, SOL_SOCKET, SO_ERROR, + (char *)&res, &res_size) == 0) + { + s.set_alert_notify(std::bind(&alert_socket_notify, sock)); + } + else +#endif + { + s.set_alert_notify(std::bind(&alert_fd_notify, fd)); + } + } + + alert const* + wait_for_alert(lt::session& s, int ms) + { + alert const* a; + { + allow_threading_guard guard; + a = s.wait_for_alert(milliseconds(ms)); + } + return a; + } + + list get_torrents(lt::session& s) + { + std::vector torrents; + { + allow_threading_guard guard; + torrents = s.get_torrents(); + } + + list ret; + for (std::vector::iterator i = torrents.begin(); i != torrents.end(); ++i) + ret.append(*i); + return ret; + } + + bool wrap_pred(object pred, torrent_status const& st) + { + return pred(st); + } + + list get_torrent_status(lt::session& s, object pred, int const flags) + { + // keep a reference to the predicate here, in the python thread, to + // ensure it's freed in this thread at the end. If we move it into the + // libtorrent thread the python predicate will be freed from that + // thread, which won't work + auto wrapped_pred = std::bind(&wrap_pred, pred, std::placeholders::_1); + std::vector torrents + = s.get_torrent_status(std::ref(wrapped_pred), status_flags_t(flags)); + + list ret; + for (std::vector::iterator i = torrents.begin(); i != torrents.end(); ++i) + ret.append(*i); + return ret; + } + + list refresh_torrent_status(lt::session& s, list in_torrents, int const flags) + { + std::vector torrents; + int const n = int(boost::python::len(in_torrents)); + for (int i = 0; i < n; ++i) + torrents.push_back(extract(in_torrents[i])); + + { + allow_threading_guard guard; + s.refresh_torrent_status(&torrents, status_flags_t(flags)); + } + + list ret; + for (std::vector::iterator i = torrents.begin(); i != torrents.end(); ++i) + ret.append(*i); + return ret; + } + +#if TORRENT_ABI_VERSION == 1 + dict get_utp_stats(session_status const& st) + { + python_deprecated("session_status is deprecated"); + dict ret; + ret["num_idle"] = st.utp_stats.num_idle; + ret["num_syn_sent"] = st.utp_stats.num_syn_sent; + ret["num_connected"] = st.utp_stats.num_connected; + ret["num_fin_sent"] = st.utp_stats.num_fin_sent; + ret["num_close_wait"] = st.utp_stats.num_close_wait; + return ret; + } +#endif + + entry save_state(lt::session const& s, std::uint32_t const flags) + { + entry e; +#if TORRENT_ABI_VERSION <= 2 + allow_threading_guard guard; + s.save_state(e, save_state_flags_t(flags)); +#endif + return e; + } + + list pop_alerts(lt::session& ses) + { + std::vector alerts; + { + allow_threading_guard guard; + ses.pop_alerts(&alerts); + } + + list ret; + for (alert* a : alerts) + { + ret.append(boost::python::ptr(a)); + } + return ret; + } + + void load_state(lt::session& ses, entry const& st, std::uint32_t const flags) + { +#if TORRENT_ABI_VERSION <= 2 + allow_threading_guard guard; + + std::vector buf; + bencode(std::back_inserter(buf), st); + bdecode_node e; + error_code ec; + bdecode(&buf[0], &buf[0] + buf.size(), e, ec); + TORRENT_ASSERT(!ec); + ses.load_state(e, save_state_flags_t(flags)); +#endif + } + + dict get_peer_class(lt::session& ses, lt::peer_class_t const pc) + { + lt::peer_class_info pci; + { + allow_threading_guard guard; + pci = ses.get_peer_class(pc); + } + dict ret; + ret["ignore_unchoke_slots"] = pci.ignore_unchoke_slots; + ret["connection_limit_factor"] = pci.connection_limit_factor; + ret["label"] = pci.label; + ret["upload_limit"] = pci.upload_limit; + ret["download_limit"] = pci.download_limit; + ret["upload_priority"] = pci.upload_priority; + ret["download_priority"] = pci.download_priority; + return ret; + } + + void set_peer_class(lt::session& ses, peer_class_t const pc, dict info) + { + lt::peer_class_info pci; + stl_input_iterator i(info.keys()), end; + for (; i != end; ++i) + { + std::string const key = *i; + + object const value = info[key]; + if (key == "ignore_unchoke_slots") + { + pci.ignore_unchoke_slots = extract(value); + } + else if (key == "connection_limit_factor") + { + pci.connection_limit_factor = extract(value); + } + else if (key == "label") + { + pci.label = extract(value); + } + else if (key == "upload_limit") + { + pci.upload_limit = extract(value); + } + else if (key == "download_limit") + { + pci.download_limit = extract(value); + } + else if (key == "upload_priority") + { + pci.upload_priority = extract(value); + } + else if (key == "download_priority") + { + pci.download_priority = extract(value); + } + else + { + PyErr_SetString(PyExc_KeyError, ("unknown name in peer_class_info: " + key).c_str()); + throw_error_already_set(); + } + } + + allow_threading_guard guard; + ses.set_peer_class(pc, pci); + } + +#ifndef TORRENT_DISABLE_DHT + void dht_get_mutable_item(lt::session& ses, std::string key, std::string salt) + { + TORRENT_ASSERT(key.size() == 32); + std::array public_key; + std::copy(key.begin(), key.end(), public_key.begin()); + ses.dht_get_item(public_key, salt); + } + + void put_string(entry& e, std::array& sig, std::int64_t& seq + , std::string const& salt, std::string pk, std::string sk + , std::string data) + { + using lt::dht::sign_mutable_item; + + e = data; + std::vector buf; + bencode(std::back_inserter(buf), e); + ++seq; + dht::signature sign = sign_mutable_item(buf, salt + , dht::sequence_number(seq) + , dht::public_key(pk.data()) + , dht::secret_key(sk.data())); + sig = sign.bytes; + } + + void dht_put_mutable_item(lt::session& ses, std::string private_key, std::string public_key, + std::string data, std::string salt) + { + TORRENT_ASSERT(private_key.size() == 64); + TORRENT_ASSERT(public_key.size() == 32); + std::array key; + std::copy(public_key.begin(), public_key.end(), key.begin()); + ses.dht_put_item(key + , [pk=std::move(public_key), sk=std::move(private_key), d=std::move(data)] + (entry& e, std::array& sig, std::int64_t& seq, std::string const& salt) + { + put_string(e, sig, seq, salt, pk, sk, d); + } + , salt); + } +#endif + + add_torrent_params read_resume_data_wrapper0(bytes const& b) + { + return read_resume_data(b.arr); + } + + add_torrent_params read_resume_data_wrapper1(bytes const& b, dict cfg) + { + return read_resume_data(b.arr, dict_to_limits(cfg)); + } + + int find_metric_idx_wrap(char const* name) + { + return lt::find_metric_idx(name); + } + + bytes write_resume_data_buf_(add_torrent_params const& atp) + { + bytes ret; + auto buf = write_resume_data_buf(atp); + ret.arr.resize(buf.size()); + std::copy(buf.begin(), buf.end(), ret.arr.begin()); + return ret; + } + + session_params read_session_params_entry(dict e + , save_state_flags_t flags) + { + entry ent = extract(e); + std::vector buf; + bencode(std::back_inserter(buf), ent); + return lt::read_session_params(buf, flags); + } + + session_params read_session_params_buffer(bytes const& bytes + , save_state_flags_t flags) + { + return lt::read_session_params(bytes.arr, flags); + } + + bytes write_session_params_bytes(session_params const& sp + , save_state_flags_t flags) + { + auto buf = write_session_params_buf(sp, flags); + return bytes(buf.data(), buf.size()); + } + + struct dict_to_settings + { + dict_to_settings() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyDict_Check(x) ? x: nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + + dict o(borrowed(x)); + auto p = new (storage) lt::settings_pack; + data->convertible = p; + make_settings_pack(*p, o); + } + }; + + struct settings_to_dict + { + static PyObject* convert(lt::settings_pack const& p) + { + dict ret = make_dict(p); + return incref(ret.ptr()); + } + }; + +} // anonymous namespace + +struct dummy1 {}; +#if TORRENT_ABI_VERSION == 1 +struct dummy2 {}; +#endif +struct dummy9 {}; +struct dummy10 {}; +struct dummy11 {}; +struct dummy17 {}; + +void bind_session() +{ + dict_to_settings(); + to_python_converter(); + +#ifndef TORRENT_DISABLE_DHT + void (lt::session::*dht_get_immutable_item)(sha1_hash const&) = <::session::dht_get_item; + sha1_hash (lt::session::*dht_put_immutable_item)(entry data) = <::session::dht_put_item; +#endif // TORRENT_DISABLE_DHT + +#if TORRENT_ABI_VERSION == 1 +#ifndef TORRENT_DISABLE_DHT + void (lt::session::*start_dht0)() = <::session::start_dht; + void (lt::session::*start_dht1)(entry const&) = <::session::start_dht; +#endif + + class_("session_status") + .def_readonly("has_incoming_connections", &session_status::has_incoming_connections) + + .def_readonly("upload_rate", &session_status::upload_rate) + .def_readonly("download_rate", &session_status::download_rate) + .def_readonly("total_download", &session_status::total_download) + .def_readonly("total_upload", &session_status::total_upload) + + .def_readonly("payload_upload_rate", &session_status::payload_upload_rate) + .def_readonly("payload_download_rate", &session_status::payload_download_rate) + .def_readonly("total_payload_download", &session_status::total_payload_download) + .def_readonly("total_payload_upload", &session_status::total_payload_upload) + + .def_readonly("ip_overhead_upload_rate", &session_status::ip_overhead_upload_rate) + .def_readonly("ip_overhead_download_rate", &session_status::ip_overhead_download_rate) + .def_readonly("total_ip_overhead_download", &session_status::total_ip_overhead_download) + .def_readonly("total_ip_overhead_upload", &session_status::total_ip_overhead_upload) + + .def_readonly("dht_upload_rate", &session_status::dht_upload_rate) + .def_readonly("dht_download_rate", &session_status::dht_download_rate) + .def_readonly("total_dht_download", &session_status::total_dht_download) + .def_readonly("total_dht_upload", &session_status::total_dht_upload) + + .def_readonly("tracker_upload_rate", &session_status::tracker_upload_rate) + .def_readonly("tracker_download_rate", &session_status::tracker_download_rate) + .def_readonly("total_tracker_download", &session_status::total_tracker_download) + .def_readonly("total_tracker_upload", &session_status::total_tracker_upload) + + .def_readonly("total_redundant_bytes", &session_status::total_redundant_bytes) + .def_readonly("total_failed_bytes", &session_status::total_failed_bytes) + + .def_readonly("num_peers", &session_status::num_peers) + .def_readonly("num_unchoked", &session_status::num_unchoked) + .def_readonly("allowed_upload_slots", &session_status::allowed_upload_slots) + + .def_readonly("up_bandwidth_queue", &session_status::up_bandwidth_queue) + .def_readonly("down_bandwidth_queue", &session_status::down_bandwidth_queue) + + .def_readonly("up_bandwidth_bytes_queue", &session_status::up_bandwidth_bytes_queue) + .def_readonly("down_bandwidth_bytes_queue", &session_status::down_bandwidth_bytes_queue) + + .def_readonly("optimistic_unchoke_counter", &session_status::optimistic_unchoke_counter) + .def_readonly("unchoke_counter", &session_status::unchoke_counter) + +#ifndef TORRENT_DISABLE_DHT + .def_readonly("dht_nodes", &session_status::dht_nodes) + .def_readonly("dht_node_cache", &session_status::dht_node_cache) + .def_readonly("dht_torrents", &session_status::dht_torrents) + .def_readonly("dht_global_nodes", &session_status::dht_global_nodes) + .add_property("active_requests", make_getter(&session_status::active_requests, return_value_policy())) + .def_readonly("dht_total_allocations", &session_status::dht_total_allocations) +#endif // TORRENT_DISABLE_DHT + .add_property("utp_stats", &get_utp_stats) + ; + +#ifndef TORRENT_DISABLE_DHT + class_("dht_lookup") + .def_readonly("type", &dht_lookup::type) + .def_readonly("outstanding_requests", &dht_lookup::outstanding_requests) + .def_readonly("timeouts", &dht_lookup::timeouts) + .def_readonly("response", &dht_lookup::responses) + .def_readonly("branch_factor", &dht_lookup::branch_factor) + ; +#endif // TORRENT_DISABLE_DHT +#endif // TORRENT_ABI_VERSION + +#define PROP(val) \ + make_getter(val, return_value_policy()), \ + make_setter(val, return_value_policy()) + + class_("add_torrent_params") + .def_readwrite("version", &add_torrent_params::version) + .def_readwrite("ti", &add_torrent_params::ti) + .add_property("trackers", PROP(&add_torrent_params::trackers)) + .add_property("tracker_tiers", PROP(&add_torrent_params::tracker_tiers)) + .add_property("dht_nodes", PROP(&add_torrent_params::dht_nodes)) + .def_readwrite("name", &add_torrent_params::name) + .def_readwrite("save_path", &add_torrent_params::save_path) + .def_readwrite("storage_mode", &add_torrent_params::storage_mode) +// .def_readwrite("storage", &add_torrent_params::storage) + .add_property("file_priorities", PROP(&add_torrent_params::file_priorities)) + .def_readwrite("trackerid", &add_torrent_params::trackerid) + .add_property("flags", PROP(&add_torrent_params::flags)) + .def_readwrite("max_uploads", &add_torrent_params::max_uploads) + .def_readwrite("max_connections", &add_torrent_params::max_connections) + .def_readwrite("upload_limit", &add_torrent_params::upload_limit) + .def_readwrite("download_limit", &add_torrent_params::download_limit) + .def_readwrite("total_uploaded", &add_torrent_params::total_uploaded) + .def_readwrite("total_downloaded", &add_torrent_params::total_downloaded) + .def_readwrite("active_time", &add_torrent_params::active_time) + .def_readwrite("finished_time", &add_torrent_params::finished_time) + .def_readwrite("seeding_time", &add_torrent_params::seeding_time) + .def_readwrite("added_time", &add_torrent_params::added_time) + .def_readwrite("completed_time", &add_torrent_params::completed_time) + .def_readwrite("last_seen_complete", &add_torrent_params::last_seen_complete) + .def_readwrite("last_download", &add_torrent_params::last_download) + .def_readwrite("last_upload", &add_torrent_params::last_upload) + .def_readwrite("num_complete", &add_torrent_params::num_complete) + .def_readwrite("num_incomplete", &add_torrent_params::num_incomplete) + .def_readwrite("num_downloaded", &add_torrent_params::num_downloaded) +#if TORRENT_ABI_VERSION < 3 + .def_readwrite("info_hash", &add_torrent_params::info_hash) +#endif + .def_readwrite("info_hashes", &add_torrent_params::info_hashes) + .add_property("http_seeds", PROP(&add_torrent_params::http_seeds)) + .add_property("url_seeds", PROP(&add_torrent_params::url_seeds)) + .add_property("peers", PROP(&add_torrent_params::peers)) + .add_property("banned_peers", PROP(&add_torrent_params::banned_peers)) + .add_property("unfinished_pieces", PROP(&add_torrent_params::unfinished_pieces)) + .add_property("have_pieces", PROP(&add_torrent_params::have_pieces)) + .add_property("verified_pieces", PROP(&add_torrent_params::verified_pieces)) + .add_property("piece_priorities", PROP(&add_torrent_params::piece_priorities)) +#if TORRENT_ABI_VERSION <= 2 + .add_property("merkle_tree", PROP(&add_torrent_params::merkle_tree)) +#endif + .add_property("renamed_files", PROP(&add_torrent_params::renamed_files)) + +#if TORRENT_ABI_VERSION == 1 + .def_readwrite("url", &add_torrent_params::url) + .add_property("resume_data", PROP(&add_torrent_params::resume_data)) +#endif + ; + +#ifndef TORRENT_DISABLE_DHT + class_("dht_state") + .add_property("nids", <::dht::dht_state::nids) + .add_property("nodes", <::dht::dht_state::nodes) + .add_property("nodes6", <::dht::dht_state::nodes6) + ; +#endif + + class_("session_params") + .def(init()) + .def(init<>()) + // TODO: since there's not binding for settings_pack, but they are just + // represented as dicts, this won't return a reference, but a copy of + // the settings + .add_property("settings", PROP(&session_params::settings)) +#ifndef TORRENT_DISABLE_DHT + .def_readwrite("dht_state", &session_params::dht_state) +#endif + .add_property("ext_state", PROP(&session_params::ext_state)) + .def_readwrite("ip_filter", &session_params::ip_filter) + ; + + def("read_session_params", &read_session_params_entry, (arg("dict"), arg("flags")=save_state_flags_t::all())); + def("read_session_params", &read_session_params_buffer, (arg("buffer"), arg("flags")=save_state_flags_t::all())); + def("write_session_params", <::write_session_params, (arg("entry"), arg("flags")=save_state_flags_t::all())); + def("write_session_params_buf", &write_session_params_bytes, (arg("buffer"), arg("flags")=save_state_flags_t::all())); + + enum_("storage_mode_t") + .value("storage_mode_allocate", storage_mode_allocate) + .value("storage_mode_sparse", storage_mode_sparse) + ; + + { + scope s = class_("options_t"); + s.attr("delete_files") = lt::session::delete_files; + } + + { + scope s = class_("session_flags_t"); + s.attr("paused") = lt::session::paused; +#if TORRENT_ABI_VERSION <= 2 + s.attr("add_default_plugins") = lt::session::add_default_plugins; +#endif +#if TORRENT_ABI_VERSION == 1 + s.attr("start_default_features") = lt::session::start_default_features; +#endif + } + + { + scope s = class_("torrent_flags"); + s.attr("seed_mode") = torrent_flags::seed_mode; + s.attr("upload_mode") = torrent_flags::upload_mode; + s.attr("share_mode") = torrent_flags::share_mode; + s.attr("apply_ip_filter") = torrent_flags::apply_ip_filter; + s.attr("paused") = torrent_flags::paused; + s.attr("auto_managed") = torrent_flags::auto_managed; + s.attr("duplicate_is_error") = torrent_flags::duplicate_is_error; + s.attr("update_subscribe") = torrent_flags::update_subscribe; + s.attr("super_seeding") = torrent_flags::super_seeding; + s.attr("sequential_download") = torrent_flags::sequential_download; + s.attr("stop_when_ready") = torrent_flags::stop_when_ready; + s.attr("override_trackers") = torrent_flags::override_trackers; + s.attr("override_web_seeds") = torrent_flags::override_web_seeds; + s.attr("disable_dht") = torrent_flags::disable_dht; + s.attr("disable_lsd") = torrent_flags::disable_lsd; + s.attr("disable_pex") = torrent_flags::disable_pex; + s.attr("no_verify_files") = torrent_flags::no_verify_files; + s.attr("default_flags") = torrent_flags::default_flags; + } + +#if TORRENT_ABI_VERSION == 1 + { + scope s = class_("add_torrent_params_flags_t"); + s.attr("flag_seed_mode") = add_torrent_params::flag_seed_mode; + s.attr("flag_upload_mode") = add_torrent_params::flag_upload_mode; + s.attr("flag_share_mode") = add_torrent_params::flag_share_mode; + s.attr("flag_apply_ip_filter") = add_torrent_params::flag_apply_ip_filter; + s.attr("flag_paused") = add_torrent_params::flag_paused; + s.attr("flag_auto_managed") = add_torrent_params::flag_auto_managed; + s.attr("flag_duplicate_is_error") = add_torrent_params::flag_duplicate_is_error; + s.attr("flag_update_subscribe") = add_torrent_params::flag_update_subscribe; + s.attr("flag_super_seeding") = add_torrent_params::flag_super_seeding; + s.attr("flag_sequential_download") = add_torrent_params::flag_sequential_download; + s.attr("flag_stop_when_ready") = add_torrent_params::flag_stop_when_ready; + s.attr("flag_override_trackers") = add_torrent_params::flag_override_trackers; + s.attr("flag_override_web_seeds") = add_torrent_params::flag_override_web_seeds; + s.attr("flag_pinned") = add_torrent_params::flag_pinned; + s.attr("flag_override_resume_data") = add_torrent_params::flag_override_resume_data; + s.attr("flag_merge_resume_trackers") = add_torrent_params::flag_merge_resume_trackers; + s.attr("flag_use_resume_save_path") = add_torrent_params::flag_use_resume_save_path; + s.attr("flag_merge_resume_http_seeds") = add_torrent_params::flag_merge_resume_http_seeds; + s.attr("default_flags") = add_torrent_params::flag_default_flags; + } +#endif + ; + + enum_("portmap_protocol") + .value("none", lt::portmap_protocol::none) + .value("udp", lt::portmap_protocol::udp) + .value("tcp", lt::portmap_protocol::tcp) + ; + + enum_("portmap_transport") + .value("natpmp", lt::portmap_transport::natpmp) + .value("upnp", lt::portmap_transport::upnp) + ; + + enum_("peer_class_type_filter_socket_type_t") + .value("tcp_socket", peer_class_type_filter::tcp_socket) + .value("utp_socket", peer_class_type_filter::utp_socket) + .value("ssl_tcp_socket", peer_class_type_filter::ssl_tcp_socket) + .value("ssl_utp_socket", peer_class_type_filter::ssl_utp_socket) + .value("i2p_socket", peer_class_type_filter::i2p_socket) + ; + + { + scope s = class_("peer_class_type_filter") + .def(init<>()) + .def("add", <::peer_class_type_filter::add) + .def("remove", <::peer_class_type_filter::remove) + .def("disallow", <::peer_class_type_filter::disallow) + .def("allow", <::peer_class_type_filter::allow) + .def("apply", <::peer_class_type_filter::apply) + ; + s.attr("tcp_socket") = peer_class_type_filter::tcp_socket; + s.attr("utp_socket") = peer_class_type_filter::utp_socket; + s.attr("ssl_tcp_socket") = peer_class_type_filter::ssl_tcp_socket; + s.attr("ssl_utp_socket") = peer_class_type_filter::ssl_utp_socket; + s.attr("i2p_socket") = peer_class_type_filter::i2p_socket; + } + + { + scope s = class_("session", no_init) + .def(init()) + .def(init<>()) + .def("__init__", boost::python::make_constructor(&make_session + , default_call_policies() + , (arg("settings"), arg("flags")= +#if TORRENT_ABI_VERSION <= 2 + lt::session::add_default_plugins +#else + lt::session_flags_t{} +#endif + )) + ) +#if TORRENT_ABI_VERSION == 1 + .def( + init(( + arg("fingerprint")=fingerprint("LT",0,1,0,0) + , arg("flags")=lt::session::start_default_features | lt::session::add_default_plugins + , arg("alert_mask")=alert::error_notification)) + ) + .def("outgoing_ports", depr(&outgoing_ports)) +#endif + .def("post_torrent_updates", allow_threads(<::session::post_torrent_updates), arg("flags") = 0xffffffff) + .def("post_dht_stats", allow_threads(<::session::post_dht_stats)) + .def("post_session_stats", allow_threads(<::session::post_session_stats)) + .def("is_listening", allow_threads(<::session::is_listening)) + .def("listen_port", allow_threads(<::session::listen_port)) +#ifndef TORRENT_DISABLE_DHT + .def("add_dht_node", &add_dht_node) +#if TORRENT_ABI_VERSION == 1 + .def( + "add_dht_router", depr(&add_dht_router) + , (arg("router"), "port") + ) +#endif // TORRENT_ABI_VERSION + .def("is_dht_running", allow_threads(<::session::is_dht_running)) +#if TORRENT_ABI_VERSION <= 2 + .def("set_dht_settings", allow_threads(<::session::set_dht_settings)) + .def("get_dht_settings", allow_threads(<::session::get_dht_settings)) +#endif + .def("dht_get_immutable_item", allow_threads(dht_get_immutable_item)) + .def("dht_get_mutable_item", &dht_get_mutable_item) + .def("dht_put_immutable_item", allow_threads(dht_put_immutable_item)) + .def("dht_put_mutable_item", &dht_put_mutable_item) + .def("dht_get_peers", allow_threads(<::session::dht_get_peers)) + .def("dht_announce", allow_threads(<::session::dht_announce)) + .def("dht_live_nodes", allow_threads(<::session::dht_live_nodes)) + .def("dht_sample_infohashes", allow_threads(<::session::dht_sample_infohashes)) +#endif // TORRENT_DISABLE_DHT + .def("add_torrent", &add_torrent) + .def("async_add_torrent", &async_add_torrent) + .def("async_add_torrent", &wrap_async_add_torrent) + .def("add_torrent", &wrap_add_torrent) +#ifndef BOOST_NO_EXCEPTIONS +#if TORRENT_ABI_VERSION == 1 + .def( + "add_torrent", depr(&add_torrent_depr) + , ( + arg("resume_data") = entry(), + arg("storage_mode") = storage_mode_sparse, + arg("paused") = false + ) + ) +#endif // TORRENT_ABI_VERSION +#endif // BOOST_NO_EXCEPTIONS + .def("remove_torrent", allow_threads(<::session::remove_torrent), arg("option") = 0) +#if TORRENT_ABI_VERSION == 1 + .def("status", depr(<::session::status)) +#endif + .def("get_settings", &session_get_settings) + .def("apply_settings", &session_apply_settings) +#if TORRENT_ABI_VERSION == 1 +#ifndef TORRENT_DISABLE_ENCRYPTION + .def("set_pe_settings", depr(<::session::set_pe_settings)) + .def("get_pe_settings", depr(<::session::get_pe_settings)) +#endif +#endif + .def("load_state", &load_state, (arg("entry"), arg("flags") = 0xffffffff)) + .def("save_state", &save_state, (arg("entry"), arg("flags") = 0xffffffff)) + .def("pop_alerts", &pop_alerts) + .def("wait_for_alert", &wait_for_alert, return_internal_reference<>()) + .def("set_alert_notify", &set_alert_notify) + .def("set_alert_fd", &set_alert_fd) + .def("add_extension", &add_extension) +#if TORRENT_ABI_VERSION == 1 +#if TORRENT_USE_I2P + .def("set_i2p_proxy", depr(<::session::set_i2p_proxy)) + .def("i2p_proxy", depr(<::session::i2p_proxy)) +#endif +#endif + .def("set_ip_filter", allow_threads(<::session::set_ip_filter)) + .def("get_ip_filter", allow_threads(<::session::get_ip_filter)) + .def("find_torrent", allow_threads(<::session::find_torrent)) + .def("get_torrents", &get_torrents) + .def("get_torrent_status", &get_torrent_status, (arg("session"), arg("pred"), arg("flags") = 0)) + .def("refresh_torrent_status", &refresh_torrent_status, (arg("session"), arg("torrents"), arg("flags") = 0)) + .def("pause", allow_threads(<::session::pause)) + .def("resume", allow_threads(<::session::resume)) + .def("is_paused", allow_threads(<::session::is_paused)) + .def("add_port_mapping", allow_threads(<::session::add_port_mapping)) + .def("delete_port_mapping", allow_threads(<::session::delete_port_mapping)) + .def("reopen_network_sockets", allow_threads(<::session::reopen_network_sockets)) + .def("set_peer_class_filter", <::session::set_peer_class_filter) + .def("set_peer_class_type_filter", <::session::set_peer_class_type_filter) + .def("create_peer_class", <::session::create_peer_class) + .def("delete_peer_class", <::session::delete_peer_class) + .def("get_peer_class", &get_peer_class) + .def("set_peer_class", &set_peer_class) + +#if TORRENT_ABI_VERSION == 1 + .def("id", depr(<::session::id)) + .def( + "listen_on", depr(&listen_on) + , (arg("min"), "max", arg("interface") = (char const*)nullptr, arg("flags") = 0) + ) +#ifndef TORRENT_DISABLE_DHT + .def("start_dht", depr(start_dht0)) + .def("stop_dht", depr(<::session::stop_dht)) + .def("start_dht", depr(start_dht1)) + .def("dht_state", depr(<::session::dht_state)) + .def("set_dht_proxy", depr(<::session::set_dht_proxy)) + .def("dht_proxy", depr(<::session::dht_proxy)) +#endif + .def("set_local_download_rate_limit", depr(<::session::set_local_download_rate_limit)) + .def("local_download_rate_limit", depr(<::session::local_download_rate_limit)) + .def("set_local_upload_rate_limit", depr(<::session::set_local_upload_rate_limit)) + .def("local_upload_rate_limit", depr(<::session::local_upload_rate_limit)) + .def("set_download_rate_limit", depr(<::session::set_download_rate_limit)) + .def("download_rate_limit", depr(<::session::download_rate_limit)) + .def("set_upload_rate_limit", depr(<::session::set_upload_rate_limit)) + .def("upload_rate_limit", depr(<::session::upload_rate_limit)) + .def("set_max_uploads", depr(<::session::set_max_uploads)) + .def("set_max_connections", depr(<::session::set_max_connections)) + .def("max_connections", depr(<::session::max_connections)) + .def("num_connections", depr(<::session::num_connections)) + .def("set_max_half_open_connections", depr(<::session::set_max_half_open_connections)) + .def("set_alert_queue_size_limit", depr(<::session::set_alert_queue_size_limit)) + .def("set_alert_mask", depr(<::session::set_alert_mask)) + .def("set_peer_proxy", depr(<::session::set_peer_proxy)) + .def("set_tracker_proxy", depr(<::session::set_tracker_proxy)) + .def("set_web_seed_proxy", depr(<::session::set_web_seed_proxy)) + .def("peer_proxy", depr(<::session::peer_proxy)) + .def("tracker_proxy", depr(<::session::tracker_proxy)) + .def("web_seed_proxy", depr(<::session::web_seed_proxy)) + .def("set_proxy", depr(<::session::set_proxy)) + .def("proxy", depr(<::session::proxy)) + .def("start_upnp", depr(&start_upnp)) + .def("stop_upnp", depr(<::session::stop_upnp)) + .def("start_lsd", depr(<::session::start_lsd)) + .def("stop_lsd", depr(<::session::stop_lsd)) + .def("start_natpmp", depr(&start_natpmp)) + .def("stop_natpmp", depr(<::session::stop_natpmp)) + .def("set_peer_id", depr(<::session::set_peer_id)) +#endif // TORRENT_ABI_VERSION + ; + + s.attr("tcp") = lt::portmap_protocol::tcp; + s.attr("udp") = lt::portmap_protocol::udp; + + s.attr("global_peer_class_id") = session::global_peer_class_id; + s.attr("tcp_peer_class_id") = session::tcp_peer_class_id; + s.attr("local_peer_class_id") = session::local_peer_class_id; + + s.attr("reopen_map_ports") = lt::session::reopen_map_ports; + + s.attr("delete_files") = lt::session::delete_files; + s.attr("delete_partfile") = lt::session::delete_partfile; + } + +#if TORRENT_ABI_VERSION == 1 + { + scope s = class_("protocol_type"); + s.attr("udp") = lt::portmap_protocol::udp; + s.attr("tcp") = lt::portmap_protocol::tcp; + } +#endif + + { + scope s = class_("save_state_flags_t"); + s.attr("save_settings") = lt::session::save_settings; +#if TORRENT_ABI_VERSION <= 2 + s.attr("save_dht_settings") = lt::session::save_dht_settings; +#endif + s.attr("save_dht_state") = lt::session::save_dht_state; +#if TORRENT_ABI_VERSION == 1 + s.attr("save_encryption_settings") = lt::session:: save_encryption_settings; + s.attr("save_as_map") = lt::session::save_as_map; + s.attr("save_i2p_proxy") = lt::session::save_i2p_proxy; + s.attr("save_proxy") = lt::session::save_proxy; + s.attr("save_dht_proxy") = lt::session::save_dht_proxy; + s.attr("save_peer_proxy") = lt::session::save_peer_proxy; + s.attr("save_web_proxy") = lt::session::save_web_proxy; + s.attr("save_tracker_proxy") = lt::session::save_tracker_proxy; +#endif + } + +#if TORRENT_ABI_VERSION == 1 + enum_("listen_on_flags_t") + .value("listen_reuse_address", lt::session::listen_reuse_address) + .value("listen_no_system_port", lt::session::listen_no_system_port) + ; +#endif + + def("high_performance_seed", high_performance_seed_wrapper); + def("min_memory_usage", min_memory_usage_wrapper); + def("default_settings", default_settings_wrapper); + def("read_resume_data", read_resume_data_wrapper0); + def("read_resume_data", read_resume_data_wrapper1); + def("write_resume_data", write_resume_data); + def("write_resume_data_buf", write_resume_data_buf_); + + entry (*write_torrent_file0)(add_torrent_params const&, write_torrent_flags_t) = &write_torrent_file; + def("write_torrent_file", write_torrent_file0, (arg("atp"), arg("flags") = 0)); + + { + scope s = class_("write_flags"); + s.attr("allow_missing_piece_layer") = lt::write_flags::allow_missing_piece_layer; + s.attr("no_http_seeds") = lt::write_flags::no_http_seeds; + s.attr("include_dht_nodes") = lt::write_flags::include_dht_nodes; + } + + class_("stats_metric") + .def_readonly("name", &stats_metric::name) + .def_readonly("value_index", &stats_metric::value_index) + .def_readonly("type", &stats_metric::type) + ; + + enum_("metric_type_t") + .value("counter", metric_type_t::counter) + .value("gauge", metric_type_t::gauge) + ; + + def("session_stats_metrics", session_stats_metrics); + def("find_metric_idx", find_metric_idx_wrap); + + scope().attr("create_ut_metadata_plugin") = "ut_metadata"; + scope().attr("create_ut_pex_plugin") = "ut_pex"; + scope().attr("create_smart_ban_plugin") = "smart_ban"; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/bindings/python/src/session_settings.cpp b/bindings/python/src/session_settings.cpp new file mode 100644 index 0000000..46be488 --- /dev/null +++ b/bindings/python/src/session_settings.cpp @@ -0,0 +1,131 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include + +using namespace boost::python; +using namespace lt; + +void bind_session_settings() +{ + enum_("choking_algorithm_t") + .value("fixed_slots_choker", settings_pack::fixed_slots_choker) +#if TORRENT_ABI_VERSION == 1 + .value("auto_expand_choker", settings_pack::rate_based_choker) +#endif + .value("rate_based_choker", settings_pack::rate_based_choker) +#if TORRENT_ABI_VERSION == 1 + .value("bittyrant_choker", settings_pack::bittyrant_choker) +#endif + ; + + enum_("seed_choking_algorithm_t") + .value("round_robin", settings_pack::round_robin) + .value("fastest_upload", settings_pack::fastest_upload) + .value("anti_leech", settings_pack::anti_leech) + ; + + enum_("suggest_mode_t") + .value("no_piece_suggestions", settings_pack::no_piece_suggestions) + .value("suggest_read_cache", settings_pack::suggest_read_cache) + ; + + enum_("io_buffer_mode_t") + .value("enable_os_cache", settings_pack::enable_os_cache) +#if TORRENT_ABI_VERSION == 1 + .value("disable_os_cache_for_aligned_files", settings_pack::disable_os_cache_for_aligned_files) +#endif + .value("disable_os_cache", settings_pack::disable_os_cache) + .value("write_through", settings_pack::write_through) + ; + + enum_("bandwidth_mixed_algo_t") + .value("prefer_tcp", settings_pack::prefer_tcp) + .value("peer_proportional", settings_pack::peer_proportional) + ; + + enum_("enc_policy") + .value("pe_forced", settings_pack::pe_forced) + .value("pe_enabled", settings_pack::pe_enabled) + .value("pe_disabled", settings_pack::pe_disabled) +#if TORRENT_ABI_VERSION == 1 + .value("forced", settings_pack::pe_forced) + .value("enabled", settings_pack::pe_enabled) + .value("disabled", settings_pack::pe_disabled) +#endif + ; + + enum_("enc_level") + .value("pe_rc4", settings_pack::pe_rc4) + .value("pe_plaintext", settings_pack::pe_plaintext) + .value("pe_both", settings_pack::pe_both) +#if TORRENT_ABI_VERSION == 1 + .value("rc4", settings_pack::pe_rc4) + .value("plaintext", settings_pack::pe_plaintext) + .value("both", settings_pack::pe_both) +#endif + ; + + { + scope s = enum_("proxy_type_t") + .value("none", settings_pack::none) + .value("socks4", settings_pack::socks4) + .value("socks5", settings_pack::socks5) + .value("socks5_pw", settings_pack::socks5_pw) + .value("http", settings_pack::http) + .value("http_pw", settings_pack::http_pw) + .value("i2p_proxy", settings_pack::i2p_proxy) + ; + +#if TORRENT_ABI_VERSION == 1 + scope().attr("proxy_type") = s; + + class_("proxy_settings") + .def_readwrite("hostname", &proxy_settings::hostname) + .def_readwrite("port", &proxy_settings::port) + .def_readwrite("password", &proxy_settings::password) + .def_readwrite("username", &proxy_settings::username) + .def_readwrite("type", &proxy_settings::type) + .def_readwrite("proxy_peer_connections", &proxy_settings::proxy_peer_connections) + .def_readwrite("proxy_hostnames", &proxy_settings::proxy_hostnames) + ; +#endif + } + +#ifndef TORRENT_DISABLE_DHT +#if TORRENT_ABI_VERSION <= 2 + class_("dht_settings") + .def_readwrite("max_peers_reply", &dht::dht_settings::max_peers_reply) + .def_readwrite("search_branching", &dht::dht_settings::search_branching) + .def_readwrite("max_fail_count", &dht::dht_settings::max_fail_count) + .def_readwrite("max_torrents", &dht::dht_settings::max_torrents) + .def_readwrite("max_dht_items", &dht::dht_settings::max_dht_items) + .def_readwrite("restrict_routing_ips", &dht::dht_settings::restrict_routing_ips) + .def_readwrite("restrict_search_ips", &dht::dht_settings::restrict_search_ips) + .def_readwrite("max_torrent_search_reply", &dht::dht_settings::max_torrent_search_reply) + .def_readwrite("extended_routing_table", &dht::dht_settings::extended_routing_table) + .def_readwrite("aggressive_lookups", &dht::dht_settings::aggressive_lookups) + .def_readwrite("privacy_lookups", &dht::dht_settings::privacy_lookups) + .def_readwrite("enforce_node_id", &dht::dht_settings::enforce_node_id) + .def_readwrite("ignore_dark_internet", &dht::dht_settings::ignore_dark_internet) + .def_readwrite("block_timeout", &dht::dht_settings::block_timeout) + .def_readwrite("block_ratelimit", &dht::dht_settings::block_ratelimit) + .def_readwrite("read_only", &dht::dht_settings::read_only) + .def_readwrite("item_lifetime", &dht::dht_settings::item_lifetime) + ; +#endif +#endif + +#if TORRENT_ABI_VERSION == 1 + class_("pe_settings") + .def_readwrite("out_enc_policy", &pe_settings::out_enc_policy) + .def_readwrite("in_enc_policy", &pe_settings::in_enc_policy) + .def_readwrite("allowed_enc_level", &pe_settings::allowed_enc_level) + .def_readwrite("prefer_rc4", &pe_settings::prefer_rc4) + ; +#endif + +} diff --git a/bindings/python/src/sha1_hash.cpp b/bindings/python/src/sha1_hash.cpp new file mode 100644 index 0000000..9fa4d0c --- /dev/null +++ b/bindings/python/src/sha1_hash.cpp @@ -0,0 +1,46 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include + +#include "bytes.hpp" + +namespace { + +using namespace lt; + +long get_hash(sha1_hash const& s) +{ + return std::hash{}(s); +} + +bytes sha1_hash_bytes(const sha1_hash& bn) { + return bytes(bn.to_string()); +} + +} + +void bind_sha1_hash() +{ + using namespace boost::python; + using namespace lt; + + class_("sha1_hash") + .def(self == self) + .def(self != self) + .def(self < self) + .def(self_ns::str(self)) + .def(init()) + .def("clear", &sha1_hash::clear) + .def("is_all_zeros", &sha1_hash::is_all_zeros) + .def("to_string", sha1_hash_bytes) + .def("__hash__", get_hash) + .def("to_bytes", sha1_hash_bytes) + ; + + scope().attr("peer_id") = scope().attr("sha1_hash"); +} + diff --git a/bindings/python/src/sha256_hash.cpp b/bindings/python/src/sha256_hash.cpp new file mode 100644 index 0000000..5da2ad7 --- /dev/null +++ b/bindings/python/src/sha256_hash.cpp @@ -0,0 +1,44 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include + +#include "bytes.hpp" + +namespace { + +using namespace lt; + +long get_hash(sha256_hash const& s) +{ + return std::hash{}(s); +} + +bytes sha256_hash_bytes(const sha256_hash& bn) { + return bytes(bn.to_string()); +} + +} + +void bind_sha256_hash() +{ + using namespace boost::python; + using namespace lt; + + class_("sha256_hash") + .def(self == self) + .def(self != self) + .def(self < self) + .def(self_ns::str(self)) + .def(init()) + .def("clear", &sha256_hash::clear) + .def("is_all_zeros", &sha256_hash::is_all_zeros) + .def("to_string", sha256_hash_bytes) + .def("__hash__", get_hash) + .def("to_bytes", sha256_hash_bytes) + ; +} + diff --git a/bindings/python/src/string.cpp b/bindings/python/src/string.cpp new file mode 100644 index 0000000..408b014 --- /dev/null +++ b/bindings/python/src/string.cpp @@ -0,0 +1,53 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include + +using namespace boost::python; + +struct unicode_from_python +{ + unicode_from_python() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { +#if PY_VERSION_HEX >= 0x03020000 + return PyUnicode_Check(x) ? x : nullptr; +#else + return PyString_Check(x) ? x : PyUnicode_Check(x) ? x : nullptr; +#endif + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + std::string>*)data)->storage.bytes; + +#if PY_VERSION_HEX < 0x03000000 + if (PyString_Check(x)) + { + data->convertible = new (storage) std::string(PyString_AsString(x) + , PyString_Size(x)); + } + else +#endif + { + Py_ssize_t size = 0; + char const* unicode = PyUnicode_AsUTF8AndSize(x, &size); + data->convertible = new (storage) std::string(unicode, size); + } + } +}; + +void bind_unicode_string_conversion() +{ + unicode_from_python(); +} + diff --git a/bindings/python/src/torrent_handle.cpp b/bindings/python/src/torrent_handle.cpp new file mode 100644 index 0000000..9b8f03d --- /dev/null +++ b/bindings/python/src/torrent_handle.cpp @@ -0,0 +1,665 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include "bytes.hpp" +#include +#include +#include +#include +#include +#include "libtorrent/announce_entry.hpp" +#include +#include "gil.hpp" + +using namespace boost::python; +using namespace lt; + +#ifdef _MSC_VER +#pragma warning(push) +// warning c4996: x: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +namespace +{ + + list url_seeds(torrent_handle& handle) + { + list ret; + std::set urls; + { + allow_threading_guard guard; + urls = handle.url_seeds(); + } + + for (std::set::iterator i(urls.begin()) + , end(urls.end()); i != end; ++i) + ret.append(*i); + return ret; + } + + list http_seeds(torrent_handle& handle) + { + list ret; + std::set urls; + { + allow_threading_guard guard; + urls = handle.http_seeds(); + } + + for (std::set::iterator i(urls.begin()) + , end(urls.end()); i != end; ++i) + ret.append(*i); + return ret; + } + + list piece_availability(torrent_handle& handle) + { + list ret; + std::vector avail; + { + allow_threading_guard guard; + handle.piece_availability(avail); + } + + for (auto const a : avail) + ret.append(a); + return ret; + } + + list piece_priorities(torrent_handle& handle) + { + list ret; + std::vector prio; + { + allow_threading_guard guard; + prio = handle.get_piece_priorities(); + } + + for (auto const p : prio) + ret.append(p); + return ret; + } + +} // namespace unnamed + +list file_progress(torrent_handle& handle, file_progress_flags_t const flags) +{ + std::vector p; + + { + allow_threading_guard guard; + std::shared_ptr ti = handle.torrent_file(); + if (ti) + { + p.reserve(ti->num_files()); + handle.file_progress(p, flags); + } + } + + list result; + + for (std::vector::iterator i(p.begin()), e(p.end()); i != e; ++i) + result.append(*i); + + return result; +} + +list get_peer_info(torrent_handle const& handle) +{ + std::vector pi; + + { + allow_threading_guard guard; + handle.get_peer_info(pi); + } + + list result; + + for (std::vector::iterator i = pi.begin(); i != pi.end(); ++i) + result.append(*i); + + return result; +} + +namespace +{ + template + T extract_fn(object o) + { + return boost::python::extract(o); + } +} + +void prioritize_pieces(torrent_handle& info, object o) +{ + stl_input_iterator begin(o), end; + if (begin == end) return; + + // determine which overload should be selected. the one taking a list of + // priorities or the one taking a list of piece -> priority mappings + bool const is_piece_list = extract>(*begin).check(); + + if (is_piece_list) + { + std::vector> piece_list; + std::transform(begin, end, std::back_inserter(piece_list) + , &extract_fn>); + info.prioritize_pieces(piece_list); + } + else + { + std::vector priority_vector; + std::transform(begin, end, std::back_inserter(priority_vector) + , &extract_fn); + info.prioritize_pieces(priority_vector); + } +} + +void prioritize_files(torrent_handle& info, object o) +{ + stl_input_iterator begin(o), end; + info.prioritize_files(std::vector(begin, end)); +} + +list file_priorities(torrent_handle& handle) +{ + list ret; + std::vector priorities = handle.get_file_priorities(); + + for (auto const p : priorities) + ret.append(p); + + return ret; +} + +download_priority_t file_prioritity0(torrent_handle& h, file_index_t index) +{ + return h.file_priority(index); +} + +void file_prioritity1(torrent_handle& h, file_index_t index, download_priority_t prio) +{ + return h.file_priority(index, prio); +} + +void dict_to_announce_entry(dict d, announce_entry& ae) +{ + ae.url = extract(d["url"]); + if (d.has_key("tier")) + ae.tier = extract(d["tier"]); + if (d.has_key("fail_limit")) + ae.fail_limit = extract(d["fail_limit"]); +} + +void replace_trackers(torrent_handle& h, object trackers) +{ + object iter(trackers.attr("__iter__")()); + + std::vector result; + + for (;;) + { + handle<> entry(allow_null(PyIter_Next(iter.ptr()))); + + if (entry == handle<>()) + break; + + if (extract(object(entry)).check()) + { + result.push_back(extract(object(entry))); + } + else + { + dict d; + d = extract(object(entry)); + announce_entry ae; + dict_to_announce_entry(d, ae); + result.push_back(ae); + } + } + + allow_threading_guard guard; + h.replace_trackers(result); +} + +void add_tracker(torrent_handle& h, dict d) +{ + announce_entry ae; + dict_to_announce_entry(d, ae); + h.add_tracker(ae); +} + +namespace +{ + using std::chrono::system_clock; + + object to_ptime(time_point tpt) + { + object ret; + if (tpt > min_time()) + { + ret = long_(system_clock::to_time_t(system_clock::now() + + duration_cast(tpt - clock_type::now()))); + } + return ret; + } +} + +list trackers(torrent_handle& h) +{ + list ret; + std::vector const trackers = h.trackers(); + for (std::vector::const_iterator i = trackers.begin(), end(trackers.end()); i != end; ++i) + { + dict d; + d["url"] = i->url; + d["trackerid"] = i->trackerid; + d["tier"] = i->tier; + d["fail_limit"] = i->fail_limit; + d["source"] = i->source; + d["verified"] = i->verified; + +#if TORRENT_ABI_VERSION == 1 + if (!i->endpoints.empty()) + { + announce_endpoint const& aep = i->endpoints.front(); + announce_infohash const& aih = aep.info_hashes[protocol_version::V1]; + d["message"] = aih.message; + dict last_error; + last_error["value"] = aih.last_error.value(); + last_error["category"] = aih.last_error.category().name(); + d["last_error"] = last_error; + d["next_announce"] = to_ptime(aih.next_announce); + d["min_announce"] = to_ptime(aih.min_announce); + d["scrape_incomplete"] = aih.scrape_incomplete; + d["scrape_complete"] = aih.scrape_complete; + d["scrape_downloaded"] = aih.scrape_downloaded; + d["fails"] = aih.fails; + d["updating"] = aih.updating; + d["start_sent"] = aih.start_sent; + d["complete_sent"] = aih.complete_sent; + } + else + { + d["message"] = std::string(); + dict last_error; + last_error["value"] = 0; + last_error["category"] = ""; + d["last_error"] = last_error; + d["next_announce"] = object(); + d["min_announce"] = object(); + d["scrape_incomplete"] = 0; + d["scrape_complete"] = 0; + d["scrape_downloaded"] = 0; + d["fails"] = 0; + d["updating"] = false; + d["start_sent"] = false; + d["complete_sent"] = false; + } +#endif + + list aeps; + for (auto const& aep : i->endpoints) + { + dict e; + e["local_address"] = boost::python::make_tuple(aep.local_endpoint.address().to_string(), aep.local_endpoint.port()); + + list aihs; + for (auto const& aih : aep.info_hashes) + { + dict i; + i["message"] = aih.message; + dict last_error; + last_error["value"] = aih.last_error.value(); + last_error["category"] = aih.last_error.category().name(); + i["last_error"] = last_error; + i["next_announce"] = to_ptime(aih.next_announce); + i["min_announce"] = to_ptime(aih.min_announce); + i["scrape_incomplete"] = aih.scrape_incomplete; + i["scrape_complete"] = aih.scrape_complete; + i["scrape_downloaded"] = aih.scrape_downloaded; + i["fails"] = aih.fails; + i["updating"] = aih.updating; + i["start_sent"] = aih.start_sent; + i["complete_sent"] = aih.complete_sent; + aihs.append(std::move(i)); + } + e["info_hashes"] = std::move(aihs); + +#if TORRENT_ABI_VERSION <= 2 + announce_infohash const& aih = aep.info_hashes[protocol_version::V1]; + e["message"] = aih.message; + dict last_error; + last_error["value"] = aih.last_error.value(); + last_error["category"] = aih.last_error.category().name(); + e["last_error"] = last_error; + e["next_announce"] = to_ptime(aih.next_announce); + e["min_announce"] = to_ptime(aih.min_announce); + e["scrape_incomplete"] = aih.scrape_incomplete; + e["scrape_complete"] = aih.scrape_complete; + e["scrape_downloaded"] = aih.scrape_downloaded; + e["fails"] = aih.fails; + e["updating"] = aih.updating; + e["start_sent"] = aih.start_sent; + e["complete_sent"] = aih.complete_sent; +#endif + aeps.append(std::move(e)); + } + d["endpoints"] = std::move(aeps); + +#if TORRENT_ABI_VERSION == 1 + d["send_stats"] = i->send_stats; +#endif + ret.append(std::move(d)); + } + return ret; +} + +list get_download_queue(torrent_handle& handle) +{ + list ret; + + std::vector downloading; + + { + allow_threading_guard guard; + downloading = handle.get_download_queue(); + } + + for (std::vector::iterator i = downloading.begin() + , end(downloading.end()); i != end; ++i) + { + dict partial_piece; + partial_piece["piece_index"] = i->piece_index; + partial_piece["blocks_in_piece"] = i->blocks_in_piece; + list block_list; + for (int k = 0; k < i->blocks_in_piece; ++k) + { + dict block_info; + block_info["state"] = i->blocks[k].state; + block_info["num_peers"] = i->blocks[k].num_peers; + block_info["bytes_progress"] = i->blocks[k].bytes_progress; + block_info["block_size"] = i->blocks[k].block_size; + block_info["peer"] = boost::python::make_tuple( + i->blocks[k].peer().address().to_string() + , i->blocks[k].peer().port()); + block_list.append(block_info); + } + partial_piece["blocks"] = block_list; + + ret.append(partial_piece); + } + + return ret; +} + +void set_metadata(torrent_handle& handle, std::string const& buf) +{ + handle.set_metadata(buf); +} + +#if TORRENT_ABI_VERSION == 1 + +std::shared_ptr get_torrent_info(torrent_handle const& h) +{ + allow_threading_guard guard; + return h.torrent_file(); +} + +#endif // TORRENT_ABI_VERSION + +// TODO: this overload should probably be deprecated +void add_piece_str(torrent_handle& th, piece_index_t piece, char const *data + , add_piece_flags_t const flags) +{ + th.add_piece(piece, data, flags); +} + +void add_piece_bytes(torrent_handle& th, piece_index_t piece, bytes data + , add_piece_flags_t const flags) +{ + std::vector buffer; + buffer.reserve(data.arr.size()); + std::copy(data.arr.begin(), data.arr.end(), std::back_inserter(buffer)); + th.add_piece(piece, std::move(buffer), flags); +} + +class dummy5 {}; +class dummy {}; +class dummy4 {}; +class dummy6 {}; +class dummy7 {}; +class dummy8 {}; +class dummy15 {}; +class dummy16 {}; + +using by_value = return_value_policy; +void bind_torrent_handle() +{ + // arguments are: number of seconds and tracker index + void (torrent_handle::*force_reannounce0)(int, int, reannounce_flags_t) const = &torrent_handle::force_reannounce; + +#if TORRENT_ABI_VERSION == 1 + bool (torrent_handle::*super_seeding0)() const = &torrent_handle::super_seeding; + void (torrent_handle::*super_seeding1)(bool) const = &torrent_handle::super_seeding; +#endif + void (torrent_handle::*set_flags0)(torrent_flags_t) const = &torrent_handle::set_flags; + void (torrent_handle::*set_flags1)(torrent_flags_t, torrent_flags_t) const = &torrent_handle::set_flags; + + download_priority_t (torrent_handle::*piece_priority0)(piece_index_t) const = &torrent_handle::piece_priority; + void (torrent_handle::*piece_priority1)(piece_index_t, download_priority_t) const = &torrent_handle::piece_priority; + + void (torrent_handle::*move_storage0)(std::string const&, lt::move_flags_t) const = &torrent_handle::move_storage; + void (torrent_handle::*rename_file0)(file_index_t, std::string const&) const = &torrent_handle::rename_file; + + std::vector (torrent_handle::*file_status0)() const = &torrent_handle::file_status; + +#define _ allow_threads + + enum_("move_flags_t") + .value("always_replace_files", move_flags_t::always_replace_files) + .value("fail_if_exist", move_flags_t::fail_if_exist) + .value("dont_replace", move_flags_t::dont_replace) + ; + +#if TORRENT_ABI_VERSION == 1 + enum_("deprecated_move_flags_t") + .value("always_replace_files", deprecated_move_flags_t::always_replace_files) + .value("fail_if_exist", deprecated_move_flags_t::fail_if_exist) + .value("dont_replace", deprecated_move_flags_t::dont_replace) + ; +#endif + + { + scope s = class_("torrent_handle") + .def(self == self) + .def(self != self) + .def(self < self) + .def("__hash__", (std::size_t (*)(torrent_handle const&))&libtorrent::hash_value) + .def("get_peer_info", get_peer_info) + .def("status", _(&torrent_handle::status), arg("flags") = 0xffffffff) + .def("get_download_queue", get_download_queue) + .def("file_progress", file_progress, arg("flags") = file_progress_flags_t{}) + .def("trackers", trackers) + .def("replace_trackers", replace_trackers) + .def("add_tracker", add_tracker) + .def("add_url_seed", _(&torrent_handle::add_url_seed)) + .def("remove_url_seed", _(&torrent_handle::remove_url_seed)) + .def("url_seeds", url_seeds) + .def("add_http_seed", _(&torrent_handle::add_http_seed)) + .def("remove_http_seed", _(&torrent_handle::remove_http_seed)) + .def("http_seeds", http_seeds) + .def("torrent_file", _(&torrent_handle::torrent_file)) + .def("set_metadata", set_metadata) + .def("is_valid", _(&torrent_handle::is_valid)) + .def("pause", _(&torrent_handle::pause), arg("flags") = 0) + .def("resume", _(&torrent_handle::resume)) + .def("clear_error", _(&torrent_handle::clear_error)) + .def("queue_position", _(&torrent_handle::queue_position)) + .def("queue_position_up", _(&torrent_handle::queue_position_up)) + .def("queue_position_down", _(&torrent_handle::queue_position_down)) + .def("queue_position_top", _(&torrent_handle::queue_position_top)) + .def("queue_position_bottom", _(&torrent_handle::queue_position_bottom)) + + .def("add_piece", add_piece_str) + .def("add_piece", add_piece_bytes) + .def("read_piece", _(&torrent_handle::read_piece)) + .def("have_piece", _(&torrent_handle::have_piece)) + .def("set_piece_deadline", _(&torrent_handle::set_piece_deadline) + , (arg("index"), arg("deadline"), arg("flags") = 0)) + .def("reset_piece_deadline", _(&torrent_handle::reset_piece_deadline), (arg("index"))) + .def("clear_piece_deadlines", _(&torrent_handle::clear_piece_deadlines), (arg("index"))) + .def("piece_availability", &piece_availability) + .def("piece_priority", _(piece_priority0)) + .def("piece_priority", _(piece_priority1)) + .def("prioritize_pieces", &prioritize_pieces) + .def("get_piece_priorities", &piece_priorities) + .def("prioritize_files", &prioritize_files) + .def("get_file_priorities", &file_priorities) + .def("file_priority", &file_prioritity0) + .def("file_priority", &file_prioritity1) + .def("file_status", _(file_status0)) + .def("save_resume_data", _(&torrent_handle::save_resume_data), arg("flags") = 0) + .def("need_save_resume_data", _(&torrent_handle::need_save_resume_data)) + .def("force_reannounce", _(force_reannounce0) + , (arg("seconds") = 0, arg("tracker_idx") = -1, arg("flags") = reannounce_flags_t{})) +#ifndef TORRENT_DISABLE_DHT + .def("force_dht_announce", _(&torrent_handle::force_dht_announce)) +#endif + .def("scrape_tracker", _(&torrent_handle::scrape_tracker), arg("index") = -1) + .def("flush_cache", &torrent_handle::flush_cache) + .def("set_upload_limit", _(&torrent_handle::set_upload_limit)) + .def("upload_limit", _(&torrent_handle::upload_limit)) + .def("set_download_limit", _(&torrent_handle::set_download_limit)) + .def("download_limit", _(&torrent_handle::download_limit)) + .def("connect_peer", &torrent_handle::connect_peer, (arg("endpoint"), arg("source")=0, arg("flags")=0xd)) + .def("set_max_uploads", &torrent_handle::set_max_uploads) + .def("max_uploads", _(&torrent_handle::max_uploads)) + .def("set_max_connections", &torrent_handle::set_max_connections) + .def("max_connections", _(&torrent_handle::max_connections)) + .def("move_storage", _(move_storage0), (arg("path"), arg("flags") = move_flags_t::always_replace_files)) + .def("info_hash", _(&torrent_handle::info_hash)) + .def("info_hashes", _(&torrent_handle::info_hashes)) + .def("force_recheck", _(&torrent_handle::force_recheck)) + .def("rename_file", _(rename_file0)) + .def("set_ssl_certificate", &torrent_handle::set_ssl_certificate, (arg("cert"), arg("private_key"), arg("dh_params"), arg("passphrase")="")) + .def("flags", _(&torrent_handle::flags)) + .def("set_flags", _(set_flags0)) + .def("set_flags", _(set_flags1)) + .def("unset_flags", _(&torrent_handle::unset_flags)) + // deprecated +#if TORRENT_ABI_VERSION == 1 + .def("piece_priorities", depr(&piece_priorities)) + .def("file_priorities", depr(&file_priorities)) + .def("stop_when_ready", depr(&torrent_handle::stop_when_ready)) + .def("super_seeding", depr(super_seeding1)) + .def("auto_managed", depr(&torrent_handle::auto_managed)) + .def("set_priority", depr(&torrent_handle::set_priority)) + .def("get_torrent_info", depr(&get_torrent_info)) + .def("super_seeding", depr(super_seeding0)) + .def("write_resume_data", depr(&torrent_handle::write_resume_data)) + .def("is_seed", depr(&torrent_handle::is_seed)) + .def("is_finished", depr(&torrent_handle::is_finished)) + .def("has_metadata", depr(&torrent_handle::has_metadata)) + .def("use_interface", depr(&torrent_handle::use_interface)) + .def("name", depr(&torrent_handle::name)) + .def("is_paused", depr(&torrent_handle::is_paused)) + .def("is_auto_managed", depr(&torrent_handle::is_auto_managed)) + .def("set_upload_mode", depr(&torrent_handle::set_upload_mode)) + .def("set_share_mode", depr(&torrent_handle::set_share_mode)) + .def("apply_ip_filter", depr(&torrent_handle::apply_ip_filter)) + .def("set_sequential_download", depr(&torrent_handle::set_sequential_download)) + .def("set_peer_upload_limit", depr(&torrent_handle::set_peer_upload_limit)) + .def("set_peer_download_limit", depr(&torrent_handle::set_peer_download_limit)) + .def("set_ratio", depr(&torrent_handle::set_ratio)) + .def("save_path", depr(&torrent_handle::save_path)) + .def("set_tracker_login", depr(&torrent_handle::set_tracker_login)) +#endif + ; + + s.attr("ignore_min_interval") = torrent_handle::ignore_min_interval; + s.attr("overwrite_existing") = torrent_handle::overwrite_existing; + s.attr("piece_granularity") = torrent_handle::piece_granularity; + s.attr("graceful_pause") = torrent_handle::graceful_pause; + s.attr("flush_disk_cache") = torrent_handle::flush_disk_cache; + s.attr("save_info_dict") = torrent_handle::save_info_dict; + s.attr("only_if_modified") = torrent_handle::only_if_modified; + s.attr("alert_when_available") = torrent_handle::alert_when_available; + s.attr("query_distributed_copies") = torrent_handle::query_distributed_copies; + s.attr("query_accurate_download_counters") = torrent_handle::query_accurate_download_counters; + s.attr("query_last_seen_complete") = torrent_handle::query_last_seen_complete; + s.attr("query_pieces") = torrent_handle::query_pieces; + s.attr("query_verified_pieces") = torrent_handle::query_verified_pieces; + } + + class_("open_file_state") + .add_property("file_index", make_getter((&open_file_state::file_index), by_value())) + .def_readonly("last_use", &open_file_state::last_use) + .def_readonly("open_mode", &open_file_state::open_mode) + ; + + { + scope s = class_("file_open_mode"); + s.attr("read_only") = file_open_mode::read_only; + s.attr("write_only") = file_open_mode::write_only; + s.attr("read_write") = file_open_mode::read_write; + s.attr("rw_mask") = file_open_mode::rw_mask; + s.attr("sparse") = file_open_mode::sparse; + s.attr("no_atime") = file_open_mode::no_atime; + s.attr("random_access") = file_open_mode::random_access; +#if TORRENT_ABI_VERSION == 1 + s.attr("locked") = 0; +#endif + } + + { + scope s = class_("file_progress_flags_t"); + s.attr("piece_granularity") = torrent_handle::piece_granularity; + } + + { + scope s = class_("add_piece_flags_t"); + s.attr("overwrite_existing") = torrent_handle::overwrite_existing; + } + + { + scope s = class_("pause_flags_t"); + s.attr("graceful_pause") = torrent_handle::graceful_pause; + } + + { + scope s = class_("save_resume_flags_t"); + s.attr("flush_disk_cache") = torrent_handle::flush_disk_cache; + s.attr("save_info_dict") = torrent_handle::save_info_dict; + s.attr("only_if_modified") = torrent_handle::only_if_modified; + } + + { + scope s = class_("reannounce_flags_t"); + s.attr("ignore_min_interval") = torrent_handle::ignore_min_interval; + } + + { + scope s = class_("deadline_flags_t"); + s.attr("alert_when_available") = torrent_handle::alert_when_available; + } + + { + scope s = class_("status_flags_t"); + s.attr("query_distributed_copies") = torrent_handle::query_distributed_copies; + s.attr("query_accurate_download_counters") = torrent_handle::query_accurate_download_counters; + s.attr("query_last_seen_complete") = torrent_handle::query_last_seen_complete; + s.attr("query_pieces") = torrent_handle::query_pieces; + s.attr("query_verified_pieces") = torrent_handle::query_verified_pieces; + } + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/bindings/python/src/torrent_info.cpp b/bindings/python/src/torrent_info.cpp new file mode 100644 index 0000000..6509e05 --- /dev/null +++ b/bindings/python/src/torrent_info.cpp @@ -0,0 +1,514 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +#include "boost_python.hpp" +#include + +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/info_hash.hpp" // for protocol_version +#include "libtorrent/tracker_manager.hpp" // for event_t +#include "bytes.hpp" +#include "gil.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +using namespace boost::python; +using namespace lt; + +namespace +{ + + std::vector::const_iterator begin_trackers(torrent_info& i) + { + return i.trackers().begin(); + } + + std::vector::const_iterator end_trackers(torrent_info& i) + { + return i.trackers().end(); + } + + void add_node(torrent_info& ti, char const* hostname, int port) + { + ti.add_node(std::make_pair(hostname, port)); + } + + list nodes(torrent_info const& ti) + { + list result; + + for (auto const& i : ti.nodes()) + result.append(boost::python::make_tuple(i.first, i.second)); + + return result; + } + + list get_web_seeds(torrent_info const& ti) + { + std::vector const& ws = ti.web_seeds(); + list ret; + for (std::vector::const_iterator i = ws.begin() + , end(ws.end()); i != end; ++i) + { + dict d; + d["url"] = i->url; + d["type"] = i->type; + d["auth"] = i->auth; + ret.append(d); + } + + return ret; + } + + void set_web_seeds(torrent_info& ti, list ws) + { + std::vector web_seeds; + int const len = static_cast(boost::python::len(ws)); + for (int i = 0; i < len; i++) + { + dict e = extract(ws[i]); + int const type = extract(e["type"]); + web_seeds.push_back(web_seed_entry( + extract(e["url"]) + , static_cast(type) + , extract(e["auth"]))); + } + ti.set_web_seeds(web_seeds); + } + +#if TORRENT_ABI_VERSION <= 2 + list get_merkle_tree(torrent_info const& ti) + { + std::vector const& mt = ti.merkle_tree(); + list ret; + for (std::vector::const_iterator i = mt.begin() + , end(mt.end()); i != end; ++i) + { + ret.append(bytes(i->to_string())); + } + return ret; + } + + void set_merkle_tree(torrent_info& ti, list hashes) + { + std::vector h; + for (int i = 0, e = int(len(hashes)); i < e; ++i) + h.push_back(sha1_hash(bytes(extract(hashes[i])).arr.data())); + + ti.set_merkle_tree(h); + } +#endif + + bytes hash_for_piece(torrent_info const& ti, piece_index_t i) + { + return bytes(ti.hash_for_piece(i).to_string()); + } + +#if TORRENT_ABI_VERSION <= 2 + bytes metadata(torrent_info const& ti) + { + auto const s = ti.info_section(); + return bytes(s.data(), s.size()); + } +#endif + + bytes get_info_section(torrent_info const& ti) + { + auto const s = ti.info_section(); + return bytes(s.data(), s.size()); + } + + list map_block(torrent_info& ti, piece_index_t piece, std::int64_t offset, int size) + { + std::vector p = ti.map_block(piece, offset, size); + list result; + + for (std::vector::iterator i(p.begin()), e(p.end()); i != e; ++i) + result.append(*i); + + return result; + } + +#if TORRENT_ABI_VERSION == 1 + // Create getters for announce_entry data members with non-trivial types which need converting. + lt::time_point get_next_announce(announce_entry const& ae) + { + python_deprecated("next_announce is deprecated"); + return ae.endpoints.empty() ? lt::time_point() : lt::time_point(ae.endpoints.front().info_hashes[protocol_version::V1].next_announce); + } + lt::time_point get_min_announce(announce_entry const& ae) + { + python_deprecated("min_announce is deprecated"); + return ae.endpoints.empty() ? lt::time_point() : lt::time_point(ae.endpoints.front().info_hashes[protocol_version::V1].min_announce); + } + // announce_entry data member bit-fields. + int get_fails(announce_entry const& ae) + { + python_deprecated("fails is deprecated"); + return ae.endpoints.empty() ? 0 : ae.endpoints.front().info_hashes[protocol_version::V1].fails; + } + bool get_updating(announce_entry const& ae) + { + python_deprecated("updating is deprecated"); + return ae.endpoints.empty() ? false : ae.endpoints.front().info_hashes[protocol_version::V1].updating; + } + bool get_start_sent(announce_entry const& ae) + { + python_deprecated("start_sent is deprecated"); + return ae.endpoints.empty() ? false : ae.endpoints.front().info_hashes[protocol_version::V1].start_sent; + } + bool get_complete_sent(announce_entry const& ae) + { + python_deprecated("complete_sent is deprecated"); + return ae.endpoints.empty() ? false : ae.endpoints.front().info_hashes[protocol_version::V1].complete_sent; + } + // announce_entry method requires lt::time_point. + bool can_announce(announce_entry const& ae, bool is_seed) { + python_deprecated("can_announce() is deprecated"); + // an entry without endpoints implies it has never been announced so it can be now + if (ae.endpoints.empty()) return true; + lt::time_point now = lt::clock_type::now(); + return ae.endpoints.front().can_announce(now, is_seed, ae.fail_limit); + } + bool is_working(announce_entry const& ae) + { + python_deprecated("is_working is deprecated"); + return ae.endpoints.empty() ? false : ae.endpoints.front().is_working(); + } +#endif + int get_source(announce_entry const& ae) { return ae.source; } + bool get_verified(announce_entry const& ae) { return ae.verified; } + +#if TORRENT_ABI_VERSION == 1 + std::string get_message(announce_entry const& ae) + { + python_deprecated("message is deprecated"); + return ae.endpoints.empty() ? "" : ae.endpoints.front().info_hashes[protocol_version::V1].message; + } + error_code get_last_error(announce_entry const& ae) + { + python_deprecated("last_error is deprecated"); + return ae.endpoints.empty() ? error_code() : ae.endpoints.front().info_hashes[protocol_version::V1].last_error; + } + int get_scrape_incomplete(announce_entry const& ae) + { + python_deprecated("scrape_incomplete is deprecated"); + return ae.endpoints.empty() ? 0 : ae.endpoints.front().info_hashes[protocol_version::V1].scrape_incomplete; + } + int get_scrape_complete(announce_entry const& ae) + { + python_deprecated("scrape_complete is deprecated"); + return ae.endpoints.empty() ? 0 : ae.endpoints.front().info_hashes[protocol_version::V1].scrape_complete; + } + int get_scrape_downloaded(announce_entry const& ae) + { + python_deprecated("scrape_downloaded is deprecated"); + return ae.endpoints.empty() ? 0 : ae.endpoints.front().info_hashes[protocol_version::V1].scrape_downloaded; + } + int next_announce_in(announce_entry const&) + { + python_deprecated("next_announce_in is deprecated"); + return 0; + } + int min_announce_in(announce_entry const&) + { + python_deprecated("min_announce_in is deprecated"); + return 0; + } + bool get_send_stats(announce_entry const& ae) + { + python_deprecated("send_stats is deprecated"); + return ae.send_stats; + } + std::int64_t get_size(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.size; + } + std::int64_t get_offset(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.offset; + } + bool get_pad_file(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.pad_file; + } + bool get_executable_attribute(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.executable_attribute; + } + bool get_hidden_attribute(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.hidden_attribute; + } + bool get_symlink_attribute(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.symlink_attribute; + } +#endif + +} // namespace unnamed + +load_torrent_limits dict_to_limits(dict limits) +{ + load_torrent_limits ret; + + list items = limits.items(); + int const len = int(boost::python::len(limits)); + for (int i = 0; i < len; i++) + { + boost::python::api::object_item item = items[i]; + std::string const key = extract(item[0]); + object const value = item[1]; + + if (key == "max_buffer_size") + { + ret.max_buffer_size = extract(value); + continue; + } + else if (key == "max_pieces") + { + ret.max_pieces = extract(value); + continue; + } + else if (key == "max_decode_depth") + { + ret.max_decode_depth = extract(value); + continue; + } + else if (key == "max_decode_tokens") + { + ret.max_decode_tokens = extract(value); + continue; + } + } + return ret; +} + +std::shared_ptr buffer_constructor0(bytes b) +{ + return std::make_shared(b.arr, from_span); +} + +std::shared_ptr buffer_constructor1(bytes b, dict limits) +{ + std::shared_ptr ret = std::make_shared(b.arr + , dict_to_limits(limits), from_span); + return ret; +} + +std::shared_ptr file_constructor0(lt::string_view filename) +{ + return std::make_shared(std::string(filename)); +} + +std::shared_ptr file_constructor1(lt::string_view filename, dict limits) +{ + return std::make_shared(std::string(filename), dict_to_limits(limits)); +} + +std::shared_ptr sha1_constructor0(sha1_hash const& ih) +{ + return std::make_shared(info_hash_t(ih)); +} + +std::shared_ptr sha256_constructor0(sha256_hash const& ih) +{ + return std::make_shared(info_hash_t(ih)); +} + +std::shared_ptr bencoded_constructor0(dict d) +{ + entry ent = extract(d); + std::vector buf; + bencode(std::back_inserter(buf), ent); + return std::make_shared(buf, lt::from_span); +} + +std::shared_ptr bencoded_constructor1(dict d, dict limits) +{ + entry ent = extract(d); + std::vector buf; + bencode(std::back_inserter(buf), ent); + return std::make_shared(buf, dict_to_limits(limits) + , lt::from_span); +} + +using by_value = return_value_policy; +void bind_torrent_info() +{ + return_value_policy copy; + + void (torrent_info::*rename_file0)(file_index_t, std::string const&) = &torrent_info::rename_file; + + class_("file_slice") + .add_property("file_index", make_getter((&file_slice::file_index), by_value())) + .def_readwrite("offset", &file_slice::offset) + .def_readwrite("size", &file_slice::size) + ; + + enum_("protocol_version") + .value("V1", protocol_version::V1) + .value("V2", protocol_version::V2) + ; + + enum_("tracker_source") + .value("source_torrent", announce_entry::source_torrent) + .value("source_client", announce_entry::source_client) + .value("source_magnet_link", announce_entry::source_magnet_link) + .value("source_tex", announce_entry::source_tex) + ; + + using add_tracker1 = void (torrent_info::*)(std::string const&, int, announce_entry::tracker_source); + + class_>("torrent_info", no_init) + .def(init(arg("info_hash"))) + .def("__init__", make_constructor(&bencoded_constructor0)) + .def("__init__", make_constructor(&bencoded_constructor1)) + .def("__init__", make_constructor(&buffer_constructor0)) + .def("__init__", make_constructor(&buffer_constructor1)) + .def("__init__", make_constructor(&file_constructor0)) + .def("__init__", make_constructor(&file_constructor1)) + .def(init((arg("ti")))) + + .def("__init__", make_constructor(&sha1_constructor0)) + .def("__init__", make_constructor(&sha256_constructor0)) + + .def("add_tracker", (add_tracker1)&torrent_info::add_tracker + , (arg("url"), arg("tier") = 0 + , arg("source") = announce_entry::source_client)) + .def("add_url_seed", &torrent_info::add_url_seed, (arg("url") + , arg("extern_auth") = std::string{} + , arg("extra_headers") = web_seed_entry::headers_t{})) + .def("add_http_seed", &torrent_info::add_http_seed, (arg("url") + , arg("extern_auth") = std::string{} + , arg("extra_headers") = web_seed_entry::headers_t{})) + .def("web_seeds", get_web_seeds) + .def("set_web_seeds", set_web_seeds) + + .def("name", &torrent_info::name, copy) + .def("comment", &torrent_info::comment, copy) + .def("creator", &torrent_info::creator, copy) + .def("total_size", &torrent_info::total_size) + .def("piece_length", &torrent_info::piece_length) + .def("num_pieces", &torrent_info::num_pieces) + .def("info_hash", &torrent_info::info_hash) + .def("info_hashes", &torrent_info::info_hashes, copy) + .def("hash_for_piece", &hash_for_piece) +#if TORRENT_ABI_VERSION <= 2 + .def("merkle_tree", depr(&get_merkle_tree)) + .def("set_merkle_tree", depr(&set_merkle_tree)) +#endif + .def("piece_size", &torrent_info::piece_size) + + .def("similar_torrents", &torrent_info::similar_torrents) + .def("collections", &torrent_info::collections) + .def("ssl_cert", &torrent_info::ssl_cert) + .def("num_files", &torrent_info::num_files) + .def("rename_file", rename_file0) + .def("remap_files", &torrent_info::remap_files) + .def("files", &torrent_info::files, return_internal_reference<>()) + .def("orig_files", &torrent_info::orig_files, return_internal_reference<>()) +#if TORRENT_ABI_VERSION == 1 + .def("file_at", depr(&torrent_info::file_at)) +#endif // TORRENT_ABI_VERSION + + .def("is_valid", &torrent_info::is_valid) + .def("priv", &torrent_info::priv) + .def("is_i2p", &torrent_info::is_i2p) +#if TORRENT_ABI_VERSION <= 2 + .def("is_merkle_torrent", depr(&torrent_info::is_merkle_torrent)) +#endif + .def("trackers", range(begin_trackers, end_trackers)) + + .def("creation_date", &torrent_info::creation_date) + + .def("add_node", &add_node) + .def("nodes", &nodes) +#if TORRENT_ABI_VERSION <= 2 + .def("metadata", depr(&metadata)) + .def("metadata_size", depr(&torrent_info::metadata_size)) +#endif + .def("info_section", &get_info_section) + .def("map_block", map_block) + .def("map_file", &torrent_info::map_file) + ; + +#if TORRENT_ABI_VERSION == 1 + class_("file_entry") + .def_readwrite("path", &file_entry::path) + .def_readwrite("symlink_path", &file_entry::symlink_path) + .def_readwrite("filehash", &file_entry::filehash) + .def_readwrite("mtime", &file_entry::mtime) + .add_property("pad_file", &get_pad_file) + .add_property("executable_attribute", &get_executable_attribute) + .add_property("hidden_attribute", &get_hidden_attribute) + .add_property("symlink_attribute", &get_symlink_attribute) + .add_property("offset", &get_offset) + .add_property("size", &get_size) + ; +#endif + + class_("announce_entry", init()) + .def_readwrite("url", &announce_entry::url) + .def_readonly("trackerid", &announce_entry::trackerid) +#if TORRENT_ABI_VERSION == 1 + .add_property("message", &get_message) + .add_property("last_error", &get_last_error) + .add_property("next_announce", &get_next_announce) + .add_property("min_announce", &get_min_announce) + .add_property("scrape_incomplete", &get_scrape_incomplete) + .add_property("scrape_complete", &get_scrape_complete) + .add_property("scrape_downloaded", &get_scrape_downloaded) +#endif + .def_readwrite("tier", &announce_entry::tier) + .def_readwrite("fail_limit", &announce_entry::fail_limit) + .add_property("source", &get_source) + .add_property("verified", &get_verified) +#if TORRENT_ABI_VERSION == 1 + .add_property("fails", &get_fails) + .add_property("updating", &get_updating) + .add_property("start_sent", &get_start_sent) + .add_property("complete_sent", &get_complete_sent) + .add_property("send_stats", &get_send_stats) + .def("next_announce_in", depr(&next_announce_in)) + .def("min_announce_in", depr(&min_announce_in)) + .def("can_announce", depr(&can_announce)) + .def("is_working", depr(&is_working)) +#endif +#if TORRENT_ABI_VERSION <= 2 + .def("reset", depr(&announce_entry::reset)) + .def("trim", depr(&announce_entry::trim)) +#endif + ; + + enum_("event_t") + .value("none", event_t::none) + .value("completed", event_t::completed) + .value("started", event_t::started) + .value("stopped", event_t::stopped) + .value("paused", event_t::paused) + ; + + implicitly_convertible, std::shared_ptr>(); + boost::python::register_ptr_to_python>(); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/bindings/python/src/torrent_status.cpp b/bindings/python/src/torrent_status.cpp new file mode 100644 index 0000000..6e48275 --- /dev/null +++ b/bindings/python/src/torrent_status.cpp @@ -0,0 +1,143 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include "gil.hpp" +#include +#include +#include + +using namespace boost::python; +using namespace lt; + +using by_value = return_value_policy; +std::shared_ptr get_torrent_file(torrent_status const& st) +{ + return st.torrent_file.lock(); +} + +void bind_torrent_status() +{ + scope status = class_("torrent_status") + .def(self == self) + .def_readonly("handle", &torrent_status::handle) + .add_property("torrent_file", &get_torrent_file) + .def_readonly("state", &torrent_status::state) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("paused", &torrent_status::paused) + .def_readonly("stop_when_ready", &torrent_status::stop_when_ready) + .def_readonly("auto_managed", &torrent_status::auto_managed) + .def_readonly("sequential_download", &torrent_status::sequential_download) +#endif + .def_readonly("is_seeding", &torrent_status::is_seeding) + .def_readonly("is_finished", &torrent_status::is_finished) + .def_readonly("has_metadata", &torrent_status::has_metadata) + .def_readonly("progress", &torrent_status::progress) + .def_readonly("progress_ppm", &torrent_status::progress_ppm) + .add_property("next_announce", make_getter(&torrent_status::next_announce, by_value())) +#if TORRENT_ABI_VERSION == 1 + .add_property("announce_interval", make_getter(&torrent_status::announce_interval, by_value())) +#endif + .def_readonly("current_tracker", &torrent_status::current_tracker) + .def_readonly("total_download", &torrent_status::total_download) + .def_readonly("total_upload", &torrent_status::total_upload) + .def_readonly("total_payload_download", &torrent_status::total_payload_download) + .def_readonly("total_payload_upload", &torrent_status::total_payload_upload) + .def_readonly("total_failed_bytes", &torrent_status::total_failed_bytes) + .def_readonly("total_redundant_bytes", &torrent_status::total_redundant_bytes) + .def_readonly("download_rate", &torrent_status::download_rate) + .def_readonly("upload_rate", &torrent_status::upload_rate) + .def_readonly("download_payload_rate", &torrent_status::download_payload_rate) + .def_readonly("upload_payload_rate", &torrent_status::upload_payload_rate) + .def_readonly("num_seeds", &torrent_status::num_seeds) + .def_readonly("num_peers", &torrent_status::num_peers) + .def_readonly("num_complete", &torrent_status::num_complete) + .def_readonly("num_incomplete", &torrent_status::num_incomplete) + .def_readonly("list_seeds", &torrent_status::list_seeds) + .def_readonly("list_peers", &torrent_status::list_peers) + .def_readonly("connect_candidates", &torrent_status::connect_candidates) + .add_property("pieces", make_getter(&torrent_status::pieces, by_value())) + .add_property("verified_pieces", make_getter(&torrent_status::verified_pieces, by_value())) + .def_readonly("num_pieces", &torrent_status::num_pieces) + .def_readonly("total_done", &torrent_status::total_done) + .def_readonly("total", &torrent_status::total) + .def_readonly("total_wanted_done", &torrent_status::total_wanted_done) + .def_readonly("total_wanted", &torrent_status::total_wanted) + .def_readonly("distributed_full_copies", &torrent_status::distributed_full_copies) + .def_readonly("distributed_fraction", &torrent_status::distributed_fraction) + .def_readonly("distributed_copies", &torrent_status::distributed_copies) + .def_readonly("block_size", &torrent_status::block_size) + .def_readonly("num_uploads", &torrent_status::num_uploads) + .def_readonly("num_connections", &torrent_status::num_connections) + .def_readonly("uploads_limit", &torrent_status::uploads_limit) + .def_readonly("connections_limit", &torrent_status::connections_limit) + .def_readonly("storage_mode", &torrent_status::storage_mode) + .def_readonly("up_bandwidth_queue", &torrent_status::up_bandwidth_queue) + .def_readonly("down_bandwidth_queue", &torrent_status::down_bandwidth_queue) + .def_readonly("all_time_upload", &torrent_status::all_time_upload) + .def_readonly("all_time_download", &torrent_status::all_time_download) + .def_readonly("seed_rank", &torrent_status::seed_rank) + .def_readonly("has_incoming", &torrent_status::has_incoming) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("seed_mode", &torrent_status::seed_mode) + .def_readonly("upload_mode", &torrent_status::upload_mode) + .def_readonly("share_mode", &torrent_status::share_mode) + .def_readonly("super_seeding", &torrent_status::super_seeding) + .def_readonly("active_time", &torrent_status::active_time) + .def_readonly("finished_time", &torrent_status::finished_time) + .def_readonly("seeding_time", &torrent_status::seeding_time) + .def_readonly("last_scrape", &torrent_status::last_scrape) + .def_readonly("error", &torrent_status::error) + .def_readonly("priority", &torrent_status::priority) + .def_readonly("time_since_upload", &torrent_status::time_since_upload) + .def_readonly("time_since_download", &torrent_status::time_since_download) +#endif + .def_readonly("errc", &torrent_status::errc) + .add_property("error_file", make_getter(&torrent_status::error_file, by_value())) + .def_readonly("name", &torrent_status::name) + .def_readonly("save_path", &torrent_status::save_path) + .def_readonly("added_time", &torrent_status::added_time) + .def_readonly("completed_time", &torrent_status::completed_time) + .def_readonly("last_seen_complete", &torrent_status::last_seen_complete) + .add_property("queue_position", make_getter(&torrent_status::queue_position, by_value())) + .def_readonly("need_save_resume", &torrent_status::need_save_resume) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("ip_filter_applies", &torrent_status::ip_filter_applies) +#endif + .def_readonly("moving_storage", &torrent_status::moving_storage) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("is_loaded", &torrent_status::is_loaded) +#endif + .def_readonly("announcing_to_trackers", &torrent_status::announcing_to_trackers) + .def_readonly("announcing_to_lsd", &torrent_status::announcing_to_lsd) + .def_readonly("announcing_to_dht", &torrent_status::announcing_to_dht) +#if TORRENT_ABI_VERSION < 3 + .def_readonly("info_hash", &torrent_status::info_hash) +#endif + .def_readonly("info_hashes", &torrent_status::info_hashes) + .add_property("last_upload", make_getter(&torrent_status::last_upload, by_value())) + .add_property("last_download", make_getter(&torrent_status::last_download, by_value())) + .add_property("active_duration", make_getter(&torrent_status::active_duration, by_value())) + .add_property("finished_duration", make_getter(&torrent_status::finished_duration, by_value())) + .add_property("seeding_duration", make_getter(&torrent_status::seeding_duration, by_value())) + .add_property("flags", make_getter(&torrent_status::flags, by_value())) + ; + + enum_("states") +#if TORRENT_ABI_VERSION == 1 + .value("queued_for_checking", torrent_status::queued_for_checking) +#endif + .value("checking_files", torrent_status::checking_files) + .value("downloading_metadata", torrent_status::downloading_metadata) + .value("downloading", torrent_status::downloading) + .value("finished", torrent_status::finished) + .value("seeding", torrent_status::seeding) +#if TORRENT_ABI_VERSION == 1 + .value("allocating", torrent_status::allocating) +#endif + .value("checking_resume_data", torrent_status::checking_resume_data) + .export_values() + ; +} + diff --git a/bindings/python/src/utility.cpp b/bindings/python/src/utility.cpp new file mode 100644 index 0000000..0e8f513 --- /dev/null +++ b/bindings/python/src/utility.cpp @@ -0,0 +1,130 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include +#include "bytes.hpp" + +using namespace boost::python; +using namespace lt; + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +struct bytes_to_python +{ + static PyObject* convert(bytes const& p) + { +#if PY_MAJOR_VERSION >= 3 + PyObject *ret = PyBytes_FromStringAndSize(p.arr.c_str(), p.arr.size()); +#else + PyObject *ret = PyString_FromStringAndSize(p.arr.c_str(), p.arr.size()); +#endif + return ret; + } +}; + +template +struct array_to_python +{ + static PyObject* convert(std::array const& p) + { +#if PY_MAJOR_VERSION >= 3 + PyObject *ret = PyBytes_FromStringAndSize(p.data(), p.size()); +#else + PyObject *ret = PyString_FromStringAndSize(p.data(), p.size()); +#endif + return ret; + } +}; + +struct bytes_from_python +{ + bytes_from_python() + { + converter::registry::push_back( + &convertible, &construct, type_id()); + } + + static void* convertible(PyObject* x) + { +#if PY_MAJOR_VERSION >= 3 + return (PyBytes_Check(x) || PyByteArray_Check(x)) ? x : nullptr; +#else + return PyString_Check(x) ? x : nullptr; +#endif + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { +#if PY_MAJOR_VERSION >= 3 + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + bytes* ret = new (storage) bytes(); + if (PyByteArray_Check(x)) + { + ret->arr.resize(PyByteArray_Size(x)); + memcpy(&ret->arr[0], PyByteArray_AsString(x), ret->arr.size()); + } + else + { + ret->arr.resize(PyBytes_Size(x)); + memcpy(&ret->arr[0], PyBytes_AsString(x), ret->arr.size()); + } + data->convertible = storage; +#else + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + bytes* ret = new (storage) bytes(); + ret->arr.resize(PyString_Size(x)); + memcpy(&ret->arr[0], PyString_AsString(x), ret->arr.size()); + data->convertible = storage; +#endif + } +}; + +#if TORRENT_ABI_VERSION == 1 +object client_fingerprint_(peer_id const& id) +{ + python_deprecated("client_fingerprint is deprecated"); + boost::optional result = client_fingerprint(id); + return result ? object(*result) : object(); +} +#endif + +entry bdecode_(bytes const& data) +{ + return bdecode(data.arr); +} + +bytes bencode_(entry const& e) +{ + bytes result; + bencode(std::back_inserter(result.arr), e); + return result; +} + +void bind_utility() +{ + // TODO: it would be nice to install converters for sha1_hash as well + to_python_converter(); + to_python_converter, array_to_python<32>>(); + to_python_converter, array_to_python<64>>(); + bytes_from_python(); + +#if TORRENT_ABI_VERSION == 1 + def("identify_client", <::identify_client); + def("client_fingerprint", &client_fingerprint_); +#endif + def("bdecode", &bdecode_); + def("bencode", &bencode_); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + diff --git a/bindings/python/src/version.cpp b/bindings/python/src/version.cpp new file mode 100644 index 0000000..832a526 --- /dev/null +++ b/bindings/python/src/version.cpp @@ -0,0 +1,21 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include + +using namespace boost::python; +using lt::version; + +void bind_version() +{ + scope().attr("__version__") = version(); + +#if TORRENT_ABI_VERSION == 1 + scope().attr("version") = lt::version_str; + scope().attr("version_major") = LIBTORRENT_VERSION_MAJOR; + scope().attr("version_minor") = LIBTORRENT_VERSION_MINOR; +#endif +} + diff --git a/cmake/Modules/FindLibGcrypt.cmake b/cmake/Modules/FindLibGcrypt.cmake new file mode 100644 index 0000000..dcf1c98 --- /dev/null +++ b/cmake/Modules/FindLibGcrypt.cmake @@ -0,0 +1,127 @@ +#.rst: +# FindLibGcrypt +# ------------- +# +# Try to find libgcrypt. +# +# This will define the following variables: +# +# ``LibGcrypt_FOUND`` +# True if libgcrypt is available. +# +# ``LibGcrypt_VERSION`` +# The version of LibGcrypt +# +# ``LibGcrypt_INCLUDE_DIRS`` +# This should be passed to target_include_directories() if +# the target is not used for linking +# +# ``LibGcrypt_LIBRARIES`` +# This can be passed to target_link_libraries() instead of +# the ``LibGcrypt::LibGcrypt`` target +# +# If ``LibGcrypt_FOUND`` is TRUE, the following imported target +# will be available: +# +# ``LibGcrypt::LibGcrypt`` +# The libgcrypt library +# +# Since 1.9.50. + +#============================================================================= +# Copyright 2007 Charles Connell (This was based upon FindKopete.cmake) +# Copyright 2010 Joris Guisson +# Copyright 2016 Christophe Giboudeaux +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#============================================================================= + +find_path(LibGcrypt_INCLUDE_DIRS + NAMES gcrypt.h + PATH_SUFFIXES libgcrypt +) + +find_library(LibGcrypt_LIBRARIES + NAMES gcrypt +) + +if(MSVC) + find_library(LibGcrypt_LIBRARIES_DEBUG + NAMES gcryptd + ) + + if(NOT LibGcrypt_LIBRARIES_DEBUG) + unset(LibGcrypt_LIBRARIES CACHE) + endif() + + if(MSVC_IDE) + if(NOT (LibGcrypt_LIBRARIES_DEBUG AND LibGcrypt_LIBRARIES)) + message(STATUS + "\nCould NOT find the debug AND release version of the libgcrypt library.\n + You need to have both to use MSVC projects.\n + Please build and install both libgcrypt libraries first.\n" + ) + unset(LibGcrypt_LIBRARIES CACHE) + endif() + else() + string(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_TOLOWER) + if(CMAKE_BUILD_TYPE_TOLOWER MATCHES debug) + set(LibGcrypt_LIBRARIES ${LibGcrypt_LIBRARIES_DEBUG}) + endif() + endif() +endif() + +# Get version from gcrypt.h +# #define GCRYPT_VERSION "1.6.4" +if(LibGcrypt_INCLUDE_DIRS AND LibGcrypt_LIBRARIES) + file(STRINGS ${LibGcrypt_INCLUDE_DIRS}/gcrypt.h _GCRYPT_H REGEX "^#define GCRYPT_VERSION[ ]+.*$") + string(REGEX REPLACE "^.*GCRYPT_VERSION[ ]+\"([0-9]+).([0-9]+).([0-9]+).*\".*$" "\\1" LibGcrypt_MAJOR_VERSION "${_GCRYPT_H}") + string(REGEX REPLACE "^.*GCRYPT_VERSION[ ]+\"([0-9]+).([0-9]+).([0-9]+).*\".*$" "\\2" LibGcrypt_MINOR_VERSION "${_GCRYPT_H}") + string(REGEX REPLACE "^.*GCRYPT_VERSION[ ]+\"([0-9]+).([0-9]+).([0-9]+).*\".*$" "\\3" LibGcrypt_PATCH_VERSION "${_GCRYPT_H}") + + set(LibGcrypt_VERSION "${LibGcrypt_MAJOR_VERSION}.${LibGcrypt_MINOR_VERSION}.${LibGcrypt_PATCH_VERSION}") + unset(_GCRYPT_H) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LibGcrypt + FOUND_VAR LibGcrypt_FOUND + REQUIRED_VARS LibGcrypt_INCLUDE_DIRS LibGcrypt_LIBRARIES + VERSION_VAR LibGcrypt_VERSION +) + +if(LibGcrypt_FOUND AND NOT TARGET LibGcrypt::LibGcrypt) + add_library(LibGcrypt::LibGcrypt UNKNOWN IMPORTED) + set_target_properties(LibGcrypt::LibGcrypt PROPERTIES + IMPORTED_LOCATION "${LibGcrypt_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${LibGcrypt_INCLUDE_DIRS}") +endif() + +mark_as_advanced(LibGcrypt_INCLUDE_DIRS LibGcrypt_LIBRARIES) + +include(FeatureSummary) +set_package_properties(LibGcrypt PROPERTIES + URL "http://directory.fsf.org/wiki/Libgcrypt" + DESCRIPTION "General purpose crypto library based on the code used in GnuPG." +) diff --git a/cmake/Modules/GeneratePkgConfig.cmake b/cmake/Modules/GeneratePkgConfig.cmake new file mode 100644 index 0000000..fa52349 --- /dev/null +++ b/cmake/Modules/GeneratePkgConfig.cmake @@ -0,0 +1,183 @@ +# This module provides generate_and_install_pkg_config_file() function. +# The function takes target name and expects a fully configured project, i.e. with set version and +# description. The function extracts interface libraries, include dirs, definitions and options +# from the target and generates pkg-config file with install() command +# The function expands imported targets and generator expressions + +# save the current file dir for later use in the generate_and_install_pkg_config_file() function +set(_GeneratePkGConfigDir "${CMAKE_CURRENT_LIST_DIR}/GeneratePkgConfig") + +include(GNUInstallDirs) + +function(_get_target_property_merging_configs _var_name _target_name _propert_name) + get_property(prop_set TARGET ${_target_name} PROPERTY ${_propert_name} SET) + if (prop_set) + get_property(vals TARGET ${_target_name} PROPERTY ${_propert_name}) + else() + if (CMAKE_BUILD_TYPE) + list(APPEND configs ${CMAKE_BUILD_TYPE}) + elseif(CMAKE_CONFIGURATION_TYPES) + list(APPEND configs ${CMAKE_CONFIGURATION_TYPES}) + endif() + foreach(cfg ${configs}) + string(TOUPPER "${cfg}" UPPERCFG) + get_property(mapped_configs TARGET ${_target_name} PROPERTY "MAP_IMPORTED_CONFIG_${UPPERCFG}") + if (mapped_configs) + list(GET "${mapped_configs}" 0 target_cfg) + else() + set(target_cfg "${UPPERCFG}") + endif() + get_property(prop_set TARGET ${_target_name} PROPERTY ${_propert_name}_${target_cfg} SET) + if (prop_set) + get_property(val_for_cfg TARGET ${_target_name} PROPERTY ${_propert_name}_${target_cfg}) + list(APPEND vals "$<$:${val_for_cfg}>") + break() + endif() + endforeach() + if (NOT prop_set) + get_property(imported_cfgs TARGET ${_target_name} PROPERTY IMPORTED_CONFIGURATIONS) + # CMake docs say we can use any of the imported configs + list(GET imported_cfgs 0 imported_config) + get_property(vals TARGET ${_target_name} PROPERTY ${_propert_name}_${imported_config}) + # remove config generator expression. Only in this case! Notice we use such expression + # ourselves in the loop above + string(REPLACE "$<$:" "$<1:" vals "${vals}") + endif() + endif() + # HACK for static libraries cmake populates link dependencies as $. + # pkg-config does not support special handling for static libraries and as such we will remove + # that generator expression + string(REPLACE "$" "" _interface_compile_options "${_interface_compile_options}") + endif() + + # put target and project properties into a file + configure_file("${_GeneratePkGConfigDir}/target-compile-settings.cmake.in" + "${_generate_target_dir}/compile-settings.cmake" @ONLY) + + get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if (NOT _isMultiConfig) + set(_variables_file_name "${_generate_target_dir}/compile-settings-expanded.cmake") + + file(GENERATE OUTPUT "${_variables_file_name}" INPUT "${_generate_target_dir}/compile-settings.cmake" ${_target_arg}) + + configure_file("${_GeneratePkGConfigDir}/generate-pkg-config.cmake.in" + "${_generate_target_dir}/generate-pkg-config.cmake" @ONLY) + + install(SCRIPT "${_generate_target_dir}/generate-pkg-config.cmake") + else() + foreach(cfg IN LISTS CMAKE_CONFIGURATION_TYPES) + set(_variables_file_name "${_generate_target_dir}/${cfg}/compile-settings-expanded.cmake") + + file(GENERATE OUTPUT "${_variables_file_name}" INPUT "${_generate_target_dir}/compile-settings.cmake" CONDITION "$" ${_target_arg}) + + configure_file("${_GeneratePkGConfigDir}/generate-pkg-config.cmake.in" + "${_generate_target_dir}/${cfg}/generate-pkg-config.cmake" @ONLY) + + install(SCRIPT "${_generate_target_dir}/${cfg}/generate-pkg-config.cmake") + endforeach() + endif() +endfunction() diff --git a/cmake/Modules/GeneratePkgConfig/generate-pkg-config.cmake.in b/cmake/Modules/GeneratePkgConfig/generate-pkg-config.cmake.in new file mode 100644 index 0000000..6adea52 --- /dev/null +++ b/cmake/Modules/GeneratePkgConfig/generate-pkg-config.cmake.in @@ -0,0 +1,60 @@ +cmake_policy(SET CMP0007 NEW) +cmake_policy(SET CMP0011 NEW) + +include("@_variables_file_name@") + +function (cmake_list_to_pkg_config _result _list _prefix) + set(_tmp_list "${_list}") + list(REMOVE_ITEM _tmp_list "") + # remove prefix from prefixed items + string(REGEX REPLACE "(^|;)(${_prefix})" "\\1" _tmp_list "${_tmp_list}") + # append 'prefix' to each element + string(REGEX REPLACE "([^;]+)" "${_prefix}\\1" _tmp_list "${_tmp_list}") + # transform cmake list into a space delimited list + string(REPLACE ";" " " _tmp_list "${_tmp_list}") + set(${_result} "${_tmp_list}" PARENT_SCOPE) +endfunction() + +# Helper function for splitting full library paths into [dir, name] and merging repetitive dir entries +function(split_library_dirs _libraries _base_library_dir _library_dirs_var _library_names_var) + set(libdirs "${_base_library_dir}") + set(libs "") + foreach (l IN LISTS _libraries) + get_filename_component(lDir "${l}" DIRECTORY) + if (lDir) + get_filename_component(lDir "${lDir}" REALPATH) + endif() + get_filename_component(lFile "${l}" NAME_WE) + string(REPLACE "${_SHARED_LIBRARY_PREFIX}" "" lFile "${lFile}") + list(APPEND libdirs "${lDir}") + list(APPEND libs "${lFile}") + endforeach() + list(REMOVE_DUPLICATES libdirs) + list(REMOVE_AT libdirs 0) # as it is the base libdir and will be handled separately + + set(${_library_dirs_var} "${libdirs}" PARENT_SCOPE) + set(${_library_names_var} "${libs}" PARENT_SCOPE) +endfunction() + +split_library_dirs("${_TARGET_INTERFACE_LINK_LIBRARIES}" "${CMAKE_INSTALL_PREFIX}/${_INSTALL_LIBDIR}" _lib_dirs _library_names) +set(_linker_options "${_library_names}") +list(FILTER _linker_options INCLUDE REGEX "^-.*") +list(FILTER _library_names EXCLUDE REGEX "^-.*") +cmake_list_to_pkg_config(_libs "${_library_names}" "-l") +list(JOIN _linker_options " " _linker_options) +string(JOIN " " _libs "${_linker_options}" "${_libs}") +list(LENGTH _lib_dirs _additional_libdirs_count) +if (_additional_libdirs_count GREATER 0) + cmake_list_to_pkg_config(_additional_libdirs "${_lib_dirs}" "-L") + set(_interface_link_libraries "${_additional_libdirs} ${_libs}") +else() + set(_interface_link_libraries "${_libs}") +endif() + +cmake_list_to_pkg_config(_interface_definitions "${_TARGET_INTERFACE_DEFINITIONS}" "-D") +cmake_list_to_pkg_config(_interface_include_dirs "${_TARGET_INTERFACE_INCLUDE_DIRS}" "-I") +set(_interface_compile_options "${_TARGET_INTERFACE_COMPILE_OPTIONS}") +string(REPLACE ";" " " _interface_compile_options "${_interface_compile_options}") + +configure_file("@_pkg_config_file_template_filename@" "@_generate_target_dir@/@_package_name@.pc" @ONLY) +file(INSTALL "@_generate_target_dir@/@_package_name@.pc" DESTINATION "@CMAKE_INSTALL_FULL_LIBDIR@/pkgconfig") diff --git a/cmake/Modules/GeneratePkgConfig/pkg-config.cmake.in b/cmake/Modules/GeneratePkgConfig/pkg-config.cmake.in new file mode 100644 index 0000000..4354134 --- /dev/null +++ b/cmake/Modules/GeneratePkgConfig/pkg-config.cmake.in @@ -0,0 +1,8 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +libdir=${prefix}/@_INSTALL_LIBDIR@ + +Name: @_PROJECT_NAME@ +Description: @_PROJECT_DESCRIPTION@ +Version: @_PROJECT_VERSION@ +Libs: -L${libdir} -l@_TARGET_OUTPUT_NAME@ @_interface_link_libraries@ +Cflags: @_interface_compile_options@ @_interface_include_dirs@ @_interface_definitions@ diff --git a/cmake/Modules/GeneratePkgConfig/target-compile-settings.cmake.in b/cmake/Modules/GeneratePkgConfig/target-compile-settings.cmake.in new file mode 100644 index 0000000..e16d7bc --- /dev/null +++ b/cmake/Modules/GeneratePkgConfig/target-compile-settings.cmake.in @@ -0,0 +1,13 @@ +set(_TARGET_INTERFACE_LINK_LIBRARIES "@_interface_link_libraries@") +set(_TARGET_INTERFACE_COMPILE_OPTIONS "@_interface_compile_options@") +set(_TARGET_INTERFACE_INCLUDE_DIRS "@_interface_include_dirs@") +set(_TARGET_INTERFACE_DEFINITIONS "@_interface_definitions@") +set(_TARGET_OUTPUT_NAME "@_output_name@") + +set(_INSTALL_LIBDIR "@CMAKE_INSTALL_LIBDIR@") +set(_INSTALL_INCLUDEDIR "@CMAKE_INSTALL_INCLUDEDIR@") +set(_SHARED_LIBRARY_PREFIX "@CMAKE_SHARED_LIBRARY_PREFIX@") + +set(_PROJECT_NAME "@PROJECT_NAME@") +set(_PROJECT_DESCRIPTION "@PROJECT_DESCRIPTION@") +set(_PROJECT_VERSION "@PROJECT_VERSION@") diff --git a/cmake/Modules/LibtorrentMacros.cmake b/cmake/Modules/LibtorrentMacros.cmake new file mode 100644 index 0000000..e3500c7 --- /dev/null +++ b/cmake/Modules/LibtorrentMacros.cmake @@ -0,0 +1,80 @@ +# Various helper function and macros for building libtorrent + +include(FeatureSummary) + +# macro for issuing option() and add_feature_info() in a single call. +# Synopsis: +# feature_option( ) +macro(feature_option _name _description _default) + option(${_name} "${_description}" ${_default}) + add_feature_info(${_name} ${_name} "${_description}") +endmacro() + +# function to add a simple build option which controls compile definition(s) for a target. +# Synopsis: +# target_optional_compile_definitions( [FEATURE] +# NAME DESCRIPTION DEFAULT +# [ENABLED [enabled_compile_definitions...]] +# [DISABLED [disabled_compile_defnitions...]] +# ) +# NAME, DESCRIPTION and DEFAULT are passed to option() call +# if FEATURE is given, they are passed to add_feature_info() +# ENABLED lists compile definitions that will be set on when option is enabled, +# DISABLED lists definitions that will be set otherwise +function(target_optional_compile_definitions _target _scope) + set(options FEATURE) + set(oneValueArgs NAME DESCRIPTION DEFAULT) + set(multiValueArgs ENABLED DISABLED) + cmake_parse_arguments(TOCD ${options} "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + option(${TOCD_NAME} "${TOCD_DESCRIPTION}" ${TOCD_DEFAULT}) + if (${${TOCD_NAME}}) + target_compile_definitions(${_target} ${_scope} ${TOCD_ENABLED}) + else() + target_compile_definitions(${_target} ${_scope} ${TOCD_DISABLED}) + endif() + if(${TOCD_FEATURE}) + add_feature_info(${TOCD_NAME} ${TOCD_NAME} "${TOCD_DESCRIPTION}") + endif() +endfunction() + +# a helper macro that calls find_package() and appends the package (if found) to the +# _package_dependencies list, which can be used later to generate package config file +macro(find_public_dependency _name) + find_package(${_name} ${ARGN}) + string(TOUPPER "${_name}" _name_uppercased) + if (${_name}_FOUND OR ${_name_uppercased}_FOUND) + # Dependencies to be used below for generating Config.cmake file + # We don't need the 'REQUIRED' argument there + set(_args "${_name}") + list(APPEND _args "${ARGN}") + list(REMOVE_ITEM _args "REQUIRED") + list(REMOVE_ITEM _args "") # just in case + string(REPLACE ";" " " _args "${_args}") + list(APPEND _package_dependencies "${_args}") + endif() +endmacro() + +# function for parsing version variables that are set in version.hpp file +# the version identifiers there are defined as follows: +# #define LIBTORRENT_VERSION_MAJOR 1 +# #define LIBTORRENT_VERSION_MINOR 2 +# #define LIBTORRENT_VERSION_TINY 0 + +function(read_version _verFile _outVarMajor _outVarMinor _outVarTiny) + file(STRINGS ${_verFile} verFileContents REGEX ".+LIBTORRENT_VERSION_[A-Z]+.[0-9]+.*") +# message(STATUS "version file contents: ${verFileContents}") + # the verFileContents variable contains something like the following: + # #define LIBTORRENT_VERSION_MAJOR 1;#define LIBTORRENT_VERSION_MINOR 2;#define LIBTORRENT_VERSION_TINY 0 + set(_regex ".+_MAJOR +([0-9]+);.+_MINOR +([0-9]+);.+_TINY +([0-9]+)") + # note quotes around _regex, they are needed because the variable contains semicolons + string(REGEX MATCH "${_regex}" _tmp "${verFileContents}") + if (NOT _tmp) + message(FATAL_ERROR "Could not detect project version number from ${_verFile}") + endif() + +# message(STATUS "Matched version string: ${_tmp}") + + set(${_outVarMajor} ${CMAKE_MATCH_1} PARENT_SCOPE) + set(${_outVarMinor} ${CMAKE_MATCH_2} PARENT_SCOPE) + set(${_outVarTiny} ${CMAKE_MATCH_3} PARENT_SCOPE) +endfunction() diff --git a/cmake/Modules/ucm_flags.cmake b/cmake/Modules/ucm_flags.cmake new file mode 100644 index 0000000..7b3af2a --- /dev/null +++ b/cmake/Modules/ucm_flags.cmake @@ -0,0 +1,118 @@ +# taken from https://github.com/onqtam/ucm/blob/master/cmake/ucm.cmake + +# +# ucm.cmake - useful cmake macros +# +# Copyright (c) 2016 Viktor Kirilov +# +# Distributed under the MIT Software License +# See accompanying file LICENSE.txt or copy at +# https://opensource.org/licenses/MIT +# +# The documentation can be found at the library's page: +# https://github.com/onqtam/ucm + +include(CMakeParseArguments) + +# ucm_gather_flags +# Gathers all lists of flags for printing or manipulation +macro(ucm_gather_flags with_linker result) + set(${result} "") + # add the main flags without a config + list(APPEND ${result} CMAKE_C_FLAGS) + list(APPEND ${result} CMAKE_CXX_FLAGS) + if(${with_linker}) + list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS) + list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS) + list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS) + list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS) + endif() + + if("${CMAKE_CONFIGURATION_TYPES}" STREQUAL "" AND NOT "${CMAKE_BUILD_TYPE}" STREQUAL "") + # handle single config generators - like makefiles/ninja - when CMAKE_BUILD_TYPE is set + string(TOUPPER ${CMAKE_BUILD_TYPE} config) + list(APPEND ${result} CMAKE_C_FLAGS_${config}) + list(APPEND ${result} CMAKE_CXX_FLAGS_${config}) + if(${with_linker}) + list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS_${config}) + endif() + else() + # handle multi config generators (like msvc, xcode) + foreach(config ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${config} config) + list(APPEND ${result} CMAKE_C_FLAGS_${config}) + list(APPEND ${result} CMAKE_CXX_FLAGS_${config}) + if(${with_linker}) + list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS_${config}) + endif() + endforeach() + endif() +endmacro() + +# ucm_set_runtime +# Sets the runtime (static/dynamic) for msvc/gcc +macro(ucm_set_runtime) + cmake_parse_arguments(ARG "STATIC;DYNAMIC" "" "" ${ARGN}) + + if(ARG_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "unrecognized arguments: ${ARG_UNPARSED_ARGUMENTS}") + endif() + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" STREQUAL "") + message(AUTHOR_WARNING "ucm_set_runtime() does not support clang yet!") + endif() + + ucm_gather_flags(0 flags_configs) + + # add/replace the flags + # note that if the user has messed with the flags directly this function might fail + # - for example if with MSVC and the user has removed the flags - here we just switch/replace them + if("${ARG_STATIC}") + foreach(flags ${flags_configs}) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + if(NOT ${flags} MATCHES "-static-libstdc\\+\\+") + set(${flags} "${${flags}} -static-libstdc++") + endif() + if(NOT ${flags} MATCHES "-static-libgcc") + set(${flags} "${${flags}} -static-libgcc") + endif() + elseif(MSVC) + if(${flags} MATCHES "/MD") + string(REGEX REPLACE "/MD" "/MT" ${flags} "${${flags}}") + endif() + endif() + endforeach() + elseif("${ARG_DYNAMIC}") + foreach(flags ${flags_configs}) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + if(${flags} MATCHES "-static-libstdc\\+\\+") + string(REGEX REPLACE "-static-libstdc\\+\\+" "" ${flags} "${${flags}}") + endif() + if(${flags} MATCHES "-static-libgcc") + string(REGEX REPLACE "-static-libgcc" "" ${flags} "${${flags}}") + endif() + elseif(MSVC) + if(${flags} MATCHES "/MT") + string(REGEX REPLACE "/MT" "/MD" ${flags} "${${flags}}") + endif() + endif() + endforeach() + endif() +endmacro() + +# ucm_print_flags +# Prints all compiler flags for all configurations +macro(ucm_print_flags) + ucm_gather_flags(1 flags_configs) + message("") + foreach(flags ${flags_configs}) + message("${flags}: ${${flags}}") + endforeach() + message("") +endmacro() diff --git a/deps/asio-gnutls/Jamfile b/deps/asio-gnutls/Jamfile new file mode 100644 index 0000000..7b8c575 --- /dev/null +++ b/deps/asio-gnutls/Jamfile @@ -0,0 +1,6 @@ +alias asio-gnutls + : # no sources + : # no build requirements + : # no default build + : ./include ; + diff --git a/deps/asio-gnutls/LICENSE_1_0.txt b/deps/asio-gnutls/LICENSE_1_0.txt new file mode 100644 index 0000000..36b7cd9 --- /dev/null +++ b/deps/asio-gnutls/LICENSE_1_0.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/deps/asio-gnutls/README.md b/deps/asio-gnutls/README.md new file mode 100644 index 0000000..28db2f9 --- /dev/null +++ b/deps/asio-gnutls/README.md @@ -0,0 +1,17 @@ +# boost-asio-gnutls +GnuTLS wrapper for Boost.Asio + +## Usage + +Add `include` as include directory for your project, then include the header `boost/asio/gnutls.hpp`. +Don't forget to link against GnuTLS instead of OpenSSL. + +The two classes `context` and `stream` in `boost::asio::gnutls` mimic the ones in `boost::asio::ssl`. + +## Test + +From the boost root directory, run: +``` +b2 [PATH_TO_THIS_REPOSITORY]/test/gnutls +``` + diff --git a/deps/asio-gnutls/include/boost/asio/gnutls.hpp b/deps/asio-gnutls/include/boost/asio/gnutls.hpp new file mode 100644 index 0000000..11fb480 --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls.hpp @@ -0,0 +1,23 @@ +// +// gnutls.hpp +// ~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_HPP +#define BOOST_ASIO_GNUTLS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#endif // BOOST_ASIO_GNUTLS_HPP diff --git a/deps/asio-gnutls/include/boost/asio/gnutls/context.hpp b/deps/asio-gnutls/include/boost/asio/gnutls/context.hpp new file mode 100644 index 0000000..6582312 --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls/context.hpp @@ -0,0 +1,404 @@ +// +// gnutls/context.hpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_CONTEXT_HPP +#define BOOST_ASIO_GNUTLS_CONTEXT_HPP + +#include "context_base.hpp" +#include "error.hpp" +#include "verify_context.hpp" + +#include +#include + +#ifndef BOOST_NO_EXCEPTIONS +#include +#endif + +#include + +#include +#include +#include +#include +#include + +namespace boost { +namespace asio { +namespace gnutls { + +class stream_base; +template class stream; + +using const_buffer = boost::asio::const_buffer; + +class context : public context_base +{ +public: + enum password_purpose + { + for_reading, + for_writing + }; + + explicit context(method m) + : m_impl(std::make_shared(this, m)) + {} + context(context&& other) { *this = std::move(other); } + context(const context& other) = delete; + ~context() + { + if (m_impl) m_impl->parent = nullptr; + } + + context& operator=(context&& other) + { + m_impl = std::move(other.m_impl); + m_impl->parent = this; + return *this; + } + + native_handle_type native_handle() { return m_impl->cred; } + +#ifndef BOOST_NO_EXCEPTIONS + void set_options(options opts) + { + error_code ec; + set_options(opts, ec); + } +#endif + + error_code set_options(options opts, error_code& ec) + { + m_impl->opts |= opts; + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void clear_options() + { + error_code ec; + clear_options(ec); + } +#endif + + void clear_options(error_code& ec) { m_impl->opts = 0; } + +#ifndef BOOST_NO_EXCEPTIONS + void set_default_verify_paths() + { + error_code ec; + set_default_verify_paths(ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code set_default_verify_paths(error_code& ec) + { + // Returns the number of certificates processed + int ret = gnutls_certificate_set_x509_system_trust(m_impl->cred); + if (ret < 0) ec = error_code(ret, error::get_ssl_category()); + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void set_verify_mode(verify_mode v) + { + error_code ec; + set_verify_mode(v, ec); + } +#endif + + error_code set_verify_mode(verify_mode v, error_code& ec) + { + m_impl->verify = v; + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void set_verify_depth(int depth) + { + error_code ec; + set_verify_depth(depth, ec); + } +#endif + + error_code set_verify_depth(int depth, error_code& ec) + { + unsigned int const max_bits = 8200; // default + unsigned int const max_depth = static_cast(depth); + gnutls_certificate_set_verify_limits(m_impl->cred, max_bits, max_depth); + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + template void set_verify_callback(VerifyCallback callback) + { + error_code ec; + set_verify_callback(callback, ec); + } +#endif + + template + error_code set_verify_callback(VerifyCallback callback, error_code& ec) + { + m_impl->verify_callback = callback; + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + template void set_password_callback(PasswordCallback callback) + { + error_code ec; + set_password_callback(callback, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + template + error_code set_password_callback(PasswordCallback callback, error_code& ec) + { + m_impl->password_callback = callback; + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void use_certificate_file(std::string const& filename, file_format format) + { + error_code ec; + use_certificate_file(filename, format, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code use_certificate_file(std::string const& filename, file_format, error_code& ec) + { + m_impl->certificate_file = filename; + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void use_private_key_file(std::string const& filename, file_format format) + { + error_code ec; + use_private_key_file(filename, format, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code use_private_key_file(std::string const& filename, file_format format, error_code& ec) + { + if (m_impl->certificate_file.empty()) + return ec = boost::asio::error::operation_not_supported; + + std::size_t const max_len = 256; + std::string pass; + if (m_impl->password_callback) pass = m_impl->password_callback(max_len, for_reading); + + m_impl->private_key_file = filename; + int ret = gnutls_certificate_set_x509_key_file2(m_impl->cred, + m_impl->certificate_file.c_str(), + m_impl->private_key_file.c_str(), + format == pem ? GNUTLS_X509_FMT_PEM + : GNUTLS_X509_FMT_DER, + pass.c_str(), + 0); + if (ret != GNUTLS_E_SUCCESS) ec = error_code(ret, error::get_ssl_category()); + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void use_tmp_dh_file(std::string const& filename) + { + error_code ec; + use_tmp_dh_file(filename, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code use_tmp_dh_file(std::string const&, error_code& ec) + { + // Unnecessary and discouraged on GnuTLS 3.6.0 or later. + // Since 3.6.0, DH parameters are negotiated following RFC7919. + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void use_certificate(const_buffer const& certificate, file_format format) + { + error_code ec; + use_certificate(certificate, format, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code use_certificate(const_buffer const& certificate, file_format, error_code& ec) + { + m_impl->certificate.assign(static_cast(certificate.data()), + certificate.size()); + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void use_private_key(const_buffer const& private_key, file_format format) + { + error_code ec; + use_private_key(private_key, format, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code use_private_key(const_buffer const& private_key, file_format format, error_code& ec) + { + if (m_impl->certificate.empty()) return ec = boost::asio::error::operation_not_supported; + + m_impl->private_key.assign(reinterpret_cast(private_key.data()), + private_key.size()); + + gnutls_datum_t cert; + cert.data = reinterpret_cast( + const_cast(m_impl->certificate.c_str())); // must be null terminated + cert.size = m_impl->certificate.size(); + + gnutls_datum_t key; + key.data = reinterpret_cast( + const_cast(m_impl->private_key.c_str())); // must be null terminated + key.size = m_impl->private_key.size(); + + int ret = gnutls_certificate_set_x509_key_mem2(m_impl->cred, + &cert, + &key, + format == pem ? GNUTLS_X509_FMT_PEM + : GNUTLS_X509_FMT_DER, + m_impl->passphrase.c_str(), + 0); + if (ret != GNUTLS_E_SUCCESS) ec = error_code(ret, error::get_ssl_category()); + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void load_verify_file(std::string const& ca_file) + { + error_code ec; + load_verify_file(ca_file, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + void load_verify_file(std::string const& ca_file, error_code& ec) + { + int ret = gnutls_certificate_set_x509_trust_file(m_impl->cred, + ca_file.c_str(), + GNUTLS_X509_FMT_PEM); + if (ret < 0) ec = error_code(ret, error::get_ssl_category()); + } + +#ifndef BOOST_NO_EXCEPTIONS + void add_verify_path(std::string const& dir) + { + error_code ec; + add_verify_path(dir, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + void add_verify_path(std::string const& dir, error_code& ec) + { + int ret = gnutls_certificate_set_x509_trust_dir(m_impl->cred, + dir.c_str(), + GNUTLS_X509_FMT_PEM); + if (ret < 0) ec = error_code(ret, error::get_ssl_category()); + } + +#ifndef BOOST_NO_EXCEPTIONS + void use_tmp_dh(const_buffer const& dh) + { + error_code ec; + use_tmp_dh(dh, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code use_tmp_dh(const_buffer const&, error_code& ec) + { + // Unnecessary and discouraged on GnuTLS 3.6.0 or later. + // Since 3.6.0, DH parameters are negotiated following RFC7919. + return ec; + } + + // ---------- SNI extension ---------- + +#ifndef BOOST_NO_EXCEPTIONS + void set_server_name_callback(std::function cb) + { + error_code ec; + set_server_name_callback(cb, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } + +#endif + + error_code set_server_name_callback(std::function cb, + error_code& ec) + { + m_impl->server_name_callback = std::move(cb); + return ec; + } + + // ----------------------------------- + +private: + struct impl + { + impl(context* p_, method m_) + : m(m_) + , parent(p_) + { + int ret = gnutls_certificate_allocate_credentials(&cred); + if (ret != GNUTLS_E_SUCCESS) + throw std::runtime_error("gnutls_certificate_allocate_credentials failed: " + + std::string(gnutls_strerror(ret))); + + gnutls_certificate_set_known_dh_params(cred, GNUTLS_SEC_PARAM_MEDIUM); + } + ~impl() { gnutls_certificate_free_credentials(cred); } + + bool is_server() const { return (static_cast(m) & 0x2) != 0; } + unsigned int tls_version() const { return static_cast(m) >> 16; } + + const method m; + context* parent; + + gnutls_certificate_credentials_t cred; + verify_mode verify = 0; + options opts = 0; + + std::string certificate_file, private_key_file; + std::string certificate, private_key; + std::string passphrase; + + std::function verify_callback; + std::function password_callback; + std::function server_name_callback; + }; + + std::shared_ptr m_impl; + + friend class stream_base; + template friend class stream; +}; + +} // namespace gnutls +} // namespace asio +} // namespace boost + +#include + +#endif diff --git a/deps/asio-gnutls/include/boost/asio/gnutls/context_base.hpp b/deps/asio-gnutls/include/boost/asio/gnutls/context_base.hpp new file mode 100644 index 0000000..c4e90d1 --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls/context_base.hpp @@ -0,0 +1,92 @@ +// +// gnutls/context_base.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_CONTEXT_BASE_HPP +#define BOOST_ASIO_GNUTLS_CONTEXT_BASE_HPP + +#include +#include + +#include + +namespace boost { +namespace asio { +namespace gnutls { + +class stream_base; +template class stream; + +typedef int verify_mode; + +BOOST_ASIO_STATIC_CONSTANT(int, verify_none = 0x00); +BOOST_ASIO_STATIC_CONSTANT(int, verify_peer = 0x01); +BOOST_ASIO_STATIC_CONSTANT(int, verify_fail_if_no_peer_cert = 0x02); +BOOST_ASIO_STATIC_CONSTANT(int, verify_client_once = 0x04); // Ignored + +class context_base +{ +public: + using error_code = boost::system::error_code; + using native_handle_type = gnutls_certificate_credentials_t; + + enum method : int + { + // Any TLS version + tls = 0x0000, + tls_client = 0x0001, + tls_server = 0x0002, + + // Force specific TLS version + tlsv1 = 0x1000, // 0xXY.. => TLS X.Y + tlsv1_client = 0x1001, + tlsv1_server = 0x1002, + tlsv11 = 0x1100, + tlsv11_client = 0x1101, + tlsv11_server = 0x1102, + tlsv12 = 0x1200, + tlsv12_client = 0x1201, + tlsv12_server = 0x1202, + tlsv13 = 0x1300, + tlsv13_client = 0x1301, + tlsv13_server = 0x1302, + + // SSLv3 + TLS (for compatibility only) + sslv23 = 0x0300, + sslv23_client = 0x0301, + sslv23_server = 0x0302, + }; + + enum file_format + { + pem, + der + }; + + typedef long options; + + BOOST_ASIO_STATIC_CONSTANT(long, default_workarounds = 0x01); + BOOST_ASIO_STATIC_CONSTANT(long, single_dh_use = 0x02); // Ignored + BOOST_ASIO_STATIC_CONSTANT(long, no_sslv2 = 0x04); // Ignored, always disabled + BOOST_ASIO_STATIC_CONSTANT(long, no_sslv3 = 0x08); + + using verify_mode = gnutls::verify_mode; + + BOOST_ASIO_STATIC_CONSTANT(int, verify_none = gnutls::verify_none); + BOOST_ASIO_STATIC_CONSTANT(int, verify_peer = gnutls::verify_peer); + BOOST_ASIO_STATIC_CONSTANT(int, + verify_fail_if_no_peer_cert = gnutls::verify_fail_if_no_peer_cert); + BOOST_ASIO_STATIC_CONSTANT(int, verify_client_once = gnutls::verify_client_once); +}; + +} // namespace gnutls +} // namespace asio +} // namespace boost + +#endif diff --git a/deps/asio-gnutls/include/boost/asio/gnutls/error.hpp b/deps/asio-gnutls/include/boost/asio/gnutls/error.hpp new file mode 100644 index 0000000..3b59d03 --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls/error.hpp @@ -0,0 +1,88 @@ +// +// gnutls/error.hpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_ERROR_HPP +#define BOOST_ASIO_GNUTLS_ERROR_HPP + +#include +#include + +#include + +#include + +namespace boost { +namespace asio { +namespace gnutls { + +class error_category : public boost::system::error_category +{ +public: + char const* name() const BOOST_ASIO_ERROR_CATEGORY_NOEXCEPT { return "GnuTLS"; } + + std::string message(int value) const + { + char const* s = gnutls_strerror(value); + return s ? s : "GnuTLS error"; + } +}; + +namespace error { + +enum stream_errors +{ + stream_truncated = 1, + unspecified_system_error = 2, + unexpected_result = 3 +}; + +static const boost::system::error_category& get_ssl_category() +{ + static error_category instance; + return instance; +} + +static const boost::system::error_category& get_stream_category() { return get_ssl_category(); } + +static const auto& ssl_category BOOST_ASIO_UNUSED_VARIABLE = get_ssl_category(); +static const auto& stream_category BOOST_ASIO_UNUSED_VARIABLE = get_stream_category(); + +} // namespace error +} // namespace gnutls +} // namespace asio +} // namespace boost + +namespace boost { +namespace system { + +template<> struct is_error_code_enum +{ + static const bool value = true; +}; + +} // namespace system +} // namespace boost + +namespace boost { +namespace asio { +namespace gnutls { +namespace error { +inline boost::system::error_code make_error_code(stream_errors e) +{ + return boost::system::error_code( + static_cast(e), get_stream_category()); +} + +} // namespace error +} // namespace gnutls +} // namespace asio +} // namespace boost + +#endif diff --git a/deps/asio-gnutls/include/boost/asio/gnutls/host_name_verification.hpp b/deps/asio-gnutls/include/boost/asio/gnutls/host_name_verification.hpp new file mode 100644 index 0000000..243960d --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls/host_name_verification.hpp @@ -0,0 +1,53 @@ +// +// gnutls/host_name_verification.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_HOST_NAME_VERIFICATION_HPP +#define BOOST_ASIO_GNUTLS_HOST_NAME_VERIFICATION_HPP + +#include + +#include + +#include + +namespace boost { +namespace asio { +namespace gnutls { + +// Verifies a certificate against a host_name according to the rules described in RFC 6125. +class host_name_verification +{ +public: + typedef bool result_type; + + explicit host_name_verification(std::string host) + : m_host(std::move(host)) + {} + + // Perform certificate verification. + bool operator()(bool preverified, verify_context& ctx) const + { + // Don't bother looking at certificates that have failed pre-verification. + if (!preverified) return false; + + // Return non-zero for a successful match + return gnutls_x509_crt_check_hostname(ctx.native_handle(), m_host.c_str()) != 0; + } + +private: + // The host name to be checked. + std::string m_host; +}; + +} // namespace gnutls +} // namespace asio +} // namespace boost + +#endif // BOOST_ASIO_GNUTLS_HOST_NAME_VERIFICATION_HPP diff --git a/deps/asio-gnutls/include/boost/asio/gnutls/rfc2818_verification.hpp b/deps/asio-gnutls/include/boost/asio/gnutls/rfc2818_verification.hpp new file mode 100644 index 0000000..ca5c260 --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls/rfc2818_verification.hpp @@ -0,0 +1,28 @@ +// +// gnutls/rfc2818_verification.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_RFC2818_VERIFICATION_HPP +#define BOOST_ASIO_GNUTLS_RFC2818_VERIFICATION_HPP + +#include + +namespace boost { +namespace asio { +namespace gnutls { + +// Verifies a certificate against a hostname according to the rules described in RFC 2818. +// Deprecated, use host_name_verification instead. +using rfc2818_verification = host_name_verification; + +} // namespace gnutls +} // namespace asio +} // namespace boost + +#endif // BOOST_ASIO_GNUTLS_RFC2818_VERIFICATION_HPP diff --git a/deps/asio-gnutls/include/boost/asio/gnutls/stream.hpp b/deps/asio-gnutls/include/boost/asio/gnutls/stream.hpp new file mode 100644 index 0000000..a44a21c --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls/stream.hpp @@ -0,0 +1,942 @@ +// +// gnutls/stream.hpp +// ~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_STREAM_HPP +#define BOOST_ASIO_GNUTLS_STREAM_HPP + +#include "context.hpp" +#include "stream_base.hpp" + +#include + +#ifndef BOOST_NO_EXCEPTIONS +#include +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace asio { +namespace gnutls { + +template class stream : public stream_base +{ +public: + using next_layer_type = NextLayer; + using lowest_layer_type = + typename std::remove_reference::type::lowest_layer_type; + using executor_type = typename std::remove_reference::type::executor_type; + using io_context = boost::asio::io_context; + + template + stream(Arg&& arg, context& ctx) + : stream_base(ctx) + , m_next_layer(std::forward(arg)) + , m_tls_version(ctx.m_impl->tls_version()) + { + ensure_impl(ctx.m_impl->is_server() ? server : client); + } + + stream(stream&& other) + : stream_base(std::move(other)) + , m_next_layer(std::move(other.m_next_layer)) + , m_verify(std::move(other.m_verify)) + , m_verify_callback(std::move(other.m_verify_callback)) + , m_tls_version(other.m_tls_version) + , m_impl(std::move(other.m_impl)) + { + m_impl->parent = this; + } + + stream(stream const& other) = delete; + ~stream() + { + if (m_impl) + { + m_impl->abort(); + m_impl->parent = nullptr; + } + } + + executor_type get_executor() { return m_next_layer.get_executor(); } + io_context& get_io_context() { return m_next_layer.lowest_layer().get_io_context(); } + const lowest_layer_type& lowest_layer() const { return m_next_layer.lowest_layer(); } + lowest_layer_type& lowest_layer() { return m_next_layer.lowest_layer(); } + const next_layer_type& next_layer() const { return m_next_layer; } + next_layer_type& next_layer() { return m_next_layer; } + + native_handle_type native_handle() { return m_impl->session; } + +#ifndef BOOST_NO_EXCEPTIONS + void set_verify_mode(verify_mode v) + { + error_code ec; + set_verify_mode(v, ec); + } +#endif + + // Warning: for clients only (verify_none or verify_peer) + error_code set_verify_mode(verify_mode v, error_code& ec) + { + m_verify = v; + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void set_verify_depth(int depth) + { + error_code ec; + set_verify_depth(depth, ec); + } +#endif + + // Warning: ignored + error_code set_verify_depth(int, error_code& ec) { return ec; } + +#ifndef BOOST_NO_EXCEPTIONS + template void set_verify_callback(VerifyCallback callback) + { + error_code ec; + set_verify_callback(callback, ec); + } +#endif + + template + error_code set_verify_callback(VerifyCallback callback, error_code& ec) + { + m_verify_callback = callback; + return ec; + } + + template + BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, void(error_code)) + async_handshake(handshake_type type, HandshakeHandler&& handler) + { + // If you get an error on the following line it means that your handler does + // not meet the documented type requirements for a HandshakeHandler. + BOOST_ASIO_HANDSHAKE_HANDLER_CHECK(HandshakeHandler, handler) type_check; + + async_callable callable(std::move(handler)); + + if (m_impl->handshake_handler || m_impl->is_handshake_done) + { + post(get_executor(), std::bind(callable, boost::asio::error::operation_not_supported)); + return; + } + + error_code ec; + m_next_layer.non_blocking(true, ec); + if (ec) + { + post(get_executor(), std::bind(callable, ec)); + return; + } + + ensure_impl(type); + m_impl->handshake_handler = std::bind(callable, std::placeholders::_1); + m_impl->handle_handshake(); + return callable.get_completion_result(); + } + + template + BOOST_ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler, void(error_code, std::size_t)) + async_handshake(handshake_type type, + const ConstBufferSequence& buffers, + BufferedHandshakeHandler&& handler) + { + // If you get an error on the following line it means that your handler does + // not meet the documented type requirements for a BufferedHandshakeHandler. + BOOST_ASIO_BUFFERED_HANDSHAKE_HANDLER_CHECK(BufferedHandshakeHandler, handler) type_check; + + async_callable callable( + std::move(handler)); + + async_handshake(type, std::bind(callable, std::placeholders::_1, std::size_t(0))); + return callable.get_completion_result(); + } + + template + BOOST_ASIO_INITFN_RESULT_TYPE(ShutdownHandler, void(error_code)) + async_shutdown(ShutdownHandler&& handler) + { + // If you get an error on the following line it means that your handler does + // not meet the documented type requirements for a ShutdownHandler. + BOOST_ASIO_SHUTDOWN_HANDLER_CHECK(ShutdownHandler, handler) type_check; + + async_callable callable(std::move(handler)); + + if (m_impl->shutdown_handler || !m_impl->is_handshake_done) + { + post(get_executor(), std::bind(callable, boost::asio::error::operation_not_supported)); + return; + } + + error_code ec; + m_next_layer.non_blocking(true, ec); + if (ec) + { + post(get_executor(), std::bind(callable, ec)); + return; + } + + m_impl->abort(); + m_impl->shutdown_handler = std::bind(callable, std::placeholders::_1); + m_impl->handle_shutdown(); + return callable.get_completion_result(); + } + + template + BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, void(error_code, std::size_t)) + async_read_some(const MutableBufferSequence& buffers, ReadHandler&& handler) + { + // If you get an error on the following line it means that your handler does + // not meet the documented type requirements for a ReadHandler. + BOOST_ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; + + async_callable callable(std::move(handler)); + + if (m_impl->read_handler) + { + post(get_executor(), + std::bind(callable, boost::asio::error::operation_not_supported, std::size_t(0))); + return; + } + + error_code ec; + m_next_layer.non_blocking(true, ec); + if (ec) + { + post(get_executor(), std::bind(callable, ec, std::size_t(0))); + return; + } + + std::size_t bytes_added = 0; + for (auto b = buffer_sequence_begin(buffers), end(buffer_sequence_end(buffers)); b != end; + ++b) + { + auto r = *b; // operator -> might be deleted + if (r.size() == 0) continue; + m_impl->read_buffers.push_back(r); + bytes_added += r.size(); + } + + if (bytes_added == 0) + { + // if we're reading 0 bytes, post handler immediately + post(get_executor(), std::bind(callable, error_code(), std::size_t(0))); + return; + } + + m_impl->read_handler = std::bind(callable, std::placeholders::_1, std::placeholders::_2); + m_impl->bytes_read = 0; + m_impl->async_schedule(); + return callable.get_completion_result(); + } + + template + BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, void(error_code, std::size_t)) + async_write_some(const ConstBufferSequence& buffers, WriteHandler&& handler) + { + // If you get an error on the following line it means that your handler does + // not meet the documented type requirements for a WriteHandler. + BOOST_ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; + + async_callable callable(std::move(handler)); + + if (m_impl->write_handler) + { + post(get_executor(), + std::bind(callable, boost::asio::error::operation_not_supported, std::size_t(0))); + return; + } + + error_code ec; + m_next_layer.non_blocking(true, ec); + if (ec) + { + post(get_executor(), std::bind(callable, ec, std::size_t(0))); + return; + } + + size_t bytes_added = 0; + for (auto b = buffer_sequence_begin(buffers), end(buffer_sequence_end(buffers)); b != end; + ++b) + { + auto r = *b; // operator -> might be deleted + if (r.size() == 0) continue; + m_impl->write_buffers.push_back(r); + bytes_added += r.size(); + } + + if (bytes_added == 0) + { + // if we're writing 0 bytes, post handler immediately + post(get_executor(), std::bind(callable, error_code(), std::size_t(0))); + return; + } + + m_impl->write_handler = std::bind(callable, std::placeholders::_1, std::placeholders::_2); + m_impl->bytes_written = 0; + m_impl->async_schedule(); + return callable.get_completion_result(); + } + + void handshake(handshake_type type) + { + error_code ec; + handshake(type, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } + + error_code handshake(handshake_type type, error_code& ec) + { + if (m_impl->is_handshake_done) return ec = boost::asio::error::operation_not_supported; + + ensure_impl(type); + int ret; + do { + ret = gnutls_handshake(m_impl->session); + } while (ret != GNUTLS_E_SUCCESS && !gnutls_error_is_fatal(ret)); + + if (ret == GNUTLS_E_PREMATURE_TERMINATION) + return ec = error::stream_truncated; + else if (ret != GNUTLS_E_SUCCESS) + return ec = error_code(ret, error::get_ssl_category()); + + m_impl->is_handshake_done = true; + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void handshake(handshake_type type, const ConstBufferSequence& buffers) + { + error_code ec; + handshake(type, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + template + error_code handshake(handshake_type type, const ConstBufferSequence& buffers, error_code& ec) + { + handshake(type, ec); + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + void shutdown() + { + error_code ec; + shutdown(ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code shutdown(error_code& ec) + { + int ret; + do { + ret = gnutls_bye(m_impl->session, GNUTLS_SHUT_RDWR); + } while (ret != GNUTLS_E_SUCCESS && !gnutls_error_is_fatal(ret)); + + if (ret == GNUTLS_E_PREMATURE_TERMINATION) + return ec = error::stream_truncated; + else if (ret != GNUTLS_E_SUCCESS) + return ec = error_code(ret, error::get_ssl_category()); + + m_impl->is_handshake_done = false; + return ec; + } + +#ifndef BOOST_NO_EXCEPTIONS + template size_t read_some(const MutableBufferSequence& buffers) + { + error_code ec; + std::size_t bytes_read = read_some(buffers, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + return bytes_read; + } +#endif + + template + size_t read_some(const MutableBufferSequence& buffers, error_code& ec) + { + if (m_impl->read_handler || !m_impl->is_handshake_done) + { + ec = boost::asio::error::operation_not_supported; + return 0; + } + + std::copy(buffer_sequence_begin(buffers), + buffer_sequence_end(buffers), + std::back_inserter(m_impl->read_buffers)); + + std::size_t bytes_read = m_impl->recv_some(ec); + m_impl->read_buffers.clear(); + return bytes_read; + } + +#ifndef BOOST_NO_EXCEPTIONS + template + std::size_t write_some(const ConstBufferSequence& buffers) + { + error_code ec; + std::size_t bytes_written = write_some(buffers, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + return bytes_written; + } +#endif + + template + std::size_t write_some(const ConstBufferSequence& buffers, error_code& ec) + { + if (m_impl->write_handler || !m_impl->is_handshake_done) + { + ec = boost::asio::error::operation_not_supported; + return 0; + } + + std::copy(buffer_sequence_begin(buffers), + buffer_sequence_end(buffers), + std::back_inserter(m_impl->write_buffers)); + + std::size_t bytes_written = m_impl->send_some(ec); + m_impl->write_buffers.clear(); + return bytes_written; + } + + // ---------- SNI extension ---------- + +#ifndef BOOST_NO_EXCEPTIONS + void set_host_name(std::string const& name) + { + error_code ec; + set_host_name(name, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } +#endif + + error_code set_host_name(std::string const& name, error_code& ec) + { + int ret = + gnutls_server_name_set(m_impl->session, GNUTLS_NAME_DNS, name.c_str(), name.size()); + return ec = ret == GNUTLS_E_SUCCESS ? error_code() + : error_code(ret, error::get_ssl_category()); + } + + // ----------------------------------- + +private: + template class async_callable + { + public: + async_callable(Handler&& h) + : m_impl(std::make_shared(std::move(h))) + {} + + void operator()(Args... args) const + { + m_impl->completion.completion_handler(std::forward(args)...); + } + + auto get_completion_result() { return m_impl->completion.result.get(); } + + private: + struct impl + { + impl(Handler&& h) + : handler(std::move(h)) + , completion(handler) + {} + + Handler handler; + boost::asio::async_completion completion; + }; + + std::shared_ptr m_impl; + }; + + enum class direction + { + none, + read, + write + }; + + next_layer_type m_next_layer; + verify_mode m_verify = -1; + std::function m_verify_callback; + unsigned int m_tls_version; // X*10 + Y => TLS X.Y, 0*10 + Z => SSL Z + + struct impl : public std::enable_shared_from_this + { + impl(stream* p, handshake_type t) + : type(t) + , parent(p) + { + int ret = gnutls_init( + &session, (type == client ? GNUTLS_CLIENT : GNUTLS_SERVER) | GNUTLS_NONBLOCK); + if (ret != GNUTLS_E_SUCCESS) + throw std::runtime_error("gnutls_init failed: " + + std::string(gnutls_strerror(ret))); + + gnutls_session_set_ptr(session, this); + gnutls_handshake_set_post_client_hello_function(session, post_client_hello_func); + + gnutls_transport_set_ptr(session, this); + gnutls_transport_set_push_function(session, push_func); + gnutls_transport_set_pull_function(session, pull_func); + + auto context_impl = parent->m_context_impl; + auto const opts = context_impl->opts; + auto const tls_version = parent->m_tls_version; + + std::ostringstream priority; + priority << "NORMAL"; + if (opts & context::default_workarounds) priority << ":%COMPAT"; + if (tls_version > 0 && tls_version < 10 && !(opts & context::no_sslv3)) + priority << ":+VERS-SSL3.0"; + if (tls_version >= 10) + priority << ":-VERS-TLS-ALL:+VERS-TLS" << (tls_version / 10) << '.' + << (tls_version % 10); + + char const* err_pos = nullptr; + ret = gnutls_priority_set_direct(session, priority.str().c_str(), &err_pos); + if (ret != GNUTLS_E_SUCCESS) + throw std::runtime_error("gnutls_priority_set_direct failed for \"" + + priority.str() + + "\": " + std::string(gnutls_strerror(ret))); + + gnutls_certificate_set_verify_function(context_impl->cred, verify_func); + ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, context_impl->cred); + if (ret != GNUTLS_E_SUCCESS) + throw std::runtime_error("gnutls_credentials_set failed: " + + std::string(gnutls_strerror(ret))); + } + + ~impl() { gnutls_deinit(session); } + + template void post(Function&& function) const + { + if (parent) boost::asio::post(parent->get_executor(), std::forward(function)); + } + + void abort() + { + if (auto handler = std::exchange(handshake_handler, nullptr)) + handler(boost::asio::error::operation_aborted); + if (auto handler = std::exchange(shutdown_handler, nullptr)) + handler(boost::asio::error::operation_aborted); + if (auto handler = std::exchange(read_handler, nullptr)) + handler(boost::asio::error::operation_aborted, std::size_t(0)); + if (auto handler = std::exchange(write_handler, nullptr)) + handler(boost::asio::error::operation_aborted, std::size_t(0)); + } + + std::string get_server_name() const + { + char buf[256]; + size_t len = sizeof(buf); + unsigned int type = GNUTLS_NAME_DNS; + int ret = gnutls_server_name_get(session, buf, &len, &type, 0); + return ret == GNUTLS_E_SUCCESS ? std::string(buf, len) : ""; + } + + bool want_read() const { return want_direction == direction::read || read_handler; } + bool want_write() const { return want_direction == direction::write || write_handler; } + + void async_schedule() + { + constexpr auto wait_read = std::remove_reference::type::wait_read; + constexpr auto wait_write = std::remove_reference::type::wait_write; + + if (!parent) return; + auto& next_layer = parent->m_next_layer; + + // Start a read operation if GnuTLS wants one + if (want_read() && !std::exchange(is_reading, true)) + { + if (gnutls_record_check_pending(session) > 0 && read_handler) + handle_read(); + else + next_layer.async_wait(wait_read, + std::bind(&impl::handle_read, + this->shared_from_this(), + std::placeholders::_1)); + } + + // Start a write operation if GnuTLS wants one + if (want_write() && !std::exchange(is_writing, true)) + { + next_layer.async_wait(wait_write, + std::bind(&impl::handle_write, + this->shared_from_this(), + std::placeholders::_1)); + } + } + + void handle_read(error_code ec = {}) + { + namespace error = boost::asio::error; + + is_reading = false; + if (read_handler) + { + if (!ec) bytes_read += recv_some(ec); + + if (ec == error::try_again || ec == error::would_block) return async_schedule(); + + read_buffers.clear(); + auto handler = std::exchange(read_handler, nullptr); + post(std::bind(std::move(handler), ec, std::exchange(bytes_read, std::size_t(0)))); + return; + } + + if (handshake_handler) return handle_handshake(ec); + if (shutdown_handler) return handle_shutdown(ec); + } + + void handle_write(error_code ec = {}) + { + namespace error = boost::asio::error; + + is_writing = false; + if (write_handler) + { + if (!ec) bytes_written += send_some(ec); + + if (ec == error::try_again || ec == error::would_block) return async_schedule(); + + write_buffers.clear(); + auto handler = std::exchange(write_handler, nullptr); + post(std::bind( + std::move(handler), ec, std::exchange(bytes_written, std::size_t(0)))); + } + + if (handshake_handler) return handle_handshake(ec); + if (shutdown_handler) return handle_shutdown(ec); + } + + void handle_handshake(error_code ec = {}) + { + if (!ec) + { + int ret = gnutls_handshake(session); + if (ret == GNUTLS_E_AGAIN) + { + want_direction = gnutls_record_get_direction(session) == 0 ? direction::read + : direction::write; + return async_schedule(); + } + + want_direction = direction::none; + if (ret == GNUTLS_E_SUCCESS) + is_handshake_done = true; + else if (ret == GNUTLS_E_PREMATURE_TERMINATION) + ec = error::stream_truncated; + else + ec = error_code(ret, error::get_ssl_category()); + } + + if (handshake_handler != nullptr) + { + auto handler = std::exchange(handshake_handler, nullptr); + post(std::bind(std::move(handler), ec)); + } + } + + bool is_safe_renegotiation_enabled() + { + return gnutls_safe_renegotiation_status(session) != 0; + } + + void handle_shutdown(error_code ec = {}) + { + if (!ec) + { + int ret = gnutls_bye(session, GNUTLS_SHUT_RDWR); + if (ret == GNUTLS_E_AGAIN) + { + want_direction = gnutls_record_get_direction(session) == 0 ? direction::read + : direction::write; + return async_schedule(); + } + + want_direction = direction::none; + if (ret == GNUTLS_E_SUCCESS) + is_handshake_done = false; + else + ec = error_code(ret, error::get_ssl_category()); + } + + auto handler = std::exchange(shutdown_handler, nullptr); + post(std::bind(std::move(handler), ec)); + } + + std::size_t recv_some(error_code& ec) + { + std::size_t bytes_read = 0; + while (!read_buffers.empty()) + { + auto& front = read_buffers.front(); + int ret = gnutls_record_recv(session, front.data(), front.size()); + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN) + ec = boost::asio::error::would_block; + else if (ret == GNUTLS_E_PREMATURE_TERMINATION) + ec = error::stream_truncated; + else if (ret == GNUTLS_E_REHANDSHAKE && is_safe_renegotiation_enabled()) + handle_handshake(ec); + else if (gnutls_error_is_fatal(ret)) + ec = error_code(ret, error::get_ssl_category()); + else + continue; + + break; + } + + if (front.size() > 0 && ret == 0) + { + ec = boost::asio::error::eof; + break; + } + + front += ret; + bytes_read += ret; + if (front.size() == 0) read_buffers.pop_front(); + + if (gnutls_record_check_pending(session) == 0) break; + } + + if (bytes_read > 0) ec.clear(); + + return bytes_read; + } + + std::size_t send_some(error_code& ec) + { + gnutls_record_cork(session); + while (!write_buffers.empty()) + { + auto& front = write_buffers.front(); + int ret = gnutls_record_send(session, front.data(), front.size()); + if (ret < 0) break; + + front += ret; + if (front.size() == 0) write_buffers.pop_front(); + } + + std::size_t bytes_written = 0; + do { + int ret = gnutls_record_uncork(session, 0); + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN) + ec = boost::asio::error::would_block; + else if (ret == GNUTLS_E_PREMATURE_TERMINATION) + ec = error::stream_truncated; + else if (gnutls_error_is_fatal(ret)) + ec = error_code(ret, error::get_ssl_category()); + else + continue; + + break; + } + + bytes_written += ret; + } while (gnutls_record_check_corked(session) > 0); + + if (bytes_written > 0) ec.clear(); + + return bytes_written; + } + + static ssize_t pull_func(void* ptr, void* buffer, std::size_t size) + { + namespace error = boost::asio::error; + + auto* im = static_cast(ptr); + if (!im->parent) + { + gnutls_transport_set_errno(im->session, ECONNRESET); + return -1; + } + + auto& next_layer = im->parent->m_next_layer; + error_code ec; + std::size_t bytes_read = next_layer.read_some(boost::asio::buffer(buffer, size), ec); + if (ec && ec != error::eof && ec != error::connection_reset) // consider reset as close + { + gnutls_transport_set_errno( + im->session, + (ec == error::try_again || ec == error::would_block) ? EAGAIN : ECONNRESET); + return -1; + } + + gnutls_transport_set_errno(im->session, 0); + return ssize_t(bytes_read); + } + + static ssize_t push_func(void* ptr, const void* data, std::size_t len) + { + namespace error = boost::asio::error; + + auto* im = static_cast(ptr); + if (!im->parent) + { + gnutls_transport_set_errno(im->session, ECONNRESET); + return -1; + } + + auto& next_layer = im->parent->m_next_layer; + error_code ec; + std::size_t bytes_written = + next_layer.write_some(boost::asio::const_buffer(data, len), ec); + if (ec) + { + gnutls_transport_set_errno( + im->session, + (ec == error::try_again || ec == error::would_block) ? EAGAIN : ECONNRESET); + return -1; + } + + gnutls_transport_set_errno(im->session, 0); + return ssize_t(bytes_written); + } + + static int verify_func(gnutls_session_t session) + { + auto* im = static_cast(gnutls_session_get_ptr(session)); + if (!im->parent) return GNUTLS_E_INVALID_SESSION; + auto context_impl = im->parent->m_context_impl; + + auto verify = im->parent->m_verify >= 0 ? im->parent->m_verify : context_impl->verify; + auto verify_callback = im->parent->m_verify_callback ? im->parent->m_verify_callback + : context_impl->verify_callback; + + if (!(verify & context::verify_peer)) + return GNUTLS_E_SUCCESS; // no verification requested + + if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) + return GNUTLS_E_NO_CERTIFICATE_FOUND; + + unsigned int count = 0; + gnutls_datum_t const* array = gnutls_certificate_get_peers(session, &count); + if (!array || count == 0) return GNUTLS_E_NO_CERTIFICATE_FOUND; + + gnutls_x509_crt_t cert; + gnutls_x509_crt_init(&cert); + int ret = gnutls_x509_crt_import(cert, &array[0], GNUTLS_X509_FMT_DER); + if (ret != GNUTLS_E_SUCCESS) + { + gnutls_x509_crt_deinit(cert); + return ret; + } + + bool verified = false; + unsigned int status = 0; + ret = gnutls_certificate_verify_peers2(session, &status); + if (ret == GNUTLS_E_SUCCESS && !(status & GNUTLS_CERT_INVALID)) verified = true; + + if (verify_callback) + { + verify_context ctx(cert); + verified = verify_callback(verified, ctx); + } + + gnutls_x509_crt_deinit(cert); + return verified ? GNUTLS_E_SUCCESS : GNUTLS_E_CERTIFICATE_ERROR; + } + + static int post_client_hello_func(gnutls_session_t session) + { + auto* im = static_cast(gnutls_session_get_ptr(session)); + if (!im->parent) return GNUTLS_E_INVALID_SESSION; + auto context_impl = im->parent->m_context_impl; + + auto& callback = context_impl->server_name_callback; + if (!callback) return GNUTLS_E_SUCCESS; + + if (!callback(*im->parent, im->get_server_name())) return GNUTLS_E_UNRECOGNIZED_NAME; + + // context may have been switched + context_impl = im->parent->m_context_impl; + + // set credentials now + gnutls_certificate_set_verify_function(context_impl->cred, verify_func); + int ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, context_impl->cred); + if (ret != GNUTLS_E_SUCCESS) return ret; + + // set certificate request + auto const verify = context_impl->verify; + gnutls_certificate_request_t req = GNUTLS_CERT_IGNORE; + if (verify & context::verify_peer) + { + if (verify & context::verify_fail_if_no_peer_cert) + req = GNUTLS_CERT_REQUIRE; + else + req = GNUTLS_CERT_REQUEST; + } + gnutls_certificate_server_set_request(session, req); + + return GNUTLS_E_SUCCESS; + } + + const handshake_type type; + stream* parent; + + gnutls_session_t session; + + direction want_direction = direction::none; + bool is_handshake_done = false; + bool is_reading = false; + bool is_writing = false; + + std::function handshake_handler; + std::function shutdown_handler; + std::function read_handler; + std::function write_handler; + + std::list read_buffers; + std::list write_buffers; + + std::size_t bytes_read = 0; + std::size_t bytes_written = 0; + }; + + std::shared_ptr ensure_impl(handshake_type type) + { + if (!m_impl || m_impl->type != type) + if (auto old = std::exchange(m_impl, std::make_shared(this, type))) + { + old->abort(); + old->parent = nullptr; + } + return m_impl; + } + + std::shared_ptr m_impl; +}; + +} // namespace gnutls +} // namespace asio +} // namespace boost + +#endif diff --git a/deps/asio-gnutls/include/boost/asio/gnutls/stream_base.hpp b/deps/asio-gnutls/include/boost/asio/gnutls/stream_base.hpp new file mode 100644 index 0000000..c1bda18 --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls/stream_base.hpp @@ -0,0 +1,71 @@ +// +// gnutls/stream_base.hpp +// ~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_STREAM_BASE_HPP +#define BOOST_ASIO_GNUTLS_STREAM_BASE_HPP + +#include "context.hpp" + +#include + +#include + +#include +#include +#include + +namespace boost { +namespace asio { +namespace gnutls { + +class stream_base +{ +public: + using error_code = boost::system::error_code; + using native_handle_type = gnutls_session_t; + + enum handshake_type + { + client, + server + }; + + stream_base(context& ctx) { set_context(ctx); } + stream_base(stream_base&& other) + : m_context_impl(std::move(other.m_context_impl)) + {} + stream_base(stream_base const& other) = delete; + virtual ~stream_base() = default; + + context& get_context() const + { + if (!m_context_impl->parent) throw std::logic_error("access to destroyed ssl context"); + + return *m_context_impl->parent; + } + + void set_context(context& ctx) { m_context_impl = ctx.m_impl; } + + virtual native_handle_type native_handle() = 0; + +#ifndef BOOST_NO_EXCEPTIONS + virtual void set_host_name(std::string const& name) = 0; +#endif + virtual error_code set_host_name(std::string const& name, error_code& ec) = 0; + +protected: + std::shared_ptr m_context_impl; +}; + +} // namespace gnutls +} // namespace asio +} // namespace boost + +#endif diff --git a/deps/asio-gnutls/include/boost/asio/gnutls/verify_context.hpp b/deps/asio-gnutls/include/boost/asio/gnutls/verify_context.hpp new file mode 100644 index 0000000..22cfca1 --- /dev/null +++ b/deps/asio-gnutls/include/boost/asio/gnutls/verify_context.hpp @@ -0,0 +1,40 @@ +// +// gnutls/context.hpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_ASIO_GNUTLS_VERIFY_CONTEXT_HPP +#define BOOST_ASIO_GNUTLS_VERIFY_CONTEXT_HPP + +#include + +namespace boost { +namespace asio { +namespace gnutls { + +class verify_context +{ +public: + using native_handle_type = gnutls_x509_crt_t; + + explicit verify_context(native_handle_type cert) + : m_cert(cert) + {} + + native_handle_type native_handle() { return m_cert; } + +private: + gnutls_x509_crt_t m_cert; +}; + +} // namespace gnutls +} // namespace asio +} // namespace boost + +#endif + diff --git a/deps/asio-gnutls/test/gnutls/Jamfile.v2 b/deps/asio-gnutls/test/gnutls/Jamfile.v2 new file mode 100644 index 0000000..7dfc11d --- /dev/null +++ b/deps/asio-gnutls/test/gnutls/Jamfile.v2 @@ -0,0 +1,55 @@ +# +# Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +import os ; +import feature ; + +lib gnutls ; +lib crypto ; + +lib socket ; # SOLARIS +lib nsl ; # SOLARIS +lib ws2_32 ; # NT +lib mswsock ; # NT +lib ipv6 ; # HPUX +lib network ; # HAIKU + +local USE_SELECT = + BOOST_ASIO_DISABLE_EPOLL + BOOST_ASIO_DISABLE_KQUEUE + BOOST_ASIO_DISABLE_IOCP + ; + +project + : requirements + ../../include + /boost/system//boost_system + BOOST_ALL_NO_LIB=1 + multi + solaris:socket + solaris:nsl + windows:_WIN32_WINNT=0x0501 + windows,gcc:ws2_32 + windows,gcc:mswsock + windows,gcc-cygwin:__USE_W32_SOCKETS + hpux,gcc:_XOPEN_SOURCE_EXTENDED + hpux:ipv6 + haiku:network + ; + +test-suite "asio-gnutls" : + [ compile context_base.cpp ] + [ compile context_base.cpp : $(USE_SELECT) : context_base_select ] + [ compile context.cpp ] + [ compile context.cpp : $(USE_SELECT) : context_select ] + [ compile error.cpp ] + [ compile error.cpp : $(USE_SELECT) : error_select ] + [ compile stream_base.cpp ] + [ compile stream_base.cpp : $(USE_SELECT) : stream_base_select ] + [ compile stream.cpp ] + [ compile stream.cpp : $(USE_SELECT) : stream_select ] + ; diff --git a/deps/asio-gnutls/test/gnutls/context.cpp b/deps/asio-gnutls/test/gnutls/context.cpp new file mode 100644 index 0000000..c832dc8 --- /dev/null +++ b/deps/asio-gnutls/test/gnutls/context.cpp @@ -0,0 +1,65 @@ +// +// context.cpp +// ~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include "../unit_test.hpp" + +//------------------------------------------------------------------------------ + +// gnutls_context_compile test +// ~~~~~~~~~~~~~~~~~~~~~~~ +// The following test checks that all public member functions on the class +// gnutls::context compile and link correctly. Runtime failures are ignored. + +namespace gnutls_context_compile { + +bool verify_callback(bool, boost::asio::gnutls::verify_context&) { return false; } + +bool server_name_callback(boost::asio::gnutls::stream_base& s, std::string name) { return false; } + +void test() +{ + using namespace boost::asio; + + try + { + boost::asio::gnutls::context context(boost::asio::gnutls::context::tls); + boost::system::error_code ec; + + context.set_verify_mode(gnutls::context::verify_none); + context.set_verify_mode(gnutls::context::verify_none, ec); + + context.set_verify_depth(1); + context.set_verify_depth(1, ec); + + context.set_verify_callback(verify_callback); + context.set_verify_callback(verify_callback, ec); + + // SNI extension + + context.set_server_name_callback(server_name_callback); + context.set_server_name_callback(server_name_callback, ec); + } + catch (std::exception&) + {} +} + +} // namespace gnutls_context_compile + +//------------------------------------------------------------------------------ + +BOOST_ASIO_TEST_SUITE("gnutls/context", BOOST_ASIO_TEST_CASE(gnutls_context_compile::test)) diff --git a/deps/asio-gnutls/test/gnutls/context_base.cpp b/deps/asio-gnutls/test/gnutls/context_base.cpp new file mode 100644 index 0000000..5a474be --- /dev/null +++ b/deps/asio-gnutls/test/gnutls/context_base.cpp @@ -0,0 +1,21 @@ +// +// context_base.cpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include "../unit_test.hpp" + +BOOST_ASIO_TEST_SUITE("gnutls/context_base", BOOST_ASIO_TEST_CASE(null_test)) diff --git a/deps/asio-gnutls/test/gnutls/error.cpp b/deps/asio-gnutls/test/gnutls/error.cpp new file mode 100644 index 0000000..af47a5c --- /dev/null +++ b/deps/asio-gnutls/test/gnutls/error.cpp @@ -0,0 +1,21 @@ +// +// error.cpp +// ~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include "../unit_test.hpp" + +BOOST_ASIO_TEST_SUITE("gnutls/error", BOOST_ASIO_TEST_CASE(null_test)) diff --git a/deps/asio-gnutls/test/gnutls/host_name_verification.cpp b/deps/asio-gnutls/test/gnutls/host_name_verification.cpp new file mode 100644 index 0000000..a161061 --- /dev/null +++ b/deps/asio-gnutls/test/gnutls/host_name_verification.cpp @@ -0,0 +1,21 @@ +// +// host_name_verification.cpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include "../unit_test.hpp" + +BOOST_ASIO_TEST_SUITE("gnutls/host_name_verification", BOOST_ASIO_TEST_CASE(null_test)) diff --git a/deps/asio-gnutls/test/gnutls/rfc2818_verification.cpp b/deps/asio-gnutls/test/gnutls/rfc2818_verification.cpp new file mode 100644 index 0000000..9a524f6 --- /dev/null +++ b/deps/asio-gnutls/test/gnutls/rfc2818_verification.cpp @@ -0,0 +1,21 @@ +// +// rfc2818_verification.cpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include "../unit_test.hpp" + +BOOST_ASIO_TEST_SUITE("gnutls/rfc2818_verification", BOOST_ASIO_TEST_CASE(null_test)) diff --git a/deps/asio-gnutls/test/gnutls/stream.cpp b/deps/asio-gnutls/test/gnutls/stream.cpp new file mode 100644 index 0000000..cbe13cb --- /dev/null +++ b/deps/asio-gnutls/test/gnutls/stream.cpp @@ -0,0 +1,149 @@ +// +// stream.cpp +// ~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include "../unit_test.hpp" +#include +#include + +//------------------------------------------------------------------------------ + +// gnutls_stream_compile test +// ~~~~~~~~~~~~~~~~~~~~~~~ +// The following test checks that all public member functions on the class +// gnutls::stream::socket compile and link correctly. Runtime failures are ignored. + +namespace gnutls_stream_compile { + +bool verify_callback(bool, boost::asio::gnutls::verify_context&) { return false; } + +void handshake_handler(const boost::system::error_code&) {} + +void buffered_handshake_handler(const boost::system::error_code&, std::size_t) {} + +void shutdown_handler(const boost::system::error_code&) {} + +void write_some_handler(const boost::system::error_code&, std::size_t) {} + +void read_some_handler(const boost::system::error_code&, std::size_t) {} + +void test() +{ + using namespace boost::asio; + namespace ip = boost::asio::ip; + + try + { + io_context ioc; + char mutable_char_buffer[128] = ""; + const char const_char_buffer[128] = ""; + std::string hostname = "hostname"; + boost::asio::gnutls::context context(boost::asio::gnutls::context::tls); + boost::system::error_code ec; + + // gnutls::stream constructors. + + gnutls::stream stream1(ioc, context); + ip::tcp::socket socket1(ioc, ip::tcp::v4()); + gnutls::stream stream2(socket1, context); + + // basic_io_object functions. + + gnutls::stream::executor_type ex = stream1.get_executor(); + (void)ex; + + // gnutls::stream functions. + + stream1.set_verify_mode(gnutls::verify_none); + stream1.set_verify_mode(gnutls::verify_none, ec); + + stream1.set_verify_depth(1); + stream1.set_verify_depth(1, ec); + + stream1.set_verify_callback(verify_callback); + stream1.set_verify_callback(verify_callback, ec); + + gnutls_session_t session1 = stream1.native_handle(); + (void)session1; + + gnutls::stream::lowest_layer_type& lowest_layer = stream1.lowest_layer(); + (void)lowest_layer; + + const gnutls::stream& stream3 = stream1; + const gnutls::stream::lowest_layer_type& lowest_layer2 = + stream3.lowest_layer(); + (void)lowest_layer2; + + stream1.handshake(gnutls::stream_base::client); + stream1.handshake(gnutls::stream_base::server); + stream1.handshake(gnutls::stream_base::client, ec); + stream1.handshake(gnutls::stream_base::server, ec); + + stream1.handshake(gnutls::stream_base::client, buffer(mutable_char_buffer)); + stream1.handshake(gnutls::stream_base::server, buffer(mutable_char_buffer)); + stream1.handshake(gnutls::stream_base::client, buffer(const_char_buffer)); + stream1.handshake(gnutls::stream_base::server, buffer(const_char_buffer)); + stream1.handshake(gnutls::stream_base::client, buffer(mutable_char_buffer), ec); + stream1.handshake(gnutls::stream_base::server, buffer(mutable_char_buffer), ec); + stream1.handshake(gnutls::stream_base::client, buffer(const_char_buffer), ec); + stream1.handshake(gnutls::stream_base::server, buffer(const_char_buffer), ec); + + stream1.async_handshake(gnutls::stream_base::client, handshake_handler); + stream1.async_handshake(gnutls::stream_base::server, handshake_handler); + + stream1.async_handshake( + gnutls::stream_base::client, buffer(mutable_char_buffer), buffered_handshake_handler); + stream1.async_handshake( + gnutls::stream_base::server, buffer(mutable_char_buffer), buffered_handshake_handler); + stream1.async_handshake( + gnutls::stream_base::client, buffer(const_char_buffer), buffered_handshake_handler); + stream1.async_handshake( + gnutls::stream_base::server, buffer(const_char_buffer), buffered_handshake_handler); + + stream1.shutdown(); + stream1.shutdown(ec); + + stream1.async_shutdown(shutdown_handler); + + stream1.write_some(buffer(mutable_char_buffer)); + stream1.write_some(buffer(const_char_buffer)); + stream1.write_some(buffer(mutable_char_buffer), ec); + stream1.write_some(buffer(const_char_buffer), ec); + + stream1.async_write_some(buffer(mutable_char_buffer), write_some_handler); + stream1.async_write_some(buffer(const_char_buffer), write_some_handler); + + stream1.read_some(buffer(mutable_char_buffer)); + stream1.read_some(buffer(mutable_char_buffer), ec); + + stream1.async_read_some(buffer(mutable_char_buffer), read_some_handler); + + // SNI extension + + stream1.set_host_name(hostname); + stream1.set_host_name(hostname, ec); + } + catch (std::exception&) + { + } +} + +} // namespace gnutls_stream_compile + +//------------------------------------------------------------------------------ + +BOOST_ASIO_TEST_SUITE("gnutls/stream", BOOST_ASIO_TEST_CASE(gnutls_stream_compile::test)) diff --git a/deps/asio-gnutls/test/gnutls/stream_base.cpp b/deps/asio-gnutls/test/gnutls/stream_base.cpp new file mode 100644 index 0000000..13215aa --- /dev/null +++ b/deps/asio-gnutls/test/gnutls/stream_base.cpp @@ -0,0 +1,21 @@ +// +// stream_base.cpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Disable autolinking for unit tests. +#if !defined(BOOST_ALL_NO_LIB) +#define BOOST_ALL_NO_LIB 1 +#endif // !defined(BOOST_ALL_NO_LIB) + +// Test that header file is self-contained. +#include + +#include "../unit_test.hpp" + +BOOST_ASIO_TEST_SUITE("gnutls/stream_base", BOOST_ASIO_TEST_CASE(null_test)) diff --git a/deps/asio-gnutls/test/unit_test.hpp b/deps/asio-gnutls/test/unit_test.hpp new file mode 100644 index 0000000..260a381 --- /dev/null +++ b/deps/asio-gnutls/test/unit_test.hpp @@ -0,0 +1,177 @@ +// +// unit_test.hpp +// ~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef UNIT_TEST_HPP +#define UNIT_TEST_HPP + +#include +#include +#include + +#if defined(__sun) +# include // Needed for lrand48. +#endif // defined(__sun) + +#if defined(__BORLANDC__) + +// Prevent use of intrinsic for strcmp. +# include +# undef strcmp + +// Suppress error about condition always being true. +# pragma option -w-ccc + +#endif // defined(__BORLANDC__) + +#if defined(BOOST_ASIO_MSVC) +# pragma warning (disable:4127) +# pragma warning (push) +# pragma warning (disable:4244) +# pragma warning (disable:4702) +#endif // defined(BOOST_ASIO_MSVC) + +#if !defined(BOOST_ASIO_TEST_IOSTREAM) +# define BOOST_ASIO_TEST_IOSTREAM std::cerr +#endif // !defined(BOOST_ASIO_TEST_IOSTREAM) + +namespace boost { +namespace asio { +namespace detail { + +inline const char*& test_name() +{ + static const char* name = 0; + return name; +} + +inline atomic_count& test_errors() +{ + static atomic_count errors(0); + return errors; +} + +inline void begin_test_suite(const char* name) +{ + boost::asio::detail::test_name(); + boost::asio::detail::test_errors(); + BOOST_ASIO_TEST_IOSTREAM << name << " test suite begins" << std::endl; +} + +inline int end_test_suite(const char* name) +{ + BOOST_ASIO_TEST_IOSTREAM << name << " test suite ends" << std::endl; + BOOST_ASIO_TEST_IOSTREAM << "\n*** "; + long errors = boost::asio::detail::test_errors(); + if (errors == 0) + BOOST_ASIO_TEST_IOSTREAM << "No errors detected."; + else if (errors == 1) + BOOST_ASIO_TEST_IOSTREAM << "1 error detected."; + else + BOOST_ASIO_TEST_IOSTREAM << errors << " errors detected." << std::endl; + BOOST_ASIO_TEST_IOSTREAM << std::endl; + return errors == 0 ? 0 : 1; +} + +template +inline void run_test(const char* name) +{ + test_name() = name; + long errors_before = boost::asio::detail::test_errors(); + Test(); + if (test_errors() == errors_before) + BOOST_ASIO_TEST_IOSTREAM << name << " passed" << std::endl; + else + BOOST_ASIO_TEST_IOSTREAM << name << " failed" << std::endl; +} + +template +inline void compile_test(const char* name) +{ + BOOST_ASIO_TEST_IOSTREAM << name << " passed" << std::endl; +} + +#if defined(BOOST_ASIO_NO_EXCEPTIONS) + +template +void throw_exception(const T& t) +{ + BOOST_ASIO_TEST_IOSTREAM << "Exception: " << t.what() << std::endl; + std::abort(); +} + +#endif // defined(BOOST_ASIO_NO_EXCEPTIONS) + +} // namespace detail +} // namespace asio +} // namespace boost + +#define BOOST_ASIO_CHECK(expr) \ + do { if (!(expr)) { \ + BOOST_ASIO_TEST_IOSTREAM << __FILE__ << "(" << __LINE__ << "): " \ + << boost::asio::detail::test_name() << ": " \ + << "check '" << #expr << "' failed" << std::endl; \ + ++boost::asio::detail::test_errors(); \ + } } while (0) + +#define BOOST_ASIO_CHECK_MESSAGE(expr, msg) \ + do { if (!(expr)) { \ + BOOST_ASIO_TEST_IOSTREAM << __FILE__ << "(" << __LINE__ << "): " \ + << boost::asio::detail::test_name() << ": " \ + << msg << std::endl; \ + ++boost::asio::detail::test_errors(); \ + } } while (0) + +#define BOOST_ASIO_WARN_MESSAGE(expr, msg) \ + do { if (!(expr)) { \ + BOOST_ASIO_TEST_IOSTREAM << __FILE__ << "(" << __LINE__ << "): " \ + << boost::asio::detail::test_name() << ": " \ + << msg << std::endl; \ + } } while (0) + +#define BOOST_ASIO_ERROR(msg) \ + do { \ + BOOST_ASIO_TEST_IOSTREAM << __FILE__ << "(" << __LINE__ << "): " \ + << boost::asio::detail::test_name() << ": " \ + << msg << std::endl; \ + ++boost::asio::detail::test_errors(); \ + } while (0) + +#define BOOST_ASIO_TEST_SUITE(name, tests) \ + int main() \ + { \ + boost::asio::detail::begin_test_suite(name); \ + tests \ + return boost::asio::detail::end_test_suite(name); \ + } + +#define BOOST_ASIO_TEST_CASE(test) \ + boost::asio::detail::run_test<&test>(#test); + +#define BOOST_ASIO_COMPILE_TEST_CASE(test) \ + boost::asio::detail::compile_test<&test>(#test); + +inline void null_test() +{ +} + +#if defined(__GNUC__) && defined(_AIX) + +// AIX needs this symbol defined in asio, even if it doesn't do anything. +int test_main(int, char**) +{ +} + +#endif // defined(__GNUC__) && defined(_AIX) + +#if defined(BOOST_ASIO_MSVC) +# pragma warning (pop) +#endif // defined(BOOST_ASIO_MSVC) + +#endif // UNIT_TEST_HPP diff --git a/deps/try_signal/CMakeLists.txt b/deps/try_signal/CMakeLists.txt new file mode 100644 index 0000000..945cd6c --- /dev/null +++ b/deps/try_signal/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12) +project(try_signal) + +add_library(try_signal signal_error_code try_signal) +target_include_directories(try_signal PUBLIC .) + diff --git a/deps/try_signal/Jamfile b/deps/try_signal/Jamfile new file mode 100644 index 0000000..ec001a8 --- /dev/null +++ b/deps/try_signal/Jamfile @@ -0,0 +1,18 @@ +lib try_signal + : # sources + signal_error_code.cpp try_signal.cpp + : # requirements + : # default build + static + : # usage requirements + . + ; + +exe test : test.cpp : try_signal static ; +explicit test ; + +exe example : example.cpp : try_signal static ; +explicit example ; + +install stage_test : test : . ; + diff --git a/deps/try_signal/LICENSE b/deps/try_signal/LICENSE new file mode 100644 index 0000000..1523026 --- /dev/null +++ b/deps/try_signal/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/deps/try_signal/README.rst b/deps/try_signal/README.rst new file mode 100644 index 0000000..22cbe18 --- /dev/null +++ b/deps/try_signal/README.rst @@ -0,0 +1,53 @@ +try_signal +========== + +.. image:: https://travis-ci.org/arvidn/try_signal.svg?branch=master + :target: https://travis-ci.org/arvidn/try_signal + +.. image:: https://ci.appveyor.com/api/projects/status/le8jjroaai8081f1?svg=true + :target: https://ci.appveyor.com/project/arvidn/try-signal/branch/master + +The ``try_signal`` library provide a way to turn signals into C++ exceptions. +This is especially useful when performing disk I/O via memory mapped files, +where I/O errors are reported as ``SIGBUS`` and ``SIGSEGV`` or as structured +exceptions on windows. + +The function ``try_signal`` takes a function object that will be executed once. +If the function causes a signal (or structured exception) to be raised, it will +throw a C++ exception. Note that RAII may not be relied upon within this function. +It may not rely on destructors being called. Stick to simple operations like +memcopy. + +Example:: + + #include + #include + #include + #include "try_signal.hpp" + #include + #include + #include + + int main() try + { + int fd = open("test_file", O_RDWR); + void* map = mmap(nullptr, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + + std::vector buf(1024); + std::iota(buf.begin(), buf.end(), 0); + + // disk full or access after EOF are reported as exceptions + sig::try_signal([&]{ + std::memcpy(map, buf.data(), buf.size()); + }); + + munmap(map, 1024); + close(fd); + return 0; + } + catch (std::exception const& e) + { + fprintf(stderr, "exited with exception: %s\n", e.what()); + return 1; + } + diff --git a/deps/try_signal/signal_error_code.cpp b/deps/try_signal/signal_error_code.cpp new file mode 100644 index 0000000..ae016cf --- /dev/null +++ b/deps/try_signal/signal_error_code.cpp @@ -0,0 +1,209 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include + +#include "signal_error_code.hpp" + +namespace { + + struct signal_error_category : std::error_category + { + const char* name() const noexcept override + { return "signal"; } + std::string message(int ev) const noexcept override + { +#define SIGNAL_CASE(x) case sig::errors::error_code_enum:: x: return #x; + switch (ev) + { + SIGNAL_CASE(abort) + SIGNAL_CASE(alarm) + SIGNAL_CASE(arithmetic_exception) + SIGNAL_CASE(hangup) + SIGNAL_CASE(illegal) + SIGNAL_CASE(interrupt) + SIGNAL_CASE(kill) + SIGNAL_CASE(pipe) + SIGNAL_CASE(quit) + case sig::errors::error_code_enum::segmentation: return "segmentation fault"; + SIGNAL_CASE(terminate) + SIGNAL_CASE(user1) + SIGNAL_CASE(user2) + SIGNAL_CASE(child) + SIGNAL_CASE(cont) + SIGNAL_CASE(stop) + SIGNAL_CASE(terminal_stop) + SIGNAL_CASE(terminal_in) + SIGNAL_CASE(terminal_out) + SIGNAL_CASE(bus) +#ifdef SIGPOLL + SIGNAL_CASE(poll) +#endif + SIGNAL_CASE(profiler) + SIGNAL_CASE(system_call) + SIGNAL_CASE(trap) + SIGNAL_CASE(urgent_data) + SIGNAL_CASE(virtual_timer) + SIGNAL_CASE(cpu_limit) + SIGNAL_CASE(file_size_limit) + default: return "unknown"; + } +#undef SIGNAL_CASE + } + std::error_condition default_error_condition(int ev) const noexcept override + { return {ev, *this}; } + }; +} // anonymous namespace + +namespace sig { +namespace errors { + + std::error_code make_error_code(error_code_enum e) + { + return {e, sig_category()}; + } + + std::error_condition make_error_condition(error_code_enum e) + { + return {e, sig_category()}; + } + +} // namespace errors + +std::error_category& sig_category() +{ + static signal_error_category signal_category; + return signal_category; +} + +#ifdef _WIN32 + +namespace { + sig::errors::error_code_enum map_exception_code(int const ev) + { + switch (ev) + { + case seh_errors::error_code_enum::access_violation: + case seh_errors::error_code_enum::array_bounds_exceeded: + case seh_errors::error_code_enum::guard_page: + case seh_errors::error_code_enum::stack_overflow: + case seh_errors::error_code_enum::flt_stack_check: + case seh_errors::error_code_enum::in_page_error: + return sig::errors::segmentation; + case seh_errors::error_code_enum::breakpoint: + case seh_errors::error_code_enum::single_step: + return sig::errors::trap; + case seh_errors::error_code_enum::datatype_misalignment: + return sig::errors::bus; + case seh_errors::error_code_enum::flt_denormal_operand: + case seh_errors::error_code_enum::flt_divide_by_zero: + case seh_errors::error_code_enum::flt_inexact_result: + case seh_errors::error_code_enum::flt_invalid_operation: + case seh_errors::error_code_enum::flt_overflow: + case seh_errors::error_code_enum::flt_underflow: + case seh_errors::error_code_enum::int_divide_by_zero: + case seh_errors::error_code_enum::int_overflow: + return sig::errors::arithmetic_exception; + case seh_errors::error_code_enum::illegal_instruction: + case seh_errors::error_code_enum::invalid_disposition: + case seh_errors::error_code_enum::priv_instruction: + case seh_errors::error_code_enum::noncontinuable_exception: + case seh_errors::error_code_enum::status_unwind_consolidate: + return sig::errors::illegal; + case seh_errors::error_code_enum::invalid_handle: + return sig::errors::pipe; + default: + return sig::errors::illegal; + } + } + + struct seh_error_category : std::error_category + { + const char* name() const noexcept override + { return "SEH"; } + std::string message(int ev) const noexcept override + { +#define SIGNAL_CASE(x) case sig::seh_errors::error_code_enum:: x: return #x; + switch (ev) + { + SIGNAL_CASE(access_violation) + SIGNAL_CASE(array_bounds_exceeded) + SIGNAL_CASE(guard_page) + SIGNAL_CASE(stack_overflow) + SIGNAL_CASE(flt_stack_check) + SIGNAL_CASE(in_page_error) + SIGNAL_CASE(breakpoint) + SIGNAL_CASE(single_step) + SIGNAL_CASE(datatype_misalignment) + SIGNAL_CASE(flt_denormal_operand) + SIGNAL_CASE(flt_divide_by_zero) + SIGNAL_CASE(flt_inexact_result) + SIGNAL_CASE(flt_invalid_operation) + SIGNAL_CASE(flt_overflow) + SIGNAL_CASE(flt_underflow) + SIGNAL_CASE(int_divide_by_zero) + SIGNAL_CASE(int_overflow) + SIGNAL_CASE(illegal_instruction) + SIGNAL_CASE(invalid_disposition) + SIGNAL_CASE(priv_instruction) + SIGNAL_CASE(noncontinuable_exception) + SIGNAL_CASE(status_unwind_consolidate) + SIGNAL_CASE(invalid_handle) + default: return "unknown"; + } +#undef SIGNAL_CASE + } + std::error_condition default_error_condition(int ev) const noexcept override + { return std::error_condition(map_exception_code(ev), sig_category()); } + }; +} // anonymous namespace + +namespace seh_errors { + + std::error_code make_error_code(error_code_enum e) + { + return {static_cast(e), seh_category()}; + } + +} // namespace errors + +std::error_category& seh_category() +{ + static seh_error_category seh_category; + return seh_category; +} + +#endif + +} // namespace sig + diff --git a/deps/try_signal/signal_error_code.hpp b/deps/try_signal/signal_error_code.hpp new file mode 100644 index 0000000..27a6c64 --- /dev/null +++ b/deps/try_signal/signal_error_code.hpp @@ -0,0 +1,159 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SIGNAL_ERROR_CODE_HPP_INCLUDED +#define SIGNAL_ERROR_CODE_HPP_INCLUDED + +#include +#include + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#ifdef __GNUC__ +#include +#else +#include +#endif +#endif + +namespace sig { +namespace errors { + +#ifdef _WIN32 +#define SIG_ENUM(name, sig) name, +#else +#define SIG_ENUM(name, sig) name = sig, +#endif + + enum error_code_enum + { + SIG_ENUM(abort, SIGABRT) + SIG_ENUM(alarm, SIGALRM) + SIG_ENUM(arithmetic_exception, SIGFPE) + SIG_ENUM(hangup, SIGHUP) + SIG_ENUM(illegal, SIGILL) + SIG_ENUM(interrupt, SIGINT) + SIG_ENUM(kill, SIGKILL) + SIG_ENUM(pipe, SIGPIPE) + SIG_ENUM(quit, SIGQUIT) + SIG_ENUM(segmentation, SIGSEGV) + SIG_ENUM(terminate, SIGTERM) + SIG_ENUM(user1, SIGUSR1) + SIG_ENUM(user2, SIGUSR2) + SIG_ENUM(child, SIGCHLD) + SIG_ENUM(cont, SIGCONT) + SIG_ENUM(stop, SIGSTOP) + SIG_ENUM(terminal_stop, SIGTSTP) + SIG_ENUM(terminal_in, SIGTTIN) + SIG_ENUM(terminal_out, SIGTTOU) + SIG_ENUM(bus, SIGBUS) +#ifdef SIGPOLL + SIG_ENUM(poll, SIGPOLL) +#endif + SIG_ENUM(profiler, SIGPROF) + SIG_ENUM(system_call, SIGSYS) + SIG_ENUM(trap, SIGTRAP) + SIG_ENUM(urgent_data, SIGURG) + SIG_ENUM(virtual_timer, SIGVTALRM) + SIG_ENUM(cpu_limit, SIGXCPU) + SIG_ENUM(file_size_limit, SIGXFSZ) + }; + +#undef SIG_ENUM + + std::error_code make_error_code(error_code_enum e); + std::error_condition make_error_condition(error_code_enum e); + +} // namespace errors + +std::error_category& sig_category(); + +#ifdef _WIN32 +namespace seh_errors { + + enum error_code_enum + { + access_violation = EXCEPTION_ACCESS_VIOLATION, + array_bounds_exceeded = EXCEPTION_ARRAY_BOUNDS_EXCEEDED, + guard_page = EXCEPTION_GUARD_PAGE, + stack_overflow = EXCEPTION_STACK_OVERFLOW, + flt_stack_check = EXCEPTION_FLT_STACK_CHECK, + in_page_error = EXCEPTION_IN_PAGE_ERROR, + breakpoint = EXCEPTION_BREAKPOINT, + single_step = EXCEPTION_SINGLE_STEP, + datatype_misalignment = EXCEPTION_DATATYPE_MISALIGNMENT, + flt_denormal_operand = EXCEPTION_FLT_DENORMAL_OPERAND, + flt_divide_by_zero = EXCEPTION_FLT_DIVIDE_BY_ZERO, + flt_inexact_result = EXCEPTION_FLT_INEXACT_RESULT, + flt_invalid_operation = EXCEPTION_FLT_INVALID_OPERATION, + flt_overflow = EXCEPTION_FLT_OVERFLOW, + flt_underflow = EXCEPTION_FLT_UNDERFLOW, + int_divide_by_zero = EXCEPTION_INT_DIVIDE_BY_ZERO, + int_overflow = EXCEPTION_INT_OVERFLOW, + illegal_instruction = EXCEPTION_ILLEGAL_INSTRUCTION, + invalid_disposition = EXCEPTION_INVALID_DISPOSITION, + priv_instruction = EXCEPTION_PRIV_INSTRUCTION, + noncontinuable_exception = EXCEPTION_NONCONTINUABLE_EXCEPTION, + status_unwind_consolidate = STATUS_UNWIND_CONSOLIDATE, + invalid_handle = EXCEPTION_INVALID_HANDLE, + }; + + std::error_code make_error_code(error_code_enum e); +} + +std::error_category& seh_category(); + +#endif // _WIN32 + +} // namespace sig + +namespace std +{ +template<> +struct is_error_code_enum : std::true_type {}; + +template<> +struct is_error_condition_enum : std::true_type {}; + +#ifdef _WIN32 +template<> +struct is_error_code_enum : std::true_type {}; +#endif + +} // namespace std + +#endif + diff --git a/deps/try_signal/try_signal.cpp b/deps/try_signal/try_signal.cpp new file mode 100644 index 0000000..be5cd8d --- /dev/null +++ b/deps/try_signal/try_signal.cpp @@ -0,0 +1,144 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include + +#include "try_signal.hpp" + +#if !defined _WIN32 +// linux + +namespace sig { +namespace detail { + +namespace { +thread_local sigjmp_buf* jmpbuf = nullptr; +} + +std::atomic_flag once = ATOMIC_FLAG_INIT; + +scoped_jmpbuf::scoped_jmpbuf(sigjmp_buf* ptr) +{ + _previous_ptr = jmpbuf; + jmpbuf = ptr; + std::atomic_signal_fence(std::memory_order_release); +} + +scoped_jmpbuf::~scoped_jmpbuf() { jmpbuf = _previous_ptr; } + +void handler(int const signo, siginfo_t*, void*) +{ + std::atomic_signal_fence(std::memory_order_acquire); + if (jmpbuf) + siglongjmp(*jmpbuf, signo); + + // this signal was not caused within the scope of a try_signal object, + // invoke the default handler + signal(signo, SIG_DFL); + raise(signo); +} + +void setup_handler() +{ + struct sigaction sa; + sa.sa_sigaction = &sig::detail::handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + sigaction(SIGSEGV, &sa, nullptr); + sigaction(SIGBUS, &sa, nullptr); +} + +} // detail namespace +} // sig namespace + +#elif __GNUC__ +// mingw + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +namespace sig { +namespace detail { + +thread_local jmp_buf* jmpbuf = nullptr; + +long CALLBACK handler(EXCEPTION_POINTERS* pointers) +{ + std::atomic_signal_fence(std::memory_order_acquire); + if (jmpbuf) + longjmp(*jmpbuf, pointers->ExceptionRecord->ExceptionCode); + return EXCEPTION_CONTINUE_SEARCH; +} + +scoped_handler::scoped_handler(jmp_buf* ptr) +{ + _previous_ptr = jmpbuf; + jmpbuf = ptr; + std::atomic_signal_fence(std::memory_order_release); + _handle = AddVectoredExceptionHandler(1, sig::detail::handler); +} +scoped_handler::~scoped_handler() +{ + RemoveVectoredExceptionHandler(_handle); + jmpbuf = _previous_ptr; +} + +} // detail namespace +} // sig namespace + +#else +// windows + +#include // for EXCEPTION_* + +namespace sig { +namespace detail { + + // these are the kinds of SEH exceptions we'll translate into C++ exceptions + bool catch_error(int const code) + { + return code == EXCEPTION_IN_PAGE_ERROR + || code == EXCEPTION_ACCESS_VIOLATION + || code == EXCEPTION_ARRAY_BOUNDS_EXCEEDED; + } +} // detail namespace +} // namespace sig + +#endif // _WIN32 + + diff --git a/deps/try_signal/try_signal.hpp b/deps/try_signal/try_signal.hpp new file mode 100644 index 0000000..557d92b --- /dev/null +++ b/deps/try_signal/try_signal.hpp @@ -0,0 +1,49 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRY_SIGNAL_HPP_INCLUDED +#define TRY_SIGNAL_HPP_INCLUDED + +#if !defined _WIN32 +// linux +#include "try_signal_posix.hpp" +#elif __GNUC__ +// mingw +#include "try_signal_mingw.hpp" +#else +// windows +#include "try_signal_msvc.hpp" +#endif + + +#endif // TRY_SIGNAL_HPP_INCLUDED + diff --git a/deps/try_signal/try_signal_mingw.hpp b/deps/try_signal/try_signal_mingw.hpp new file mode 100644 index 0000000..e4db043 --- /dev/null +++ b/deps/try_signal/try_signal_mingw.hpp @@ -0,0 +1,78 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRY_SIGNAL_MINGW_HPP_INCLUDED +#define TRY_SIGNAL_MINGW_HPP_INCLUDED + +#include "signal_error_code.hpp" + +#include // for jmp_buf + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +namespace sig { +namespace detail { + +struct scoped_handler +{ + scoped_handler(jmp_buf* ptr); + ~scoped_handler(); + scoped_handler(scoped_handler const&) = delete; + scoped_handler& operator=(scoped_handler const&) = delete; +private: + void* _handle; + jmp_buf* _previous_ptr; +}; + +} // detail namespace + +template +void try_signal(Fun&& f) +{ + jmp_buf buf; + int const code = setjmp(buf); + // set the thread local jmpbuf pointer, and make sure it's cleared when we + // leave the scope + sig::detail::scoped_handler scope(&buf); + if (code != 0) + throw std::system_error(std::error_code(code, seh_category())); + + f(); +} + +} // sig namespace + +#endif + diff --git a/deps/try_signal/try_signal_msvc.hpp b/deps/try_signal/try_signal_msvc.hpp new file mode 100644 index 0000000..04cc62d --- /dev/null +++ b/deps/try_signal/try_signal_msvc.hpp @@ -0,0 +1,61 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRY_SIGNAL_MSVC_HPP_INCLUDED +#define TRY_SIGNAL_MSVC_HPP_INCLUDED + +#include "signal_error_code.hpp" + +namespace sig { +namespace detail { + +bool catch_error(int const code); + +} // detail namespace + +template +void try_signal(Fun&& f) +{ + __try + { + f(); + } + __except (detail::catch_error(GetExceptionCode())) + { + throw std::system_error(std::error_code(GetExceptionCode(), seh_category())); + } +} + +} // sig namespace + +#endif + diff --git a/deps/try_signal/try_signal_posix.hpp b/deps/try_signal/try_signal_posix.hpp new file mode 100644 index 0000000..2c4615d --- /dev/null +++ b/deps/try_signal/try_signal_posix.hpp @@ -0,0 +1,82 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRY_SIGNAL_POSIX_HPP_INCLUDED +#define TRY_SIGNAL_POSIX_HPP_INCLUDED + +#include "signal_error_code.hpp" +#include // for sigjmp_buf +#include + +namespace sig { + +namespace detail { + +extern std::atomic_flag once; + +struct scoped_jmpbuf +{ + explicit scoped_jmpbuf(sigjmp_buf* ptr); + ~scoped_jmpbuf(); + scoped_jmpbuf(scoped_jmpbuf const&) = delete; + scoped_jmpbuf& operator=(scoped_jmpbuf const&) = delete; +private: + sigjmp_buf* _previous_ptr; +}; + +void handler(int const signo, siginfo_t* si, void*); +void setup_handler(); + +} // detail namespace + +template +void try_signal(Fun&& f) +{ + if (sig::detail::once.test_and_set() == false) { + sig::detail::setup_handler(); + } + + sigjmp_buf buf; + int const sig = sigsetjmp(buf, 1); + // set the thread local jmpbuf pointer, and make sure it's cleared when we + // leave the scope + sig::detail::scoped_jmpbuf scope(&buf); + if (sig != 0) + throw std::system_error(static_cast(sig)); + + f(); +} + +} + +#endif + diff --git a/docs/building.html b/docs/building.html new file mode 100644 index 0000000..c7b061a --- /dev/null +++ b/docs/building.html @@ -0,0 +1,943 @@ + + + + + + + +libtorrent + + + + + + + +
+
+ + libtorrent logo + +
+ + +++ + + + +
Version:2.0.7
+ +
+

downloading and building

+

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

+

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

+
+

Warning

+

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

+

Always make sure that the same TORRENT_* and BOOST_* macros are defined +when you link against libtorrent as when you build it. The simplest way +to see the full list of macros defined is to build libtorrent with +-n -a switches added to b2 command line, which output all compiler +switches.

+

Boost-build supports propagating configuration options to dependencies.

+
+
+
+

building from git

+

To build libtorrent from git you need to clone the libtorrent repository from +github. Note that the git repository depends on other git repositories via +submodules, which also need to be initialized and updated. If you downloaded a +release tarball, you can skip this section.

+
+git clone --recurse-submodules https://github.com/arvidn/libtorrent.git
+
+
+
+

building with boost build

+

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

+
+

build commands

+

Linux:

+
+sudo apt install libboost-tools-dev libboost-dev libboost-system-dev
+echo "using gcc ;" >>~/user-config.jam
+b2 crypto=openssl cxxstd=14 release
+
+

Mac OS:

+
+brew install boost-build boost openssl@1.1
+echo "using darwin ;" >>~/user-config.jam
+b2 crypto=openssl cxxstd=14 release
+
+

Windows (assuming the boost package is saved to C:\boost_1_69_0):

+
+set BOOST_ROOT=c:\boost_1_69_0
+set BOOST_BUILD_PATH=%BOOST_ROOT%\tools\build
+(cd %BOOST_ROOT% && .\bootstrap.bat)
+echo using msvc ; >>%HOMEDRIVE%%HOMEPATH%\user-config.jam
+%BOOST_ROOT%\b2.exe --hash cxxstd=14 release
+
+
+
+

docker file

+

A Docker file is available that's used to build and run the fuzzers, at +OSS-Fuzz.

+
+
+

Step 1: Download boost

+

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

+

You'll find boost here.

+

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

+
+
+

Step 2: Setup BBv2

+

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

+

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

+

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

+

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

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

In a terminal window.

+

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

+
+using msvc : 14.2 ;
+
+

If you use GCC, add the line:

+
+using gcc ;
+
+

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

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

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

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

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

+

Also see the boost-build documentation.

+
+
+

Step 3: Building libtorrent

+

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

+

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

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

Note

+

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

+
+
+

Note

+

Also see the Visual Studio versions.

+
+

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

+

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

+

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

+
+b2 msvc-14.2 variant=release link=static runtime-link=static debug-symbols=on
+
+
+

Note

+

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

+
+
+

Warning

+

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

+
+
+

Note

+

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

+
+

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

+

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

+

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

+
+

Note

+

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

+
+

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

+

Jamfile will look in some default directory for the openssl +headers and libraries. On macOS, it will look for the homebrew openssl package. +On Windows, it will look in C:\OpenSSL-Win32, or C:\OpenSSL-Win64 if +compiling in 64-bit.

+

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

+

The option to link with wolfSSL (by setting the crypto feature to +wolfssl), requires a custom build of wolfSSL using the following +options: --enable-asio --enable-sni --enable-nginx.

+

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

+

To disable linking against any SSL library, set the crypto build feature to +built-in. This will use an embedded version if SHA-1.

+
+
+

Build features

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

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

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

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

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

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

+

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

+

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

+
+
+

Step 4: Installing libtorrent

+

To install libtorrent run b2 with the install target:

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

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

+
+
+

Custom build flags

+

Custom build flags can be passed to the command line via the cflags, +cxxflags and linkflags features. When specifying custom flags, make sure +to build everything from scratch, to not accidentally mix incompatible flags. +Example:

+
+b2 cxxflags=-msse4.1
+
+

Custom flags can also be configured in the toolset, in ~/user-config.jam, +Jamroot.jam or project-config.jam. Example:

+
+using gcc : sse41 : g++ : <cxxflags>-msse4.1 ;
+
+
+
+

Cross compiling

+

To cross compile libtorrent, configure a new toolset for b2 to use. Toolsets +can be configured in ~/user-config.jam, Jamroot.jam or +project-config.jam. The last two live in the libtorrent root directory.

+

A toolset configuration is in this form:

+
+using toolset : version : command-line : features ;
+
+

Toolset is essentially the family of compiler you're setting up, choose from this list.

+

Perhaps the most common ones would be gcc, clang, msvc and +darwin (Apple's version of clang).

+

The version can be left empty to be auto configured, or a custom name can be +used to identify this toolset.

+

The command-line is what to execute to run the compiler. This is also an +opportunity to insert a call to ccache for example.

+

features are boost-build features. Typical features to set here are +<compileflags>, <cflags> and <cxxflags>. For the gcc toolset, +the <archiver> can be set to specify which tool to use to create a static +library/archive. This is especially handy when cross compiling.

+

Here's an example toolset for cross compiling for ARM Linux:

+
+using gcc : arm : arm-linux-gnueabihf-g++ : <archiver>arm-linux-gnueabihf-ar ;
+
+

To build using this toolset, specify gcc-arm as the toolset on the b2 command line. For example:

+
+b2 toolset=gcc-arm
+
+
+
+
+

building with cmake

+

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

+
+

Step 1: Generating the build system

+

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

+
+mkdir build
+
+

and cd there:

+
+cd build
+
+

Run cmake in the build directory, like this:

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

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

+
+

Warning

+

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

+
+

Other build options are:

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

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

+
+
+

Step 2: Building libtorrent

+

In the terminal, run:

+
+ninja
+
+

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

+
+ctest
+
+
+
+
+

building with VCPKG

+

You can download and install libtorrent using the vcpkg dependency manager:

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

The libtorrent port in vcpkg is kept up to date by Microsoft team members and community contributors. +If the version is out of date, please create an issue or pull request on the vcpkg repository.

+
+
+

build configurations

+

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

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

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

+
+
+

building openssl for windows

+

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

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

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

+
+
+

list of macros

+

The following is a list of defines that libtorrent is built with: BOOST_ALL_NO_LIB, +BOOST_ASIO_ENABLE_CANCELIO, BOOST_ASIO_HAS_STD_CHRONO, +BOOST_MULTI_INDEX_DISABLE_SERIALIZATION, BOOST_NO_DEPRECATED, +BOOST_SYSTEM_NO_DEPRECATED

+

Make sure you define the same at compile time for your code to avoid any runtime errors +and other issues.

+

These might change in the future, so it's always best to verify these every time you +upgrade to a new version of libtorrent. The simplest way to see the full list of macros +defined is to build libtorrent with -n -a switches added to b2 command line:

+
+b2 -n -a toolset=msvc-14.2 link=static runtime-link=static boost-link=static variant=release
+
+

This will output all compiler switches, including defines (such as -DBOOST_ASIO_ENABLE_CANCELIO).

+
+ +
+
+
+ + +
+ + diff --git a/docs/building.rst b/docs/building.rst new file mode 100644 index 0000000..d71e2d4 --- /dev/null +++ b/docs/building.rst @@ -0,0 +1,781 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +downloading and building +------------------------ + +To download the latest version of libtorrent, clone the `github repository`__. + +__ https://github.com/arvidn/libtorrent + +The build systems supported "out of the box" in libtorrent are boost-build +cmake. If you still can't build after following these instructions, you can +usually get help in the ``#libtorrent`` IRC channel on ``irc.freenode.net``. + +.. warning:: + + A common mistake when building and linking against libtorrent is + to build with one set of configuration options (#defines) and + link against it using a different set of configuration options. Since + libtorrent has some code in header files, that code will not be + compatible with the built library if they see different configurations. + + Always make sure that the same TORRENT_* and BOOST_* macros are defined + when you link against libtorrent as when you build it. The simplest way + to see the full list of macros defined is to build libtorrent with + ``-n -a`` switches added to ``b2`` command line, which output all compiler + switches. + + Boost-build supports propagating configuration options to dependencies. + +building from git +----------------- + +To build libtorrent from git you need to clone the libtorrent repository from +github. Note that the git repository depends on other git repositories via +submodules, which also need to be initialized and updated. If you downloaded a +release `tarball`__, you can skip this section. + +__ https://github.com/arvidn/libtorrent/releases/latest + +:: + + git clone --recurse-submodules https://github.com/arvidn/libtorrent.git + + +building with boost build +------------------------- + +The primary reason to use boost-build is that it will automatically build the +dependent boost libraries with the correct compiler settings, in order to +ensure that the build targets are link compatible (see `boost guidelines`__ +for some details on this issue). + +__ https://boost.org/more/separate_compilation.html + +Since BBv2 will build the boost libraries for you, you need the full boost +source package. Having boost installed via some package system is usually not +enough (and even if it is enough, the necessary environment variables are +usually not set by the package installer). + +If you want to build against an installed copy of boost, you can skip directly +to step 3 (assuming you also have boost build installed). + + +build commands +~~~~~~~~~~~~~~ + +Linux:: + + sudo apt install libboost-tools-dev libboost-dev libboost-system-dev + echo "using gcc ;" >>~/user-config.jam + b2 crypto=openssl cxxstd=14 release + +Mac OS:: + + brew install boost-build boost openssl@1.1 + echo "using darwin ;" >>~/user-config.jam + b2 crypto=openssl cxxstd=14 release + +Windows (assuming the boost package is saved to ``C:\boost_1_69_0``):: + + set BOOST_ROOT=c:\boost_1_69_0 + set BOOST_BUILD_PATH=%BOOST_ROOT%\tools\build + (cd %BOOST_ROOT% && .\bootstrap.bat) + echo using msvc ; >>%HOMEDRIVE%%HOMEPATH%\user-config.jam + %BOOST_ROOT%\b2.exe --hash cxxstd=14 release + +docker file +~~~~~~~~~~~ + +A Docker file is available that's used to build and run the fuzzers, at +OSS-Fuzz__. + +__ https://github.com/google/oss-fuzz/tree/master/projects/libtorrent + +Step 1: Download boost +~~~~~~~~~~~~~~~~~~~~~~ + +If you want to build against boost installed on your system, you can skip this +strep. Just make sure to have `BOOST_ROOT` unset for the `b2` invocation. + +You'll find boost here__. + +__ https://www.boost.org/users/download/#live + +Extract the archive to some directory where you want it. For the sake of this +guide, let's assume you extract the package to ``c:\boost_1_69_0``. You'll +need at least version 1.67 of the boost library in order to build libtorrent. + + +Step 2: Setup BBv2 +~~~~~~~~~~~~~~~~~~ + +If you have installed ``boost-build`` via a package manager, you can skip this +step. If not, you need to build boost build from the boost source package. + +First you need to build ``b2``. You do this by opening a terminal (In windows, +run ``cmd``). Change directory to ``c:\boost_1_69_0\tools\build``. Then run the +script called ``bootstrap.bat`` or ``bootstrap.sh`` on a Unix system. This will +build ``b2`` and place it in a directory ``src/engine/bin.``. +Copy the ``b2.exe`` (or ``b2`` on a Unix system) to a place that's in you +shell's ``PATH``. On Linux systems a place commonly used may be +``/usr/local/bin`` or on Windows ``c:\windows`` (you can also add directories +to the search paths by modifying the environment variable called ``PATH``). + +Now you have ``b2`` installed. ``b2`` can be considered an interpreter +that the boost-build system is implemented on. So boost-build uses ``b2``. +So, to complete the installation you need to make two more things. You need to +set the environment variable ``BOOST_BUILD_PATH``. This is the path that tells +``b2`` where it can find boost-build, your configuration file and all the +toolsets (descriptions used by boost-build to know how to use different +compilers on different platforms). Assuming the boost install path above, set +it to ``c:\boost_1_69_0\tools\build``. + +To set an environment variable in windows, type for example:: + + set BOOST_BUILD_PATH=c:\boost_1_69_0\tools\build\v2 + +In a terminal window. + +The last thing to do is to configure which compiler(s) to use. Create a file +``user-config.jam`` in your home directory. Depending on your platform and which +compiler you're using, you should add a line for each compiler and compiler +version you have installed on your system that you want to be able to use with +BBv2. For example, if you're using Microsoft Visual Studio 14.2 (2019), just +add a line:: + + using msvc : 14.2 ; + +If you use GCC, add the line:: + + using gcc ; + +If you have more than one version of GCC installed, you can add the +command line used to invoke g++ after the version number, like this:: + + using gcc : 6.0 : g++-6 ; + using gcc : 7.0 : g++-7 ; + +Another toolset worth mentioning is the ``darwin`` toolset (for macOS). +From Tiger (10.4) macOS comes with both GCC 3.3 and GCC 4.0. Then you can +use the following toolsets:: + + using darwin : 3.3 : g++-3.3 ; + using darwin : 4.0 : g++-4.0 ; + +Note that the spaces around the semi-colons and colons are important! + +Also see the `boost-build documentation`_. + +.. _`boost-build documentation`: https://boostorg.github.io/build/ + + +Step 3: Building libtorrent +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When building libtorrent, boost is either picked up from system installed +locations or from a boost source package, if the ``BOOST_ROOT`` environment +variable is set pointing to one. If you're building boost from source, set +``BOOST_ROOT`` to your boost directory, e.g. ``c:\boost_1_69_0``. + +Then the only thing left is simply to invoke ``b2``. If you want to specify +a specific toolset to use (compiler) you can just add that to the command line. +For example:: + + b2 msvc-14.2 + b2 gcc-7.0 + b2 darwin-4.0 + +.. note:: + + If the environment variable ``BOOST_ROOT`` is not set, the Jamfile will + attempt to link against "installed" boost libraries. i.e. assume the + headers and libraries are available in default search paths. + In this case it's critical that you build your project with the same version + of C++ and the same build flags as the system libraries were built with. + +.. note:: Also see the `Visual Studio versions`_. + +.. _`Visual Studio versions`: https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering + +To build different versions you can also just add the name of the build +variant. Some default build variants in BBv2 are ``release``, ``debug``, +``profile``. + +You can build libtorrent as a DLL too, by typing ``link=shared``, or +``link=static`` to build a static library. + +If you want to explicitly say how to link against the runtime library, you +can set the ``runtime-link`` feature on the command line, either to ``shared`` +or ``static``. Most operating systems will only allow linking shared against +the runtime, but on windows you can do both. Example:: + + b2 msvc-14.2 variant=release link=static runtime-link=static debug-symbols=on + +.. note:: + + When building on windows, the path boost-build puts targets in may be too + long. If you get an error message like: "The input line is long", try to + pass --hash on the ``b2`` command line. + +.. warning:: + + If you link statically to the runtime library, you cannot build libtorrent + as a shared library (DLL), since you will get separate heaps in the library + and in the client application. It will result in crashes and possibly link + errors. + +.. note:: + + When building on Solaris, you may have to specify ``stdlib=sun-stlport`` + on the b2 command line. + +The build targets are put in a directory called bin, and under it they are +sorted in directories depending on the toolset and build variant used. + +To build the examples, just change directory to the examples directory and +invoke ``b2`` from there. To build and run the tests, go to the test +directory and run ``b2``. + +Note that if you're building on windows using the ``msvc`` toolset, you cannot run it +from a cygwin terminal, you'll have to run it from a ``cmd`` terminal. The same goes for +cygwin, if you're building with gcc in cygwin you'll have to run it from a cygwin terminal. +Also, make sure the paths are correct in the different environments. In cygwin, the paths +(``BOOST_BUILD_PATH`` and ``BOOST_ROOT``) should be in the typical Unix-format (e.g. +``/cygdrive/c/boost_1_69_0``). In the windows environment, they should have the typical +windows format (``c:/boost_1_69_0``). + +.. note:: + In Jamfiles, spaces are separators. It's typically easiest to avoid spaces + in path names. If you want spaces in your paths, make sure to quote them + with double quotes ("). + +The ``Jamfile`` will define ``NDEBUG`` when it's building a release build. +For more build configuration flags see `Build configurations`_. + +Jamfile will look in some default directory for the openssl +headers and libraries. On macOS, it will look for the homebrew openssl package. +On Windows, it will look in ``C:\OpenSSL-Win32``, or ``C:\OpenSSL-Win64`` if +compiling in 64-bit. + +To customize the library path and include path for openssl, set the features +``openssl-lib`` and ``openssl-include`` respectively. + +The option to link with wolfSSL (by setting the ``crypto`` feature to +``wolfssl``), requires a custom build of wolfSSL using the following +options: ``--enable-asio --enable-sni --enable-nginx``. + +To customize the library path and include path for wolfSSL, set the features +``wolfssl-lib`` and ``wolfssl-include`` respectively. + +To disable linking against any SSL library, set the ``crypto`` build feature to +``built-in``. This will use an embedded version if SHA-1. + +Build features +~~~~~~~~~~~~~~ + ++--------------------------+----------------------------------------------------+ +| boost build feature | values | ++==========================+====================================================+ +| ``cxxstd`` | The version of C++ to use, e.g. ``11``, ``14``, | +| | ``17``, ``20``. The C++ version *may* affect the | +| | libtorrent ABI (the ambition is to avoid that). | ++--------------------------+----------------------------------------------------+ +| ``boost-link`` | * ``static`` - links statically against the boost | +| | libraries. | +| | * ``shared`` - links dynamically against the boost | +| | libraries. | ++--------------------------+----------------------------------------------------+ +| ``openssl-lib`` | can be used to specify the directory where libssl | +| | and libcrypto are installed (or the windows | +| | counterparts). | ++--------------------------+----------------------------------------------------+ +| ``openssl-include`` | can be used to specify the include directory where | +| | the openssl headers are installed. | ++--------------------------+----------------------------------------------------+ +| ``logging`` | * ``off`` - logging alerts disabled. The | +| | reason to disable logging is to keep the binary | +| | size low where that matters. | +| | * ``on`` - default. logging alerts available, | +| | still need to be enabled by the alert mask. | ++--------------------------+----------------------------------------------------+ +| ``lto`` | * ``on`` - enables link time optimization, also | +| | known as whole program optimization. | ++--------------------------+----------------------------------------------------+ +| ``alert-msg`` | * ``on`` - (default) return human readable | +| | messages from the ``alert::message()`` call. | +| | * ``off`` - Always return empty strings from | +| | ``alert::message()``, and save binary size. | ++--------------------------+----------------------------------------------------+ +| ``dht`` | * ``on`` - build with DHT support | +| | * ``off`` - build without DHT support. | ++--------------------------+----------------------------------------------------+ +| ``asserts`` | * ``auto`` - asserts are on if in debug mode | +| | * ``on`` - asserts are on, even in release mode | +| | * ``off`` - asserts are disabled | +| | * ``production`` - assertion failures are logged | +| | to ``asserts.log`` in the current working | +| | directory, but won't abort the process. | +| | The file they are logged to can be customized | +| | by setting the global pointer ``extern char | +| | const* libtorrent_assert_log`` to a different | +| | filename. | +| | * ``system`` use the libc assert macro | ++--------------------------+----------------------------------------------------+ +| ``encryption`` | * ``on`` - encrypted bittorrent connections | +| | enabled. (Message Stream encryption).(default) | +| | * ``off`` - turns off support for encrypted | +| | connections. The shipped public domain SHA-1 | +| | implementation is used. | ++--------------------------+----------------------------------------------------+ +| ``mutable-torrents`` | * ``on`` - mutable torrents are supported | +| | (`BEP 38`_) (default). | +| | * ``off`` - mutable torrents are not supported. | ++--------------------------+----------------------------------------------------+ +| ``crypto`` | * ``openssl`` - (default) links against openssl | +| | and libcrypto to use for SHA-1 hashing. | +| | This also enables HTTPS-tracker support and | +| | support for bittorrent over SSL. | +| | * ``built-in`` - (default) uses built-in SHA-1 | +| | implementation. In macOS/iOS it uses | +| | CommonCrypto SHA-1 implementation. | +| | * ``wolfssl`` - links against wolfssl to use it | +| | for SHA-1 hashing and HTTPS tracker support. | +| | * ``libcrypto`` - links against libcrypto | +| | to use the SHA-1 implementation. (no SSL support)| +| | * ``gcrypt`` - links against libgcrypt | +| | to use the SHA-1 implementation. (no SSL support)| ++--------------------------+----------------------------------------------------+ +| ``openssl-version`` | This can be used on windows to link against the | +| | special OpenSSL library names used on windows | +| | prior to OpenSSL 1.1. | +| | | +| | * ``1.1`` - link against the normal openssl | +| | library name. (default) | +| | * ``pre1.1`` - link against the old windows names | +| | (i.e. ``ssleay32`` and ``libeay32``. | ++--------------------------+----------------------------------------------------+ +| ``link`` | * ``static`` - builds libtorrent as a static | +| | library (.a / .lib) | +| | * ``shared`` - builds libtorrent as a shared | +| | library (.so / .dll). | ++--------------------------+----------------------------------------------------+ +| ``runtime-link`` | * ``static`` - links statically against the | +| | run-time library (if available on your | +| | platform). | +| | * ``shared`` - link dynamically against the | +| | run-time library (default). | ++--------------------------+----------------------------------------------------+ +| ``variant`` | * ``debug`` - builds libtorrent with debug | +| | information and invariant checks. | +| | * ``release`` - builds libtorrent in release mode | +| | without invariant checks and with optimization. | +| | * ``profile`` - builds libtorrent with profile | +| | information. | ++--------------------------+----------------------------------------------------+ +| ``invariant-checks`` | This setting only affects debug builds (where | +| | ``NDEBUG`` is not defined). It defaults to ``on``. | +| | | +| | * ``on`` - internal invariant checks are enabled. | +| | * ``off`` - internal invariant checks are | +| | disabled. The resulting executable will run | +| | faster than a regular debug build. | +| | * ``full`` - turns on extra expensive invariant | +| | checks. | ++--------------------------+----------------------------------------------------+ +| ``debug-symbols`` | * ``on`` - default for debug builds. This setting | +| | is useful for building release builds with | +| | symbols. | +| | * ``off`` - default for release builds. | ++--------------------------+----------------------------------------------------+ +| ``deprecated-functions`` | * ``on`` - default. Includes deprecated functions | +| | of the API (might produce warnings during build | +| | when deprecated functions are used). | +| | * ``off`` - excludes deprecated functions from the | +| | API. Generates build errors when deprecated | +| | functions are used. | ++--------------------------+----------------------------------------------------+ +| ``i2p`` | * ``on`` - default. build with I2P support | +| | * ``off`` - build without I2P support | ++--------------------------+----------------------------------------------------+ +| ``profile-calls`` | * ``off`` - default. No additional call profiling. | +| | * ``on`` - Enable logging of stack traces of | +| | calls into libtorrent that are blocking. On | +| | session shutdown, a file ``blocking_calls.txt`` | +| | is written with stack traces of blocking calls | +| | ordered by the number of them. | ++--------------------------+----------------------------------------------------+ +| ``utp-log`` | * ``off`` - default. Do not print verbose uTP | +| | log. | +| | * ``on`` - Print verbose uTP log, used to debug | +| | the uTP implementation. | ++--------------------------+----------------------------------------------------+ +| ``picker-debugging`` | * ``off`` - default. no extra invariant checks in | +| | piece picker. | +| | * ``on`` - include additional invariant checks in | +| | piece picker. Used for testing the piece picker. | ++--------------------------+----------------------------------------------------+ +| ``extensions`` | * ``on`` - enable extensions to the bittorrent | +| | protocol.(default) | +| | * ``off`` - disable bittorrent extensions. | ++--------------------------+----------------------------------------------------+ +| ``streaming`` | * ``on`` - enable streaming functionality. i.e. | +| | ``set_piece_deadline()``. (default) | +| | * ``off`` - disable streaming functionality. | ++--------------------------+----------------------------------------------------+ +| ``super-seeding`` | * ``on`` - enable super seeding feature. (default) | +| | * ``off`` - disable super seeding feature | ++--------------------------+----------------------------------------------------+ +| ``share-mode`` | * ``on`` - enable share-mode feature. (default) | +| | * ``off`` - disable share-mode feature | ++--------------------------+----------------------------------------------------+ +| ``predictive-pieces`` | * ``on`` - enable predictive piece announce | +| | feature. i.e. | +| | settings_pack::predictive_piece_announce | +| | (default) | +| | * ``off`` - disable feature. | ++--------------------------+----------------------------------------------------+ +| ``fpic`` | * ``off`` - default. Build without specifying | +| | ``-fPIC``. | +| | * ``on`` - Force build with ``-fPIC`` (useful for | +| | building a static library to be linked into a | +| | shared library). | ++--------------------------+----------------------------------------------------+ +| ``mmap-disk-io`` | * ``on`` - default. Enable mmap disk storage (if | +| | available. | +| | * ``off`` - disable mmap storage, and fall back to | +| | single-threaded, portable file operations. | ++--------------------------+----------------------------------------------------+ + +The ``variant`` feature is *implicit*, which means you don't need to specify +the name of the feature, just the value. + +When building the example client on windows, you need to build with +``link=static`` otherwise you may get unresolved external symbols for some +boost.program-options symbols. + +For more information, see the `Boost build v2 documentation`__, or more +specifically `the section on built-in features`__. + +__ https://boostorg.github.io/build/manual/develop/index.html +__ https://boostorg.github.io/build/manual/develop/index.html#bbv2.overview.builtins.features + + +Step 4: Installing libtorrent +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To install libtorrent run ``b2`` with the ``install`` target:: + + b2 install --prefix=/usr/local + +Change the value of the ``--prefix`` argument to install it in a different location. + + +Custom build flags +~~~~~~~~~~~~~~~~~~ + +Custom build flags can be passed to the command line via the ``cflags``, +``cxxflags`` and ``linkflags`` features. When specifying custom flags, make sure +to build everything from scratch, to not accidentally mix incompatible flags. +Example:: + + b2 cxxflags=-msse4.1 + +Custom flags can also be configured in the toolset, in ``~/user-config.jam``, +``Jamroot.jam`` or ``project-config.jam``. Example:: + + using gcc : sse41 : g++ : -msse4.1 ; + + +Cross compiling +~~~~~~~~~~~~~~~ + +To cross compile libtorrent, configure a new toolset for ``b2`` to use. Toolsets +can be configured in ``~/user-config.jam``, ``Jamroot.jam`` or +``project-config.jam``. The last two live in the libtorrent root directory. + +A toolset configuration is in this form: + +.. parsed-literal:: + + using *toolset* : *version* : *command-line* : *features* ; + +Toolset is essentially the family of compiler you're setting up, choose from `this list`__. + +__ https://boostorg.github.io/build/manual/master/index.html#bbv2.reference.tools.compilers + +Perhaps the most common ones would be ``gcc``, ``clang``, ``msvc`` and +``darwin`` (Apple's version of clang). + +The version can be left empty to be auto configured, or a custom name can be +used to identify this toolset. + +The *command-line* is what to execute to run the compiler. This is also an +opportunity to insert a call to ``ccache`` for example. + +*features* are boost-build features. Typical features to set here are +````, ```` and ````. For the ``gcc`` toolset, +the ```` can be set to specify which tool to use to create a static +library/archive. This is especially handy when cross compiling. + +Here's an example toolset for cross compiling for ARM Linux:: + + using gcc : arm : arm-linux-gnueabihf-g++ : arm-linux-gnueabihf-ar ; + +To build using this toolset, specify ``gcc-arm`` as the toolset on the ``b2`` command line. For example:: + + b2 toolset=gcc-arm + + +building with cmake +------------------- + +First of all, you need to install ``cmake``. Additionally you need a build +system to actually schedule builds, for example ``ninja``. + +Step 1: Generating the build system +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Create a build directory for out-of-source build inside the libtorrent root directory:: + + mkdir build + +and ``cd`` there:: + + cd build + +Run ``cmake`` in the build directory, like this:: + + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=14 -G Ninja .. + +The ``CMAKE_CXX_STANDARD`` has to be at least 14, but you may want to raise it +to ``17`` if your project use a newer version of the C++ standard. + +.. warning:: + + The detection of boost sometimes fail in subtle ways. If you have the + ``BOOST_ROOT`` environment variable set, it may find the pre-built system + libraries, but use the header files from your source package. To avoid this, + invoke ``cmake`` with ``BOOST_ROOT`` set to an empty string: + ``BOOST_ROOT="" cmake ...``. + +Other build options are: + ++-----------------------+---------------------------------------------------+ +| ``BUILD_SHARED_LIBS`` | Defaults ``ON``. Builds libtorrent as a shared | +| | library. | ++-----------------------+---------------------------------------------------+ +| ``static_runtime`` | Defaults ``OFF``. Link libtorrent statically | +| | against the runtime libraries. | ++-----------------------+---------------------------------------------------+ +| ``build_tests`` | Defaults ``OFF``. Also build the libtorrent | +| | tests. | ++-----------------------+---------------------------------------------------+ +| ``build_examples`` | Defaults ``OFF``. Also build the examples in the | +| | examples directory. | ++-----------------------+---------------------------------------------------+ +| ``build_tools`` | Defaults ``OFF``. Also build the tools in the | +| | tools directory. | ++-----------------------+---------------------------------------------------+ +| ``python-bindings`` | Defaults ``OFF``. Also build the python bindings | +| | in bindings/python directory. | ++-----------------------+---------------------------------------------------+ +| ``encryption`` | Defaults ``ON``. Support trackers and bittorrent | +| | over TLS, and obfuscated bittorrent connections. | ++-----------------------+---------------------------------------------------+ + +Options are set on the ``cmake`` command line with the ``-D`` option or later on using ``ccmake`` or ``cmake-gui`` applications. ``cmake`` run outputs a summary of all available options and their current values. + +Step 2: Building libtorrent +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In the terminal, run:: + + ninja + +If you enabled test in the configuration step, to run them, run:: + + ctest + +building with VCPKG +------------------- + +You can download and install libtorrent using the vcpkg_ dependency manager:: + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + ./vcpkg install libtorrent + +.. _vcpkg: https://github.com/Microsoft/vcpkg/ + +The libtorrent port in vcpkg is kept up to date by Microsoft team members and community contributors. +If the version is out of date, please `create an issue or pull request`__ on the vcpkg repository. + +__ https://github.com/Microsoft/vcpkg + +build configurations +-------------------- + +By default libtorrent is built In debug mode, and will have pretty expensive +invariant checks and asserts built into it. If you want to disable such checks +(you want to do that in a release build) you can see the table below for which +defines you can use to control the build. Make sure to define the same macros in your +own code that compiles and links with libtorrent. + ++----------------------------------------+-------------------------------------------------+ +| macro | description | ++========================================+=================================================+ +| ``NDEBUG`` | If you define this macro, all asserts, | +| | invariant checks and general debug code will be | +| | removed. Since there is quite a lot of code in | +| | in header files in libtorrent, it may be | +| | important to define the symbol consistently | +| | across compilation units, including the clients | +| | files. Potential problems is different | +| | compilation units having different views of | +| | structs and class layouts and sizes. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_LOGGING`` | This macro will disable support for logging | +| | alerts, like log_alert, torrent_log_alert and | +| | peer_log_alert. With this build flag, you | +| | cannot enable those alerts. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_ALERT_MSG`` | Human readable messages returned from the alert | +| | ``message()`` member functions will return | +| | empty strings. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_SUPERSEEDING`` | This macro will disable support for super | +| | seeding. The settings will exist, but will not | +| | have an effect, when this macro is defined. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_SHARE_MODE`` | This macro will disable support for share-mode. | +| | i.e. the mode to maximize upload/download | +| | ratio for a torrent. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_MUTABLE_TORRENTS`` | Disables mutable torrent support (`BEP 38`_) | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_STREAMING`` | Disables set_piece_deadline() and associated | +| | functionality. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_PREDICTIVE_PIECES`` | Disables | +| | settings_pack::predictive_piece_announce | +| | feature. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_LINKING_SHARED`` | If this is defined when including the | +| | libtorrent headers, the classes and functions | +| | will be tagged with ``__declspec(dllimport)`` | +| | on msvc and default visibility on GCC 4 and | +| | later. Set this in your project if you're | +| | linking against libtorrent as a shared library. | +| | (This is set by the Jamfile when | +| | ``link=shared`` is set). | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_BUILDING_SHARED`` | If this is defined, the functions and classes | +| | in libtorrent are marked with | +| | ``__declspec(dllexport)`` on msvc, or with | +| | default visibility on GCC 4 and later. This | +| | should be defined when building libtorrent as | +| | a shared library. (This is set by the Jamfile | +| | when ``link=shared`` is set). | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_DHT`` | If this is defined, the support for trackerless | +| | torrents will be disabled. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_ENCRYPTION`` | This will disable any encryption support and | +| | the dependencies of a crypto library. | +| | Encryption support is the peer connection | +| | encrypted supported by clients such as | +| | uTorrent, Azureus and KTorrent. | +| | If this is not defined, either | +| | ``TORRENT_USE_LIBCRYPTO`` or | +| | ``TORRENT_USE_LIBGCRYPT`` must be defined. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_EXTENSIONS`` | When defined, libtorrent plugin support is | +| | disabled along with support for the extension | +| | handshake (BEP 10). | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_INVARIANT_CHECKS`` | If defined to non-zero, this will enable | +| | internal invariant checks in libtorrent. | +| | The invariant checks can sometimes | +| | be quite expensive, they typically don't scale | +| | very well. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_EXPENSIVE_INVARIANT_CHECKS`` | This will enable extra expensive invariant | +| | checks. Useful for finding particular bugs | +| | or for running before releases. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_NO_DEPRECATE`` | This will exclude all deprecated functions from | +| | the header files and source files. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_PRODUCTION_ASSERTS`` | Define to either 0 or 1. Enables assert logging | +| | in release builds. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_ASSERTS`` | Define as 0 to disable asserts unconditionally. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_SYSTEM_ASSERTS`` | Uses the libc assert macro rather then the | +| | custom one. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_HAVE_MMAP`` | Define as 0 to disable mmap support. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_OPENSSL`` | Link against ``libssl`` for SSL support. Must | +| | be combined with ``TORRENT_USE_LIBCRYPTO`` | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_GNUTLS`` | Link against ``libgnutls`` for SSL support. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_LIBCRYPTO`` | Link against ``libcrypto`` for SHA-1 support | +| | and other hashing algorithms. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_LIBGCRYPT`` | Link against ``libgcrypt`` for SHA-1 support | +| | and other hashing algorithms. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_SSL_PEERS`` | Define to enable support for SSL torrents, | +| | peers are connected over authenticated SSL | +| | streams. | ++----------------------------------------+-------------------------------------------------+ + +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html + +If you experience that libtorrent uses unreasonable amounts of CPU, it will +definitely help to define ``NDEBUG``, since it will remove the invariant checks +within the library. + +building openssl for windows +---------------------------- + +To build openssl for windows with Visual Studio 7.1 (2003) execute the following commands +in a command shell:: + + perl Configure VC-WIN32 --prefix="c:/openssl + call ms\do_nasm + call "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\bin\vcvars32.bat" + nmake -f ms\nt.mak + copy inc32\openssl "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\include\" + copy out32\libeay32.lib "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\lib" + copy out32\ssleay32.lib "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\lib" + +This will also install the headers and library files in the visual studio directories to +be picked up by libtorrent. + +list of macros +-------------- + +The following is a list of defines that libtorrent is built with: ``BOOST_ALL_NO_LIB``, +``BOOST_ASIO_ENABLE_CANCELIO``, ``BOOST_ASIO_HAS_STD_CHRONO``, +``BOOST_MULTI_INDEX_DISABLE_SERIALIZATION``, ``BOOST_NO_DEPRECATED``, +``BOOST_SYSTEM_NO_DEPRECATED`` + +Make sure you define the same at compile time for your code to avoid any runtime errors +and other issues. + +These might change in the future, so it's always best to verify these every time you +upgrade to a new version of libtorrent. The simplest way to see the full list of macros +defined is to build libtorrent with ``-n -a`` switches added to ``b2`` command line:: + + b2 -n -a toolset=msvc-14.2 link=static runtime-link=static boost-link=static variant=release + +This will output all compiler switches, including defines (such as ``-DBOOST_ASIO_ENABLE_CANCELIO``). diff --git a/docs/client_test.html b/docs/client_test.html new file mode 100644 index 0000000..774021f --- /dev/null +++ b/docs/client_test.html @@ -0,0 +1,94 @@ + + + + + + + +libtorrent + + + + + + + +
+
+ + libtorrent logo + +
+

client_test example program

+ +++ + + + +
Version:2.0.7
+

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

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

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

+

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

+img/screenshot.png +

The commands available in the client are:

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

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

+ +
+
+
+ + +
+ + diff --git a/docs/client_test.rst b/docs/client_test.rst new file mode 100644 index 0000000..04d4c2e --- /dev/null +++ b/docs/client_test.rst @@ -0,0 +1,49 @@ +=========================== +client_test example program +=========================== + +.. include:: header.rst + +Client test is a, more or less, complete bittorrent client. It lacks most +settings and you can't start or stop torrents once you've started it. All +the settings are hard coded. The command line arguments are:: + + client_test ... + +You can start any number of torrent downloads/seeds via the command line. +If one argument starts with ``http://`` it is interpreted as a tracker +announce url, and it expects an info-hash as the next argument. The info-hash +has to be hex-encoded. For example: ``2410d4554d5ed856d69f426c38791673c59f4418``. +If you pass an announce url and info-hash, a torrent-less download is started. +It relies on that at least one peer on the tracker is running a libtorrent based +client and has the metadata (.torrent file). The metadata extension in +libtorrent will then download it from that peer (or from those peers if more +than one). + +While running, the ``client_test`` sample will look something like this: + +.. image:: img/screenshot.png + :class: screenshot + :target: img/screenshot.png + +The commands available in the client are: + +* ``q`` quits the client (there will be a delay while the client waits + for tracker responses) +* ``l`` toggle log. Will display the log at the bottom, informing about + tracker and peer events. +* ``i`` toggles torrent info. Will show the peer list for each torrent. +* ``d`` toggle download info. Will show the block list for each torrent, + showing downloaded and requested blocks. +* ``p`` pause all torrents. +* ``u`` resume all torrents. +* ``r`` force tracker reannounce for all torrents. +* ``f`` toggle show file progress. Displays a list of all files and the + download progress for each file. + +The list at the bottom (shown if you press ``d``) shows which blocks has +been requested from which peer. The green background means that it has been +downloaded. It shows that fast peers will prefer to request whole pieces +instead of downloading parts of pieces. It may make it easier to determine +which peer that sent the corrupt data if a piece fails the hash test. + diff --git a/docs/contributing.html b/docs/contributing.html new file mode 100644 index 0000000..9fdf467 --- /dev/null +++ b/docs/contributing.html @@ -0,0 +1,116 @@ + + + + + + + +libtorrent + + + + + + + +
+
+ + libtorrent logo + +
+ + +++ + + + +
Version:2.0.7
+
+

Table of contents

+ +
+
+

contributing to libtorrent

+

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

+
    +
  1. +
    Testing
    +

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

    +

    New features that need testing are streaming (set_piece_deadline()), the different +choking algorithms (like the rate-based choker).

    +

    Additional fuzzers are also always welcome. Find a libtorrent interface +that's not already covered by a fuzzer (see the fuzzers directory in the +root) and add a new fuzzer to it. Alternatively, improve an existing fuzzer by +producing inputs that gets coverage deeper in to libtorrent.

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

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

    +

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

    +

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

    +
    +
    +
  2. +
  3. +
    Code
    +

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

    +

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

    +
    +
    +
  4. +
+

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

+

For outstanding things to do, see the todo list.

+
+ +
+
+
+ + +
+ + diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..dcdf015 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1,63 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +contributing to libtorrent +========================== + +There are several ways to contribute to libtorrent at various levels. Any help is +much appreciated. If you're interested in something libtorrent related that's not +enumerated on this page, please contact arvid@libtorrent.org or the `mailing list`_. + +.. _`mailing list`: https://lists.sourceforge.net/lists/listinfo/libtorrent-discuss + +1. Testing + This is not just limited to finding bugs and ways to reproduce crashes, but also + sub-optimal behavior is certain scenarios and finding ways to reproduce those. Please + report any issue to the bug tracker at `github`_. + + New features that need testing are streaming (``set_piece_deadline()``), the different + choking algorithms (like the rate-based choker). + + Additional fuzzers are also always welcome. Find a libtorrent interface + that's not already covered by a fuzzer (see the ``fuzzers`` directory in the + root) and add a new fuzzer to it. Alternatively, improve an existing fuzzer by + producing inputs that gets coverage deeper in to libtorrent. + +.. _`github`: https://github.com/arvidn/libtorrent/issues + +2. Documentation + Finding typos or outdated sections in the documentation. Contributing documentation + based on your own experience and experimentation with the library or with BitTorrent + in general. Non-reference documentation is very much welcome as well, higher level + descriptions on how to configure libtorrent for various situations for instance. + The reference documentation for libtorrent is generated from the header files. + + Each heading in the online documentation has a short-cut link to file a new issue + against the documentation. + + For updates, please submit a `pull request`_. All documentation is in + restructured text (rst_). All documentation is spell checked with hunspell + which can be invoked via ``make spell-check`` in the docs directory. If + words are missing, please add them to ``docs/hunspell/libtorrent.dic`` + +3. Code + Contributing code for new features or bug-fixes is highly welcome. If you're interested + in adding a feature but not sure where to start, please contact the `mailing list`_ or + ``#libtorrent`` @ ``irc.freenode.net``. For proposed fixes or updates, please + submit a `pull request`_. + + New features might be better support for integrating with other services, new choking + algorithms, seeding policies, ports to new platforms etc. + +For an overview of the internals of libtorrent, see the hacking_ page. + +For outstanding things to do, see the `todo list`_. + +.. _hacking: hacking.html +.. _`pull request`: https://github.com/arvidn/libtorrent +.. _`todo list`: todo.html +.. _rst: https://docutils.sourceforge.io/rst.html + diff --git a/docs/dht_extensions.html b/docs/dht_extensions.html new file mode 100644 index 0000000..756eec9 --- /dev/null +++ b/docs/dht_extensions.html @@ -0,0 +1,119 @@ + + + + + + + +libtorrent + + + + + + + +
+
+ + libtorrent logo + +
+

Mainline DHT extensions

+ +++ + + + +
Version:2.0.7
+

libtorrent implements a few extensions to the Mainline DHT protocol.

+
+

get_peers response

+

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

+
+
+

forward compatibility

+

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

+
+
+

client identification

+

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

+

Currently known clients:

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

IPv6 support

+

This extension is superseded by BEP 32.

+

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

+

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

+

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

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

BitTorrent extension for DHT RSS feeds

+ +++ + + + +
Version:2.0.7
+ +

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

+

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

+

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

+

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

+

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

+
+

terminology

+

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

+
+
+

linked lists

+

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

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

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

+

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

+

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

+

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

+

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

+
+
+

skip lists

+

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

+

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

+

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

+

Figure of the skip list in the first list item:

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

n refers to the byte offset into the next field.

+
+
+

list-head

+

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

+

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

+

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

+
+
+

messages

+

These are the messages to deal with linked lists.

+

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

+

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

+

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

+
+

requesting items

+

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

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

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

+

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

+
+
+

request item response

+

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

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

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

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

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

+

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

+
+
+

announcing items

+

The message format for announcing a list head:

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

The message format for announcing a list item:

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

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

+
+
+
+

re-announcing

+

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

+

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

+

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

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

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

+
+
+

timeouts

+

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

+

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

+
+
+

RSS feeds

+

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

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

example

+

This is an example of an announce_item message:

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

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

+

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

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

(note that binary data is printed as hex)

+
+
+
+

RSS feed URI scheme

+

The proposed URI scheme for DHT feeds is:

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

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

+

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

+
+
+

rationale

+

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

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

BitTorrent DHT security extension

+

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

+

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

+

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

+
+
+

considerations

+

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

+

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

+

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

+
+2^20 * 8 = 8388608
+

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

+

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

+

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

+
+
+

Node ID restriction

+

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

+

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

+

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

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

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

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

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

+

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

+

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

+img/ip_id_v4.png +img/ip_id_v6.png +

Example code code for calculating a valid node ID:

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

test vectors:

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

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

+
+
+

bootstrapping

+

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

+

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

+

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

+

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

+
+
+

rationale

+

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

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

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

+

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

+img/complete_bit_prefixes.png +

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

+

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

+

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

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

BitTorrent extension for arbitrary DHT store

+ +++ + + + +
Version:2.0.7
+ +

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

+

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

+

There are two new proposed messages, put and get.

+
+

terminology

+

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

+
+
+

messages

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+
+
+

immutable items

+

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

+

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

+
+

put message

+

Request:

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

Response:

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

get message

+

Request:

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

Response:

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

mutable items

+

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

+

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

+

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

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

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

+

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

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

put message

+

Request:

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

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

+

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

+

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

+

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

+

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

+

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

+
+
+

CAS

+

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

+

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

+

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

+

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

+
+
+

response

+

Response:

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

errors

+

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

+

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

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

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

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

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

+
+
+

get message

+

Request:

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

Response:

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

signature verification

+

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

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

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

+

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

+
+
+

expiration

+

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

+

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

+
+
+

test vectors

+
+

test 1 (mutable)

+

value:

+
+12:Hello World!
+
+

buffer being signed:

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

public key:

+
+77ff84905a91936367c01360803104f92432fcd904a43511876df5cdf3e7e548
+
+

private key:

+
+e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d
+b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d
+
+

target ID:

+
+4a533d47ec9c7d95b1ad75f576cffc641853b750
+
+

signature:

+
+305ac8aeb6c9c151fa120f120ea2cfb923564e11552d06a5d856091e5e853cff
+1260d3f39e4999684aa92eb73ffd136e6f4f3ecbfda0ce53a1608ecd7ae21f01
+
+
+
+

test 2 (mutable with salt)

+

value:

+
+12:Hello World!
+
+

salt:

+
+foobar
+
+

buffer being signed:

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

public key:

+
+77ff84905a91936367c01360803104f92432fcd904a43511876df5cdf3e7e548
+
+

private key:

+
+e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d
+b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d
+
+

target ID:

+
+411eba73b6f087ca51a3795d9c8c938d365e32c1
+
+

signature:

+
+6834284b6b24c3204eb2fea824d82f88883a3d95e8b4a21b8c0ded553d17d17d
+df9a8a7104b1258f30bed3787e6cb896fca78c58f8e03b5f18f14951a87d9a08
+
+
+
+

test 3 (immutable)

+

value:

+
+12:Hello World!
+
+

target ID:

+
+e5f96f6f38320f0f33959cb4d3d656452117aadb
+
+
+
+
+

resources

+

Libraries that implement ed25519 DSA:

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

Table of contents

+ +
+
+

examples

+

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

+
+

simple client

+

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

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

make_torrent

+

Shows how to create a torrent from a directory tree:

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

dump_torrent

+

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

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

extension protocol for bittorrent

+

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

+

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

+

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

+

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

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

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

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

handshake message

+

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

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

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

+

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

+
+

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

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

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

+

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

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

and in the encoded form:

+

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

+

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

+

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

+

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

+

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

+

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

+
+
+

rationale

+

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

+

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

+

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

+

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

+

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

+
+
+
+

extensions

+

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

+

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

+

These are the extensions that are currently implemented.

+
+

metadata from peers

+

Extension name: "LT_metadata"

+
+

Note

+

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

+
+

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

+

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

+

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

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

request metadata:

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

metadata:

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

Don't have metadata:

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

dont_have

+

Extension name: "lt_donthave"

+

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

+

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

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

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

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

introduction

+

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

+

BitTorrent v2 is supported as of libtorrent 2.0. This replaces the previous +merkle hash tree extension.

+
+
+

features

+

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

+
+

BitTorrent v2

+

Starting with version 2.0, libtorrent supports BitTorrent V2 (as specified in +BEP 52). BitTorrent V2 introduces a new format for .torrent files, which generally +has a smaller info-dict than the original format. The .torrent files still contain +piece hashes by default, but they can also be downloaded from peers.

+
    +
  1. Files are organized in a directory structure, instead of listing full paths. +Torrents that have a lot of files in deep directory structures will use a lot +less space to represent that structure in a v2 torrent.
  2. +
  3. Piece hashes are organized in a merkle hash trees per file, and only the +roots of the trees are included in the .torrent file. The actual hashes are +delivered by peers.
  4. +
+

The hash tree allows validating payload received from a peer immediately, down +to 16 kiB blocks. In the original bittorrent protocol a whole piece would have +to be downloaded before it could be validated against the hashes.

+

The fact that each file has its own hash tree, and that its leaves are defined +to be 16 kiB, means that files with identical content will always have the same +merkle root. This enables finding matches of the same file across different +torrents.

+

The new format for torrent files is compatible with the original torrent file +format, which enables hybrid torrents. Such torrents that can be used both as +V1 and V2 and will have two swarms, one with V1 and V2 clients and one with only +V2 clients.

+

Another major feature of the BitTorrent V2 protocol is that the SHA-1 hash +function has been replaced by SHA-256.

+
+
+

extensions

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

disk management

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

network

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

highlighted features

+
+

disk I/O

+

All disk I/O in libtorrent is done asynchronously to the network thread, by the +disk io threads. Files are mapped into memory and the kernel's page cache is +relied on for caching disk blocks. This has the advantage that the disk cache +size adapts to global system load and memory pressure, maximizing the cache +without bogging down the whole system. Since memory mapped I/O is inherently +synchronous, files can be accessed from multiple disk I/O threads.

+

Similarly, for write requests, blocks are queued in a store-buffer while waiting +to be flushed to disk. Read requests that happen before a block has been +flushed, will short circuit by picking the block from the store buffer.

+

Memory mapped files are available on Windows and posix 64 bit systems. When +building on other, simpler platforms, or 32 bits, a simple portable and +single-threaded disk I/O back-end is available, using fopen() and fclose() +family of functions.

+
+
+

network buffers

+

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

+

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

+img/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:

+img/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.

+
+
+

customizable file I/O

+img/storage.png +

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

+

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

+
+
+

easy to use API

+

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

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

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

+

libtorrent also comes with python bindings.

+
+
+
+

portability

+

libtorrent runs on most major operating systems including:

+
    +
  • Windows
  • +
  • macOS
  • +
  • Linux
  • +
  • BSD
  • +
  • Solaris
  • +
+

It uses Boost.Asio, Boost.Optional, Boost.System, Boost.Multiprecision, +Boost.Pool, Boost.Python (for bindings), Boost.CRC and various +other boost libraries. At least version 1.70 of boost is required.

+

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

+

libtorrent requires a C++11 compiler and does not build with the following compilers:

+
    +
  • GCC older than 5.4
  • +
  • Visual Studio older than Visual Studio 15 2017 (aka msvc-14.1)
  • +
+
+ +
+
+
+ + +
+ + diff --git a/docs/features.rst b/docs/features.rst new file mode 100644 index 0000000..f719244 --- /dev/null +++ b/docs/features.rst @@ -0,0 +1,323 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +introduction +============ + +libtorrent is a feature complete C++ bittorrent implementation focusing +on efficiency and scalability. It runs on embedded devices as well as +desktops. It boasts a well documented library interface that is easy to +use. It comes with a simple bittorrent client demonstrating the use of +the library. + +BitTorrent v2 is supported as of libtorrent 2.0. This replaces the previous +merkle hash tree extension. + +features +======== + +libtorrent is an ongoing project under active development. Its +current state supports and includes the following features: + +BitTorrent v2 +------------- + +Starting with version 2.0, libtorrent supports BitTorrent V2 (as specified in +`BEP 52`_). BitTorrent V2 introduces a new format for .torrent files, which generally +has a smaller info-dict than the original format. The .torrent files still contain +piece hashes by default, but they can also be downloaded from peers. + +1. Files are organized in a directory structure, instead of listing full paths. + Torrents that have a lot of files in deep directory structures will use a lot + less space to represent that structure in a v2 torrent. + +2. Piece hashes are organized in a merkle hash trees per file, and only the + roots of the trees are included in the .torrent file. The actual hashes are + delivered by peers. + +The hash tree allows validating payload received from a peer immediately, down +to 16 kiB blocks. In the original bittorrent protocol a whole piece would have +to be downloaded before it could be validated against the hashes. + +The fact that each file has its own hash tree, and that its leaves are defined +to be 16 kiB, means that files with identical content will always have the same +merkle root. This enables finding matches of the same file across different +torrents. + +The new format for torrent files is compatible with the original torrent file +format, which enables *hybrid* torrents. Such torrents that can be used both as +V1 and V2 and will have two swarms, one with V1 and V2 clients and one with only +V2 clients. + +Another major feature of the BitTorrent V2 protocol is that the SHA-1 hash +function has been replaced by SHA-256. + +extensions +---------- + +* plugin interface for implementing custom bittorrent extensions + without having to modify libtorrent +* supports trackerless torrents (using the Mainline kademlia DHT protocol) with + some `DHT extensions`_. `BEP 5`_. +* supports the bittorrent `extension protocol`_. See extensions_. `BEP 10`_. +* supports the uTorrent metadata transfer protocol `BEP 9`_ (i.e. magnet links). +* supports the uTorrent peer exchange protocol (PEX). +* supports local peer discovery (multicast for peers on the same local network) +* multi-tracker extension support (supports both strict `BEP 12`_ and the + uTorrent interpretation). +* tracker scrapes +* supports lt_trackers extension, to exchange trackers between peers +* `HTTP seeding`_, as specified in `BEP 17`_ and `BEP 19`_. +* supports the UDP-tracker protocol. (`BEP 15`_). +* supports the ``no_peer_id=1`` extension that will ease the load off trackers. +* supports the ``compact=1`` tracker parameter. +* super seeding/initial seeding (`BEP 16`_). +* private torrents (`BEP 27`_). +* upload-only extension (`BEP 21`_). +* support for IPv6, including `BEP 7`_ and `BEP 24`_. +* share-mode. This is a special mode torrents can be put in to optimize share + ratio rather than downloading the torrent. +* supports the Magnet URI extension - Select specific file indices for + download. `BEP 53`_. + +.. _article: utp.html +.. _extensions: manual-ref.html#extensions +.. _`http seeding`: manual-ref.html#http-seeding + +disk management +--------------- + +* can use multiple disk I/O threads to not have the disk block network or + client interaction. +* supports verifying the SHA-1 hash of pieces in multiple threads, to take + advantage of multi core machines. +* supports files > 2 gigabytes. +* fast resume support, a way to avoid the costly piece check at the + start of a resumed torrent. Saves the storage state, piece_picker state + as well as all local peers in a fast-resume file. +* queues torrents for file check, instead of checking all of them in parallel. + resumes. This means it can resume a torrent downloaded by any client. +* seed mode, where the files on disk are assumed to be complete, and each + piece's hash is verified the first time it is requested. + +network +------- + +* a high quality uTP implementation (`BEP 29`_). A transport protocol with + delay based congestion control. See separate article_. +* adjusts the length of the request queue depending on download rate. +* serves multiple torrents on a single port and in a single thread +* piece picking on block-level (as opposed to piece-level). + This means it can download parts of the same piece from different peers. + It will also prefer to download whole pieces from single peers if the + download speed is high enough from that particular peer. +* supports http proxies and basic proxy authentication +* supports gzip tracker-responses +* can limit the upload and download bandwidth usage and the maximum number of + unchoked peers +* possibility to limit the number of connections. +* delays have messages if there's no other outgoing traffic to the peer, and + doesn't send have messages to peers that already has the piece. This saves + bandwidth. +* selective downloading. The ability to select which parts of a torrent you + want to download. +* ip filter to disallow ip addresses and ip ranges from connecting and + being connected. +* NAT-PMP, PCP and UPnP support (automatic port mapping on routers that supports it) +* implements automatic upload slots, to optimize download rate without spreading + upload capacity too thin. The number of upload slots is adjusted based on the + peers' download capacity to work even for connections that are orders of + magnitude faster than others. + + +.. _`DHT extensions`: dht_extensions.html +.. _`BEP 5`: https://www.bittorrent.org/beps/bep_0005.html +.. _`BEP 7`: https://www.bittorrent.org/beps/bep_0007.html +.. _`BEP 9`: https://www.bittorrent.org/beps/bep_0009.html +.. _`BEP 10`: https://www.bittorrent.org/beps/bep_0010.html +.. _`BEP 12`: https://www.bittorrent.org/beps/bep_0012.html +.. _`BEP 15`: https://www.bittorrent.org/beps/bep_0015.html +.. _`BEP 16`: https://www.bittorrent.org/beps/bep_0016.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _`BEP 21`: https://www.bittorrent.org/beps/bep_0021.html +.. _`BEP 24`: https://www.bittorrent.org/beps/bep_0024.html +.. _`BEP 27`: https://www.bittorrent.org/beps/bep_0027.html +.. _`BEP 29`: https://www.bittorrent.org/beps/bep_0029.html +.. _`BEP 52`: https://www.bittorrent.org/beps/bep_0052.html +.. _`BEP 53`: https://www.bittorrent.org/beps/bep_0053.html +.. _`extension protocol`: extension_protocol.html + +highlighted features +==================== + +disk I/O +-------- + +All disk I/O in libtorrent is done asynchronously to the network thread, by the +disk io threads. Files are mapped into memory and the kernel's page cache is +relied on for caching disk blocks. This has the advantage that the disk cache +size adapts to global system load and memory pressure, maximizing the cache +without bogging down the whole system. Since memory mapped I/O is inherently +synchronous, files can be accessed from multiple disk I/O threads. + +Similarly, for write requests, blocks are queued in a store-buffer while waiting +to be flushed to disk. Read requests that happen before a block has been +flushed, will short circuit by picking the block from the store buffer. + +Memory mapped files are available on Windows and posix 64 bit systems. When +building on other, simpler platforms, or 32 bits, a simple portable and +single-threaded disk I/O back-end is available, using `fopen()` and `fclose()` +family of functions. + +network buffers +--------------- + +On CPUs with small L2 caches, copying memory can be expensive operations. It is important +to keep copying to a minimum on such machines. This mostly applies to embedded systems. + +In order to minimize the number of times received data is copied, the receive buffer +for payload data is received directly into a page aligned disk buffer. If the connection +is encrypted, the buffer is decrypted in-place. The buffer is then moved into the disk +cache without being copied. Once all the blocks for a piece have been received, or the +cache needs to be flushed, all the blocks are passed directly to ``writev()`` to flush +them in a single system call. This means a single copy into user space memory, and a single +copy back into kernel memory, as illustrated by this figure: + +.. image:: img/write_disk_buffers.png + :width: 100% + :class: bw + +When seeding and uploading in general, unnecessary copying is avoided by caching blocks +in aligned buffers, that are copied once into the peer's send buffer. The peer's send buffer +is not guaranteed to be aligned, even though it is most of the time. The send buffer is +then encrypted with the peer specific key and chained onto the ``iovec`` for sending. +This means there is one user space copy in order to allow unaligned peer requests and +peer-specific encryption. This is illustrated by the following figure: + +.. image:: img/read_disk_buffers.png + :width: 100% + :class: bw + + +piece picker +------------ + +The piece picker is a central component in a bittorrent implementation. The piece picker +in libtorrent is optimized for quickly finding the rarest pieces. It keeps a list of all +available pieces sorted by rarity, and pieces with the same rarity, shuffled. The rarest +first mode is the dominant piece picker mode. Other modes are supported as well, and +used by peers in specific situations. + +The piece picker allows to combine the availability of a piece with a priority. Together +they determine the sort order of the piece list. Pieces with priority 0 will never be +picked, which is used for the selective download feature. + +In order to have as few partially finished pieces as possible, peers have an affinity +towards picking blocks from the same pieces as other peers in the same speed category. +The speed category is a coarse categorization of peers based on their download rate. This +makes slow peers pick blocks from the same piece, and fast peers pick from the same piece, +and hence decreasing the likelihood of slow peers blocking the completion of pieces. + +The piece picker can also be set to download pieces in sequential order. + +share mode +---------- + +The share mode feature in libtorrent is intended for users who are only interested in +helping out swarms, not downloading the torrents. + +It works by predicting the demand for pieces, and only download pieces if there is enough +demand. New pieces will only be downloaded once the share ratio has hit a certain target. + +This feature is especially useful when combined with RSS, so that a client can be set up +to provide additional bandwidth to an entire feed. + +customizable file I/O +--------------------- + +.. image:: img/storage.png + :align: right + :class: bw + +libtorrent's disk I/O implementation is customizable. That means a special +purpose bittorrent client can replace the default way to store files on disk. + +When implementing a bittorrent cache, it doesn't matter how the data is stored on disk, as +long as it can be retrieved and seeded. In that case a new disk I/O class can be implemented +(inheriting from the disk_interface) that avoids the unnecessary step of mapping +pieces to files and offsets. The storage can ignore the file boundaries and just store the +entire torrent in a single file (which will end up being all the files concatenated). The main +advantage of this, other than a slight CPU performance gain, is that all file operations would +be page (and sector) aligned. This enables efficient unbuffered I/O, and can potentially +lead to more efficient read caching (using the built in disk cache rather than relying on the +operating system's disk cache). + +easy to use API +--------------- + +One of the design goals of the libtorrent API is to make common operations simple, but still +have it possible to do complicated and advanced operations. This is best illustrated by example +code to implement a simple bittorrent client: + +.. code:: c++ + + #include + #include "libtorrent/session.hpp" + + // usage a.out [torrent-file] + int main(int argc, char* argv[]) try + { + lt::session s; + lt::add_torrent_params p; + p.save_path = "./"; + p.ti = std::make_shared(argv[1]); + lt::torrent_handle h = s.add_torrent(p); + + // wait for the user to end + char a; + std::cin.unsetf(std::ios_base::skipws); + std::cin >> a; + return 0; + } + catch (std::exception const& e) + { + std::cerr << ec.what() << std::endl; + return 1; + } + +This client doesn't give the user any status information or progress about the +torrent, but it is fully functional. + +libtorrent also comes with `python bindings`_. + +.. _`python bindings`: python_binding.html + + +portability +=========== + +libtorrent runs on most major operating systems including: + +* Windows +* macOS +* Linux +* BSD +* Solaris + +It uses Boost.Asio, Boost.Optional, Boost.System, Boost.Multiprecision, +Boost.Pool, Boost.Python (for bindings), Boost.CRC and various +other boost libraries. At least version 1.70 of boost is required. + +Since libtorrent uses Boost.Asio it will take full advantage of high performance +network APIs on the most popular platforms. I/O completion ports on windows, +epoll on Linux and kqueue on macOS and BSD. + +libtorrent requires a C++11 compiler and does not build with the following compilers: + +* GCC older than 5.4 +* Visual Studio older than Visual Studio 15 2017 (aka msvc-14.1) + diff --git a/docs/hacking.html b/docs/hacking.html new file mode 100644 index 0000000..693b20a --- /dev/null +++ b/docs/hacking.html @@ -0,0 +1,174 @@ + + + + + + + +libtorrent + + + + + + + +
+
+ + libtorrent logo + +
+

libtorrent hacking

+ +++ + + + +
Version:2.0.7
+ +

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

+
+

terminology

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+
+
+

structure

+

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

+img/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 at least 3 threads, but likely more, depending on the +settings_pack::aio_threads setting. The kinds of threads are:

+
+
    +
  • The main network thread that manages all sockets; +sending and receiving messages and maintaining all session, torrent and peer +state. In an idle session, this thread will mostly be blocked in a system call, +waiting for socket activity, such as epoll().
  • +
  • A disk I/O thread. There may be multiple disk threads. All disk read and +write operations are passed to this thread and messages are passed back to +the main thread when the operation completes. This kind of thread also performs +the SHA-1/SHA-256 calculations to verify pieces. Some disk threads may have an +affinity for those jobs, to avoid starvation of the disk.
  • +
  • At least one thread is spawned by boost.asio on systems that don't support +asynchronous host name resolution, in order to simulate non-blocking getaddrinfo().
  • +
+
+
+ +
+
+
+ + +
+ + diff --git a/docs/hacking.rst b/docs/hacking.rst new file mode 100644 index 0000000..ebd15ee --- /dev/null +++ b/docs/hacking.rst @@ -0,0 +1,123 @@ +================== +libtorrent hacking +================== + +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +This describe some of the internals of libtorrent. If you're looking for +something to contribute, please take a look at the `todo list`_. + +.. _`todo list`: todo.html + +terminology +=========== + +This section describes some of the terminology used throughout the +libtorrent source. Having a good understanding of some of these keywords +helps understanding what's going on. + +A *piece* is a part of the data of a torrent that has a SHA-1 hash in +the .torrent file. Pieces are almost always a power of two in size, but not +necessarily. Each piece is split up in *blocks*, which is a 16 kiB. A block +never spans two pieces. If a piece is smaller than 16 kiB or not divisible +by 16 kiB, there are blocks smaller than that. + +16 kiB is a de-facto standard of the largest transfer unit in the bittorrent +protocol. Clients typically reject any request for larger pieces than this. + +The *piece picker* is the part of a bittorrent client that is responsible for +the logic to determine which requests to send to peers. It doesn't actually +pick full pieces, but blocks (from pieces). + +The file layout of a torrent is represented by *file storage* objects. This +class contains a list of all files in the torrent (in a well defined order), +the size of the pieces and implicitly the total size of the whole torrent and +number of pieces. The file storage determines the mapping from *pieces* +to *files*. This representation may be quite complex in order to keep it extremely +compact. This is useful to load very large torrents without exploding in memory +usage. + +A *torrent* object represents all the state of swarm download. This includes +a piece picker, a list of peer connections, file storage (torrent file). One +important distinction is between a connected peer (*peer_connection*) and a peer +we just know about, and may have been connected to, and may connect to in the +future (*torrent_peer*). The list of (not connected) peers may grow very large +if not limited (through tracker responses, DHT and peer exchange). This list +is typically limited to a few thousand peers. + +The *peer_list* maintains a potentially large list of known peers for a swarm +(not necessarily connected). + +structure +========= + +This is the high level structure of libtorrent. Bold types are part of the public +interface: + + +.. image:: img/hacking.png + :class: bw + +session_impl +------------ + +This is the session state object, containing all session global information, such as: + + * the list of all torrents ``m_torrent``. + * the list of all peer connections ``m_connections``. + * the global rate limits ``m_settings``. + * the DHT state ``m_dht``. + * the port mapping state, ``m_upnp`` and ``m_natpmp``. + +session +------- + +This is the public interface to the session. It implements pimpl (pointer to implementation) +in order to hide the internal representation of the ``session_impl`` object from the user and +make binary compatibility simpler to maintain. + +torrent_handle +-------------- + +This is the public interface to a ``torrent``. It holds a weak reference to the internal +``torrent`` object and manipulates it by sending messages to the network thread. + +torrent +------- + +peer_connection +--------------- + +peer_list +--------- + +piece_picker +------------ + +torrent_info +------------ + +threads +======= + +libtorrent starts at least 3 threads, but likely more, depending on the +settings_pack::aio_threads setting. The kinds of threads are: + + * The main network thread that manages all sockets; + sending and receiving messages and maintaining all session, torrent and peer + state. In an idle session, this thread will mostly be blocked in a system call, + waiting for socket activity, such as ``epoll()``. + + * A disk I/O thread. There may be multiple disk threads. All disk read and + write operations are passed to this thread and messages are passed back to + the main thread when the operation completes. This kind of thread also performs + the SHA-1/SHA-256 calculations to verify pieces. Some disk threads may have an + affinity for those jobs, to avoid starvation of the disk. + + * At least one thread is spawned by boost.asio on systems that don't support + asynchronous host name resolution, in order to simulate non-blocking ``getaddrinfo()``. + diff --git a/docs/header.rst b/docs/header.rst new file mode 100644 index 0000000..e898029 --- /dev/null +++ b/docs/header.rst @@ -0,0 +1 @@ +:Version: 2.0.7 diff --git a/docs/img/bitcoin.png b/docs/img/bitcoin.png new file mode 100644 index 0000000000000000000000000000000000000000..25e9f15ceb2a616525cdb6f08091b494f8908e81 GIT binary patch literal 2611 zcmZ`*c{H0@A5NL6Bm^5=$^Tgj$-KsGjei?|k>1_uk)m-*eA>@AG?}=l4r=u!D(OBB}@Xi(JC8t{-S6-UgE7{8bP9?$Aoh-@#-9o3^V2NAB zDCvh82CoV%u-gKmxVUcBSIKZ_1$IVgUzl_`xDz|I@eNOLDXz znAuMv>8uaIV6cC#R|(;;SeePP1eS}7OB#D*{+&cf`{4osYr&oD*o&QDGc(&lcyk_= zM%#&CV%BH6(lFt^zP>RrjZBoAn_&yYz#vahp3e2uk`W1>Y`zoHf4vw%TEy1N6UsU) zAD_e#%Dh(UddZ%%z-L`QZYaG_l4C^x7F#pP-Er~p@yW@e>($_M=RRIc> zyd6k-qWkaFRY>gDMrOy%F?ok^2#{ouF~&6Z#T6{*SCRmkji1`Y+R<-dj#QSR;_{8{(L z=h@jeLGfx^UwI87$A77eG2dGpe1lnEUbZp0z5WSppd|sha6uoxw6xSy2MwWz7XVG! zVHYyHpF>Kw1;!^Pnqs!$TbFqA=kcoMF~{nq=tmIqB7(6615>{r-A@QB!b>CDCMVOo ztzJT0JdsEyKw@*Ki;Ih!{mvvtUwixDG-43j(9p2HzMiyb1^%a^vJ%~_b1Wq-&6kW^ z_vlCqG-^?$}g{} z`OI=0S&CR+ZvQsY!kknWKhE0e)rX*_NP7b%wm&pV@Np;*;OV_&GMR^B7v{m{=Af3C zn3w?EuS6&4c^%1IYmFzY^aRJ?z`!~U)!WMO7U8Nd4E~<|J8=!|&Z>ivTR~xA@a^-2 zegc7Dj3+wv6b7qTRigCp-T5szp7i91iCJk8??^8aZ_^dC2>BuD;8yg3zxRV%vHAdx|AbBC7 zvQ@v@&Q2sg0Jf=cuFI;7pRGd$MMP9F9%y{^SbH5d6&@ZwXYW9C;y#eG8xxK2XV>M& zlh2@5!WgOK?&@E{i1RCvkITF7j_`rIb9XSH;(3~)qN3m4XvhiRJ?jrLn`H=6l$g1` zzP^dcD5p11?W#tbqc53Br_(WHC++MA(Gx@jX|8Oct{~f{^XATI{^VnSniXWmni`(_Phn9QTq zN+#fDNBu(E z>NIfdjo^1E_;iIWq*0IXFlHPb@E6)1_^SnCZ0{UiY`lzb?gNJw5^UvOgOM7PauNbK zS>0gCG5c&PfiqMp?`{$yUP-%44)n-HF zRL)lN^Wj$R7@QT)DjXJ`kH%D*|w~uC3rYhivC3+%$xv$K!S}VrbKkF z>tqa=u8IT@9KG%cg}o1!ik5n`0shALQM6_fWqM^LP-iJ;E7B{^_3nfn68KZjD5cp+ zJ5f<5^=o^dxcJ!*_9KiRe!M3`GJb;$eBPLEFz88xd(A zXZ7BCkF0n;TzcN|>u^VC`o)9H1MmU&-2FNV6);|2ULhf6j5h~1Or4va&SN}qA~~mB zhC|s>*G!9?w6)Afs#6nBXQPI)kUKxd_&J z9@=;14|01Q@;fY6Yae#V!w5R(FETtl{Dj>%g@UKQx>+St?{NCL>5beMFBVp9O5oa; zn=!MZ3U}xYDrb}%{h5Rv;4?-~#^1vup-3gPX~D~}ciM9vgzpvCv8KCx+Es(nLx;&- zDHnMwew?;U9x`6FxepKMWv{W> zK05Ib5U9GcQfA~dWPpk8QgA1+(n;f`x2J1ffvb)jDBd50M4MURnA*1tvbnD7m(a0E zUwUsh1g_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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 W|3LUK8xeQ_OG8yh1*T;E`hNj}<_6yY literal 0 HcmV?d00001 diff --git a/docs/img/delays.png b/docs/img/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/img/delays_thumb.png b/docs/img/delays_thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..4e72fe52f56832829b0abaafba6df5f0eb84ae28 GIT binary patch literal 10933 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 literal 0 HcmV?d00001 diff --git a/docs/img/hacking.diagram b/docs/img/hacking.diagram new file mode 100644 index 0000000..d9604ef --- /dev/null +++ b/docs/img/hacking.diagram @@ -0,0 +1,30 @@ ++--------------+ "pimpl" +----------------+ +| "session" +--------------------------->| "session_impl" | ++--------------+ +------+-----+---+ + "m_torrents[]" | | ++---------------------+ | | +| "torrent_handle" +--------+ | | ++---------------------+ "weak" | +--------------+ | + | | | "m_connections[]" + | | +-------------+ +--+ + | | | | | + "m_picker" v v | v v "peers we are connected to" + +----------------+ +----------++ +-------------------+ + | "piece_picker" |<---+-+ "torrent" ++ +--+ "peer_connection" ++ + +----------------+ | ++----------+| | ++------------------+| + "m_torrent_file" | +-----------+ | +-------------------+ + +-------------------+ | | + | "torrent_info" |<---+ | "m_socket" + +-------------------+ | | +----------------------------+ + | +->| "socket_type (variant)" | + "m_peer_list" v | | "(TCP/uTP/SSL/socks5/...)" | + +--------------+ | +----------------------------+ + | "peer_list" | | + +------------+-+ | "m_peer_info" + "list of all" | "m_peers[]" | "contains contact information" + "peers we" | | "for peers we're not necessarily" + "know of" | v "connected to" + | +----------------+ + +---->| "torrent_peer" ++ + ++---------------+| + +----------------+ diff --git a/docs/img/hacking.png b/docs/img/hacking.png new file mode 100644 index 0000000000000000000000000000000000000000..dc31245d76982124b87bcdb0bafe69054ef57e14 GIT binary patch literal 15692 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 JS5o?S{vVh;j+g)d literal 0 HcmV?d00001 diff --git a/docs/img/hash_distribution.png b/docs/img/hash_distribution.png new file mode 100644 index 0000000000000000000000000000000000000000..7bc1be79e5adb6fd1c53182f67b2e971573404e7 GIT binary patch literal 9035 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 literal 0 HcmV?d00001 diff --git a/docs/img/ip_id_v4.png b/docs/img/ip_id_v4.png new file mode 100644 index 0000000000000000000000000000000000000000..55d02390e9b2965af7d83816bb50bea2e778236f GIT binary patch literal 5825 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 D8#_6^ literal 0 HcmV?d00001 diff --git a/docs/img/logo-color-text.png b/docs/img/logo-color-text.png new file mode 100644 index 0000000000000000000000000000000000000000..edffe3703395815b5976cc8ebbff6c0d0422eb85 GIT binary patch literal 17420 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 literal 0 HcmV?d00001 diff --git a/docs/img/our_delay_base.png b/docs/img/our_delay_base.png new file mode 100644 index 0000000000000000000000000000000000000000..d14ad9ed86ad21a5fed783be6a3beecd1f9c1f96 GIT binary patch literal 34999 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 G#{CEQiY{>g literal 0 HcmV?d00001 diff --git a/docs/img/pp-acceptance-medium.png b/docs/img/pp-acceptance-medium.png new file mode 100644 index 0000000000000000000000000000000000000000..24f878b083e47c98b9f8bdc1fd07be67507cbb62 GIT binary patch literal 1159 zcmex=dCe{<py}g&mFV!F zUy>Dy>Vc+-GlBrZC?Jc4i5YAXlK_wtc#mnVTfpP_K&29l49sXsftE6Y)iQ(Bip^@O zeE+g#e>_mJp#TFDBNG!V58SDYOw0@{tU_!8f{q)7MMM>iO@e}piYHDoN^}l9bP?)y zMtg=P4j)5dA)&x>t|yA0Cco02(0A%EvXiJs%g-f?x zSrVf)lOw_;<)m{bo29E~Z`%{TM}3>ctmci~7xtL))U%r(J3eo@1^;A4_D`$t${k)e z@sp#7(dK7Ht9ZWVY_nipk8bpt<4nk5offo|{4;f+tjOEYf|@`{Wuw z%d~HTYgSKlI`_^#r!;BrL)(o80&*9RGM@Vr&n$7cerXhk{a(ydW@50^^q%o{)gC zEy%zkpy(*1U<^r3jKH`(n{mz4Au*{hC;QmOKNmd~-@hE?$TIDfYtPMjo_hPWU$5Gu z(ATe=xR;^(;HJk%R~<__bEek(IM4z`umwzvtmqa9DJdu#7B)f>A;^l85vzm@)oicI z{N8%~`Qk(=k2#8;7ROgEUn}AwW^Ny^ovqBs5@GX2;@8HFYPU}AapmY*{D8sY|4jhH C)oWA$ literal 0 HcmV?d00001 diff --git a/docs/img/read_disk_buffers.diagram b/docs/img/read_disk_buffers.diagram new file mode 100644 index 0000000..1d04a25 --- /dev/null +++ b/docs/img/read_disk_buffers.diagram @@ -0,0 +1,16 @@ + + "copy into peer's" "encrypt in place" ++------------------+ "send buffer" +---------------+ "(no copy)" +---------------+ +| "receive buffer" +----------------->| "send buffer" +------------------>| "encrypted" | +| | | | | "send buffer" | ++------------------+ +---------------+ +---------------+ + ^ | + | "read() from file" "write() to socket" | + | "(copy)" "user space" "(copy)" | +- - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - + | "kernel space" | + | v ++-------+-------------+ +-----------------+ +| "kernel page cache" | | "socket kernel" | +| | | "buffer" | ++---------------------+ +-----------------+ diff --git a/docs/img/read_disk_buffers.png b/docs/img/read_disk_buffers.png new file mode 100644 index 0000000000000000000000000000000000000000..1ee3623c3239ca6785d9c4d1de82d256c77049ea GIT binary patch literal 8371 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 ACIA2c literal 0 HcmV?d00001 diff --git a/docs/img/screenshot.png b/docs/img/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..e2301c58a4d467dc8613bb3e0f5dffd06a541bc2 GIT binary patch literal 501532 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⪇IY#~4S0qYUu~ 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

    %8@mA>j(%)0&z9t>{7CTF?Vfn= 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≪{_jS$A3{XB6R@A5pP@tJPi z2$sYhc6$tmbKwZzNX)$hlPrSZe|Z)k=t_-KQ$}+ofNca`gS@#p6Py#JIHIj4XVP+x zX~+r;<>YvawH=c5gzt@Iavl17 zfcv@S7zP4ONa4KzF8;#@F~Nl+__=sMjM8!Ak-RzMCWCVo9_y#0xl;3@F7avTno?yB z`Bxe_5yuUjCAYzlVQ%Wr+Ox0LFVAgfcj)dE)GeK+E5N-EZ6Lx++^FbTn67Jm@jm`$ z(Lm87IB43cP`w>yat>YL7%;`_)%_y$>^&_f^CRUus|vpkUB=6A}mRg+saTy;+ zc24#jTwudTbd;Lw2}pM@hU$A8i7Q`KrWmJAeX4ey`+ey)Hy_yyHcyxwx6iY|iMV_+^Jiq z-&@M9v843U9kL`ctqd)VXdQpw))O)8Toif~RukzC8ZL(vRM$(c!ejOrOis)!xYh2G zR|!%?eml>2pAjZ@5m<&)G+fuByWcU;F&`=NI0-fEOB?qsrMt) z5PdGv(6IAup~Zn`?eR4(u7TxR_*kBaAq;D=0;v4|M)c_3;$Teu8J=y>oB*4dkSiAQ& zr@0&R@jw!Ro3fg|elUDE#KjG*w|Ibx1I_l3`%ZJOv5iMdFh;ggSX&b|no_?eoNoE< z$N2ek&xR)kiwmj6CAYPWDk-!%$S^R7>c@|(<`(v621Gwz#78ZncfCi(@%lQZ+WPi(zmSmH z)6k+QnO1hom*3*p(Q`u0FjqUzZT8nK0MfCSlz5-NhVERVxre>V^!za5xB{6XRNQB~NzaVo}j zzl>VB@CyVKU*GpN`ADY_Fd4_m{v$I(cO^Gehn zo(h;>{+@?&aQK#CKR7HdD5(9%4bCUX;38gVJu;Ned7->uP-z6l3$TOLOfC6E^ z6+_AO!_D)~p{md^{|e@|LqkKHdPRXSzI}{k4)IwJW=r*4Fb#NA&7yR(5t@hhZ{iQVPb7z=*|ze+`gGC0pCv=5@fRMny-@ zK(NWskOtmi;Yt$Y)^w^K$7h`TjgAK7j_FuHT7ke!_VCNq6~sw|abG`LXN5u@7+bn8 zCAzgjq`Rkw$Y1I%XhSDdR8&BCcMyNM5B8~ggsBj2lg1C>g z9WN{ z%~%TF-WC{X=!Hs;;~U3R!uHnnyVPd%CGsFK+%4a~`+`pjNjp~m!F@!hiaRfd<6Sbo z^YLbpq@k4sqY8-3Cz=gIx3E3rk>3JAs%2Zb%C?}GxWVvlFZ^2H!t{}?y^D0tg9tvs zv-`I<4D{VXXRDhIunnpf2dB`wlslfCPLZszOs3VmADfx+o4lZ?i=?7`-#pxoJ5%~#Qa(OfR{HyT6V7@yf2O>ZWH>9&nR4LleY*SGrB652 z1PXZSzXVV|>3TvzLHUU0k+QducK|@l29n{7rX!(~rlgP}VVp?Ywn{8jb7^Mal_rgb zgz8%_t#q9kq5j;4xTNYJ^o`wuW?54W&4?ROJEfa%{3EYqynZb(#XDO0ZeomTF%c7O z&+zG+pblzFYkWef++dvO$Tj%{*%6v|<##tg!Gyy0ioVu&jhxZ{#v6L^Y(r&bgpq^v zYU2K>Q@6Esnj?vA8%Bl8A3UTJiKsm*(b9!6sOjvm1*vGp+AKIyUS^DD>w=hPT2u`0 zc+S3A&`~%F!wAytqk+%r29>U!0hx;F#(3rMyM$BjZE#yi!+rYMsysqH1tx0E5C&a`D5Pub3DeTcn%3iNWNx)T;lOUwNhSKhz3V`8e3 zg5Hx2USIQT#BI7!?JNw8xLo(CoMD^3{+NCw(gM{kckh;l+Q1q%x7ayO_ayo~i>5&W zK)_Cc?!2|*rOkahCTErH8coA!&E!3PBFAUvUBXjO-<;yR)NGSz`ucr#0Rw9 zP+vTR1Ju|k1FsSMs7T(L>U8%5WA7$NrXPY22pJxL2XpZ44E%rq!2pJm1rX)eCM#o9 zC9$Mn!2}5hLXATrkuxgJ{?kdG;5VwA35UukILgkhth~|4qOCQC?Vb*WiG;bThPrwR zKaM6{a!kq?>rgv`f4<0nN{4>jr-b4mg}TQ_(r=*@()`kjgzrso-`Y@Rm=Xz%#QiR!vX`Yn@D%e*#ucKgmX(wsYOBbsbQmeP!`{o;1T4k-}=KLHp z{whQPed4o~)u+3?^}%^|2^`$kk0;`mX9gF0{c774DZPHnVNfHT$tuZp8k|ooU+k5YO%x7k5I>R z*A9Hu<$M-bb7BrHa6Ch=U#}XTBhSfss?Kp?gJBKWVD4QtHz=dShk7VUNoBw+3n&zb z#7+(WtU%$8L3OL{Omz}rz_c_$(Fu$JO<*bodnvdGy3-Wi0c^^PDY!CG{1~FSj-YLV zn_*zXv$Jz0Kc&g^M(&cJ-~)9wDhG%1qffcuxuc|{L{j`n?<~}rl$V!RZ058#*VUc( zJbKi5G+E)_Kf=~eQM%DMRYT|K&g|$8`?;hk*xiJLN<;gMjQF4a6;(Oj??@xPO0)QT z#sP@x??1Ecs=NS??g`jdi^uL;{?%eqyWXtsfH_zNq1Vc1+N)pLY~tm(Xqg?>#~vgR zXtkG5o4(Sntxe9u!&Bd8`K)rYxahOw#{WvP01eU(N<Cq1EW%P$YJyD}IIVnH0QS@T!R zeio1;+F`H->S?giZfXavAp`o|AR8zfLgC<H(_c!TJFS(m`6Zk z33v7yho+XkKQO&p*Q7f{X?*)X(mJARg{Qp=l}FDBR9?;fq8BdtPjZJ@MAg0L&u{U? zf*R!!v9(nF!+6^3Pp42g*I>G2(&FK_x_3eE7qaGCh>4|0S`89{6tfuU1$_DQlTHq_ zyu1Wc5+1e>f4c#LFTHDhd3hc^r*z zZRT)gH5j5*v>ZuiB>nr5c9ZRE)g5x9X-unroTrA)JX&01XdkCAIet^YIqtfnV-i4T3<+@HXG`a@v{HmuqWbf#4E%n z4y7!(O>d{X{GVkDS^LAM`Cds(hrg&sueQ-Kty8&gMw6w#4fK=jYiWMppK8poEozX{)sznt58kRmLo9D0l*C$=<(|@{y&S9Y`#n zs&;1;T!?J01dfT4lF|W$LZPG{R18N)yHcEGe+C@I17T~26MC#tGjwqm*CXSJgI}rP zV;H_%#(I>2fUxi!KsNzb2TKY7qL4@(43L=`^I2g>lTFvS0$6M40uP3JMyT*qPnoBO z2Qsw;HZ|BwqTatRupJi)y`m`2Zp$FyL!6S56Dy?L-jh-}h?To9F6z2-hb!N7;B)F< zngOfvlhz2D7iftb;8}qL2nn%;M7JrEwztg4Jp{TYZEb0%eOAL_hLwzbZig$p;{vD2 zNTJ-(X+rzW;n-We!7!CFwVO&5_e7siQ;+SV8NbUSlAtJJQi_qumgDk zXa!3j;3Bgz)=_x{c2^6Wo zxLu-eXFAtgY#niz3}m4ajn+#Ck$3Qx?v$Pc9iUrp(J$%cGiM!rE zv^_j3o{%@BQX#Xje;)@S9M17^hTG&Abux%5@PZ5Azl-5Emd(;4rlHB*s)a?fprGKN zC;yQ{Mp*GR3?&tnRo?>ugIQT|EG#VOxiuCo<*6RqIUw4`ZNvX6T(a9hJH2A{*Clcz zEN?F>+>1V_+xZ_}0LeDk0r|{ZiKaYOZ*FgI1IY0aj4*S?;$6$33inWlUa7cLfKk1d%DGlDs2|>skKuOQ=6|gWF}8!k?BbV^Vo} z|FNW1f%Ppqr<(p~GmurS3anbkqgY#8HhBtoX3J5Ma{Zw!aD@3}qa&dyVh)2S76lJJ zmgA)Ws_??t+c=tQo&GC|Wa$aNoEOUIZHUOTIS$2G@BjMX6ObPHVf~798NI#vMw_vv zj)dX5^-R4#r2JE*$)nGlNg{0y@BTSa9$xO@60b&aDi=kEDz zI3a$EkUV+vuEbdoH_jySojECtz^@-Pdy=q6_z~|4t*Sk*w-WUEPTJw`mj3 z+W%GBWYlB^YoW%(>*Fmdme}=V@fxK>+~9YUM2&9S^VE&?Q{F^9&CMep8XDxjv3v2D zsxLv)Je+@#runPfA5qiS{%c*s>_68vVs7fos;fsXK9zx#5cB^WH95b{-g7%JC};-m z7Xa85i-zN}GfR<9Bvmj~dD#v_OMSS5P2D#gZev5Y=rj|BC>nO@3PVLH-BV!p-fMcxr2)$_w6PespX>D{0&`btppC7fx+k}D zKT%fHWX9xsu?vl-dJ1e)Qm(I_?R6HKS0@jqEQ}`|rE=9CDM;SgWUjt2N%>|x$8N-b zjJ||q+KIBVW>)dw_!(?_yvt9v8(Zc6bUslD6AfA;r}PWI+}!v*P5SX_uEa^E@M|6T zU3pcDGfjsM?my5~yO`>49{tjsg%7y|XlfyS@W zcn4W(?ODBZ&jbWLv-@gc`vE&w9hl$Tx0=Y?+S{%E5WBnpLkT4uwSWMV=e`Fz0G~zI zHOPxRhXFh6Qs8cr;62p;k5Q29@-s$(#==mr4wEFvOH)Dxrxb)j;40T!Mo7u`3euBE z>jC610I~lFGzUEsa|_h>-c-$}Hk_Wb^Qe)po0Q^c3N9}0pUa0Fu<|mpJq>CW7sEI; zhlzuf%$AVAu(glE2g28hT^bXP=!+0+={zb;ij9j~xrzg(%x#YyHgG1z8AD`>PGJi0 zuRnyRoz3>`l3O(P!a00YyO+bB|jr8`De=h`iEU6}M`AIA%L#qMY0bp~7 zMgh8q*t2+e)>u%6ZaK~ezE`p5_C~i(H)KnCZx5><+T+{X+lN-c%^GQfM@V0{96s^z z67}F4qE%y#-H8sp)gb*YU@7sR21R~AP=v6rcG(oTlu%C;(b_5xLj`{=j=_q#-*+L= zRYr2N^YNHr^^5geeUVN$RnM7%4J(?nxSQ8)+$drm7&d>3`OC$EEw#D!`qnS!VSg?h zs1QXHHYw>DXThyv>C_id#gbyJ^&o{zG_cvU*tc-!5j%*ida6?0(*qcquPiTsnZQSG zv2wNK9~uMyKQxBiOV|JVG>wJ3|Ey`41;p5UZiE+LHiWU?_5H3;2J;jMNNrb~8Rh27 z?$&|r2YfNDdqi9)uh<=&0;`SNxaf1*(*Kb0pttQFZ;u-_s-|$6w~+WnEdx+l=)<1b z#G;+b%G5G(N$+(CevO49S42TpVhYVP}c*3gNveTfp#^DoITxZJ!+ zRcc)us23?O$thEkdF|CB9O$O0nyNO*Rg2mqZ1_G9k}41SDQRFb?bF8+8KZcN8Emj6o>O0l}L$mm`yL;;ybf zpu2>mrIj1h)~IshK&bc-C-Ug&(>J-fV?^m{t^ZGx9OtllU?2fSbfnmt9H!;D5J^V; zi&&vEofUorGA56pVAI%G9I#ryS#;Cp-rt}*14DipnOBQAxDE3-Eh^nS9g>GxhSR5F zZBWn!K%dU#qp&wX&ww(iFzg+r2iWwC46Ea?&`|jMYq*RoM@x9mZ<-7xOB{OkV5a|f z1c~}rckXaIFH0Jt<%|!#DUj*O!-rW$_<1?00I&VCRJ>W53>Y{_*(3J|df=K%OG}ep zJPYsS6Sr>PzCud+N{I!u0zWugegns%keybzF+U&QEm-%EwmoR4LJnFGR4uSi_P`W^ z(G(^F4SwcVblaa4R-oP>!pCzu9 z-tf43dQV5V$m1-#rslKcxvpZDVYVs#kZDPb^K0&pcpO-xKL+zb5SPo!PoH`q0IG3W z^@{A+7Xhk^MdY>OijfGRAD67*Pj3XRG3)tVf4xfG|5=TvsuF+ss&LViJNNYn&Bu?gvG)okc)#XWg%HK{IhesiS|lGnq=d1RfN;)kVa}?& zfuS5R`O+K3bgg{{ZE^|;jGUvB8$-+kCPWC+)8Y z#_#Mb6cr7ed}L-O4SaaUaagsWS!pleVS0KSgfY|%5?-V~R)zTOU`O4;ipWK|mS-jQ zZoLGru3f9Y5`1$bD@iq3LK(sspi}IujlP2-wdKb@S3V!uPsk4sB&VmJAq;=8BlJvL z`*Ht>?rW*u=ffhE@m{xXy>gL9M=RbQ&a<@_N7-1-0+7IYeXLk>CAsV4v^K{iNx~i) z5i-E3aV5A(42Svi#ke*J=vRf10ZZI~Ga0P4NH!NTTqksko+2RiSFc_azdmFwzI=uAU^b<(n8GS?ws$H%4fB@GWf zBQUtB_5WIX@W0^uLdAJm&U=Y`y=`+&mMf+Z!eJ%DAC;72CA>xG>FNL8M$(faEqIRh z1+c|Hbx=@Jl3|Ga-F_qgDS;S*G6c0@0s7cY;IVBcE3|V$r>flF6%`3|CEWId%>|CE zUg+gaC>f0aT7;`>$xsl1+ov0ty$SF^j$J~{w?>8leDcAA2T_W<2UxHiU=HTOf$M`X z9TE>W1CQ7349Xq-VZC;ML^Et@CWQ_QXiiA@20B;ZXe|?>aNSd}0osNO;kc$6dxoP7gt77II$Q8!&Ciz{Uh$(7scbX8 zCGY8)#fIJ%5=tMPmmmG~a8ZNhBNG5N7+wZ`GAO|>uqQ9uKR&*mKXqc0nW(gT)m@_d z^#0oBABjyTi6yVD+vpPXPWZ2US4>ZhD5KM37GL9YZ_zd!$GEM`gFC#Rsu>cJoye>T z33;fVzUr4^)%c`Y;X@hjkXBw4`$lHr>=`IcdA&{{yO^z{qAW6KIfBVO95Bjeeu^TR zH8b57T4FC3o_Z?7@Lmm-PK)%!8#^+itZV8g2Zl+Nr#nSR)Iwadk+nejCCSrOsy*w?pQD65fzo2)<@p~mXV(+a z&T_0>I#F)A6MFM2Dhirr0f`T513M_3cJNL#kB|O2ZhfSa#&qLwG{HOaN48>ENOEH@ z!xei&t>cToJfvwJD=W_rXr)3AJO{V^RJF53Oh&Cgvr_^d(UxXQzDM_THiSGj$5)M{ z{RsVJ`#Ikg@GTVS@s$~9D#t6y-l;uK$RfgT-n;Om>@kWjn>NwE>#pCqEwDese%1u} zApnu!LGTI&afa|NWW$7l;S6bgUtJxy6oX(V;%x!I#v?nsE&#GlFW?&*JHkB=B9nn( zVT*;`5~r^NF#ss160sBB6aA|4@;||BfSi~tlH4=yqjGcCjXhU?eZa`tZ$~P|J3RZ) zvv4Y=x>_7jNDgL$0sI^Z<;eN@oB77uiyx7~_s1u$UR@`#8XEd8ARK%(SictN%@o86 zSBVvv{rx3>u2)~=FZ}*yXL6FfZ*1(2f&yl;{}m}YPRU`b`}uQD7BR7~%^LiVA3vTr z0r%Oam@H@xf_WvFxiPDDg11>!CFC5owzw8jt?-5?p>J70a8OjLyaG)OAhD6Lu#_T! ztotb7Tbt3@+DZ%d#iXRC@7DFjbUX$Kzsr49ef`zmUS92v%}KO_(@pdm9@2!c!abN z>N>~Goo^SdI(Tqb`R1S_`NPU zM=$wW&8<8vEs*IgE3=tcD@Ex0_MjJUUlv7{0wsxO6)NZAS{w z=rUYW;#%?Oz>|SlqV?+o=G>1&C*KD~oqugd^9HzBSJ{?Y zyGiGbZATWjw)%)uPv5LD+~8RW^3pEf_s96S9WOldSg{1q^m%21$3M1sulxPM@5)K1 zvvsi*AQRw7%9!0FFJMuBXX>zO(i?tkf_3njitWfZ-Y6~^hPrZ(9lX~cVx8<(l-{x^ zvu0+zCVv|p?!X6N((iYKKP8Czq#RBKUWfl?;i-21CLg85^d{r3&OJQT11szE;^@=D zR4WRJLips2W=tCF(Zm;8Se2Nany>jJ@th?j(PW_&%_iOJG&xy?IwUS-q6zo$^8Up` z%%Z|*hPEMQRY|Q#_aE*boL*p$RCxH#HH=U$w7IEOm#0;g4Mv5!E4&{Bv)IFxlJX78 z>kqc(kn(g0rGudGdHdTg5 z$f!4is{~>qRjcVm{s#war%nrl^V)Qx%zGUxr_X5Yv;0y ziwt5CO{kuFxU;$hj#<@{YHDgP;^V222m}zGK7C>U7uhp6H{5R1F^lKEh>0!DkF`3xL4s$$}N+<<%;2o-iXUd%nd0zgRfe0S$V z53rNa^fC&(!W(2McBvew?45k>6;a>)98x(1O~NU%mji3tY>M;T;To0^)C z=*IH00;EEIOI`m+wqJl+sWJYXT1&{Nt*s4kvG)&*-2H)Cei0uZkgIFYe#}&l0JjT- z3OBz*UGFUf+@fk1-;xF%f^WpOz?|`)k$X1|0+tn26I4{uDUohiGv$wX|fa zqD#j={8i9SOvYs}LXWYy1+AM1%?mEB$6q1@ZzS#ew$n8NO|%~UqURIG_ybR|8Hb|4><9V5Uw-?~|< z*J=3}1o8$AS@oGSv}d1mw*Q=Ma%=0JW;`!_r-#tD z;q1ms)*Y7}j&S&EEVi)iYK&bk6U#jQgUYgWqg^$i{}Hq{UvwK)$eY>+^64PCfb=3{`FynXXQwpU{Z)wIZ>|L zxmh)#-5_SF(hFUi+Mv5GUNrp2(*9BF)&f?>D6`Gn&dYLTMYa*HMT&U%Y?MFy|?aD4l=;4$e!OUD&&dIny$NMO|z zPii~l|9&u4BeguEA>(;(-o69x`aWIx#KHV)wczU3pT&L`Fd{^m771U^7u{)@?P8t? z;|;s@!>Nv;^`$TOB_;;uQ~Z*JM!`5EipxC6KLy{x2a4r0*c5*A&*-WaYz)+{Yw3p= zgqD@8>*cq%SS4I#ti9Ie|Cqe>*mFOOqQ7*wW%Uwwy=nl74@=d!Ma=+yAlyf+vL zPIRx{czbLse;|sOpT&g0L&u{!2fE#NW=OAqjDL_#g#I`$kSa=5TJ*+bG8%w>sC{0lR9kyq*q#~%?A zA}mlcva$u>jf3%;sh{;w*vn3=WupriNF|bxl`KUmtnT0RM^8pe%w= z;@}Q@SV97!f_z{`c{wJfKYVMh)@)^f1UjthJJ%^crRL9U*6PgAUc=Zw`Ee^0`Uz}o z8-8!OpS|BPbLWW0#IgLEt*xy;ZoGDRbu}mY7)r$MK!qBZ9Su<*=_1%mCm+&il%a*m^<92jm2D zHiWhO0s+BVG2dru9K`651S2PznZ13!&U2UcTBxuJyLKDthk&aBtEgx^h(+b9D|mHN zQ?%c7l|b+TSS4_Re*XOVvBXQue;Q_M2%rmm&)|!P!fcP-Fb(ZFZf$gWJJtxfXUs1H zQU&Q9g9bCW3w!IJvfHQM?K#zU?5Fip?``>v5o+t|>avYtph635dpWZxaq()o3|fR) z3kg5biEo(CjngDpji%}~H~U8J>%aA*JdC_6_byn)w+R_w3bz{{OZ%Mm$K7ChYE0xF zzs*Bs{%UD8X}&7bZ&3F}^hUPYRU>JAKe)Y(t&H_RYo4tFRadu2zhqS{d<YeoRUC4J*Ig$HJZ~m|IPTvlS>3{OvxBN8?1pr(=HI zHi~0{3ZKd{%jWK|2D#-sQiY&2>F02+6f@fWxs9s~F<^Os*{JTNhCaMJlrzo4LIP=W z>1>Uhh$w}D_FAa+lb*UJwb-r{cR#N3=Jev*y|eQLsgDorw$@#rpO!lfKWs#lVniUb z;^WG7ok(3@?+?&e7@_{Tn?b(Z(eIhQuCUw4ZarA)+}yE{*#O4KI?M(C@OGRX1? zcb5hSoD?lFPBKI~XtYg7TZRHh)8NRf!mFl=gH`i-s+z%_Cw)0Z|KQ&X4SjjnN9u6z z$w_d3+FU$8U#`iD=9~nheoB6RIN0hD?-mT=-`szeuE7p4`ir$lNlEXgdvDJ2FpCfM zo57u_3c{%J>25ZfFam~QIh?YD!G7m*KC>Nr7dWPEFv>lD@!~8@o@XG9^+m7vjqP}e z1NhYGy#?8TdIT-Fz@&x~34{_A-`$6?R|qBlYse_D`%D*RVkH=XnX0602$w&z`d5Ac zvx{t3T&QVjS$gc{WyB)v#+sha3hoDhbOzMd?_rvnn~`HJbmRtusE8EyT)+NeZEcUx z1@;{iyA5Rz^tqkgTnM|;#Kuy$wH=;+I(<@GYljh(!^Xb8@DypBqTZNDp26X(yM9KE zBO`FL+gPh&XIB6;3O*?r?cz|bfU}EoR(>rE$iy_VUiJ08o6Pp(HIw+>*a({y>V)H| zQ&VYaOp$+3j=6jlRa1|9F7hi=u?AVoQj$n3febd zp?YJisu~XJ2HbYBp)j3v*jsy8+>oQ`1CAk;Bryf>-+-^U>UTXp1vT})0`LGRco2gj z0!d_NSEoAq%$V%y5AH?8febOYJmcCfuzrHYxC%Us*Cf2k9<+b9!lWzKYL!pR%E<-b zj`wRJ^TFq~VE|E$t9B3ajnb>CuHCqCg9^Z{oSbWgXiaS&m+@{tw&`~&_9HDvmXccP z+S>Qv(0OF=O!_o1b7)XWr7xVe!7Bq>)(IP1TM#MaILYQ# zR@#Ay@f<=M;Q!j&vX+%Shln=d?P?ewk4L7Z&dVB?X>I6m$Q=l%-3UDkUK|HEH&Apm z;T#LCj0T8g1s(K_kbu4omUHA!n8qCo!)guX%Rj--92-jsGYE6ou4tN5_TLysSAxx{ z{lIN~%pdC8A<~@Z5{P?+xg<@_XI!3O01))0ST6kX`l)i=~LL{AIrC-j*h9VSDZsko8j0do%Sx9(r|KBBz@ud;GP`EaAN&nI_E z5ct^gG~vBrA2sf?q%t%S8$qhtt@x~bk|P$xb5d7|{+bTLa{i0Fk1$`js;vBoMOd;j z$hYuh0So1@WQ9sk>0N<&K{ZyFJ*toB6Fe~w4X;S<{a#UUDYmefF#7gb1&y3Tlxmj%#*7{Sl5-(Nrj7%(y z^6I;dUmp~-cf_+x;^e*cTbh=AWm$B8t(fGc-Y?W@KO>6Qcf+3j(7kgwyl`RR8uv{d zH1^Nxp%)QFJ+)86A-dV6hNEl>p4-ghzlz%}YZX*~wlSuEN`NcE{zDHq;J~=;3Ni7g z4_^&IHOo{v@>ze^Yh&W} z->Szge%AQ{v_&`fY~GY2ZYbCJ9+r|?bZ$k&c>QeNmoJ>Ko4|08h9%jqKiWt4;n`iq zF>MC4T5k@O$xh>t@;ee~#d5C3VWu2Ru@rBlRRqv4erq&APil_7egw#E8R91tM{kOz`cS%5EKHe zt*sUA-$z*1U~xdqN=KWu;m{llGoktUdA7vX-}+ijDDM6D@85eiO9I;uLUJL%li_=K zbby839Z3iwP0mOdjuU0S{J_Gw#Rr#kaQ2Ld^tv|vACteD+g;NL{@&a!s`t9-Jks2* zT`?W{G(Yf7;~Tps!u3l2uBHUW-yL@@?vu{q=uk>k%A}&fXM7|(nLCRFi%f1WcD*To zpF-BhvXK9EjB4z2N8Vg>rZ5}2;CujEKp6KNZy1OkxXRh?={QyN8|RmMc|LOUO2KNvM39h9LAs=*Bo~s>4N8b~cf(qX z_kQ-?GjqcUDvm-`DbR_$bz-*=e~crq#&+3cGr%Wm{>_&y$NC&uc3h$fb29Y ziCruf$uGZq_aeaK@@L0@tVc|_ZVp8fC`4-ZCk2rP;5bCLgHjEwNDHCOolSyRsAfVk zr2;AVSLXBJovqN-&Gz`9Dwl;SvxxMpPwuM3m(o&t$dClIFu2NPHv|ZX#KdPVW9lCv z0)#6f;yB*XzhNJAO?b&U3i;pntgKuxF*pB|LW^7JzR=Jhyt&yKtX00nT3a`az6$uz zF~ueP^3h)~Fd+>YXU`oN7fPR?x2hx2u(>}w;MZ{XYoXWTj_vNmfMswqt+8pABS?<6 zZ~xiXZmWP05L8FTW@K6Z%5_cV_{)=aF~1Bt0IsPt&Uq!#r;JQ zu|e>g!_tp$$kGS58mLgz6Z$(pa{Xulw^TiRtzYBsWbCKLRN?e{302Opb^q1;zO&CG z6YidAgCsR0q0+!hz$3-M72V!R56*wY2IBb3$p_pU<(-SGyK}pBiq-TV<9{?&=Tq$x zUAS;TNl9rBn9lW$jasP225>h^b21N(PZ(+s;oL-O3L&R%3TTVP%aHJ6@Nt3b(+{Mq z-5Qg~dMXMm)`0@jr`!Kf8M!mA8ulVtnJ>l&! z#)S23Cf7&LPxF%5kZg}K6yXAfi`(R2H3Z`OLp5%wZSH7BL&lWYCE5?`)M-Iso}7cHt`9DA*b9~RUQ zMqGXC8T#;$a8DjB>+u=d8D;xcCDMRT8x6kP{ z+_cO{h@HT}`gUTSWE4+;|9qiT98bVP1hI~Sf@%fkN_5zLli5A1K4bV&r(-d1*arpz zQz^ZkhQ{tA}s9hgNc+k1|`Ss<}G3Zx^H_p@y)3DFqD2f9E z4OveRwj<&XwAi#xWyPLt1knf}iq7uJH89R|Z#1oUr=kP~lW&Ph-Pwo^4 zWe?xj-;lXZ8)BL!E2aCZlC14xY}1bd)QRQYr_(bt6JQ6?(jtfO@@If+L3R~@HVSTT z$5C$gk2&r>f{yeDDt!i1HjIb6%pSkT$JfbiXY`3EJ_aQwQXK9Q9RKu%-3xnIMas98 zhtMAx8Cw1UXV==fy*^0aHa1;gx0&K^i;I?)m4)6W{qDR3Dv3aW(?zUSfQlhE0TVMb zRQ}eswq9^_+(n_5EWbGB5y9+ii+2l-A|VV8o+aVAr@`m}iIie2k<5be@q*~ctZEz` z%5C~}@+9X>NBiV_`h|j$OKC|WWgel@vAovLZ+-hV?4FZg>HO%3((a%XKHe96E>R#6 zgK)kDQVM28xNoWADom+@%CSx(%Ybdce^#NjO-eyQeW|uO01hf>2EGYE6@YnQ>4dnY zxnlk}l?Ifyww#Vm{6v}c3gUX?#|Qk-__nfG;Dx^QSM4DSEuz?>t#RU|zE`dQ)q5y{`qkH8!HhK5QY zR5mn3O#Ya$BP^h??Hsy{95C*YF1bOYlIImXg& zZU!_()z{QFRr|x`k{@d#(K2$#&z5DG-kA^GRguaP0}G#<7ud^G7M*YpdiZ}33X!@{;xh6Vwuc>+`p&88B=%`S@nN1TM+=>Ik+ z;aNpI<{{xzFyvyKH`@!ZZ;~kO3-8Ujf)}V^FY&_XYm+AY&ab0$S%ej$Y6E|>%^3?F zh=u|)kL36yK0oc8_utYI!>bwh?r-aUJ5ew(KiGLx9Xb0jWp!$*Z`|g<2f}9$FV8W8 zULAT|G#C;dLY!IlHM(_?#g=;S z6utPzx!=4VnnLM~2}zijyh*HLv}bNB#Vh8PR4}bv!?g^nGYD$=!3vBq-#<2mqlqL- z7`bd(5M&*#;4GFRraNQ# zcKxQF|5?&xqvUk8n71eKeI2o)8v{jj=sms zX?)7I(HgAv#KCh3%}m9XW|NT%~9;SxPY_GRgGI$o)885OZpQ!1>KH)*r)zzbzjZ>ZP^cn~n7*gvHi+_j-jbb~m*HzfFs8hkl zIk7xW(;Ua<1J4+~ufrrH2vaeRJ(x5x3`~#aEjhC=w!PDG85Kg`sT)#7(^*~~dX2Q< zCI9vcJ-P9V+SKn1uX8@HbCv35HtDi-MAfOBO_#@Z+Sz;?g+g{i)KdS>{cjzfUXyei zOPON05NEY}B?_0}apy1W@TTq&b%%UFz4+KC^+`tu2gAdi<*V1PD=R7Wd~<_`5jn5T z8?fRcW_OT>_1ylNRXf%W8BA+iTlx@%VA@2m>^K6~9zZ}cPo9v#K|~;jMbi0!y@tAo zr*N$s7#L8n1Yhr2y+Kb;555LoNJxf&Z1b6HK;|QQaGVuI@x0GxgPcRg%Z+tsT z#n2EOx_RsT`EE|`NMXBWN6g-86yNr#OP4~eY+c^Y&6({T=Ba)?d-f=nltX?J&ScHu zdw_#}>#^bKa+iqGVv&Gg`thS3ODHfmO%;A#KP<4_SNi119s{Ma@~#l03wQ?;JS^I) z-Qo$vcHF{okaHOJVyG_S=)SE<<6oSzgs!cp~4sr)ch|y0Myhh@}+H3_)kD2W73nPjzyxJNb>bcL8`T#Hb&a zt|%^=z*_?4*yw{sitm8OfS(_ze-UIDNvUXy-t6-Ca7jYBJr+8?J=cQUE^^yG1-q%M ztLxhO`Zy@35J}%UIzIjv4b3otFy_PD+?@QXuKU%ARXpH)LqbB3PviLbIEV+-y5sw& zbq@N;)-RmH5T~AIL8uDnF^>a>wI3hG9*iI1k>cj9q%Vct%>3|sznq@eaw=mw7f*Hk ze$jp7Z1d!t+>5ZT^+cpR;~lyZd7DM#?oRlZInygetnV5Z15%D|Re4ne#}b0BB||-%p9MIa*llt=j!t5lHv7BPPXTs zvKn=3^^AP0Dob(m+?!~`;TUWWu{I0(M(vUCtJxNR|E#9eo-#Z zqB%XIz!RPJk!EKpvjg=2KOoc0JT)aVxN_)*jcFF6xvA3X>Du}*gbWW2tI>)){$!pk zf2Y<7$M!<(EP5y=<^{ox_Fr8EB-okhBcLommpnBv(8XY${57*c^d(fL>rgFpmTz&U zZ18U!&c3^elP7)T>hh7oK?iqKzCn2k_+Oy($^hh`)fvPD{G!Z~JrGj^<*=EviSUG9w3C6pE zQfdP(Bjw>~C74G4{WpxtSMo~}UR7aV0_zDm$Ck|dCgwTGSxhqPeFIFWGz&eV*B`Y{ z>F5-+C7@tYyjt8##ce^=n|}1Va1(!Igjm;%Z0Ih<6<;yet!@eECX#u*h@St=&!~=t`4TF{>A+xxLlFA5gz-6tMKaw?YNS*c1}bE)BbK>6>FnH)>NJ;#x`#a ztQ_F01KTsJHH1Y8VYieAaz0mNix0yUj)kw@*8Yk9WsDL`1V99kM{o**UhxwIxv!(edW3v9fOCL< zB^nAaK)VcoE&R5V-qj5ZL;&Z(;ksu9KfPAa>Hxf3Rx1eD2-sPPZro_0!mXW!XzXaV z$&Njv*^ZBhy9^Q>AYR7`$?@?LJv>_rc;GZ_-}qEQaUgBRVOKpk%m(fN&yt^?|3F>+ zIuJ4-_U45ka2$@JiRurUfp2{k^1C21oE-x4p>b3e3`nId9;_*U@OzbqD;04()~_L$ z7YLNpowHF{TiNL_I_=(WETxGLQfe}34v}eiXjlH)5>ZwU1(H*eEEPI2!&hRQcZF;u zRbK|7Ulwt{5ocsnNWZ7sd@R?zLcH1YN+p;tNYM{EyO#50en)!7z325w(R%Hv+~m7x zMx|9}o=O&|z~5tOC+sg6P*$+majD|p(E^!klO-(|2cM{Sbnwb(ifa0B;%nn~vkg8R zmL-GkPMqGMa_G}!fuHChuZ7KimADK=9o@bEmWSZmy}25VSz$bKFJE3xN%C$rW2G?* ze#X`ISFt;r71x6S+Q|cn!Se@P@7`9PX_;&#t#r6I)HpO6f>N;7oPP`5@R^-yOxWBk zWZS7rq3X}<=ecXM5TP~2G}1ZBWCU8_?twmi|Khur1XSPX)${T>Sk(HgaYsHb6m?h_>KHAJ9TJ|8FXa(_c8U{nNZJt9hL2CT>9(9>i*?ilZRT~i@ z34^X!9!;H&^`7UcW5Ve8&an}MW*V{+yLBpFK#8dZ zt^}*RM#jjoiqjU*V1ckqz-fW*3`re>AdkoV7;`R&j#E;)0)cmr`Q43-j4&9?IjFi> z=--jrhoK1?DMs-?^8K2{$(>*lSacBUj<1*q*g3$^1G?AVk&!zoW1&L5IoOO5&@tOB zu!I&XYJc>K*FnpV-o4|B9KPgySEc3TX0Z+blAW-H{7K||RZxIZv@~cwg9_pwpy~pV zvK#!3bOr`eVd1wQ>$nG=C%R-a*;qj74xX&kM zcJQgY3B%|4`Q*Pf(nYaWTPbc$si}RNn@)FY%{w1D~Ad11K7?p|`y-W3iaGfp4hAU<(4^ntUg%TM(1=NL1veG|^aUUk+1i%a`8#3p)lVex zZ(4xAlNV!%@VKC)M2!!xkRPjOG;aRl1riAb!Vg%Xz{K$qwE-=_uPZ9}fqa62Np$&g z4gB6z%Ckc}6h>W=aeWj=c61*0jq1C29@`75NcyeK)H%R|k3pxDgBiW3!bV;oE|Umx z`u@d9=%!P#0{qaOhZ4SX=Z>+d>2nAJhtP!;&mfQf{=BnKg*-%N5*q!tCh={~=xT~0Fn!Fbnd+Oj^4@8QoS#M}8SGcGOnPNtS~ z%kRw=TJq9KE^^kIBoZ-pGinnQ@n?F}9e-;I^Dc=BzI2~q|F6iB0my_;`uxU?`$ClN zY}YeSgx3H`3r5Sv$aCJ+;51A!F+Sc+gNKKobjy>8jF?-%X>csIkTv9~YIE>sw_Ji#6X!l_H~PBYOg*y56blgk%)9hKE1gH(q6z3Seu zT>lD6W>9(k2EMKySVI8ILF)`jd{e-l!RM62w2uK6*yC42ZD0fJ6k_r=KJJ^*Sj`<6Q= zEl`#mFV}Qo9S^T=JKMKHRe{GEED74Z8(<8Lii%>W_NTh1dkofI&P*qz*G#a`AZ8AYjeSNr5c zNJuTD%D>$CX>_f8ZhAU(XK!(9z86n3B_~G?fZg`?c7fdyTj_CxxY#Ct=61MOSD!Mu zC#s5BNEuVbz<%jRKxe^90)|x@V#F>ZNb2#6GKdQHG*=eX+aksi3L@n1`$iq53VWtzmpAmz!4WCxCrRz3#NO!TzdpQ@9#JuI zIxROKDepbQzWhGEuIf1R73S|hKh$NciYFRmcpsuhnss93%*F2lcOfG8%Lr6z#&?WW z1mqf)Sx?C&(WSI2fhbPOjq6&1quaT|+y33c0ga$bWwc$rV~6N6Zuy8J|8q^=Ns}D& z+sod85`COn=D+(=lsT_){TBj+$el0l@mNsp_x5)jg&hpu+peRbi^F-$4yTwdPUujG z0ZdgJT6bQET-HpExs1%FKf?3#Bk`TZpNNRg?rg-eohHIrl-o1hh`9l9)jev9HyRri z0e`1nl;i!4Lqi1^Nr5>PArd_gG!5i!>em|{TjsCSzY=Xjl1c6qOAZRY;^{%<&-71~ z!KuGZZC5oFFid=_*~?FGH;asNApJ24aNy3;_|id_XX?6s`m{qD`lPng`jw#t4}nCvac`W^NzSX2iu#I--FYSL2yz{ zdwT$Ipx}s?f}Rl|i~MNT78d^-I zvZltAcMk>Km2UsJ#t|?~l|l#s6qw}~6dXCv^(;4XN{(1;%G&O57$>~Jt8i`)by!B9fN?{Futasv;^)g~r}*1ez8 z@|zw1)w7Y>LpZw7^}oQ0AfAmu{YrT5Zf$KL4M#xu!9Hwb8$R0vnTF7nES28$DXWL| zzo$tC=aVEYlm5<2)S${fM+zKj#a5%iGG| zLhl%6J!Rh?tyib%fwAGJQOqhcO?r706?6q?1l!DVKaW|%3lzQDBxZ|ikLI3ONX0yL zp1H1J5TtgfQSkgU+%rq}OlWSd@!SJw<~nP&j606)CWO2 zgphx99CK4=fd2DOfA*h4i~5N{qT0~>n%jt@@M=nMmm7#{7_E6r`^R__L$onk;Tk!)bT@SXvsTbQAT?;;hG5 z>*mcKZce+J0WM5pJ8~~DvaqO;2LOen4aqwI#TF|HdFtGzp-Jvdt~mAolr6D33?)nU zpbY3&R^#b#q@8$iN=>iA$oOm?h3xM^PaeU^H;+D@<|AIv-Z1B>`2ZyPofCKOt#f|c zcQUH&e4{7WT&{dnqHNA)bAJNVl2uG6v#r(qF*dZu7M?CobL%(1{%L>I#bg zp3b_JTwD^)5o~n%h}KZHmSpSmma}T94(Wu<#uTaS7ssim>W*KilSwQGv4^JQQj&cB{Ii3tc`!$ALh_F==QI+8Did+%mViLqppMH$ zU^f8z9NNxf!eAT!m22a$3R2*~^Fc&R{1)IBs5?k1DhlJZojJ#P=0s2xwlqIqu_zi8 z`2|om*>D6Fz94^m;lG(Nz9D9eYcTQ=THW2{rU&x!S7c2Ypr%S zp_)Q~Yl>TO4=n-=L|n+p+mcMQFRNFiI>>lU%d$mOUxF|fZd8P~5LNang07pul6nK* z_lob`Uj1uwVFCT&%+jTrzNV|@dgc%%kmm8kBW1hi5)N@^j83!yL!E4pkvcW-q)QUp z?|ft9#s@B`;4n!(rs7;W$~@-}hiWV~(OT;jyW*B_-30JAkJ-vr!7Xc=U34tM(^CuY}adHLS)#JJSgTb1bq88>C z1A4gpVsG%v&2A^+KFcVGGNEbE9)ag3JoIYMFIjr$JrXwLE7LX8?aIMeUOGp+v&86k zgY0zj;hT-+_7g%?&fapzJ%d!Lx3{*CLK^j4t!L1cy1P{h!IL=5tT0WubOi^619|a=V`zL~e0LuRU4+x4!|B|4%aijb_DM!b|6ek5iPy@(Z zUZv04cprX#O6pHucAhL&@h$#74I3NvnTr}HYCb(=eg0ypcD!!<>NTvSyF0nhC2xqe zly0Do$jxQk-sXn#*Yu(-+NV!ftr6qH1{uc;5y)+7ez~96*_j0J8=pqJGG{6sBq$K{ z(XcrzlV$!wB){-|O)GQ6{-z9=ZIc`hCL5Ra4z^HM4)LC`!zW&ti&mtHf+rT~?Etii zaR>*&hlh8tPYc;W;Nu@4#}FUyp^L*P#eFo&p!hX1LIe-FqM|L2{znS3vS*mWErGQJ zY9kQh=ekBlK6j1%`i_3z`U2Vi0Ko&@1dt$p#rEIx%2Oe$i{L1siX-U#mm#X7Shur; zg!)8JuUfp8US(V3${Ek?U{X(pUjt`PPwF)5X+ zr^IT!ACZ zxaH-1fcPpvEv8^I3Y6kxpq^p(f<}`x}7#>GV%NVqD>$i6quzJB%Bc9_8CA;H8gYsg0U(qE0HEVdWHD< zG4wL%>h@zsii6RB>L9__fDt^cd~=n3|(W5H^N2rW%Uq^y(4HQqHp0nHtfv;>)wYD`r?B%T zzc_f7mKRhIPkAN!bMIP7W239E1GNIVu-_BvZw;TZ-pgj9ZcoPr{TZ9wld6kz)~!B7 zpB{KCzQuM=j5?If`W1~yICnp$a(=Pt=Z1|t<6X5wY&J%V>#X;NBl_5mPN$K=L-z`6 zyEmn(R9p7;Qdn)*6G4>C*QNy)*`^Yu@;-i~fJzmxXCgh4pB_@6PM1T!5M&Gisk}yo z=OO&n{%i?nF(+*g~D!Yn4iqBThQ_|5vvaaslmBubjagx%fH`h1tCMq&C0XQZj z8~5@Z22&-ODlMvIU~tC30Ai|cD*@%SJIvk~UvY z0M;4o7z<#>panYy1EW;Ui0kE*6@^<@C3OkA($t+7KIkb}1kG2q1J@ zPyqbH4;rk3&t8!KpqY$OYt=5N2cCBl5w_py;C{f- zP>KgAH9@@<13+W^c_O;sV1;cAAanT~SRRVj1WXWQnx609UjXF{gg=FV0mS&Thurw; zX@D5Pto&S8w+2blsC43hV1k*+AmM=)tlF^aVooXe87 zU-Wm*opLV54H9Rnw5*I)43Ft?av6e}9==rw(eV?bMylx$ImL!_NnUVO zC7bkC_9wzp_g{|*xpF?|V$Ki$GV|a;j(*XBlxpjQ!^bQptm%Z6!cPqPDnmhP(}&7T zUut3px5i8Bu1$s0o;@ZnE`aa-95bQl;KhbnH>#Z0PdFPvKef|p zxR0pVbYoSk&B53(SM{D5zv_XvY71W$zTz6}{i<1N*|L_eommbayW4gqdc3kLda1N< z*nZVwe>86=(R^QkVY#kl%x|b|h;k7%%gmf%Y5pv&@IrrkJ7=>-^vYD*PrnVW#+*kM zl(HnB-0h=P=!_mdKVf)q2A{pOrdg%Tdg{4pV8O3tWec(|EMI6DgX@SWXYY=cSRS9| z?h95{Zr@EJd(qPTJgxA3Oa;35qOF$JzQ??IK=!Pq+P#CdUD^F<`s%i-7PR>3O>4z> z0|pS7F+js}<=_&a!~vRi%H&@Thq4xMA%bMpJ9 zSD_%t*w+3-$@uDZqmH;l@ORG58N-tbn(k>KMXJ8O-sQ{Q^71;BuzMWjg+qJpKqfjO z!uc3J{+&MeyzJITbfLfJcna$Bp&k=>yUh{XPC^JNl9#^(rqPyyJifCO6mBnGh-YDT zSE*i$QLB-bx(5ZcpG)asO6dXag5a4R(W~%&^W-9*CJ_yd8w`xTXj=y{@UaF`#5SkO zP!SOlUU&desK{;F4fzQiz;E!lW8O4+X$* ziN$COLa@VqI{e0E-23{_a>cRj?UwyAZ|_HB+;SVmzG#|#0Fs%!j_!(xh+KZ(z%;4m zIFnF1GYPLfB`s}Yd3iK(ect=Jj#v^@}ZE+ zr+X|w9JZODL4ofY;g8In?*?`U0+(TMH3u(*QlYkX_;vo9dm-H-cJ^{}X~HX(QDcoPiwd(lXw6vppxtzq3VD0HT)}c>_uXtaV@)9fk;A#`zX&3YwD>sLafBMv zHnoA|&ofl1I^^75*EgkyLIZuF)Pnj&4HA-k-4FbaSr#l`4wP_)q7t_X2>+%9K&3m2 z1+;ii2Q(gVo%zCCeE$svZ~gXLXQj0E=bxf^m7@KnGs94H^0)OG$phu$UZ;EC4h^Jy zeBrR;U9fAEn{RASiMnvLpzEn7dN~?5$dsa!OD->qF6~t+VmXX(xN+=>iW5vGj^+?S4EIeY*a{@}`n zjxmSjp;UGe#_Or=^-U6lSBpHs*Z`-Vav)Vs0GJFPr1XF_gSVs7X)v09mr1Y5{UQU} z$bNsr07;L6&W!Crlo3QTgEwOmy8FPJ;j;5}V!Ytb^r#l`#191?=z|NC@$m{AM)Jhn z-DOcRmmoE2`kGI8`25nCu>W}7Q6qeHUx=D2D`OsW|S)UQfhWUw@U& zNS@B^@W6U+?WHO|eE&wH<@O*B1jE1^uyIpTQljrJSzf+AD|>{y^_Mk+!zf;_>yvTu zmLPl$_$d@OwPqiVrDo-!p{C=cwM`79N2xn)G>aVU5+o*xxVeTl)5h@>K=zy*y1$cK zum`sSCEnahI63R}$4SYyzJVR8#d(^4Ts`68kCm0ZLPLoh92|BzvGDwtjk@OvE*k1! zNgfDE$?FoX(ng7U>GY5g zsMOR3Y=zrT%S*$axTQJUIG^Rk4Pz$Tlk-mQb@-|bab>v>l1LlftwM@lnM@h z2$@nI`>m*QfunbRe$PR>4XiB8!}MZT^NCeScX;T4_5QC&DR2-4H|v|S`rAiBAeyee zetqef`}-$%AY!ky?8FFo@!Fv!x)wL*D!)e` zSHQpNv@$wMa-UMS?Omt1B3L+PTH18f1FOm0GF!8fbu~S^gjODm$tJvFJz-q^Q?_vf zf9mdrgtQM_AyEvG4}qPS&uyI4ecs~HBxaMD<~QA(?xM|Wao*h@EF7*jyfP$FD>YhU zuSTEgl`)2|VWLqc!MnM*ZJ6?fR^zplqjpy=e^qab3GZllzZx1_A;W<0$px<*kfl0! z_1?fw7{iAI?v4QHn?!vFexmEo#tcfNF@vTy`QJBYaCTkud+__=ttp=mm-1!~Y4)J& zVfWgKV7&a~@y0jg_|N7lL|qI7_bj#=ww=@y-7uF<%AAC;8#5mJHrZ@#Ewf`!N;#U} zN-?PUuv2gRGdWjfQ|~;yR#u|)FVFO?@|IAVuc;=dZ&fKf+MQ(<6@~d#Tk?U8WKIa9 z{D++b0vr%KhsBE>?Pf@sV&f{ghL`*vUTt8-VJ&4SxAPWVZEe`t*Mg!geXw&Z%nuJ9 z<<8E!*ISw{ysL7nKN;(u=yA?25|Cf$)MSJok~6IT0Fk!84j`$XbZLU}fMo(|i3z8^6USuawv^Il;VW)81->_Iba ziBpRr;A(Qs=M{GlH=k~Eq@MD*OdU$YwqThuT07Jq_I*n7v)5<1^lPR`uTyu3bseqa zmJdo3OoO6hNu6SycSOBQCSO2?)rSkLS~nMSa<$pg%=e8y`&?>SEzk=h{K|h>Q78Sn z13Nxi&fOyeQ{FGnCz(*-#rDf%mu|xD6rs_OMaZ7pghWl?G5CwD;}#|NGOZ3C?YQU; zNywOAFfs)9Mqw0NQ66ED|5)6Z||NLni_|{#3#of33B>*!7$mg7)Rml0H1^f}#I6{b< z4hRSsSRb=Qf;*;LBau-82Y%}3&!0zwK3`Zb4T3Bg$bO0w_K1hU@OyPtVXWFsbp>$w zRPZCh+X&jA>f!8%>##7!KQ2yz*InR-Zwkh95luwr+SHdxZ)N97?zo%&F0i9+%p<%- zkamg)37$gk>gBrib*F1&vBw_l2i)@(@vN?{ZaK;sgPezAKMzR6CKq-mhn2XWL;w2+ zmyq3|p={S}RgazJ&2m-)kGu&-N7So?u_K}Ud2}px(tR<(M;q-9cO%u;0Sy6+F0@eqHCKCt~FwB>V;%XkV~$Ffd3i?t7(SRzV z4;Z_``M7H@_Y^s-NC^}z9vtFYO9PtTj-0_e(v+;ZejK2( zhZ?sUV3~tvn&j{bI!^Dw?!iRh*#^qh?Ck6%wL|>c^|zm)eA5KYCx{-Sy2{Sf{-g406=)3(AbuVB)Af+m5;F$n`U@+8f|9*dL zOLn^t$(zbVt0~l}8en>^m<)@XXS|;!i-(hx9j{De`!L_Wj%5AkxdZ2pQrNho_z0WJU?-zB6vw^QGW~u+D%5C+>TXKYhIQxr1XlDVd^G2E)2sIm zXjpQ5A6j|9$1*rzRaWv={3&yTT!48k+5E!#j+KX6dMdT@2=r$J;LV`*sLw4Jz0b2p z#amIntMc6u$D`2}K{f#(EH#)%VNnDT`%sA<4b4CvsuOi|psvpzy|4=vknEt3 zgGyG!UIm~=T5hiV;@&Dfs5&Y!8?AqHZdh7&f|C(c3Za)ht`DK$-h`e4&=>Ud^kjVg ztYR(%`U6;Rnw1p<8#;xL+CU()mvFRCH64{|CYd0lvo+7*57vzmms+rHTm$QdKS{yc z<0l)(=XlN=%?^`U!2n9m%EALG6c8vA5W>!N4bw~2)jHhMYkV5_F@18rEDAD8*ETe= zY!dD>f2^t5{_)eB&2H}KG6bNm{l0w+pAthpGjno(z3H5vpF1@-cLbIDIlZ~AX(pP8 z#6;*-;D!?!p+=&ksjM~)vj(QQ4clgAqix8~`uKu{M7PnO!~e#%!hK;d(;^#M{Fq0| zPzOuc@srf+J35V+{o6dea@~A5@G`Tx9prue+Ez2_c6?{&nRcx3doe(q!hssew5tPZ zWc4Yg&@h$gYSD52ihGrnI{HA309t5FQLRA5>K|a<-OGW?x$FGw8Jpx?58yWu$f^=| zun35!>fy5Q76Ca=EPcYA`gU_6?4i$dODgdrT%yKqkk8Yjtuxr6pL^MI7)D0MtXUWy z2=)MbWAD&VmbL57+8R>P9jW8&3%|?_kVNL2_A^1C(nreHF{1>xMgZzMK=lXC;KYK0 z;DrU#oEd_lsj=49_b_unce%ITLB z&)O8bbNgP2&{*f>M_EktW01sK3GKu<8CWH{kjjeK8H3cGK(A_)qVjaSyfdVYOLNB4sR}|3%6r-cmZ!di#o68ZAwDGpRu@0f(EL|Mu1gOi4Fx zND-y>l0dJeAOR3S6B?ucYn(%OVLIzoyk+Mf&^m7fTGK|zg8JTC;a1DR$ao$MJoxzd zB*N|jfH)h%Cg6%$Z!#Sp%+Zv9x@jK*DmCoP#01jg4*FcE|GxMNb{AXlW}G;Ave@s) z-DXkR9}a0KRPj?`x=l-a%GTBvwhHKI7Qu|xjA}cnu7J~R$Ud?)HZ!;5+xHC1ij{$Z zYJ4gjH#DsH$HcTQA0R0JND0hescXO|YG{NV{A@O1mzNLqeQzt0_<_0U>;E!UQGe+V zcLpC1kG~O&y{K6J&=2<#7EIe&zmZ#6ZaW&UGy zRM)46$VbHz8gt_tJT?tuvT$wf+E%u)odINLi(}Dhvp0joXhNmw(B-+gxf~b@L9jCkHypmPoN+X|iaBqm zAZ#+}jt++M?O#Shj$YQyH(xcS&6)AHa5U{ug`QQbbj+{ z+3!RKGO!x^AjLs$eeiy$nAO1+2Oyyh4&wxZR1k{(t|Jhx2h}A++}qgL^jYZd3C2u0 z&H&Lql8HfFZ{WC$i;Mfv8os#;OEwY(xwA5w?Hrt2h;fuSGD~8fT=n`2`yNmC5L2I# z7}Zw$7GyTP^$b7mOy>;kgmung_shl0Z;Y;rU8PT=PE3;7c<978sny2&y82R*mHI8* z(N5FzsMAS2!HS`~@BRtzCv<(3{W=!?D+PQOFPfO>-`Zrx_1(^#i=KbBVCv`V)pP9m zg#PRJ^<`zAGOFp29h)294!u;x+xbl@e@TMuq{aOXjfa`gj`DK<$X&O)1qshOryYka zHdIiGH=;VkKR((Df0wxUc4D(SXuv5Ctre7{>KTq!-KC3J4htGG6!%w-&EDq>PYBK- zWcTfPXAR*|?5Iw$ffUSGenR5F^sYpAi93JthTUxlj-tNBhEr;9}wKRTS+ma+LgOL+IKG+A}mhvT!U*oad zFLArTfc{&0l&-QoF~>*kz7cGWdDP@Y%aM$N3DFLw9Z#GJ7te^s)JK-7?+<3^3SGkM z=0p92^r)6;|D|CC#iAgd<6KzDyzYD9kWG+BvIR_N5sKzqC z5)Zi10BK}>xtC5%4;KRXEgQorwH*ujZ$SQQ2Ohaw{i`%7S=mbZ*orDa9OSBn!ebQ3 z2)^yX9wmZTo}S)AtM>L~&>aD(bq*v0-@bXm9uV)c!V5Y>SQ5U$=aqLyb@o$oGLR3F z;gDP^`a!au3CMzi0#Z*E4Q~|q>RkT+qc&pJKQ0Ry|F-# zqs(qzkDB`VaZnB)t7&SI$PtDPi(A2x8J}fOUX2sj=d`w#&zjB7JIMCe?Akds_^~mE z%a*w8s>#|?@MsF`^J z+5?aTn>Ws{Ai-TQz4st;?Ps+cf2Y8vzuJ6n*wHtyvzuU*V+R5fes+z}f`de`f@{7> zFdN-5LmxA-YJrtbRf3qyw~#FtA0LlTo>5uW)Zc#%8YU`!;mVb&KeiJL0#F2Jo20zF zA%L3ZJ;AZDG7y4-XyYIg;&VQ+hDvP-!BRby|GrTm1`mu=479$b$E~R^NsnBx@b|cd;B2@ zp`pp_lE0|onN{%QGrNVo5$KN_EJjR@sjlrV z+>%xc>wkDD;z3uGkhr-Aka z$%RJb7}cZLgW?1W1$`UZI^i3Efw`B0n}hxRs@%MPtC|u)4;3VQWH^$4Q1L!Ab3At8=U zw?hZ$(F5eEa=noZzG3{;ru6pK&}{J4 zlag5UELK{$$MD7JLX)!1Bz1qg2&{|Yv9UdZe^F|z+MQfTfKu_sSE!3d^4DS4g6jkM z6#MuzSzb6K16@il+T&cI5sH>hyuK}ZmV}c*6!1^gXZ;|EojRl-zCRi)-jXEF1 z#icYPYYi@AuTyn~xty1?4;>u#?XQg{vO^fohQ*A*Kl#8Rd_!tBK?$H(|zsiS{`} zwm{yc9uT+D9D;?o?%rPIW>yv!I!M5S%C6!)ewl1s+(v(loI)cVbvuaBt?LY9<;c`3?QcH=u6v zv7+Jw2L~?pXxU}&f{M%|OZa6sK$Oc+)fv^^y~CS%9;YdlDo&$EuM_o$EbNlG+t8X8W z!EXeiz2P(#d3H{q2t*pXLby($}7ggyWCzS%GQgU;QI+V-64M zygyyu5S4^2tK91djkQLN|C*fVq|}xD{_XPHYW^bi0k2hYlvaY{wIS#&&*#+L+D#^{ zHsL8hLy)lGpTHSjcJl0YROOKXaYxIuIv0VDm*~=Vb}MG!KneVT1F91BMr6nc7^iTBFCe||duwSskt%_&*J zlKPJ9{zY=+9b-l)5pctPaMRWZ6A=-A=gCbqLEsa|!g(OQwxBfVrEV`8xDLPf?^V@l z@XpzV|Nn%rI7}FITKb{)Be1l=q?#z$h^BzTMU#-=aQ5B_hf`hjp9l~p0?QcY*?FnY z;@PsPG(3R@9u7Qt`SkaO{{=!L>bawrM-JFwh2o~RcK=E_!NWw6Ve67p?iAm>ud{sE zjySlc*LbP%<_6NOw52#Nd`%wx-Cd(%Y;~twX}C5z8vGY` z459Ujnyva+{BiIkD- zon(h(6Q^PCeQdJ#-i~vw$Lsxmf9LP>`(4-Xx^CC)x_bZfz7;y>bk6hnc--fig5`Ek z4VSL;cGF+Sh?WmCvZ(PSr>_Hr4wu%v%J8d}+hu&bZ^?#mB#Xwan@JL7cZ|=g(j^XU zA}C#k79uVBb@H+8q30O;)}DC+>Xdylgh`X&|Ilw0sI3~)sfYmzYj z#`jS&Lw*?kp4!cGir?@C0{=#Va20W1hh^D55D_1%rKvIA4u2kr(v#5hujY*inx>YK zy>30s%q&Y_HxS0-DR_`wZlNj~Hz!{m7=8vOYPIWX<7Cr5uS*Fr0=mW>Jlhn)gAoAi zF>1Wvm>FJfT{z265+ia9p4PE{*pWhIGYdAWPi*Bdl=#*gh-AuScFf_f79%AbF?V9d zL4s`|*X{lpdn#6ktHJ=4j@_VuF1EXR98$h71wFBMo~V3@4Ej`e3K!)bA_4NeGu~f} z(?A4Q@5{Kw&-bixUXn&zN8QG4+i5sLwt$=GlX9P|ovLyCnpB1Rcah;_L6tVff4Gvm zzWx}6yIOpW&&pzTUX+N-XGJmi3Uu`v|8BUde`k{&Y2f(fCAq&Nb^LL9`WFW3n(mCp zTQS2R=aL`5ZEr&c)IZ2~WF_eN0PBa5Oj$+crM$CFKJ>Ye0s0IF#s2<&!aX6f#)DzGuzh{zWQP#>5-Pzmw{xYQB$uPK7Hz2lS8y=dvVOrS|sQKQy;t}o4|{#rMyA^ zJ8P1z$dJY;%Mz}l5uLBg@J7-Td;*9*8tKu+AENP-#Tb26YKzKvoG*bHulrVgg7B`; z_BN)ow--u~885LKnwvL_|3oq>NI@zMnqdIh_VrT{*zqt1x1K^c*DDokDoP?V?cD!~AV%;aHS;wXG?}F$%AbgM{6OJ08Q}Dc5q9AKs*SA_Vlb3J*Pfeu`){AejwX(;7`?L7ewZlc8`3Ey=B$Bo z41l>9RW2f{0_~wR z(+DMQ_}jk#I+LL^L<@2T!6?}+gdedMOH64!*tc19@s%d^+D@Dd5fRW3Ab8r}PPXdu zy_3HuZASduHz{iSEZx+~Jmp!9^A^Ob`*9iHQu&)?ym_cG%z0DRO{r7&&-}*Sl~48b zrGL(4dJsdx_p|2bNjfjOTVA&x>^Sp9*~ib>lx-2&^{utwQrNlP%&+A-KM$m0FKwty%J23=gf`OEATDN={D z?rS8w6J10rEgeJ3Ee)2BxUIPJU+&Qb^rn0Abp?7-T&9>(um?#t1=gDDoCfWUP)@Uk zY{t3xMVBmW#k=NY-51_Xu|2g}e3Wd#w80qKM$NDXKW2|pY2FOh&)v?=_ReoS`VS(e zfTuY>-*`UOV4v;Q2T}hf!Keit?acSGMNugozuNzc?ISJt@G9@wFXw{ijGw-dWHFvN zCf&tmo4xPW{Z~f^RwdiU^^i?0y7fT{c7)TX3bQ$&`hLvj3f&*;ULr3&Jr&gDv>)k#rM?CO(GrCCtwU`uZLREYb|<7pu|lv2eP^z}x!* z1;uZuSuH4ek|Ybzx%I9B)Is!xLCN`_-wU*}iH)U&h2Vn=gRufe0%|_<4gz(C(#Pq5 zCL(ED5Vr<#a53@m##UBAu=z}^o?2OH%CYCvE$hgu)E$*~ayl<1-V5Ofr*tX^7E`a4$?RIr~I?sLIz`%`)G9)eQzwYwzdwf_mFPkkHJCdX?+7!BAlcDBO}w*^kaI8k5IUh z%z4#WEx4$pgprCW#wjbwc2r(9GP%6`tc}fPF%Q?s>R#kBBT?YzE3xujhKXtE>EwUA zN|4qH78VGN2q>_78u}TMXg)-jRxTeDl*hsX4Ldr9eOuACW0m;fEN@R+owN@SFW(-ybU4o8#!nmFsl=4loBluLA57lvZnA;%gC-8e}|sH~xZS-A(5^wr>0 z>F(*NDYEFk18qQ|Q;MqiMH~G0v3-h;kdQ57m#z9Lakj{B0H!*R03oOK_33edg5l)> zp(d4}O+wQTg7;BTQb75@xnEXR#)ZN`&)6(z#E~pO&$XXKh$#dNA=$-$fC_^c93)&8 zq?bqw@t**OiJ2KGSy^Hbbw3y6!O|;i2s_x?*sQ4Gzy}x$hLFdPA2&=l?H{JeMc;9p zBZJ40_U)UBF|KmRTS#JPdvl~`^+f2$Dsi1qFR85uAXEJI+vHA*GQAGxf3tu16;u@r zZW|B!+1@h0ZtQmIfdNt2M#e>OfGEP@0RlMPBTGV?!G zvN_ed2Rq)5Toiu0%Pv?AW3DpfPL->LWaZoxT|GpA(;~Ivs|VVC%Xf7Aj~uAbA$WCW zvE5YN=bXLd-^HlbmAN$pb=BXYf!|drh=8mZptdJ=KF|Vdll^bZ3}j*raddF1m73=l14+5;wLcbgOPRB~_OZ5&6n5ny+ zOzYGf+F0zHDJUgmDha-7{P0u;y;yHu)n6pdP1Ne;0n+4M&$7LRhq)sRpLYzWZ68{j zT8HlB8q}Hu-L*gMbB5r`9v7Eiba?pSp3c%o668VoiBoJG^tW))mJLK5h{M?$wnz76jPZVo% zADi{R^E9EnltvE*M1P&^-(srDo;{cNkM{u3myQfY1|*+K;A}c%i+JPq&8iO;*ntg5 z*9O9)esVhPUOAW3f?D<7#Mw)?wLf2S?#YC%>AzyJ&Ubetd4iP*-q?^a`2(U?ownb> zU8|7(v4w^cv!moNUA z6TAPx>)511;8oDXgt4nT2g|ig|lRlTro8@&Bl4ZW0dg!YBj(E z`lHGSGldQQX@mHU&LX?vfxix^PHZ;&{NfkyqFD6N1-`WW%58^tz!XF1l{m!dBOEOX zuW?j?fiN$?mKs}Hpyw_IFo;dx!w^fe?QYr)jLy6~GNldMLO;?YAc4S+f+Xju6xX)Z z9WU1nNg4%vEj(s*w_Xf}RY2fUpyV%DW^*sCfKO!3HFT}C`z_q6*28>_7#3W})a=w$ zCW&|{NgdpKpv8)`lT*c2c6)mph+YfJY;9#3{F1yH<$c+ePr2}3Bt085;|<>+O6nU^ zKv0n1!AESw5y23I*g!ZcLuwi%5o-u&WxaKue2>$yHwlw>y-tJgqu$$_1i-56?Fu%PkN#&) zlTShT!_JuUcstWR>f*2ZwjN<dQmD$`H9Ky0rD%dg}Nc_yN5F6SvFHeTwG6oUUm7TJD9W< z17*hu4J71F;w~hl8%JG>YdB+#)yv9dTxwv<6YL4+vA#sm+7j0EOieZo!Aq%mXI@xM zT)cPtR;=UJ8|{7Rf(9IQ-xH%gIpNamsq-_E zCc8UBxjR~Pl`2AS7%vm8eV--MSA4X>9SZ>%|JWDcjSJI33MB0=pI<}#k)YSUMji+; zn4hwU%!0w=JcrR}Z%3Lt7fXn17%{R3k_D#?~>+7vMfZ6Ar{4*5LJ zOBkOGk0;Tb26fQ1151}a>d5IyuJ%JWx4Uo%_Fyi5H49~@r+*hCm=7_7Ru_q>4|fkU zr+;>DTFkUGUK0|!tlE7XY$_LFzH{7JWvGMI*m(5&qU1xpy5ZqS+iL0{d6tJ7nD;+^ z?7B__UNka#8^g;QUhQ>s$J6zDb|TPJU@jB-r%42!I4No9*rgr8H}4?D15ak!UX4Ue z$MUvwn=2J*A0#BO6h8IUTrMbD!ktp^Iy$$rMExeb8=6F_n!*(?bpbO(Nx3ko1Huzt z)0S~D{5~m!fCAjLBXig-;2kUfE0E&snKJ`hI){7d(uWTox1nK!mIb~FoHyA!PVbOX zC=e@w4D^M){javRm85hu1Q#NF4Md+@CqDyu`HPeX8G>^ds89pNG5BPmG6ouS?|Ng) zo=i?czG)1X;Rz6>NM0uo3lIMdJ))4$qWl^W$w^5`{YUpXyy$)Io~tAP5CKCyIywp? zp&b~SAo~m)BhPWB?MVud$=Y=KlQqD9mA(R7J|8gn$8&Y@I9uJl=o2sB ztP z*YTxdE9}ES>(mhho!Q4PCA#TG^y2o$NPbrbzPTFT^y%u=8z)|VE1OArOA}WTtF={i zP!-vG`*8k98^Z_b9V1;|zJDk&k4lSrd&BqbOP@Doj7?w9HB`48hR=e1g|~clq01^_5GF&gK-(p7 zstt71MlK#j8k0kQR!+ffG^?(O{x8mH2|B;NxFVwk= zUEZo~kFP7nj~$=$l`xiQy6ODciJ(=k|1`HR(=PB^`0eVDIvka< z;4{Ab6aMLlhLm)M4ID9_clFmpXW~pl6z=+O3Euoodak}^^mFrKKc4W^`esAHz3D#> zW4shAXczN;*ABmQAtDOrcN5YI&v22kHIQR(lMiHUsb;MnvC>IsMMBa^l_GU_)kHDH4+?O`pFVe%-WCH3 zB@&_x7Z!3y{f#fs?1T5XA4F0@erE#goV`zyhSta4ql%*E4w=| zS_Ge?6;Of}C%7`=4S^$*gXI|hANu8u%*--e4v>oF(JBw5rM~O)BdEGu184V00Zu&{ z*EVCIfvuR@b;p6Yx+o8U3)jXUC?tQh#P(=8?gwy`=WUe+L`Po+(<+3efqWa$XfZQ0 z%O{Ig$!(aLnehx2J?`-#=k?~O-h96L-)PEAfJi{1nU$02+H+{#a{@?7=u7P3(+>txCf zPJawntv99CS*GPK!!QQL;hCQusS}yY{^WEV4=8ruU#}>y`!YkF8d0+pQtrNOI z(+^{*pKydSQ@*Jl2VsZ@MpkAm(~6v_5jVM4tsk3cGjFJr>+fHten+*obL`tC6GrwZ zr>vu`UWV_tW6rMXU7g$~5qBBx|6?>`Bn3{i>A>kbwRa4sjcaT9FVE9vR2fS%9FCmB zE8SF2cq5W>#=t8(PQTk79@SjZX6T0z=9s&&e>O`DHn_?@qGO64S9SAIb?5*!?h;tM>8x)gjXKQGGC{;o3% z3&>t>Jhw7;LI*ayFFkc?LwoSd#cbN0!`=5vO9~2F(eVl=aPN5qts*k=_(x zjFvCOU03>7FADITKDQ##M5?iA$1P~)q;y)5g`wKmUF>j)*XfAtT|r@CYD$VPAmhhi z5rp)vt5A{+%&o&z!v*+E*rJaFKv37YV@yGTSk*ue=Odod!S3xN>UE$5C2KH6)YjH& z?~Yo-C)^DJq2QBk0w!os^7(_=78&>u(&Yh~9%(v*u>&Hm;f)I#l-$NN=`tl`mYQ~#5 z)8#(pC0tW)PGv#i*{9<_Fpm@s>h?F2yYt5iHMMt4F?qDYwr}52#0qU+Bqa1GbG}Vy zY;3u(kf)-X;3LkM@p(!npy}z}T6@7adsf~}1E04aKbGF)csw=Lh~YPki?_`B@_pMy zneNIj3-k9KxkJZJ5R6^BIZu84LlvAUckPL0X3jq|$^F&PKrCH<8jb~w@R4c;^t}}& zvuk)m#9A~ot%N6h_sP+48M}N?>9kqb(?bv0coj~noRT*$+CR7CBv`P00Y|GMLhc~3 z5PuOn;B@J{)#WU3ROO)vuAb04b`$FGu&B)wDZ$L0QpomFSvVk%`PvuLfRk%ygay0z7D|=Pc1Cm>JK#Z z4ur!|UK0}D1U3Jr1;8+)F`Lm*QR(}_a3BLX2pY0&dNHR zv4uUf*|i0I7+Br2XJ0NZ-sI$D71r^%A_$sO(StcAeGmZW>6X6)+X}2rpaVa4g^muc z3rV)fiItT$&{rXfwq);YdNjZp-{3iWW7nm@eUA9LAgtu+uFdwI8R*wR@rGD}z@h`a zTh}4y0uU$DmYAQQbaI6x3nYkEI+Wf7guaL~0(A4rKvshMPC!5a^zHF*9X$_Tb*jym zv$3-J!i$5{kIBWwW{|^vEiE+z`V2@Ql~t@u$&op!S5mUA%OS_wTFTM$z@NDU0_uFn zpAP?~N-p&xqkfkusvi95fv-wl&&|uRMX0Iwd52Z{>na*WrwFq`)4I&O88-T+mA~gu zjk$O^)p2$ZX4>3-UeLBWgdy7ZBaZl^%f?%pN`A4A_L^9j5`-mrtYu;stt4A>O z>0h~lEg?hmdvBpq#U56^y)mjaoF1ZMI50*ICvtCWubz#V9@Q=v%Tzqs)V`YiP= z>K-nyz$x4$#T+eGB|k?B;6`S~t&|{~+#yrRMVAI|mu4}(a4*HOKL?PDqL|UG+2N{q zAg!)VnYXE1`Q>?ejL!H_Yt*ANXuI;fOFm{*2b;)%WB=43x*Wq!fYn->Y~CY4t6;i@ zWk)f;h;@u9DyXTMRBupqH^&COwbV8oT_YWL)uJYndt2m6NpSxBt@2`3N+L7YHNqSM0=4RKrLOdPD(!}I7B$I;b4uB)Lh3^2?4PATJp~L|S zL5(bPM%A@dRl8^69U-#`@$|y)F3^eHhf5w@Q;^-y4X#1NNeFzw{#8 zQepK7Yo3O7GD8cCz^tsBpr`8?I|E$+($h&oPT30ODg%=0>KrUABSR5Gg9p4X#uu?0 zawmIxX9r0NVHNW}m}lkCExQjQPbBt9FlWf@a<(5mWE%u0!OvDnnL@W>RU1{*C?@ULCJejOx|$cqVZi`7^)ooe?u$0M}LO;%1@ zoq)yqqfy+w)612zY0opfD=(0d-FJ1p1OGZFpTYr#6y#mG@DR+R$*xJFotZIQ~8@o1)sNfN(`k#Z~q&3+L+N zH>PE~W;2Y<4d0Z%zMFo2!Puikx5Yfm;%Ck1$=C8nQf=!N_55xpwrxI=lS60ELg|_y_p)Oyhs~yDwM&bUUt%Kk4xx&rvBus%NElTd&F9s#d4_+2L_`u*e_$_L|NzWe`{xbx{6ne#_eM+ zUH8ZV*^$m-X~koGcC)$Bq##$Q6CQjWF}!By zAxMBO1TO^fELg}shbS8iyocA5G{=RYnGW9Gh&&jow~2iK=K?jyO2hBfS9$o36bXQ- zkn&%86n0(5-_qs@`keK%XWOjk3O{ki-ve$fw=b80u?nIzr$GGy>Eb@{euN4TysIkD zWdZ_jXXghI(K)OM?mHwBEaA^DmEwXDN3Kq7$WCn#6BB#aVO+Qc1djL*L_}u0hIJL~ z?0&U&4_~CD`>NK zmsiItsMAzJWS)K2=+DmPrJ{Oc=XmAuqeor0fA?Z?E)U+i<;XdlsU{2N#!5Am+&lN# zLQSjDv;*56F(xL)nzrsifq8xOWzK6?+ZF1NoLt-b_9E4kLO zif^9GX#3PGHW4c=x9fz>-ccv5PIt)Vx%p&Sr>s?qzeV+kPHJF|V3(T8YcQD$mEh(R ztl6Ygw2zum3fFqIDQ}i?ef3yK_?5uN!KUK@1l(lQWQjDb@AqgKy6M;QkHgOo0d2K1 ze~t`^!e=3gDvxwVv~Lk>*BYEK4P#8yJ40Mc(u3hbOvT&Rf7ZHxV0e@cdclUzGA+8T zJ(VZf<4+IKN%eHz@W=68JWF~uL_s9oDSuaIRCn}6d28Z>BmG6`eojNlG|3AktU)si zrfs=*GF)(x3bOX2O+Qh&s2n;qJwvf$1(8& zIH-~~HG;jEmFps#+HJO=<)H17v5^hZV`J*oaG;&^CYJ%Q2rwU#A+aUc6zRi6UU)PG z7uqH+^9*JA%o*%{{>CSLH?s|M5@8pd9{ zc+nwbM1+_!rxIO8Ew)YLMAy-9p(TiV(LkRE-0LvOmtyNMBd#}g*4G)~*novI9DW`c z47j<49U6IJ>w71<~^ks4z#yq=e%fG)}pCskB?4vYoDf{u}!q~BeRV`34oChNzv}w9+ z_iAWWINk*R$DO-(l{KQtN zn8$9&iK}8_45Xx_5CHTD8ZkiZxVN_lA=~@)ua4A9Y+cgK+PTsdgcYp5Kx^jXj?z{6 z+^uq$?Aoqm^_f_Y?4KE}sL1hz>MmVf76=%7jmsbZ7 zr$j^n!f`mA-q6FUBR8@^46sB1>T69C*b~p9`QH5 z@P+B`6LD)jStku|3VFvl1;2qNvvYCDfB=S#4g1H5LJ#3>2J?@rhX=*Qi#;a$LlB4x z^-fsuAws<>JU&pUbmw4&jPR9_V!mjYy*%(ww6q!lC3M}Nj&6jCrjH+epna`KXy&U3 zpcqL147~af$ACC$@VHgH^9?!$1qC3$zk>Mzm>(#W?-2I(+8SfSVX>Z&b?_S^6ChP? z7OX(vx`K$g709Q1YHa))vZ7;!U2Jj=hdjKiDV8gL{0{iZ;E*GkA$7LT)#nk(BhT^8 zIidDQ{m`A~4xL}vxtC3vKDM7yj@QZAUCBQ@1HgRGOwR{2x4~LyJekfLYHaP(xIF%_ z;u~j83O7=oOC+r?NeAf|!1Hz2*}fASyE*o!Ga)Ol$72_BRO8=an2?n{HR*6yyxx%_ z%804tVbFM-Zrw{`boZC<=mpF>&gurRWLH|LJi0%wZv?>n<7978-j)#8k(s}SG*}pa zp{w>0m;VnhIj9nZ6f;D;2s7--llY5}6+u?kPIjq`o}AvFkmLBj{N)=!5cc$hugB}J z2EVlFS~1io@Dy+AjY2sTGb}h&e>n$mk0%}{eRg**e> z@l@dB+WZPAVmd2rqic7kzsf}IonP8;6pD>?c+qmQ@8oOI_uG~db6omM_@^awi9hT5 zV+f4v3c0QBNPRj)nSX;$mp_LmPUsB@9&#EE7InwO4W6x=y9%hv#2@a`<_TIMN4g6a z;*YQG>`0}lSmc3@VR#2S{AFx#P;ODPZe@hP_T6I#2eT`5sVf6poS;tq^yyk_=bIBJ z=$HY|fSe+PLsNk?%%OV~a`)F1eTiPdZ@vHtC%_l0&3l9=>8}@=hCO@8)5am({O9ZM zEA;MV>f>^Y9AWo_V!ypIktunE+*nIWv7`2SMK1V#mv+@}87CW^&qVj>ofscRx7kf^ zvWwpwWz;p&dyzC9WSx22%V16pt_p$U0=d6iof(&+xjw0;a?e|Q+8-Hg*IuOA`*YtP z>vH;(|EUUwFNZM{i8R61Z*D&seNrYeE!9(S!(Ftiuyrkj;!44b(yO+5`q6_wyg!Tl z96RZ-P_8=leV~A;PToB()B2M6D-$ufTZdXk6HmL=?aN+!1<+ARt(82z@0Flm6L$JU zA0d_*fjT=hyi2jE<>|4;sv(R=3QcPgzqt!mH3wixUt1Y}`i(`A)pBF&$woBMUO0~p zPw1WUHNQ&M0aYWak>>K|3ER->s3!@r=?#< zC#dLs4K7OdCto@ay}i0z1hv573`Yf`$h)Om_8xjGSso|cn8Q#2oeB9BNNp{oUO=t* zdziN@r=YR(|iOd;8$io3wKbs%c*}Sz0;;u$^C)8e5G~0Sk6}3?R4dE;Pguou?C$ zgu;%Qg)uaquF=EBh67?_T&yQj(q~Q>phm*!gumP7(36JVJ$Vv4nWTURds;h8qc?Bg z_8K2dKCJTU?L~v??!Qnf35m8rs8|1qJjexEFp#p4EpshW*3RC3VeM$SQy-tpj%y1M z`<&-uY_jMceh97p<3MivBDU1$rvXX2s|NRm{oPSpxA4Pu z-KwdI4kxs4zYj=Xw4dv6kx{=F6{D*(blyrD)45SA?DwqF@0ab_EFrCjUiu<$r3hPl zTLlvuH6`allxR2v->-X0Ee7;2(X{q@c-v|(=-j!xdY$dHEPH})|1mhdNpMD7{xkmC z2oJHq(Wi`hkZS-e#BxvFweR|y6T`x@N;hq5mMgoWA0(gqq@yJG#Vgk?VZ1+ukR`0( z&2d}OtA}Y;&uYJ&A8K!@4T>7PG{tPcz}ctjQhnYn>$}8(t;yJ;T{LUdB;bT5bGaA$%l}l$PcNDM zKhH%CbnmqN=izuje4xYf=mT+URB`_-Q3D&Q02NJ0lYvR00;rN zn*gNTz~2cOg?WL$;oSyFINVSw3b6kJXfZr{d6eoe(f}qyBtRIf9*`|&xG_aLnGCJv z1yG9!92R(kUd8fTMw$A0*9B2S96$olQQ=8RZO~@Y0!;ys-v#W>B}l)4clY}C_B2>4 zPo65FPcFTT<|r3kw*{d;poqK{UDuPm4=g|rCLDopJ5cmMGAtk`5qP&Bz{fz-2&X12 zEI`YZu(bS>SO)wV4|E;!@$o6Ct23+V`=Wz(mQ00dEr1s7q09~77PURR%5zECE1Txw zs}pIuS*jd77Wf86yaY-G@bTT1I`JSaTi%w0 z7A`W{r^~hq!1~mQfPjU?kD`F}eRLzYL)CUoN&PiaSactGT+1!C`cIrfA6<@{twT@+ zHDk#I;9cjrKN0n6^$xxxl>43uNA3O}`(;8O+q3IBlhSy>1J$COHxr4q)(d5nQAfAA z$!L`lwT_~#G$QqD0V}M(Z*_lnZDkMJ9gw4wXTP13BedFUV!$iN8xbtafpU-SU=Ytj zD=BJ;Qy)d|NL$@pd4IIxkD6N)+kJ~tOH=~wMvUm*!&t)qQT8 zuLqf7R;rbH8&LBeGq=qi++Jn=6s=5K9X*cqrb0f>0Azj}l)QZ{R4GP{k- zw546Q8cSzi?#`I^>Ds%luIsXL;WyZYf5vcEmfKGgwX|F@x4PBYh3VHM&g9}%Ww`9J zw|4KxG1d%a5~{DK<>WNTDa=&W(+-*M2$tR~D_4-pvbx5zakh2!=s-k5s*YG&d*L`V zMRvOHwKq4Xrl(8&kEB7E%+5m3>I4rxQ2}LSvx0Hydm+F zHg@LT9N~6K(U59Y4`m*W&CQD+HWuIDM`TfESVd5KK*==ed&zC4*I&JkT`JyarK0#X&I-3)-tSvYXtH$FpJ zL=?r2)F@csmIPh_PS_V`DFg)tK}PxD!2^i3)z2(CfjU#A z@w(#Nf`7lb{QRg&kW~;DAMa<-hLlKHU5I_-*kmj9O;6m-$Sq)kXnQ3HPMye(2dP4lsC?gDInXOsVO_w!pVAIKFskUiP$(DcE+MlP=rZO6>zrxG9ed2ZO zJ8J)QLFWO@t>Pa}dF*CmLPMt5XwmPIb?>PCR!DkpJs*|-9eHY@4WnF<5z(BiUuX9+ zHEMWpjHbRZB*7zN`?_N9&FvH2>oJou)lpner;lMrH@k&bon{x5iZj2SDViUCrT^}& zCE6f$uij`$x=6IEj=AqOedi$QLdBA$k?l+Ibp4ZeMVIdkv@>)q9kuuu&G11-uU7Fx&X?v^!?!u{QTICfLgsC}_E538mopUq<`}3@YZ}!R zj=sn+m-?Qos@u7w;_aoh4 zv5065peC?Lh(>1Ya8(_Kbt!A>+q`C37h=i(O$*TW86riXQp?E52xQR}OyQNJ6*!52 z^I?#y1@D`obC_XWBA>9Z$iCI$;%KEC&+Z71xvTtYYzzc*Rg%|^R*CJ{W7vUaW@Z+u z^V$C45aQNEJp38MAMwA_LDjX{klDL2+q;2|LCn}pkcu{*_FY#}l7od!UPjrSKQo;G za1q^k@s!xC%~MiM?cOo)Dj(b1ADOcSj|8ku&@7^?9CkeOj{Vdl0NpxuVarl2EF;Wy zc`@?6Y}+hC_U^T?F#Ty&b*EE6VT6SI9y;ezC90!2r(dR=6+uNs<(!3!y0qP9R?z=l zZhLGAB_-A8{`%bc^D_|f30UY@)K+Qj_6CToygv>m6TybIQ()?SxGY!=5vEV1zi+RO zB|~tKKIjRaP#~$aHBC*#kfh+g`y_d(3%oKdHbM`_pX|Cgd%mcgT*c%wK)e|xeBHCj zy5M^|{l>_W1;Tz+$M=j?%3_&cQLST{`G~oG7TLr=UM|vf`xILc4)AdPM*pH-zhLAbDDoldsTd zu`%yM>&jgK`AE*eT8S8WL3;-AkDdqf82}vx!Z4ZL>jeUJ-FTa?*PlxG=-amv$XlO%Jw~a5P2Hhxs951tyv6GBkV67>Mt)Ioh^KN2bvuo|u>^Xfu^|Nx}+KGpCq{f?70e$tQJpOKgM_TCG(mt;Q=O0}7-?W=XUwgD69 z*iHUk$)&?s_HC!n3JJ2dXXU1d2`TOG64lFJwcKz#A4DA*9m#LJyV_l`yZqpR=ZhT@ z%v5fux2NU!)$_rLGBckou7@PbsH;-gntYzOW++$rnKRG2_iT>KT}P7`#HCHA>-BG~ zEwtxYWa-h^!o>mdFy!_&H-DqXHVq^sN`5DMZFd(#G7M%_AC;`@LPFY{`%Xv)6%yV1 z#5R520p&4YIRtr3&_F2xMSZ~Dv%o?CI(ayWP9C{`gJw%$5diM}zB6bEQe@<;2_F9a z`xTIHVX?7+x*QoGI)&nj=a*7?NYfOqU#}q}|K{JXd4@@PMQNAe_Bqc@1>30&wz^QU znlD-!G2D|Un4~-jGNAxuW%w>|o%3>H|t*>HyMq=37=H z47hc#=)PCO!+0gouEp|la>J7eM(NFh)&O}WAvzfrYM_G3Qs-?UuX<~)6FDzH`~ZzB zYxj%9ppzy$2ju}8w!2;jd|=!F(H1yEbQB_Bse(Xdl2(q4MKyJGyFb4OTb-txC>1KZ z`uj6Cz4hjX@THegW_1iuM`i{;7bY4)pq1d(K;n=gdVsT>W2;~c=vd&Az?$lPpDhw_ ztKtx!W%>R2NmRv_OC8>Ee)S9XFTUaCECiu~NbIq(F%$Tk6NH@J0sstMl;D)>i~D+s zY>wohCmB5`I%*A%kDA-)BoZnGmkQ_^xlEcaLW9S4h%tZ|2%Vs*DFfUB_%X1nc${rO zJp()ha1@+^)f?{1tq=o-qtdq^dQOHdnhUxCV9&ejv2zQ8Ll$2b2>%fS85wA*(XGZ$ z1#N~Q3Guv6sZCl|))^uqq7XXO_I7F-n&(pwA3lTtZiqV=oe_`q@fqFh!rJLeIzBIY z8cY;Z^S*{iG!z6>^V{Z}DrTQYpNOPa$y09)Tz&3f*{J{F=*K`ODp#!dZt;|D%Vq2& zOU)&tQz-73bV~(LcZd&s@yr~2Z+7qS!en1`;2AW0`(x7?tcxq3SvI>?g!`6n;pG2S z(qTJE4JtvBV}$s_yEDcnyV7!Ba-Yt_) zaW(rvpmiQZ#`A?AL&q6VzZwSLq==m}8L#kt6SRU(=QUh9X}YyCWU}sc_tppIYJ$pp ziSSgfT;cWQe~?1@>}ZfoDCqK`Sd6u_*;j6XnIAFhRwT3`VRJZ!xTV43cMyDhmXN)} z$*T}g4y;{pR#w&1pz}OV&spf{9V%~Cr$}HfTu8yy1(%kV_7%L5@Fyu>-Y?fe7Z+c4 zn33Vv*j@20eVCDxV;Sw`iF5vJkjZc&@&6ZugSO!T=dxmh3uBq+%3B~6q)G%uC#;vv za-Gg$UtE>I37;g8AV{oe{oiLDvb<_yvchkQ;N5 zzU9L_&R2q@=|kf-{h>vyaIw3Kb?Y!OI6tNOIrQ@Qww?P0>1(GoFRw)}?P_oV9kLU6 zpB7;+2HMFfh$yO-9r6#Q9Ta%$)2~Pz#7F!I@HaNH?J77hw)t84>Q!Uhy~Hn#-8zj1 z^y7+6WsUbHrhZYZBt26MG<4)VLUU3*9nxDoaGZNl9ZK^$#nE2eZSUoN*>`E1=g2iMcE4NbF{PO#^LQ$EbL~h-L=@v=H)ga>pQg@W1*A!v04FU^Q{|9S3ZrfS_{2DRqfMR|cs1 zoY01|2e!drpa%Cp2+BUd97#b%C9}IS!oP8WNebGB&T?9NY|<5)l-HA^%mSz`ByEgKs{1r7?tVv{bzv zXjPqJi(QoiTeG9Gq2>0Y(dvrwa>R@V0vRAyY?v{<_j}+Gdst5VMfspc* z8`{5EVQ?AbF1=_XLP85YAhc%zu!|E=#O$Stl#5Q%>dna}a~#ggqVqN|%&T8e3vUfH za6<98owbdPt_TenDFCg3LUunGrO%R{I^pv9XxZwppBf@r_^ok}-LMaAG(SE*CT3Vr z00(NDn7a<9IksG5oa-(#`&ySP4gxCYQ71@QCgSnj%V|kI&Q!BM=Fyglx_t)O8RKX| zzm?O|zZTAFZWaE3So2@qsj1d!b zAiq1|3r}TaC zot*y{Z4|{Hn&0-Gtw}qST{l=9n>m7yZTZ?LAx~2KRY&Q1=S)pkO{G}vN9yjBr>+se z=ndMu(>}19!_$lSo8*WLrM!&M{cM}C6GG<&AM^60Q*MD*pgF+A!)wU_y(itcLJpYZ zn%hu7&}(Y7_gDxm4LOlwM&(CAM=s%$_dkdF(FJDrdf;nc5s{K#yeRoN5C%5=id6JikMKu? zLs{q@=^r5E0{Qdj49-a`82>S=TDNX{6k1|}69qwNET8E9_T@_v-O8wtf&zXgCk=36 z+)sJ0s$&0H!i39nZ&p*QJUn)v^ZrAnjv-Jb+iy%Q#&NZ_7OOoOJ*dn)fBp|2pUhmJ z^)X*x+pvf&67ojdW0ef(WkZ-i3v znnZ*wp^C*@{q0F_8x+0YPBFaHfC5zGaULK<(rI+sMnTtktUH$N5@gCsL;nXvTxh!i zN^u(K2Ix_P1Py8^qS>xCV4y9&jLyS}0Ozvl0;e@VHU9^1Zvj>Hp6-pCpdx~Rq=Zt^ z2nf#Eg~)5E#2L@|IhE7nYlA}&Ry?a|9jt;wPvk3 z=OD0g|GwYn`6OhQF5Ee3r|-K{)#0<#ncD1e5D;${hH~A=UO)|X5gq%eGH;fqf zoM`Y%XqPRGhU#0NsH;B)^2LR{q$&=SIC=Sn_0~wle^QXh1Z+Cf04I==lP5m81(56M z??9Y@Ium%gm6a7F{ZTnXWqm$1K7Ce#i+cc#5*0n!qlIV<1dafd6xtG8j`CqEL;m`~ zngJ15z;t|!iIG8U$uEBCHh!e9So?v401TktMjRU=CwuxJy!o1wQ$I37NkKv3usYfT zRCrU{@kDU*C=vDaSKdJ=}> zm}yX1e7$vo*M9g?^GP`AJKV3WnHGYt7J7Hd|J8g^I9X2kSI&>o>X$Pwyz`zmf*p?V zK4vA%fkplN~-dXh@;61hQVDe35Q6Xm{`jt^O&9FO1@whPN1ikvF-x01hWO*s0up z3TCUn+cr$tbYb5>xW7nz?ShwAq21>kc822!-1UD+4a{Xn)NjW^#$PgMNbpNF<qffj$F<(}QbNx^IO zJ2l>80_1am7C_^UtZjaodmQ**fqu9J>NddJTF$P+IoS##MbC=ex(lDsG&jazO?i)b%>8j|KNe~6da;2EwX!#be=^$u(rMlhb&m) zYHKE~D~U@qHS2)%t}8Ncjfxu{6}e1H>whH-N=L}Z57nQQjjxDk}cU=)4d6ilD5bxB%7D`1$1s;0{kMjPb1n%AD zY}?syFykVAU}$n61vMe4N;0*|Riq_ccfrTKw>>|Wlkc`~KyP*T&b?EhN5JekVfFlzr1*jP4k~wm8kP^Q0El`T*?|G zq~!yjQe-xes4QV>Vj?LoKMV3lXgC3rd@WRuz5yCi2wix~zq#+FQ;U%EFwDstZ>G5DkXSWP#vu%zXN9a$TJd9SblhKc^_6=pw!g>cuCktqspYA2#sa_#zzq=r`&NoE@Nb zIR~ebXJ^G(7zLk1D@vQou{~$G-FmwS4@1-{!MPW9Nrp?|Q{MlLHMt&Z zRiHeqoZ5}UnK0@#^G}Wq?SFP`I4o6bX48R5*qhkPTe4QD#Y&$68b8Nrgv2yfvPF z<#HeW*)-y7eIDpX>p`{sLEq51-;=R-{Zakr2461UU1VisX-YFSe&LrvF*cQfjT@q= z$*-;5YhrC0SI$`b9v7xXljBzghAmxG$DcyiG@@O4r(zf~^4?S?QS0e^_H>jpMW44T ztqSj~dt{T5!O6>GkQ>CY8e5v{&;BR(^6n&STO$O_zT9@J@(X2_y5AW;*L+~Q#wsP8 z9)a7cqS}$DM9^J{UEh@aK>rY*yFVK{63hVYWdEZ04-@(WB;UTOPRJnh8}yvIqZQ6k z*cm_yJ_nl`NU3M$z96CuxUDCt%ZYq0ll=hm?b>eN=;vqPw*`&{*0~;;SL$kNppF=0 zru^^QK&Dpy3meEX*g%rEUCo#2Atk1@Ro|i%V)v%Rp{D?Z+N|)%!rOcTIxAs}f&TaV zti;@Qb5B|*a{EYZb2u!H$pp*qH%y0tL5+y$C?IF`xtZC4>6WWYYjSC6+{A=uv;_8F zxwEsFO7?+hTB%6G zXU4|wKHj=@!H<>solXChhAPVNnpM1wxrX5|(v&V`5TEJNrZ(%Wz5<`Bxc?9R!PI<1bqM`Tptq7OF|L(wM*@n}`iP{ZZH!Y^OG ze29tp1z&PM9-0UD_suQX0Z243Fi6PCY8kRbv$@qPC$u?rA7H+6-oAYUtgk@!t@(%^ z-S6$CdV!s+z+_^wTPF$v%8>1Akk<-g>2^pQgvCUn)C%nxBFsYD0pps%&Eid^4Xumo zRqLAC_K^<8g2?}KJ9%FJCa3JuX;G{v(aO>fB+vM^F(is=QnxL~NtHG;a293}QL%p6 z)7Tf(z2?Oc#KGvQ#XDI==@XSrE@vqaT*?pA!dI_|AA-rrFcv zgwK+YHpEFoNmZ6xRF}@rw%c~na*BMoC@O!7NmJA5DLd7sQA=7&dYJhe`%+1(q`3UH zxMVPsBzV%;E!hdkT~y(nM0KNSk7*~{ZM!Gk_MA(fp%cntFrh?VZN(RMr!zNK zlB7ElG8BlYnX$5I;)%h%9O*JqqS?PI5j38ZnK2w}S9P#H@-&J6jy={qw-KSJJ(e=W zaz%$H7P!%M@{USXerKGl;})0K;VhG9B4o=_n-O|btnktG&Zw~KPC{~WEkww@{i7{A zB*ZqyeHCz7Ad;bO8Y#2@yH*~HHw6BccpR54IWDTyHx7WBe_)3AJCbP%1fxJ@Wbf!` zAP6p?ksFC*IzF(64F&?^xJ0R;kWMvQ6YTGhu__4;k%+NVuGe86_`R^;2j*Zt%TYSW z>jI_%RAHW5VFrMq6%-eP`}CI%=0J6DXb4F|FkA@?47@Jl_6d5Uz*hic5*}cAgeE?Bky6rUbft0R~ zdIP~l52|NjAT)@Gi6`KNhlF(H5FQTwtQ*v`SZP}tP<`h|0QQ>m-dEm(W+E$bOXKF> zwE*U~a7g^7J-oeJg|jhbWkYWJ)A(lQMx5}XX4*Ku4-dz0*zEtoG(lM8$_pq=7J8vxwQ}$ZB2xz8ioCu3ot~Z^q;QAncrN2e^sD=p;~_Wx zlBtps5%e#^^bb`oP{{*Cj#S!-JjhmhMMcy=2Z9M^JCmg$G(9*f3V;_J5D;BuICmy!_++PBM8pCpq{zG0H!UmH zvHnIOL3;;w9M7sB1jbnHZ$SPHPN~~I7eXOTbD-u`M53ajA(cZH7HA{dxbcdjXVzgGq)_l&1XZ;I26K>PSu`rIgdiSr3 zX_Lp1d@YVqr4<`ooy7BshGHjEJTVszKmU4nxE`FMpOw^%V)V2sEK`{8(mD2B_LwYv zrrcQ19|@)u>VpOH>lb1gC?69xw_+an7oZArRJEPGpi-uhxboaNEQ0azdCk(i;NfBS zH=O6r9pjUJfOk2B(bj=r#A^duR}PqH;G?|_pSL8AAkG{2zw)7^o>jho?u`7Uld(g` zvZx$Jr{f%wz>Mpz!tsMDgL~B5^~-0APpjimMGIT0&Ctx%Tefp{>kbE72Mh{aQhtA8 zBrusGXSDL+b0zT@<~`o??S`k7aq^~=S{`v+v9kI{SO$lKD|~UPk1op$br1&d+~Map zBriW=O!z#A@nHavj<_nGroG%fzup9*?A{a=MbZRV5KzShCQT-4`&$l|WSzrWIsLu8 zU47-*&--sHsGOatBn}`gTqcrh+P4MWE^^LA3VNl0jwEhkY|r?Tn(F9mAMr%wzUX-@ zETpXw0GCgZk=uyn1dG5XLsIsktU+Z&tudx6hRU%lWOg{KG$z(9JTJZ(pA!G-DvA6+ z8dvhjwe@dneJ9TEKT=EK*ZMV#MB&_P6}2hZo@e_^e0ag!)Lav@BDGek`=$-s(Z=y8 zpLS9{uzrb*+l-{2;3LtSfuFA4{hRvo6}wLQ6Q*xkG%4P+V%@%L@J+#ZQDBbXHeQbBa4RV|B8Xfy*hxQfA@?|H zT;IFCc3L323rC3X-K45k_;cYNI{jiM(}uDU?yUeSgLJ#SAh)_)QLCFGDKBJl^KOg} zL{{(&KTP{3wv7w7+!++N?IZH&Z@-++ss5PZ9I4Be#)+ko8Ur0ncbrYb;q$0sI!KvcIu8`y=ATt}F?piJKJk}cFw22zX4J$Zr; zVNGz)2tRfFfClpwq(N~)ViqJA+&4C60x%4}QH~Tf;hl3%ft#GHBGtZL%gMT^YTT&P% z(c3V5d@Rbqx^%s$sOww7eORCh)N&$392eY1v!9XW6SbT#Ey5{ zxCkoG*sAJbor%@Qn3M~K%ad=n6z?UJH<())YuK|saXlJJ3aLLBcDZz-@oLv-l!%)B zkpg@5v=&JoCN3+q{3el?>QmLvg(+dZgDOf&%92`9e)CR>-mH{2yPs2cC=KVHcA4I8 z^&fNzbm?a+qP2g_tTL|h61VpzOt0*#_A+YrK6B3}^dI!Psw*+5D(orW;5%_Yo7xv6 zP@9~X{RWQa3en9+vZ}JJSzv!Iqp zqqsX`zv$B{0v7Mw;VCLqfW$&OC&sg4Tp7%zR;dB;(tagRHQr{IJz_*Hq^FzhQplVA z7T@y7zZ-YT(91Wr5P*;A-RQnEvtJs;90ScJ#O& z*SpyzZ{4({BL*+{tmwKuf>^t7u%1-^7D;xq6^w7Q(A2LX9TSnx&&qlaa5S&Qa04)Q z1K;${!lG=0BMw-4-q>OeS`OA?D6qT?N%|WunaQ z8xwQk^5rQ|!GKYT+ihpS2#JS;DFoyDl@RJgw4JA~K_Id^;D&$!WW@%wi-Yc7V7&4AX2X z&`JdbJOiu6)YV1bE_`odKBwCEl9=^YU9Uq_=e&HB9fY`|ssSaGsP|HPhUFmb0|}yF zsnvz3JFX!Z0Eb1SMTLYYb;e}X)Xq&zY!KiOhw55Vr`S0;Iy(MpX@SzxU^WpTwHw1M z;n#%FxW%I`{xBa#p6on2@wpt?a$O&C(Z$EQVU*p5ak1BCS`7J9^cbeQzy|Bn!~s~L?OiL@o;}N&;!04K2$S6$9xCG z>e`k=TVG^kq-Lc{*ujAd7)%Z_Pe2I;a3CX`IIvW_22q;&Ntr%1l`3)=Jr|`eKF-O^ zqyuabc8P^QvaKSHBP0cSt&mh$cqc(V70f@c;Oz`czJWD(0O05G18XC2`}9yveT`-1 zy-rtG7h;+RTE7BpIG|IJCgk#*)VKZv+Oywmp#`reHRb2znY{gxTi%GdGo@0`oK{+%SVU* z%dLvd=vGSHtk3VK_8afA+S6E?Qq}tSlkK+!Uz#MDJ3mOh^o3tCKZ&Y2y75_vSAn`; z$4lC7yPhXK3d!RtXCqX#>JAkxc>)B8M>H0n8E$)!)GG&G_NRK(8AAtE>w?Kig5o5& z`ncWiyMuWjlL=XK_C#b%AKlGyNLhG(Z927iI*!Cc{oNshp3(QKXs=H+{q-fV`V;d)hMhjX@?7pYo?ptK?XVL!|b@l6FhNHAc5Uj`qOwL0a zR+{tgmfbY4-cx&4`HK|q47=RiPGU~JR9@>)Ir?*}=Z;l-U%iSdp8YjtAz7J5MVpUh z{M>K(9=Uvb`HArUOn3D0U43uL;9XO-WXlKgH6!@ zxx)C@uGNBh3vi&*3E(kk8|!w(cg*>Di;WFo)&SE6y(7HaC1B-%XY$4|)$h4}YvmO{ zu*&Qrq@vT(n;uD;-1U6u0`8XzXA~0p1B_W_HDAQTL3E&lnn#eBzqERczr7+5G1r z!OCX-4sZCG<+(YvQ((t_e(${LqLG!YhHq6B<@!3#*K~y=f|$x<)}`f|4^q(=FCED2 zxb3Zp28R~~_V&7*f~wXj1`KdUurD^8M1(UaXkLd18li&&F_@F6g6BT{*fKXy)zceq zdwrHS?{#2gq$oML%SiexSZTo5C;`DwAZ<5<=K&UJRrm0dSV7~wgRZ_}qyBH_hpXI` z(!Rlo0tsTU7lNyAu2bH`Bt6p&3N|3&3ZH_)zo_dcO!q9RO8nXN?#;$D# z0dRqQ29V{CA72#nad7V`f=U>u&;U*o#>&=DxAjY)4k&&n2ajA7+DaytmI6R@kKAW4 zuj#}8j%GzMPo+-qLV_wQg`sd6CLycII^0vhhBgl58?CReJ5OyVy>$EX)eV9egww}H zC+@GaG@gzv!`qbXv-vIH}B_KUD>Nuk}9GlmhX$GN={PaSGQE?x~KG078I^Q!H}9venN7YS5X}=>jSp0 z6>7dpoqAR*t;$vo3+fZc6ODVOSPKUWQ*Jb9(vWichyvBlwZlRAqBLhw++y;tJEOp7E~zb8Iq4TKjJTwj2DgK?!I4@EOxcsyg4^!$qoTQTzVY0CtsfFUO&Qc`d3q!>hrYbbk2Z10 ztfkc;Jhn2XJi?dBc8PKiHP00LL%Tw;wM1Xy)6Qm)TbyewDrkFBwCY4ELO7$$PbDq% z_FF6*tZkTT#Rt0oaFj*@Ja#bT_BiKD6U^J=*0>j*TJi=v08CW%#(6ljvG3goUbW zRvu8ld8>a4E>diOlSjs&lTzB)!~~?rxG!gaMT!zon6ex$k6E%04Zmq+ELj;FK44Qw zJEyFSJ6*@2tO`o@`c9i4@G}=V#0aUV_9>I5DltCAwd*Yu`YCw0 zySbIv&Rzj|a}eD>@eaR!%)lef$;tVBU?B2_n1F=^AWHXUt2EA-n3_T_JpuBJl{>D^ zAO#!wC(4oQVm^UIYf!q83wn%!Yr4W=s6VYjuzf1;$jHkF9UL5pWTj%i4r)E&h2$FYKB;DJ>YE}Yd+50Goa;douh=>zI`Q3nw{%P*Mkt&O{ZzxLOs+lVgz zHgpg{KS3>cgv*lSOV0`Mz^_ef75GsR}E3 z7iG*@ys&YYPE<|;)gF#FPc}DdN3@h!)_+dP@a~G1GstfI!cd77ElKg2;qX**WH62oeeuHMD|%+X`U4wnOa+1Oc>deuo~|1W!htwu1#ZpRsXG;;O0KTypQ6I-BvdBH zI-#A?y9DB?($P=b28`M|)!HSWJir%l5THzj#H)4D1$lsW>L|&4;*zLlYucK7fzim~UHiqIKkrQaNp5#n`C&`<5k*eLh^2MN)W zwHEc3$B@D|?|eX!!=T1c2TzaEGYFIsCEuWs+!wpFLNRjjfd1XO==*xJk7SLuA@O&Q zNw4#e9+E6Iy6|Q68KeTq;G=&m>EyrwtiW2=l(CW&9jnY;`q{R7VsE{~D1E<0plaxI ziozSZMDu(4;-$HbHl^oeQl1TQ3a`}58Ea*vF|6%54yWhP8a}!!wzU;pQ26vl@Y-hR zyylhYnntEBD_%~61>Z>Sg?dMc_cPtyWMA51WRJSZlSq@ot+bq1PLT2bO8!#SwkMO z88oyTSU$!8>|O-bSk!j=FP#1ze*OzS0(_4rzF*ZiKZC!*{cxv|_p`vikqD%! z#k^~^Bw`;V(?9Vp6K&3nK=t6FOw8W-_I2S5(83A{2|?51cvkV~^OmSPf770TQ$@#P z{#8iV^O=@r#+x_&cXoKIPM)2d$1|E0%7YM(I(s+OAHng;| zs$(<<`udx!<;zh2(F)7or|3gPZT2yc)8DZ^*t&UiTzv9ORA<}oM0jOuH4m^OL|F%M z3J`2HvuC}(UMqy;4Jvp3P-7!iSQhj)=udI9x`X3{XEk?@d z^C=+a^J`jK<3Sr7$O;Myw>UT=G+w%PA7>=4*MOy@0hU52i-6e+?i8lxc&-i9CpVyc z*4NfPunB3SJt#p00~ld^cTnPbaN@@HqvM^IC&@G(h6@tv$1wYT?CAoJ6B(}=DSRg| z4ch)0{U<3a3u)R9EvgTm_Kv}w^a(P(z(@NKNT}FYSe8lQtQsl83(9bl7ZlEoKdwY& zWM(2tmWuT{3g7`0pFH^v5CxS}X9WwdS^sx1DM0eT6quPIDq}4IeUz-Rk0j?q0W{cb zZV>oF8QX`>b(WBmw-aq`ZS~dFuOVwABtBjqY%DOg*>D7G*BRsJdVD zenhkAvW)}sM{SC_<@0|K3LnE7XR%_g z7azW#HBqLgo;A2c+2uZ$VQNKVb<>efmQ@gg!`6AyQ-uIAKzGn-Kx7rEq^D1+?R3*4 z8{6HBKk(-~CF!060S-@=2EO+_G(7`@H=q}Q4BZNan)h~jB-HA)hqu{V(~YS{9;o5#~&LyM5;-JKp!} z6d$IC{@@QvE?zsP_-Fn=U~Q-mh+cD~#Egj9iJCp$VmS+MYzPHz+1r}~i0koulcNCdbsV6kHIR4>+{qR* zW1rGEd80M?qNHs`sy^F7GWufE@VU~$bDsdNQ!6v_l|8?|0X;)S8bFu~3M z4+jl7*eNoRKOpi9NlrSIQ@In>K8~}+@{0k&?Y;0K!gX$LQjYuOruss%PHUS$&>rJ$ zLOI!c%`(#XQXJg1P2Nc%DH}fd>lKk7=`KwD8@b3ZgF*dyE~%;MqKCT3jpNq2g+blt6LK%-F4nytdgJRW7!}39 zwF`Z0z@;VmyY|c};)4dC~j@2%*>qwVfW z^kPJQetxD9EOZFg2x>+csc2;`pTnwf=ed2m8f3A6p8mc zBVtT<1#careVBFG=L+QnL=nJWIpReTzuB>R;5`u{AWH1C@xk1R|L$GHnT8~)BQZ{p zQ4Lltg#+13563k$G?1-dM~51s0O4DJs`B}m$hZjVwMVBKkFvGv*!zG!?w8CK1m`wF z6>JJTp(En=K~)A#fVQ}_@_Si*H(xmcX+5#GG#^q!EiGm@2i}{SkxmX-ZY%-l#FA6- z0BY3ziP<26`Xk7PbD`g~)g}91z*$$1{{Z&TZ1EpF`gnMV6x1W{o0YS8!x2BzfY5x~m@!0n}(+0T~c(Z`+NzYBYM(d!xU5gN_X|B8+X@^8Kr=?YJ$ zhzP~EZx`T`loJ!hFH=_$fpAAhTnBYQ^ax)@Z#-yfrf%(c_jO5WR;c-NV-s=(xXdOd z?n+6ueh6N_;kfbz#2Oa~MGOZE6xB53s|Q`U^z>%Q1b5ar zuJ!E^Pp^kG?Cc!5y7wi+7SzWBh;oTJ6whI0xDkY`O@Jyiwzj7H zStSwa3TpvYPS^XtYXM+e)2bpG?(Kb~t4jj_(-f3LBf%t)y98Rx_;-0sO*--;havw* zG}ZIs>+i3Mg8FqrM zg$9t}2KgKMwSKOXeW1sH;&E~KaiFoB0NPVLy+$Z7$oK_vbI6N22RS{Fe3n$;$UOBc zRxkpvPxv>F;XO7PB+3BkEnuvWk&!_pP_?yQ5Y1x)Sr`=+Abz4EM&EQaoEXbD!h`&B zBw88b0zbsZk7aG_{`6o$!VUn+)%Mso1hT3I$?!oMq`?RqrbF%WX14x}JVRa|y{wGD zC|%aqufGxJ2PpQS!uyhzmKNww3?9wgzjX-%xb4}5jErW;svCg99i(a&aw;=5iskf< z&YTINIKF!D^=rnHSZ%folT4|90+MfNT&}9p>US$EKh7$lg@4cZCqg*Kw_oXMh>S;& zmUr)F@bA^tAgCoC9314iGvay!Nk)N<*=ni(_5e2y4i2QOT)1>85!_Ia2f8z~adF;} zT0%nN6uVYgcXGMXpp3FI@3Uub%k048!UJ(!`p`#a@WR18&kex*>-;vJumUPV77esD zKp^_5{{I^PWA>mN(@81ddEME0uBnL~{I*XmMh>tjDRCgXeyY>djP?9rM@R0d-&^yw zuP*iWw*8(dB5-#XoR}neq8yg>1X?unzZK(&F`F~on&TeW9IwHMKN}Qyrm=XM6ZGKG zN3+w@PZbqu4?*)R3D_SLW6Uk$)po2qizn(|>MAab_4e`-#lF(NwrJ#kn|l*{895r; zZs17(Ldna%ViFp41Z z9r(_X)pdg!FNz^ARIll%^6xM=_ffxN@Ng_?lQ z3w|pfqOZeG7w&~avT?AO;}})a^654ti<|`}nvBcfLc2AWwuD6LSg5=tX4m>J(WN5* z-+6d>xol^CVS5(>&IH*-$WIK?CHQGOdV0I9c+NzG|TLK!P4+95d!WX8^NvM_FpBk0PTTR(MX`RS^Zwk_k_(oG0=G-3M!bT z3^~MyYyU1zU1;3P7u<0~K3!Xw6dJ%mz6DS`Jm2TU#A@U=e`9;0I{!GI({~w&vyfW> zrgo$#34mqbQ!P?iE^TjbL#Nm3msfs2f~ZB~K%N)^s*H~I_U}j#oZxE3TTnSbqDs~u zZ^+IUOp&WECUOVPQVLdo70J4Mg*@OGXUCv2n*Kr(NwDOQ6U+9Q_yCGFP*dKg`DX>> zp=_MSvu9QSe*yLmsihwyBOil}9-vK|6YwDX0K-B1d`ltJI%Vsi*$-im#(4$WM_?ij zW%oU|r~~51Uce5aZ$b4cdMF+lbCV`37gIp*#}C*#Yz;6+KrA%eEb(T9>G}$TGMbv_ zs;Z(9xu0?8jZ?r=c64;BvN9F7WBdARKG&|z!(Ji@ z9c&Z;V!)$^?e30UbzpMW+?+s3`OfwmV^|hKBc9i`9?Z7fDq4ACm|K!QD8?wZq4=Od_&yQvjI4_kFKGze4WIev_x7IU^Ym2>9CCgm`r z51KFvN_KBRXnB*?HuCN|4*zb1wu>*UF)+CSRbTt#N22^DCSj*zY`N`TagMy}KCn%Q zk{dW;L?rm9Dsfmq`rqLrshKLHHY&LA_hzCkLD2ZFc0x|IKt%4(=j97FrmB-o8Bx zEDQsDrOZrL7!ja}WR?36my+)mD0~W4I(~_rLZL2V@ z>iRVu77<(|dH%D|8Dyhg^~BFsQO)DpAAZF$=73(;%z_Z?7#ZY`=xDt?`e`}KOC8-JLO2gep=9FIPk1*V^){3dzj*_eH zF`Jk=>|B?rui9;?g@Np%@{z46w>{40xts4&w6V})r7X`~d=wQG6T|gh>x8iV$udSP z1a9DbcqyNspLfZz+)--*d2V|zFQI3L?2nqR>1@v>rpY>UdQL06Qy|J^)w~scjD@@5 zGBxddeSTEhm z?_kNvNZ4=N>T~gUD@3j9ZGpgKQ{@}|`lh8TEvazf1z{L>ciELP0M2zmmH68K^bSWq zp_>RP==RIEEJ5m2spm3fzC4205hcr2GmAsb&W0AB5gaLhF4L^(|6vLrc(Zou?4T1X z_X5@e7q+9lkjZua-)HSKQ%Rf{@Y}ND(9#~2S=6A--}`y+``fS6grbDrm0U96@uMH) zb><|%l0Rz{D{&O^>0&BFf?-OJFhHd1@|M)>J}u)m{V{SYf2B|yG+h@B_xe0sVT;6Yfk@Sx`EGC7arjEMDwIAjk3q^!5l zoyPjC;2i`LHr58m9$!qAd)V2nwDYrYtT6;_c(B#g-!J5!G`T7MXl8u6@X^f5D&{sU zy5C^NPBz3}S+UeLS-`b(qs^?Wq_nrUkHq{>G>F^mu*o2qXt`u zRrX=UUjj190vNxI-I97dzp0FuM_+CUeFu2{v5X7^PDLdo(7>h&4o5T}#&4LK0d)#^ zLC`UR0lcKVd~#(a5P)0-XIlq{@SX^;2SBzOvLNx>hQ`-3eZ&Bda2L9E0BS@QLhvYn z^?h5rZ&gBBPY|7Yh4Tn-bIf#%_p`Tkw9^QFz9zo2^d(-0P##jtw;WGS-2CRbfe9j6>tn-!|FI4JrattL|O-}CY z^^k}7hsAmnykF_l1o3&Ph{F^Dz}D_4M=9MZot$N;k1~nWm$M_T3yN$tmjg zcGROlzi|$_J0WZV27fXU=-&i{ujX(SjrR%$&76vjOAx9DPY@)jN+Yx<_GEghZbY&N%RsdBcF-ykd{(hO-)|JsS#&&i*0Kou}LICw^uwzD3VH!t9)ENHa5YTBqF0Ll5r=NYrN5!U#8_ zxmNH(L|?)51r!#@6hDTA!3>85JtnaeuwT#stpV3S64cAjI)Ky%M9-xz+sv?!Ku+0K z)82M=U~+0|$fr*YAgj3tdvfZQYvNc?p4*wA-YVuK(V&lj#c^?QK`C8A)gO5e11R4w z`nX0A9t&UL8;En@1Jtl|G&J{!eUg)rpA8~KxS@R+1Qcqy6Qg@~V37q{=_>eF5w;D) zOL}@>8ALX*xsD{6Pn<~DBOIxWLkV$$j_iPr!52V+DPcE~ff7G>Eub_?05Ad`2+)a5 zf#}xB*`5l8Xf0QoexhL>1`j_(>o6eo#-gHVjcF9A@Ul*5M^4{n| znjN|A>HKPha0oo@QA$2gxMPlME=jN8wUYh}4{am))M=J4R*?Le7)9e|h_%|nz0qDF zKRgodjd<3=w(%0FI7kRw~H_XGI^9)|N zqjg8AP$^jI%c;BBHU?^2oF8ZCI-ZRUGEt4(D0@k2nqcuIu;6jiZ{N3RRhn$G_0EOd z$|M=LM77w_r`HT`tV9o!t?yj$yF&8x$lf}_Sm?q(K)d5|1GTVcwH$3nZ~V$Gy*-09 ze&DETv0B-(+-A9Zw<56eO6MacKF8sos}!OOIem)6s&((~&_3x?T@62?iu5@0JEmUB zb*<9R&(BTB)!1-W*!r6$apdFEaxQ_C?ALFrM2gB>zGi%#Fn%sI>K;-NSBFW7#?H`` z;98yv>Mks@v!PBmdgYr!Tt^6}O6xY~~$1b+R(w zQmw(a&xLt`yo$d;jK{JrXXL3HrwxJJ6OT`Jgm)zX3&=Idz(AQQ1txg|%chY^f<)l6 z!7e}(ED1?52)h6XR#enV<=Qvs#{eh_{)AeDah6s&v90YUw)XZmh}$AT4UM!2K?ok< zQ|b{W^aqb!YrCZq=3Ve3Xq8DPwvz-z%2 zC;+ZLNH77?NH5;pTr9T`m!7vbd}k*xS53J`IYnkaFq#kVX0V;vPh};||Gy|?@*e$N z*F*LGA)gyzyo5wskxTn)FQSMV_ho9vg8yWoI!0-^TA z+@8?Qj@w@ILxXZPKYqlY#Wph#&iWsV5Zm5v)%^OEqq@NnCe_d^*uMO?-E{RosSd9! zhYJGgc&N`2{}fqczTbW^z%cOYmd|!Po->4nTNkUjaXk$gKfh3{A(x zP3e$1KHQL(k@-0=Kn4gNl4Vn5J_Kr=JZTsg7+o^byyNKZdQ z1|{?bTS0LNRglhUxDM-GS1aqG)DvOFfdexetteI9I(!--|GQ&Q)o^R}F+kT=zrVj` z9|7}2mptrtK(WE3t7~c5228rLZKvE^(bki0Fdu)Cas86o&t`wWZ z!^NcsDhfEGFP(wwhtOe#RBqmNT1!}ZSmqO^qwXP(Nt8sp)KuYII3oVvkQ~xJVh;XT z#&hkC@Eto!yN=wI{_pL%J@;ibWcBvarIibMi=W?ac@w@sJV-wD?LuL~#`9z|TO1lk zX?EkBFXOv~@n(vf9^@8x5Mfa#wNz>8Btv)?+w?H5@a5dg`E;{yW7s#>nz_**h=M&| zqAwJ0Ta;MZsXoK{2a4LA@B6@1(9xq*Y`wTvOS|JGg<yfr_H~l`zS^C5&Q*JHQn75v`MbfopN&iFOAb61bgLV6c(M37$JWD3{?#D z&R#}BupPCth^=E)xR{>;3I8q-}Tm7i0}6G>NU7%3h3N07(63+(S~2G|>_ zw0@5s9!}2mP~3|6dERX_r*G4x`Z&f`#ArWentzsmR&u&QV`}c%Tso+dJ4J* zC*L|wy)=Hg*3o{7ATD0KZp!#b$VKQH!9`mFxv!pjiAgmv9Qjc5RZ&{C+2i z+T>@4hx%UKy2JJ8W|#tdu$9}db6gDalM%Nkt?%46T*^&i_86lU@(kk8nJD_Q-4V2k zOa99@m+)~sPuN~AVVaoawzs}Xll9%!3J>Tg>i}i`~ZCrZx228;RI8H zNhwr^X{;Fn76AR9EMKh>`$H9}L7BVrsn09&c6AHpF60b zp}ApNVP{qK_|0ON67@+S-a}bRZKC&R>APRjFR9YPkCM ztGQ-oo{1FLzWMa&%^x*w9XVr{!}eq&xz3krW^a^~3LjM}6&4ovSC(@)I1nPQ1!_k1 z4X;V6N19+5!gwK~Q1i@Lz9SV8SHaqoxPlWwq>cnECQp@>AA&qlB8W=4S%gaUXjN6T z%;m6aWT+2zwruUnbT^C9iZnA4a&q#)u(RS1AA;);wCWF~rEzlAet|5CO8NkJgwRPJ z5G^2TMiv%DxvfGHl>Ou`9UQhzkO3~gydTj#4ZkJMX2Qj51RAj zdp_tGr3I>CZEfvvg~!tWj%hEV`T%(esK-G!xmk=p&JtS(-x`wd2EZn08vB8?8XV6^ z!g!1Wy+027J5t^WU8Nv{gbso*ygDjk`-6zXLh9uKRilE{GiT5eZ^v5w;SndaNunTJ zr5;=EdMVl#H>&6oB6Qp#7qGC~YqUo4Hmx!HhOPRG;%14QZS8J@o)5+J^5247+j%?d zcBOTqEck+R>CUR>FGZS&Y1_=pe-RU9s5{=0-0mkQ-PnG|$*!#Yjc#A2xVc{TbWN#} zdbJV1IwU0E)eQege)rCfU0btxdr{Eq z7e4-ySc1miSPB3du5|)S(f=n)K_aI1+vSlHF>wj_eg5hr4wuJ^72oOE1tFsTnGQD; zQ9Nd|+BwZOwO$cjEb?o7hK(F#azMDi>yzO3rfv;eWSi!Mt9`HD+^j606) zHEnyx(ur6IJoMjerlmgrKHt3>T^WAcegXWbY+k?SXs%wJzbn|bT(IViJ9N+L+r$+@ zMMc$_UsqY;EiFe=52pDw#XG3N>KDP&%LYtZswzr;kq)n`Tyre|XqI@m6iU=cZ09*-5h% zYkdAsiy}(B$9igkOHN{@d_#fX#a?^T`+PP^P960xOuC`hsQ2kGT7Tljn;QQso^iOf z3(gt5BSSLZsv8>L4dceTc4SZAl0K^1p8UZ=(%_;EH7`}v>rByD=wUrmt!~Zmf$LLtY0NL3JJln)@QV05+X3Y!!fsHWcDL@P4Wy| z*4mt2>w~`2nlVD}FS;)c*@@Oxaj-L_95F6FPo1g$H0pl$t_dqjfU+%`>G7KmVKF5z zWF7Qhy+ZO%G73Mi(4((R(*az5kU{{dh^>$)3%+_|`^G&=@P7J6M^nJuWJ0e2mS>n& z!8ZYEdgTTO=Z>>-i;L@_5gj7@5KSX&escr)sR|-CQ<6x83H&uJ_2(j-Ib1R6oA18rly|e*8Tpf{^QQVLWbMs`)h|grNY4# zD9wY|*xBF9$D;D)N60z$|Izl=VO6f}y0?i%s33xr2$F&{NUBImhaf54DUE=NfYL}w zcZ+~@4h3oHl9HD0jxok}J#(&n*SpuZ_deF1f6Ze~CC9j*>%PwO{GHI}`4pB<*000;#a3zj^e?KYl^AhP_ z^cpdksPiBPd~j&!)jwTHz|u0`#z$rCqSca@Q@(!7n9!u!_o|xNr7C!azx-t=4C^Rd zr+cL2kbF@p=3M#ySPRCaOk-=$vTpHA0@g=GSuOmv$LaowZ2HxKlH;nr?!j*swKw52q#9*s z+{x?8%i7amaa3Cl<&g{{k{aosIVxiYE#P)^Yb473^WFy;34e7u%GoSFB5@qXnFJ=4^=zGHs<9Gh=jSLP3g zCR5D)QnGRyr{)vq-K_BLCki|2I*-K0T3#lRv)qsjT#HdS*OH%4omJ%@zr?hZ!9v8- zU!F5x`$+h+dyyWgEXM4Do4Bi_ zbJd!zuUuCzTlgM0$M97Hf!t+NAGkElU?qfPl^$4nK|)U{O;}z;m`oVVJ_=?BRZkHp zbpUu2^_@GZ?V0l36FhMLLU!+(ngL+xWnl?|+2J93C5ZGAVCqLe&LY?oP66k&SIRK9+|QJB-@&(xnXNyd=(lFeN4NF6IVEWlF>r9S6)R-RUqS#0kwqPya`txV{moLFEEX{#yRX^AUgj0xaaDt8P?c28( z@$td(ElKJd6od!oS_3czkoG1hQ%@ChZZ3c`7c8E^U=K$O+8|}d2p+B-n1IO=iP8bV z9~^+~a2sCilh}N-2`*bHY3X|S7_i7f6esY}0OlLmTjby+g(gB6t0uwEesEAinIyt+ zvNyZ*4h4rdvSjBnyADS={r&p|qKjTvmv%%5>One2KC$~MH?*=hM(cXE!bIf?&eiH4 z#|00Eo;W%w&tUN5+Tw_A=58h*m#5p7qvA}b_oczxI# zz2Do2)%5BVOAu9J`u?qM^@GiPMkk+`Ko}Rxhp4KkXZ8(sz6(3uk49z4o~H%s(6}o` zDrQVKImR$Y&NDL#JkQ*6|GsucaLAPdO38^R8PZL?6yx~U{5DS+aFuXpOBY<~ur?C&o+~s~Ry(C7rZpyaYFO^=?jGvRl4Qp7`BoHN zyO36Aa!KEcCG4NW5s16Mv03}l5K&dY zBLDbK*i{me_eDkQkhtXH`HjA<59toXqziVmIh(=3LHPHp*RKP}mmS=6%U>6vF_PTG zq*1Z$CaadIGHW=u;`5lGM4c|4IdIq+59F!>DkC&{tc6N4P(C#VF6@f=(7c6$E;xMD-eGzje zmMai=v$P)P&;I=>iz5yQ%IUpL$5F%Rm4;r9Waa7m;ZiLf;{!+Nq8y~1&JBLPETc(l zzw53nR=2~Qrs(TeeMqWWwPBBjP}eCaI*@wDj#~G}2#ZL)NdG)w0t}&_2&wJxL?2~+ zxwjTAd?-ZKK2AVLNceX<(nBC9Kq0*?u+&)%bb(4e3yxSYBqqhi#_}|u2FT%$v9Wm2 z3XvMf%9r!+Zk{=v;&Bch1=UWg=E|7`1po|wxv{;SmXYBDOc?Q?Ta9q6Mq0y79lWZy z0UZmY32;O@zI*#`cEP?Ku|^`XUL+yg98AIFSOPQ`I32*J1%t#ic!OV^Y&JvZ2x3ix zfNa|bTvS%=1{E!|IJvzzfFsEOo^~83ODM}1 zYaiCW*B6UujCN#rrLgk*!A(betPQ$#A||8@d-!5n?=58QBW7tbw;tbRab!^sDbK9T zB{lOVTDBWhi7zlMtuVT2BvHUNa8owsi8#zNC4nVu4qQLJJuCWV^upwI6lPpoV@RY@ zZe*J4%-#ito80Re7-OMQHFL%XTJ=F4yoE6@((dr?ax@x$#eKi%@Zo5Ul(de=gGP>h(C&em{e zyJmUdh&h?ek=syBb<2B!nCxru#d2qbu;WjfY#raju+fRV{CMTF4-f*=edu%Sg#)$jy@M;2I`X{o6n z&CNlPeF{7*vIcq{K?4^;6xP>&3!Yk!VE;T=YL5v&AE_3WJ0)eNiC9MOON@ca8uuO&347jE2y7VY?_ z(ZZms;JmJ>5j-U*v>x0H7Yn$MAmY&07T3eh9Az&$oB#1VrF#4q|xUDjwA@du~{tgOF?Ni?gl zl<$u;r@i;m9AmTrJB!1YIgHmuFDKIFr9&dvY;#bCs1-$2Fpy~|P z*3fc2&J+)!i13)0R&WX(0HX;hliuLCeY+%N^@X)HQt_<&YV$ns(O^XbozrXhK`4li zc*8FXBsWmPVqguk1vshFt$naNmOOVpZEkU6<|QFv__MEj^=cAWbbVK6=QGxW32HXB zt3~RcwZ_~&ZJM~FyVkd}^3yKNOix2qY{SS%EEuf~Agb_+z;k56>wFa*6SI7y2r+C$ zRwz%d+~kmU-ptN+mRXCYqv%V5M3ScZ3~&6- zntd^+Gf@p5alLKvF>F_d7`7g3)n{dskNcR@Z{pwQw>_+FParl9kP+YLp&~UM^nOX0 z8M<*o`!z4ug;8-g`w<}p#h;y*biI#uuS75Xp|kEEWS~;4)$d(@=3$k0{wwo<>NzTO zSe#>Nx=dhlK^Xh>oCy05?`90>LfKt!p`bVs?luM?6snuYm`UnauMV%5-^~xf@v!NB zAs3h+d6b^>BP2o6NSP3PN7)G%uE#`_D|Kxi7EZy6m>4c6M(MIP#8vo{`4O|Mg!6y? z7(;6>l-B)$`w4W>L8~Dcs?+_e<%JL55eOe750EZ_X9vJks)u7WITe~*fR2JnD%Nn} zM_BZ5l_Rr#Wju0W1wJ1va1o?}(%Q<(&o^f;0K7`#PMCs{QavCSA_ElsqkHM5r|)d; z>q@9Zyoc$Yu}Q|!5u|MBq>}@I72JC;Oxx~cr=sz%T%n<-zX1F|I9gnm^UKxiUOfN` zE?9?@G~=4r#)B&=ej5DwDR_tGK27w~VU+If+1qMEBAiwl#L!9D++E0fyw4oWckjMD z#4HJL@QyBUn*65a;J9F5AhEZ$8luAVNLl%&2#O$v<1XN9&t%HBr<=&z1e3eUna%&> zqPDrQajyL4u_oYaVei_~pL6qr|MhE>p241I3@=)aRl@NA=CP(9+ z6`d_%6S+oIA2*aAP)UX~GLhPb?_%=Y&}9U=KJ5C7vvJyN1jr_$c1 zCrzRlLOFm>gYy*0y?p!jjWxUu5;Cw;fD5RpnHlVQ5m;Gr#aUy>fc;Aep|td6@HSY< zr39=TG&*7mwnafBrT@!HQE#S(y-~)p*G0JgvcAc97*dSgmY_Akx@uFpjroKW&_4~(J&T&>M-t)NgN=E0C<_s@I zE>Q-SI{vyL_;K{3IB_C6()js)t}fD%1XXDAG9%K7m}Y2j)z7+UCPb{Ka?n5 zCXj^=&TlZm`nlCh@y=afUaMf|b~NfF zFWLhAV?HcvUTOjQ3{7B`fL)r;5%oqP*jABUE49a&1Zg)nw>SysON)g6FE_A(qQSPbbGIs`0=s5`@w_egAJ=Ed_#mz&(Z_o>=YB5kkd&TdT1 zgg}G`Hos{GU((9Z3(ps?!Iu&fLfV85VqdcfOf3DaY(Uj9&=HR(5^!L`P9qoiuuG}H zV7ICJz1Q(k7(5zC!?SW)KuA3BoFyS9Adw8sxZ&lzKdamM$hvX^14+w)5)bBcjtrYa_a?(ziB<-MW97XlQS5f02N|1QPqeEo1{n`6E;;0KuTkw*iI`7^DQi z$g{IDtjtLDz@8RXrSO_VOp*XHY=?)PYJE#5CZ3Sqxcis-QUAci|MP9~&3o#}y_rH* zn_dySGg9Ru+O;04S=jUH|8XZiu$EN*x6ur?bNT?jgp--^e$o1SjL2Jlba~Z`pGd5y zX$mCGUsGRW zni2^_mo&pv%ICJj0El~FE>VR^_Wz;q56Q(|ciB^TKH0mTul{=DC`66f&v9tE1ZHU*TQ4%-d0SYeOujCX1_k$-J(prZ*UiG|V-+yZdiDK4KznU|Fb6E>Q3fX;*jP zzVz$nLv)Z^ua2z6U7LGK_d<1E)vijd@69@Q|NktD1GAP#zs$Z0_12kpJcz@T$ z&1?>({N(mYpYyx;>iGzPDg0Mnw_^%Sde!@XrJRJd&&CJvOZ<)!WctX|GWt>Cr6VW5 zkVAj3)vV<^w4l4vd{(bODz97ZPK!=t(CFpHcdy?hoMz!>flHEzCOO8WJ*|kam=})%$hb1<@>u;et zo6#Qo_;d1)JSeb|LJ|(Wi_bW6v^paQn-A{M2mDZChsTQIK>xeSFK&CoUBBMx)qZvn z*d;fSZd_@r?nH6+xsLW#M`~Kh%JQ7YrRQ;>72lU(I5Rsq7^!MQ}j@4x_HM+Wvy}^29QTgk=LR0079(~T(@We zXP*Kt&yGtG^lv~cLLQ&6F96*Kq9$ZftlDk9RgzDQ|J4hCM|KWPQHTP6IwWo3Oh9(z zIyztCwP3Re`g7n-9hWyrUYHn!;0p8_u^tn%rQS3@G9fn?Hof{B9_I=dw>mi(Lce>f z^+HMg(GF=qEqeUYU$9&zro!T48U}{EDm5xJx`boetq#xHL4 zPKMx}I}{=)ZgtHIoVJs?5OC~4Vb7d8+V>(gn5iQdCYOHnU0w@C*^nX%%kzpSmG3k1 zETpYgS>T?^z$z?e{6}J}E+N+m6GT!Jv^gXH!a@KCxL7iYucA^!vH8=Pxw-!E{mpG3 zKP}A9_g)wmz;)g?HSS`q@3gKNIg$t(X-=0yO;56sn#@1=_3NB-i!Y3O5a~4T&sJC;S-9a$y;M6JRFbFE@SH`#ORV5)y(fR6XDX9vse2Pp5vYgWVmwc|QZ< ziGZwUu%FS@)dd|hN9$pdum?YgfCooM zAHjhR7zS|&Jf1%fV5zvYyu1K-?Lx8`VV_ea4bFsI@R>-|O4xuST@KK>0LjqDoE$=9 zh$91m0p<;%9=H-WPZf(nv7m{e#KB$zqfWNK=m z5UI1wAK;yM@a7lA0L7ZRdJuWGKt=6DKakDBCA9kq!qRr{$L6 z*soXKuh-9HS8GU^X)&ISB6v8eUM+gKFVS5-(EOw0-7INq|AfjtIlhzO)2MMzT3J~o zW-lgX=EL1e-Jb5jWD^B>nBh9}DatE$2lBHzg_)F@ijqtwQUpKdEn2^6Kc(YfXXI3W z37Ted=jO$&@B%t9ZM@(0b`Go*KbKX;aC}&Lyb^b|R()yh`+=@!YFS35D&POy?%iQ+ z?MmedhP(UwcO=39G7a3?Zk>VgdS2fB9EK1934NG-kd!Lyb^+oHvJ*r(fK!x~ zjHR}9dPW}U4nAm}IgaOqk&(j-i@TP}*4Ar{$&av;-6=0}bL~eQkTy-vgQ*K(2$BXb z6ddndyv!pe0@bjs4Wi*0#IMxKT$o|H&u;uPWUQj-!iOhCSJiY9A4y4_xqJ6gL{y@| z&rjZCHHQ>9ID_D1&ReiBWjH-cLZW`wc57Df;aSbmc@jR`OHECCKbpIMkAn`GBqy)8 zv;7n(Gx$WZCAw|mc;D+oNr`F+U^HJC8)t59CB?-(b#yG7qPgNJ#?LgfI^p1W3mA zu3x`S(`OBjRXHlKpddnxz;$okYWexovQHLvRvaGx4%e-#)6M#!OBWs*SDn|eo0eBD zGM?h$rGT}7W*K14!TRX0~tI?8(ww^HN*6Y1h2r1nxnDh_W?U2IgwB_0lv5Z6bjWg{i}B7 zHSF{BVSxwSQ6z&6x^YOO@IVgLYLHiw@6IKMnv-g2EM1r+WWv=(Un(3O!4!Q#L_`Ea zl|UXcgl7OO_nt2znlbo};k9H7r7w`lAs5Uh!6MIVuvKjUb^~E|08=+bzX9Sw!1cBV zdIda3F5r_-0Y*U4}?~Iu(w@dzj?NS3eHeOX6)?5kC9>bL;>^@DQblR z1w~KE{p1~IajcWbKU?PqW1db9i>|7sPCny5ojPlJ>5phfPk*1K zn&*8m&=_qRQCy}Xs^$dMt^vfmE$2To+g`$-Tt40Qzj5jA;rlQt!*o_jv3AKTq7tIG zmN%{t;W*)#E?PLoCN3vw(OcQwI(>XLA|Q5O$*VYVgt;_=oOP<{4;i=z6vN`&V)QwO zed7r<iUXgbh{`n}eiZdPbK&?-Bn?Rt-7(wW+JN%%CkcX= zO7vo(iqGX>l?b1hyQ?*$Bkh9MjcB&%g%B4^VF)Tgz`H0IpSf2R6{;KU7ls(XPWL#%J$+Eg52F3vB)=s+ZfV01O-Rc)yK-k73Xas z>Hz~GaH(;ixTt?(<2>ha@x1J}dVwIlg>zDMB62IHw~a`hi$v83cQgDrvTnqZX? z$t2-@6jkfsU=$1-h*MPGzyL5(O|XaT*;h4AOGydxX}p@@4y(6YWQ}c&`4gZdkbFo# z^C3O{+lcQ)~AU}et&mdpMI9d9w+lQGt*6AftmR}2m?Vu9q%txS(qul8cPG+0P{(M zP866k9~PtAjZ94)ELU_Oei%Y2)Nj8$Ki>>MM@D95ciPQ$N%X?fs=Tv}?%nH`;9U>b z)pNkRJla!@|2e&|@czx4N7d+;SPc3QhLlfG2L(SGgn)-Mq`(IeDHBx5`cLQ1lH>_^ zs1CsjbOe-j^?5!k<5cB5*LSHiLr}z%0(DCyo$F1$Pp(Exmw{xaD|NB!V=G zL_t+khU_R#cD`z$h>VOzh{(tlT?+L66%nZRtDJo02czc?eMHjHYf7Rp69EomBu@xs zU_d}aM+Y7xq!E7-yx6vc4yE>W&y2&vD{Go6;KP0hy@45yeN4Oy)}<3=OQ}G9g%~#f z+|d@27oXnVJbV&(Og2;7s>xEhbv`rG=dI9rB98*MCL4E5`*b_p`ycNC*s}Ggvz5bbJJnvoFzlj;;ONLJZ;(yE&vfJ@e^9g@SPI?RX z%BOA){K>zj?uNIhp#Osh-HYTwe2gs)%}tuOGv1_jug2ca|AV+T|?-lI}8_%TevwQ)q?E^k39v)&a>cFNOYK{)9 zzN!UBM@QRFA~bhH4pKcmz1P#YTR`9qg(NIE@1WWRE)Au(TD-Pe>0bescVCFc1H3hG zr({H(sf&hpR$5k8Gc@b^{nZ#)&ab~J1)!5a$oWDS2HaA;;f4YR9gs(fcJf*wa2D1+ z2k5vhgxDC}j^ukH?Ex?Z+tY^tqy)tj2#i1>%F%kKR1?7i^)4S!SbiieeXCTy2UXU; zy;|-OCdou9f;k7Gh2haoULg4)OL9dQ)BmX?LhwH$iQp^Tvwa{SPz+Nk(3%**2{b;g z?RriJZnOFZt8gzgTY0lKA`&Q1PL*k1qc);h`^vVNKYJVdN&|zx_4n86ehtea z-^mvF~6G!Iu+iH_ZSkJgd&CiK-k0p~hY*^>+9@(Uvf zM))5elF!4hhe zOhnM~XmlH!-YmcLV;w1{pKc5QMFKor0JKpc)h(s>`g8#H^6@FSrMB4u;MWKEY7j$& zmx~?fvQW7PT))PS4q2o6BSknd7uB3kj%oYoH=5f~dHXi-NDCx?@R9eoi#MpyTie?} zctWkqZI}IXRfGuXF$8AA^Kxqn$eKg6K8K=?|7iWDr;ZLKv_bxi;|+n2!>U^ssKTUr z_+mw|oGO{`k>2;W=dCDTx@Dn%K~fxaBXBssgh8))(Cd<*yXnTL>osr`Kel!-m`2Z3 zd)!)`AMSIPw)vPvFQO!LRJ%)h()hyst3``e)bQ$74>&TppL5SzwGyt(P?iLhY@&Ok zGe>0&@3lT0yyu=d5*n>_M7Y=;^1)6-NJ*#+$M==coxrE6H||LZ_pNT(DSxwNReiSc zTrR4cecZdK48QuVguP~OOMNk__ujI`t_9Q%!cIrcNJ$pDv94YV>ue0LsPy#63MD<0 z4-AseT9qF`aJn2r-E9ODGhqILfKYdN5muZl$_P6rIe_|}tq_gil`C3EuKb_;(}ffJ znE0X~%Q?!+mX2H#4U;f4!Zmko#|7N@^cyd3pA{kb#nS#AzSH0<6z7i*DEKq^agX&# zym@dRD`zcpCxAPet=48<`}g6*6!pDrlK#cb5^lEHgHM+}lU?s-Ssu!KJ#~+=TWB=c z@4Jjzv~E+N$)#gaijL0UR?G2w6M zA3RtwWNrrg`TWj8Uj*fSv681xUOg1dpqCzqDsT2a(LLP10hx6g{~LDTV?Sk&iEQYw z;`LE_C4+!D)}3^JqB|BjYYC{cL>F!hK4s+HhWmG zI-x>xBdKorM>`%l1sYX&6nK}O;x~*jx_}lm`p17R&taZz3 zzaadT{~(??nqzVr{z3il-2b>Ug~DyNiH4WA-2DR;w%=qmSOtvG<>LzP|E%HcXN?QT z=EXfA^CaHv(ie{>d6Sr-(E>QHI&8_nM1T0|oRnjF&ar zodR%!1uj)m?ACK|j6vhoJ3qf-*aApPOH+>CT_1mhf&v$qw*efV2oErjL_}e{i`;?$ zdoM7$;H3qAaU&2YEypSp?*^`{bUm=s#|a9`kk>|22pzf8$?!=f33ttaQ;ySkc6z2* zi;)2LB(OES>xBs~C0X26L7|Cn_4PLu#;(ytx2rCVBuwn=?hf>#>}JFPMe`;kBxDO_ z|8E9T?vA^vb%#y<9>-&elD|b*(NIgJV78rDb|BJrw3+^wGw$tla5+$H;C2id>+ki` zYX`ref(6|34{KvhTUW0M$ooWv>4qmTdfsSh8R1eP~92 z)f?&R{8}I}fSkVBJ2G&>+F`Q<24_B39ykwtXs=p!1dqD>x4KhbzEt`vdeMyieo1qj zG^`$6VyX6_c00BCG3E^bNfQ2&M`@`W2CPa#9{T2EK>C;F$9zf3Ar`1#LX z2EPTXmvhR!I+@LnvCs1z)=Pf}L{$-UL>V)9aoY2w-l=@s)-MeoXMr<~Kto(3-@(k( zVzP3OFoBXk{l1)p#(mYcrR^m%CY#4Yk8gq@PAV)t(o0rbR%^m(ZVOu-_%CwGS?w#@ z)|Pc|J}dgdm%yaN6dEDf-*y)MAWoe~Xy9E^E@EycH%w_W=pfzw15f_eL2hH7%Ycjb z6z&SHDU8?m>TpLTHD$(RD?m6wr>Z)yWQTi&vW<;hFrALChEW3Jlce39XO1xg;+}Jpx`={tKYi4_sHk-W=nFEY( zMJM&H7;YB^6w!?mGA9uOLf_r5`X`%76{>3OF-q+Avo))W9rwk2a)atJ>5`7l_bRJJ zpEtRl4L7wmdkc#SmarEoSrWA+KJ~sw&#HE~N?mM132d_BMVU~4$tSNf>V&~qXBsMP z$H7CLBO_B38~tv{%)AK9{{`{r$hSvsDLo$IhilI$oqT;ilTLRf$tS6d>VoSFF2M-@ z6jc8kF+eH#{{1@yt&nadutfW4u_7zqd0+*cg1&Wz@#Y^t6qJ=k!B=4+FwkcJCUfKh z4bJn@ojgJeFh5hWlSW3=)zuYrb>)qW=9Crb03&%%r{)uoixC(Bl8;8bWYAv-)b8s$ zd1gtlI|n4poxgeom1ACkP+|bZoZv^-I6Ur5@IU?nzBkb9A4@a`vK!#r?<}adwW}Ue z%6AQI?!1CR!+fHfNspD4$z)|Iwzh9<0_*yZH-YfC_Vz0v{6ZgTXDGF3OPkWGY(nyy z1WF-6@ZtK@)!p^JP;i^ZfO~j2?sOx5_S?5f6^_fdiHU#X-r6BhQdR>8E6=}5CVaNe z^RkMHg4eFyZ0QpDxjLG&@Na2?bfrs;jamFEIFKZ0`0?Yz&W_hj-NX>(bq@F%*>37 ziwikIULGF%)e&x^BV>b$ARxL^L=PvhXx-nhwq@t;tTr^EY$kGW$@UoMJ8Q+PR%N-k zOG8`J-#30R9|-v}hS&seOoF#|VR;$Mb8SFk`-SpCZC!Ue^=}$Ua(1?4#=2@VstSEf zl~IeS7IJl8gxbCtAR(S~VNVE%EPnE0H z5jECR-W8MU`EZziAgjY=Y6KTSePOzb>Nxc-%sRs2v)(ehKJcES^`O-KtD0`MHG zaV3OX7}TP}Aa@GdStn;^fJ={T6cJ%?%8%-+Yq9=GELcBb2YG-wUWXEl7hrBgwyEA1 zZp^nPH&#-P z7JdHGT$t(f9o-kFF?9m5Rk14C*^f2ui_wLZG7t5;Z-j}i9y)vUsVep?Wzcp>H`%?R zsU)*UB=2UF&cT%*-J<~}#D6cs zFsz7{N3&ukj$X1Zk$+UWgtM3LY~pbDI9n#?hi9UP-*zi#pArTJE4V@3gWW%%aB3{} z*1a7f-JED1p0D@rl7eqpAwY-3%p$EILu_VU$H}WJXyg`g0y@&>ZNoxJ-obRv|D6U? zv{XOL%loCcN94Vi+ws@=KX^{_Z6RF!rH1lOe= zoSK^>jfkLCQ2O}LZ1F);;DeTqxCy-%FJwhUi~Q?+eXl1A$-_C^)P%Nm z@_dwko>icF6MofSE!3zytIJ3L=I`JT$gk2l-Y236&rrkw_#7?;enF-fRdr3=5@q*yh+T`SG zs3}GC+Vgz?!H1GYvy&5|EG49~aB0auIQTQs5#PUh0q~Jv7I0S$L61NVAM-=CaxX8E zk!2JWt(U?PYzvUj1<*Pe>W+4m(Y8fGt|r~2Gi0w(nB4;KCvke7xY)mZIdB<8GdD`P z--i^KKzYb__&t&Wh#p2l$kXdsVVBaq;CQMzUPHJX${>UM;Y$Vx0tml-z-j*Rf$#zN z5^3(Ef`4`O#R|U>k(2lxl1K~d9_S=16_UvOubGPst;y2>Bc}n26NohA-IOpc(Rx`1Bqg{SQ1@osr{L#YbB_}$W5cUgNuNA4RJeMT==38}uKDNJXq3tBt&Z$3p`Q)r=ZVznElUt8O*eNR+6(E2=FT*)$sYVZ&s3$G{cm z(eYbJorpjz3eEUH{jF)i#d(u>pOKnom4y81pXcUA8$D=_r?=|}qIFa!7ESoOiL%bi z(TdUMpslC%-*(|pb;<~eS~xPNdfc&W8ks~V@}dMc1D5JeIu84_zt9{!WSgGR-{Sd` zlhNl;dnrd)~!7GM-qh+*!u#C2JnUbe}?q;uY`Vo#?h! zt(ct16TC`1h7lt515E{wl%vE2f^Y0i*Q8>eSeZnpt2_p>J=?FI=7bC$SYLh zJmM|1ZK$p;;ewCXA_AU{EUoJd`)M)J8`)iU%XItnDfWeBmR9%T+^>MLDxScVAWEj= z!i{o;(V}b?Jga=eL6z|5%Ca`1XZ5wT4)?^VZ{{(H!e3vG~v!Fzlf3*IhyS$rRSE zSM3_-qEI66Mw6udsOvn#z~^y9DlIMNkV_>iP+={>8l9SFkvve#%^eN5g67|zTt-Hh z5`GU2W|GG(F2|XMp~!`VYA-~g9#g{*Sn$+I17;eS#h&fs;NaMA|B(fx6g%fKX3IIl zLvRH%uee_T>2XlcIH7)I;EXR+?*LPlqTh}gsS8IuB{22PK{BtOzb+HAZ{5@HWzmOn zrcF{|GJ=5(&MyNn0rFRMLr!yP$vkMPLnATOwtP$q1?h8gzy!mkrKYB4Eyly4DPfC> z%k(PpE6~x=fe|kpmJ6JqB_U9KKR>_0xgkScNE_dM@E``7Pk?XV0u^{U#EH-h1_4A+ z-oc=-J*a6j9=%>;ZXrUD_v*~}_N#tnup`}JWc&c_(TJBJlI3@6*NNweBj|&wnnOcF z^zf(vC=_RxU>dOv6z3*%t)EnB_Kek_4@8xZMrJ3TAHfbJ_a>b+3yI&mcejZHR0A}f zIWa5#Se>tim+pjRk9VR3hSlU3QRgUG`ky7Xc{0!lGb9GyAZ3`nJncPQd9Wik2X!3y zDI&f~N!s#{4bOZaBxENu^qt@l$*)%>VB58Vx@Vb1=OvR<7XG@Vx>B80&Z*M$g$ z#&loOTc{E{E;{antljUQ^?1=D;(PVTfT0e%Bi569TbM6#gll)4y^PQ*r|XLvOZ`}C zPT_}HvDO324eFC?!PvLg zhiAv%+goFO+w*tI&-?Y>iJsX;dw#Sj6TxgrYqRJzdrrC;oNZM$VgJ^TnMHuc$&c}A{b#AT${4e8$ZWN?3fa7b=&iQgG&IX4%T&ib) zo0e_E!?;t1jxH03svL`bK#!G3I)xm5u&(^=v9FDI`&e0<;G1`L-q)pCu_<&eAcUj! zXQIc4PbAkMpYvk!n`!A5vf=c3oZ>X@mR(17>z4J#fZADGhxoR!N>{-{!)x$2RG#JT z+i=9ZQ_dp3dj0F%(9vP0=F0k`d9MI4EZQAx=z;wcK^p&J-~GF(5h5ml?S>eMzKMym zZu`XZW0zm(uCz7X8<)?+c@H5tgt7;M3WSybSv%2GPW9>=Oh^Ax%BZcKzeH8~p+FXk zk@)+VoRF%6&KL-!s0{5c52<<}b z8C*}OP{shoK@X%s0QX%9lY9bC8@O20)6za!-m(a8$B9MNTKJ#Z>mM(=F<|T9UZv==rILhy^OyW$#)Pq`=Z=-usT@n zk(ABNo8geM0o}~&uwA!GB^c^L;g|mlj7Fd*0rbuyp^oR!Im#16PqpQRWdY){>8fSF=Su@lA~?M*T2! ze*D6(GKguQ*wZ7#1NgKPDVo-(0>2Y@NUC>mf zN`1h*XNv0Y;*lXpiT(uiQ$L;1xpR~^jXR|0do%E6=hulNsJroDoda&-Vc>NpL_V%sXl^j*P@b3`1y_Hs?+%6I%-Frf6xV?YoznW< z`-0Cp!9|CBn?3E;mEmYMp0aBZJ!0^kHg{M(D$n4xpCzuZcRJHuU4CJEK0sA1Nf@)Q zm3Scx^+C#OWtGOw^3Z3G+O`}r6kp+P`$+hW^<9`I8q%uI9iBTTa^X@34idz#>zkVX zq{!$#oK$>lMJ%OshuU41C+o4Co%w8z6YsoD>-^6OPL=rVhb0nEUfBxXu)Cq*XE80e zSy1Eb^OeTv{hoiz#7BaMYa;fQm_^4F;_6H5`!%F>FZrSxSc*t3L>EF*a9unfRv(im z+0)M2UWC^V$?z>l_ah2n_3BSoV~9SehTiy-_=itqS>{LOMN9e-Cl?f&$X!NTmhqQT zcRv4D_#l&Y50#?RKkk-)p^C0 z-n_442p4p?s=~=brFsA+D`@Px%fl0OiI5J7;XtG~H%eTtvl-}l&=0l4cTB>&CXgxf zKn#l^w0r3uyRdNP1g(}+@4Px920pA};|_f|(f(?gd@=VFH!LiSf#@=*KC1f@M;-8H z%R4Ez9+%eAB8Rat*8)cTeX+f&D#5yw-YE335Y_v&bv2B&_4ekrdvS9VR;MiF!m1ag z5jGbXonWj4-zzOZQlY;Ql8K;{tBr%`o9wg-OLMS7Wgp_oqSrx_k(7}!1a23|T?STG zcn0G}v8 z0c2MQfTtCnJ!=Ar!rD``nKEi*u**AC{vqc`{{(s?ghJ2AC}YbExKJiwi^|0Y1qM>y zqVNJj0_+={4}$*qQKPFjvEzDP`@N_uf}Ytdwxs*Yatg{55*rFUB1e0 zg+yiIU&tCbIB$7Sp^7M`@dWZa1xAV{kdpZ(na{bi6zNO8zl zQ6u!;I3stgKvI@E~;o}T?9PO6Li_pESlml zJAj1*Uhts){tTr?t ztGfCmLPyGU^1%(?^#t+pXFds|$!WxEvYh0ttXRy<%yO6aslJyLUxBYwvZf|#vtK!b zk6&((A6sOw4;RY&d;;?Dz=Iw}J?+1sPf$l&5ARJ?D=!eO_gS#di+e9!9(kXjO_G>+ zAO5xx{DmR@x{M6^rY4?+MN+JJ0Bq(6RKjiG*bIc?y&+vMiVsgxzfP<%U|*jucHL^d zqotixKgdJme#~ZRC0c4tMNo`&g{tnq5SD>>WIVP|!w0`ed9b37oU^dE|GMM`=R|fv z!TbmM#AN8z$iwUdyanPRZvhZf46H4vPkrLyf%YJyIsw@H6qFy%fiD}7cz^|cA}bp- zIQP=dE(O5Qu#1Gj3qd+nR2hBEbL}aPp+-W0qV=^s4ofD z2wcbt1!nr`)$)n8t%IeI)Ydw#Ag?vriB%F3lB>|l1B%Cpk1lMMk<^TCoeUUPVL7m$ zf#LG8P!=Ij5`H6)CW{`oz`D>w)fjvMw7_hI?KSKaRIyJp+8)FsZKx_LKa}N##7s>Y zz%B=`n%v$(Nbf=#5tm9h9(Xbk*!k9*feO+E9SSkB!=I|z>3s^uD0Yt{o0%loS^^`4 zT0DfZ14K>eLrq*d{}%op;x6|kppt`}IRcY`M@ni%1qq_2;o%`1J4h?S(`sgY!t=x~ zYlqv%A{+zgfCpX}LD-qkRL&=+6&4mg-I~b9qBM`IhcMcco1z|0%30fFnz<2#`Mw@_ zn{K05v_-Yel`X=!TSK4D-D+?QT6;Acnt4E3;L1Rz5gwRMA8z}64gcl`~6g}9sxn_aBdpAP@6E zWL^-Cx7A)Y(id;vWSl-CK5DsZe-ym<)Ji=6$Pg<{V9q+I;F_znx<6guzd5|nFj3_RIHe=&&a=uxniHB{ek3&b_mWHsV_v6Fr3ER3l&!B?K1&*I2OkAF;rxxTrZCl?WbUBgC{XFTq|6M3k z5Z2KMUW$@Z?)h_4zSC&r#S};CBt3t9GQ7fU6vk(8M4m2>Icd z7*Y@`VV~y5@5ReIr&tN*U<%*%IhOwZ*%yZWSq-+#%(}k#Xl6K=6gA^Ur-a~bKLaH> z03r(kdX3xunyf`$uZRG@Tz!w-b@zS#R0W1(KxMTDQen>yeenoV$<=zFm)n+HBlu=( z>l_mkE1TyD^%q@iluqr6sWq!|dqHI-xk7hdG{q@sMgi-`@4g)KpTACl%L%Yj5vh&*0$2*ARgoY48F9Eu_?PvF+`Ij4VM}C0vx8 z#Zd7q6eQ`OVt@$_4lgRcqSuv($$b+XBzqADCsR9j9(JY>rIC_L6l?{~k}Jd~k!rs+ zO$@J1X3kRA&!41fLs_u#4}E55VQwEP$rRd3+u!eSGOaHy?Jr#i&Kmd;{;GDq-_-PG zT*(cX>$)$jqN0MJA_^iPpdcV1 z-Jqa^bV-ARA|fE&3>5(h>6GqJK%{d33P?9d4(aZ$F~0kqYp(sRv(MW5I^Vgj?;QV{ z*R*i(zRz>tzkK-ckmMRvYWn5muz<~Ipd4psZ{Gj|4@m35A#|Yzw2*+(5rWrf*VXk6 z0qDXZKE%r29$~>ieUPhDmaMi91P`#e5IXUeA>Q#l5V_wW-Wh}mKU`Q7inn{BT~8qO zzKE^?V(a}Wc!>ZdfgsG&cdv{5!~1vl>C>ktdi(ma?3rwzyV{l58tV`F|8>rp;+8E@ z;LgnaQI7|B1K1or!ox{nibamsw|Myoi=$h1)QBtRe@-1T)` zU6;#r_}$TvYax2HhNq7@E?Rh8F4%<8#ZCtP#vE_0o(d#B7#e%Y-+IMO#jQCoV)DLZ znfbRP4gRNeUpu+G`^ek{ldEX`mp{!@45}|~JuDNX`>)v%0$Cl|wZT6>qJ2zW=2$!~ zRQS-QK~ag>oQY&`f5;jh3WKAtimpLi$YNpB(OUtEStD)*mVVCEe!sZsWCQ6&Gy(V z==frMdU_(Y_@H9I_9^yr?+v7bBIdPZ)5+;NpV8Zb*=$e0Tq|)7aYNXaNX>!e1K>XH z6r_J{{rt+A;>M-PiRKdC3w!s zU+9~-R@3lR##mL|%68;Y0I8qjY{-FptJ$aRFkaqJ6>X2Ccq-<~%E34M;-o8<&)aUA zT&G0|jN~p|w|vU6F}HhSZhmgfYVGcAYx0)R_E&dY?4c>hn*XHRfpxC(J-+G2sPTR` z>AV$wf;i=@uq>4fFmnY!8r?#h0GtTZ`(~EZ>Iv^0QLtb8$0o5x_CJ&Iay`?0Rb?}r7$wf%xzT{ z^GvrJxw(xez#^r{q7XhW!W{!Lx}GE`*S&cRdBo@>;mMJx3-p}k919`i7Sxd zY%yoY9R>?HB-S<-_G~`@T!NfjMZCL3^9g>wzA&6YBJla_^EyD#f|Gf*W$({Z9GG_% zpjiX%4A8S|EERNo2i}%7Jg=xI94vS~Fph`7cW47NDPuPKg*gfBQxyLV%ds&%9m=3E zF`p%rZv@dli0}lkv~My8RoVgtY;rH@sPA5X3qUYLXdY0W`DH8n>|Oz8B~MR!>r-{~ekspaD4Rbv~zPU)SE^(qKV$ zaW+^x0oa94+a%sef@KUgUx5U)sbYX=Yuj+3C|6pZ95ya3Av83_Iq6UTdn+labB6gE zMWuGUlM3d?IF@6Ow}bv6x#)P54ad6Xm{h!>x+6}${&3y+>#qki>X)dmcGPFEF)_Gq z+nqaRN0!Mev?NloQ6C3yaUK=#o08@_zs<5t*;_Pxf>sgzp`miry!XVqfBODLMetno z+&K2LpyrqFnKF4PC|gt#{c1>h9X$*+lLfWjYx;HM+dX%If(_#0l~%Kq^OVc}Qub*6 z&F9B$ul%$RW)6TnQ9wr@mxp1Ox%AAZT7PnKUFRl=za-C=q1UYYzK`X@%?mUWuTRsV zseQ6y7-wI^srW6H4Vgdrp*Y|*-? zKlnc=5o{9W>z$q@A6Z}HxtS!`qyds@`lzu@Hzm-6VF5S_A%S}Aw&_L)56QAwPM^GM zeBrA31;U(*Mg5r3#80kq;rdJ6Y1- z6ivUjyKHdsi20C%H*Go4T}RlK%!(vEM8uqDog`hkr@L;U(0G~Q{1k_tLRn2Cog~mI zvTvFL8I6_>*OMEc=_$xBus_ev%A#RoQ?S!T=Z-k(XlO{;*c3FZX(K^#4r5LkKRA1h z3=JW<1+YNDhSFHmcS?e?wXU61MvDd7}{VP{JRVyGc(hh9tYb1kQ^UBe*6Pf z!Vi)Z*MNh~tQ#E}ftsURkW34=GuTQ7foq|sr!Nr>D}J+KeD&c&3Ll@#?Dk9UX*kRO zAL))np^0zVOmTSlmE;0jd95)HahaJqt*tsRVmXuMtuR?wrh3j_TiJO!e91a##3L=`w?+6cHIF6B}AMedN^Wnqm{UZ13`D=$R zo%=YR;0?2+X zsi38kffy0kSyz#g1`6H)khs9lA>FYskU>IEW;dD?wYxG3^%2iZOd4VJE9|y(=!$kT zy@7j4hhc%`S8dxC;bSfzUo7CF5VEa}Z>AdjwCxmZB6I2mGcn11tnoXBU63Q$< zEC4e1%Rmocj}MFF^<$0%WzaxZ!9{$HRYwZw5FliJ8FK(oxMGHTZD3Ff?g~Mnh9ZVN zhj)zxlc4eoegxdu8K!=Y$-ir34u&HgQ3g122{-ww{t7e|J(RTi3xu>3 z^=YkQouhtV6Y_20?sE-`xnnC^VvufG6aS5UBoG)(8FUW64aNi-&qXB7-4Jrk-JJTs*h<;yM!kBpn)&9bGx73{wE!?ZKld14 z6P|OwJv;zBPo>aa*A}INhi4KEoMEjyL0x2XJ4ZWqYkwY&AD%q5Tk`*z00G%2Y%Ht! z)7OW*YeugC7y=LHnf=q{NWlXU6g*5pzSy5GL}RK`ZhIcWJ-s>HPQH^oU!-|k+ULU( z_h9S={`#!ojG@t3^qmCG$#zAnMf6_n8)+_E#g)G}k8q~=d=GX# z)GjStWS3`Y%fm8!{Ct>%Z4_*f8)HJ}G>8NQD>3fA!s)r`S6?&EJsmV}7`jx-7Swwu z%lKICkglPyE0Es#aO-J>mjzYxM>dJXlnlI@%mm5Hr3{x8sWPtH3lO4N+)+}fQFH1O z7ASk8-j0-SPIYG+Gj93_QTwz-VFX_NTejxkQ6k(`QBfOXYY%|fr!hK+wI3(Y9gP<4 z{G>>s{nXLP#!2Gpj{^;}IBZ6e!P0pO@sFQa6v|42dCYkx5BOh_IwVH(1Br6k(8#YO zjg-xqaHe~UUv_@Acl?+$?(83nu^7|~-G!&at2?#BO#2bG>Gs(Nsadn zg>wgvT>`p65pf_GY_>1@U^pRJP)Jx9{B>~)yx_-#RAUL?TtQhw*)UvaN&-4%M3l6) z_69VAMXVSPNMz-nnwjA+Y9NFJq9-utf{=!EW8ew{0|S_E${g2efl2rYvCATE2bpz^ z{^a6jX3XH>fy&Z%X9%u+gQd5K%i3B5Bn3BoQj;5tFjkY*Ff*zEHhY=3B*Q0wwFZL< zXA-&%;lv7}&~k0OwEP2LTv9W$Y2H#W11jCU`l{G{k=Mw`C}#r$YG=4^fztVs3xhA_ z`rzWi#@>dE_Ri|A>#cIM{yv*trAFKAcHsteEv+a1MSN4t^mVIc>YN|#`(6Cd5)tB_hv|;FJGW<7`G5Q8cai0u zms_{nvU~Z(EG4JZbGfcJXynnV!Oa1uGRglw4U2aWVxkBYmt#ey66rKW*FEe&gaw*6f!sH}iXH zO3cM+Vyv%CpRgB9@#$i0&ZU$GwG<-mM><-`D_eGU`}gmE5b^T5Cld3(NIQnG1hqTmjL--~EMbxxahbzjDw4Z0+W za%Z>^44caI?^?XNa&yH9S>LU=*bUqsT!Mu1G_{-2vJx5^Q46>NlMbj&6e~I(1Sm@% z23|5`Q6?e5dkZ#ffS=(3heAJY`2>+qVo84h9-jdr_k#T^eVCx}_ec#ljJd#N!% zE;0K_)hfK-XQ&g@Ate5x=Afyq-8N`y5;LVVt0Craysu=u$?gjHYB!IsCdT-iK zz`#HdXdZefe+71fkfIp+tsR=0_D?IWyk%m=LYh92f%$S{XAIcVrI7Vx~Sa} z&B)!HaSI@$oX<9vXKjid0DS(Jqfh zplaR@f+wElI%}o8_zyey41-#pgQ-TVDA0Ql0X0c$K+HHaqy^(3BMXb#qBi$-!cdsL z3eL3zVP=#9^0v40i;L5tGaBSQIgLnqB4RHAtkl-d&I!9e%g)K!BTyY2d^tltZlaAB zV+=tX;HJbT1?LGiDC}ChW=dmo3#sw&@UVv%oFN!h0K5T}yP~HqurB~PL(&)|@ub2E zG`ye?>NditLvbwpa{n|}py+CEHPzInB#?68LTE1g^B4Wn)T)ZBK88JiAG`rh(A8cB zO~a6}27@WUsV9MeX~bZt^ELcIGz6L7e>uiN6~ZP5fHOk;S4cnssOCX^IbDBUf2xya zdd~!`im)84gSn;$O8ekV0kcd}X=xP9&tkstR$b^YHL|)hzIWzgXRDf3+VqcHTTb zZxu1o>?Kum<@M};Vo_`Mc?#7Uj#bN^fEE^I6+tz}uQcmVwyjf})4FroNR> zl0;?8&UU@vdwOEEo>8UQ#qE2-y9D}Pj}1*TT_>}Wio%~{9f&QNiMwi!Pq$4api+}^ zigX3=c^CrR8Fy6R@<*?uu$u{*P+~Y zFI?EME&E#xzIp<_Nl?o2?Afy#5(S}J$Rk0bnW)|Ok{YloF&mgH)Cy~BTbmW+_&}|; zo0}U5<4UckeV)o$hjZ91sjn4S^I70D4SGKo&^~w#= zQNQ&h`u!qvQRL9EL>*FqWvAC+O$S2dC1580_WgTuSy?ndtJV-gSCPvrx6?&>SQd(< z3UfWB)hhaO9_Ku<*@$=qAvkRQe(UewQ4riqLPjP9Kro=3a5iOMb36e`h`&oo;L(bA zUSRAy)sd0l-VAIEbc_;11lo15D=HU^mzN8`CsYb^IS3x1o@OHtbxZ(98Gx}(O-)LI z!Q;CxpWx)j0m*TYMFE`+jacVFV?Sj*y>|@67UIfsg_BoRP0ch6U2$Y0`O+Ek;E);A zv)@I5#alDk^1t=1u!9xlRh-CO2NUCa9k*WUUuyCTS;v)#-ocl-v`jjFXz*GeRl5J-||kcW&aY$6w!qKy1&a|@_WYs;MhNaf1>Ut{7)_wPhMS1F{X&_ zH)$t<=hneZJ;Bbv8*Pah`>jwiyA)Qi+?^|(GY&M*NkkV1GDSFVIn-agQY-cvISUx( zy(5;)YQ@oQZ#k?&l`p;Vux4Pp3XAa^ZQQ zOPf;en=(mn{Z-?qU}P`mRC&LKf|Cs=e-Ss?!hC*ROU%VcPStVj)BaW~dzy`m9m{RK zO3kKUDHyzF-Ivt(fnv0g`9kwwpMEzP=oHq9l-!~YaODvn{UB;2sG?U%M@sNQJ|88%+S}E>Nt3i z%X<#Bw8JgMDafL}9P8;P1j^EbT5Ua!^$kBCAN1X}6R=gjg?AAoArL!?J)n0c5)e25 zN1mc)R_vSaMHe5^UItpsJ+Nr|erort)NUqUU-z0}mowL{d8qAIRInu$6$Qh=Z@&)`1;Mum-AhBbx-`w=1Bgxrj_1?d)AEQ5?&f>ztH;8mx7=x@Y z=$Vy%{`}617o++xH2qCHt5p$c?*o^$2Xl6`fF096PN;&6Fm!cwJfMNfDk}Mfg;Rqj z32Jh`#!Ou~UUuF>y12oMQtGrx4@{T|6d^8+l#vh<6MtGyQ)YzqydFBrfRpa)3IK^S zR6q5MjEqc9|Fx&yggw>WeAyBpvfB&{pwPyRw3MHv_*i~hKq)$-lGeHzMCv_{Hf)-#H+n-T7hXq75L z6;`%f-OvYYfI1e1XMCq_{z&Z0VD$Lxg|hd1iH@uyZc6;)U-A%jZLi5d=%wff6F(od zo{b(aX;b*}rVmfnjiO%vy2?S^0r|Jj6Y34>AqA}7!I)2vU>MmQ7-`Z9f2`|F39BIi zw^N|$7t55aZ_;+$k#{NO3oO9Kr@v^Q$-#pF#IJptAWE#7Br0z2Or<{IX5)4sxtoD$b*D@>5bTNoChBwU7wYjBzZ>EB z%oTY@qmuYi<=IQ~%p&YcxEuKIZp+L5gl+lT-hD9?VcPf;y!n>ylhYG+8_CA5JbRb0 zGJGlSH#~s^51FCg<~y9D7`r}e64#^`H+qM%&H6^oG`W&1hc%b8fPhZ?<@$HOtlnBb zob$L`i*PXwTl_~{794%bjGH7Az1{bD??1yn;7x|q#&K(AZ1EpOtto5QYK+20+m2tV z`ogR8f;)Hv6ckeN0)8jcUr0UY$WB$3_fHT*{v0894yu`|=r>1ES|cUiXV&tTT1LW( zMGvgDI}LuyMLjtv9Q>&e^+ZmF;7d?j!bLl(n)GDu9Z$aOx96L#T2BzDpv!-HUo)6~=iD@hEv0ig47i1J;5 z)8-4P7mXuwu&@956TDJ`APs><1JJaIi3#^O*{8?`8X0*VPAG^fripBUWV7CKSK^TV z{-^NrL2cM1=L1um5 zVuTOGES^%gX&D&2p(PRu8j{tH{s)^&ksv+(s<-PXyL-9%q_`Yd4UdgYOukHk%IGXO zk-2zzRWvoFfm6S8=MFfK|IE)zLpoQo$|tWM@OUYIZh7&cLq%`EKsg@DU4>V6SxZ*&;e8MyuLK` zAJJ!jXpow2S;w3IWlc8~vF#bsb6Z!%h`eo`xiHkVI1IKl=gs1d^^ILl`YFY8;$!3+ zQVSgpp=_*d<>z+R1qq!g5KkfDD92S@4{ykkh`sMwhF88i3FR=P?N3&dM)l-@qKYLG z_55_QHZV0|7znCJeBy@>C<(;H-Kj*QmuXViJKgrY>G6rFu2!?%>de_Y$Ti+sk|CoQ zKG!@)3k=odOmX6HHwC{n0l-)%=SGJ;_BZRZzbH`STv$6ieqny5itg-O7A`&#JG@k? z4N0z=W!7o85+0O5E!^06m+pJl_ih)2ZXnrkKtUQ98Eq=2v)x*|o5bUHn=v@3iath# zCWDWAL$xC2U81AkKeYgxB~vypM#!1uRc{T^(SirlxAa2J4{ocBJ@v5)vB6-DM=p=2 zESvCe?NXQN;I_&Y{dUhA;Tu(AS02L+M%!M5`ek=mK#}53<=IBUgRnbO`%o30L-0;4CltPnH>e zcZa#vhrhk)_o)lr3j2$!EgJZLiu#aK^4_4MNO9?IzgAXm1CArsj*oS@kvkY?7rT#a zKB4KHUA4iFb61SpKN{+YE>|n?b+*Q~a3-J2 u_Ps8A*2eVUX8M-H!WuSO9`6nB zYMW>QtKFBS$;0j0&fn+n36OM{HO-#MI3!aV?yf7Hl;l^wH*pk3{+oQaJZ~TWKQS(U zAhH4213o@7XwwEBRh9b3u`pO4egKyXOJxy(cDXGhQ2lVnfI>+Xy^296fcEY(G=)PR zHV~J}hYj`rR|%KJ@wXck1{XZV`uZ(!|J!yJJ6v`auBTVj%olCkbOjD1S>#Hr+P(%> zl*GhD3ubisR$N@%V6iy`oSYyhDE32Tya6QztOkfP3rs9uwTdplMegWWx)2Zj6_CWF z1e-`SkNFL_$Sa{Ce3UkZXWwIk0tqeu7s4taPg?XdAd}8V4s6RUKYoa(5re7{f-D|_ z*w$vIb$-XbKN!1G?CuicdJ5Hw9+NvBV*#0Ewn;Y7rpQpxhm(+yApR5pN!~_~#U^+p zvH2qiJ9>btC_=l;W6n^CZdu|QOB1Z$4_SmzMq<_7Ccd+ygOk~O&y7*bjvWZe;zv)E z4TZ*{XP;bK}ssSj93RR0>Cj9tS)2mQ*h_xCZM=DFlpkv&3rC+4km?{jEB@G3EvRa%q z*odR%!@|_7)M@3MsR8cZ=f8%<5tCx7YWKGHUaLSK2Ka4Lv)6P+kG-@G-npD=8V-^i zk}*BKO1z?cw9<|7_)aXhv3vFKuAr&}o3|byoR5dP3oUvQfcn4$kT(iOc02C0ng)ol zD=n77#OE~Tb}3Zh=+{CLcB;4k`#&5i?-OMF`r@i{S*g!@bvAYe)tivKd~K%UNy^o0 zqry}_^y?$NCdHjLIm!r|?M__Bn3MNoyR<;ukIssZS1(^;ym0FMnM>ixN=81U1UE)M zL4(KYf;(^568#LrA-ATw5zk|zeyP@m@DINYxqRp)9-Ys;d+y`aaC&h`XNqSp+)=U| z0w)bVYSbJ$#UBdY�cQoUNLT-;KAaa$*_hP~IQiIUKkvdT(p%cXY5simGo)FneUP z4CB*Hx}#xZUMm4)rTvBNv_;T5ginO z04M}?#305~)*JzaAmU+zQIZKI`sTrU6?3o@J_(6rVUb)k*Zk=WdAamtvF$NfkFN^} zVL{T(S6@GS*{AT5PhnGsdaos-SU%nv26D*I(y{|mw#cAZ%GA{Ny4|T$J6#!pg3{93 zojnE5_SV``Q(xbqGa^p>eNs(v4X?y90ot?u41Qpr7*4#f7}*P}_0X^T_3~C@!04!x zit5$2r9t(k4qUvT!96R+6I&T3WW%?_)hf9}}wX10D1*1c!XTXl`x<>d#Ue z2BqZ#zj{OD6RJK~0D)^Im}>ArHa!KjR$%JUU_mJ3Js~;0G2W=?GEP@VviyAJQn$ko zTJwinR*w}rPRYp$A8aY{B|{)vQ4#->eS~=!|KSaV>{oHSC|o1Vc-gkyA}^2eiDG}u zbx~XIZ%8aGY+Ul;3T{dbi)hH=7IFT5Jiol77I&yGxocwAjl?ed?&n2Zo0*w`BuFSIAdSg@e>Orp1F-eUL-T@W2>~+-#4gx@xP!EuoHjtasR0WG zhYA(Q@|(Xw$VhJpSOeKF1e2TqEdhkfr56@n1)#AQs=bghC%u&FjZ>JnCF{i9HJVB_$)5=09duYkeww z=qpRR$fc7yydEqbm*(Kq_n5u&M(R&C|3-r{yg|VNMuDdv4n;==+w>DDf-s^|8F>N8 z)Gaa6n6Lmii09v^a9!lOJntI>wW6!d4W@A<#RTT_g2il5o;9_sGWu?|aO`{|cYw}k z7oYxvex7MELaqlDfjnC2T0jSiS$>6A6Ep1~(r`#oM8G~li1 z_%$dw4!vbDTb z5mqfYylOS)9g!cQwIRxQpcJm?IjUS?L7{cc+4&~5sE?+Jg33_uhbn^sD%mzWQp>VP zZti><8Y&1hySwR5_R2IVBTKr6jbJ&CXxq3dMo9j6FHjzvD3Zh-kzQYV6ID*4pnszglPQw`ZMO{=J8H5niDEBV(>}3>mY1)^7%fN?EXaLfv{j=n~3cu`OxpQo>r7RZcc$ z$V0fV0_uPSjynSgvH&6g%tgNj)ydsoniKzy{tqTfe;oK>6 z-QU1#=?&j;3BtPRY@8R0a$9cRbBl$Cq55c}#r_9~<`7#oga{WF7kBse{se$KjEL$j zmSFb=TIt6Mi82Ukt)d074 zAix;lXaIF;X6&yuZGjea${HN|M4Jf^4+RK&EyVspp9~5O)Hi}@L3k-7SR||LQ~$(@ zW@3e(9I~!$B^Ik^cC3lKB!8u3{r$ALiaFOdHzU8=`@y@MkIIwff2=;f$(SxwUMaKh zVd&5>-Py9|J}rZN=URzd{*+(AJ|UIg85$U!{Y9;?zr>Kc&legAQ$HzxP1jIzO1l#M z$m^ZQNvc)Hpgm>UiM*ewUu9@)Waw9Lw(3m`iyrqv`sv3VdKKn-DDs8x( z=XbIG7XKpWip7NK`)xuq4ov&&k^oWBP0mO$RvN5WYiEx5x?`ibIr$>TBB(&ww|S07 zmyP*;U8=ZapNfvoJ$TC+99$}NSx#Jz@Frnri4vdVmiabR;Vf~vITT?gyNT_lQOa9C zdWTh?OOE0xjHGg0YkXvsnbro`s;@6zB@o>?D=}x9y<)AiFxUDMo2^}vry8a*FjOZi z=mJr>reVb!u{o5H%`IW=EN>}I2Q;Xz|7h~**zhtLUe%fE9SBj| ztE6rU4;<^_H~w`AloCJSl3Jz6NK5ksT~{zt3)6O4K1Je_Aq2pi2b>L&x()SflmNB^ zoI}Q^XvLKWcKrkNP4OF0;Q*I9>?N?;5JLQog`nZwLquu^xh_bn6Ieo^y6N2c^ECAI zGr{*(c6K%(*;>Zlo(BYg%lGwU{3gsH=)fo(V`f$m^dyGt{d*wX9 zxV%*PAtJ&V2Co&}hO}>M4B*D5AIL*VDF#i*9Yc@Y2_d1CG0Uo|Dx|0&D{DS-l+PNH zY3YK4i<=ky*zSe5vy@hNEDkK{&GuUcH{JgDXlS8LKRXw?YCMa!ONk~SwalK%%#_^r zbA~LvxQfbVemGlX3q+(Xg~S&Z_9o?+CqLDJ?Hc^g;!aNc;#ny521$hy(W=qvY!}?( z9QLdH0gT#U(rbn{O#!rzY?W(bCCzL1H+Oe-=wT8DaSN~^s@JXqR?`Xwqjoq35bPJm zKjY(r?WN(8)!?uMi0T4aIne*rhhJa4gy_{Ot@%F9-z$HgD@n+?0vZ01dwY%$Au+VG zFU;%*Yzv*Ii-QD=jM22DDsK+U;-wAnG7s$FLNyrTT#T?e=qO5#wq5om#F3>yvv-f7~; z>Tp{jsvdz7Dr4**>0y=ibhCXR)shsBD%|b5W4YqG5GhJqw$PG`aZx3xW$MwPgw6Cm z^VOZT$_XJ zxa}p(E>`Y+(S>rRy|)Jd_CJxtKz*{mLzCntb?Y}`uOyo8ThPU#bUEcJaZ8Rh@`wIz zi+VeEqT%SiS2=~|d-_aQK$DK#LXFbh+u=kQMj!RxdZ~cps=u1SHE>V zBy9>*0A7Ekb_Iawl!s&=u_{0&Q$<@$74(OIi%i97dYaLm6+{W$do7pa;h4Asy%M1H z>x2>)DAuDBq)pxQoV*6_#@faPf{8=#CXkPCQC)6U4+T9ipxq4@aonTX;PJp z_~*6ng=X3+n)cT9Px`KELNw|GAm;!Pfl$sEEbgGL0DMziSC+|QO zAu`Zij@^*yfVTy)6r2^lPD?~NOT6qspu5RQF5N8yn7l!l2TIi5)wo=MTE$* zRNqUbdIWaxKy=V8uEMuvHY)SO1Ad_l2}%KYAYg`f_X$Ke!w=+rTv|G6@i;c8rCa`1 zxCJ6HvkzXr(fpen9%tLsvoN6Y3Tl~Viu|(Zfr`wA-4vgg=x4vZz~;=T@pzrcX?Zq&t&dWNA?G#o$gZx%#}ippf^qY+u93fN0P{Z?FD z3|e5}?gNaa>z3_d&+zh>@?CVmN(W`y030b^*RNdzrON{-hA(lORE|3m7Z->3{tgGn z73evErJmnq=j+foI99~f)gvM9>@&pa!v6*(olpYO%i}}B@ExS!;8KDUA2b_Hf7iCMBgQsQ6&b z`=b7r<;2jC76=d~f_~!n0)H)reA6V3_Ya4{v2Ir&`px)gZyf>YKsO$ekkA2q(C|&0 z-KOHEnP2>X92+4v;4{-kn_paH1&9Z1yJ>%$bIw2;;4=zN2 zyYE0QF9`fnm9v@&jEH-%IA4f&gpN=XNI}%?MoT`&oQY@U+MBNY_-F4~61OvZS9MpF zoLBS~o+}A$dSQa6Ex9gC`}T_hMF6E~PMLGnFSNVx+3h-`GUZjfRZ*7<1*W*-d7K34hH(D&^#<=6k1HU(_kucO#HIo{ zA@*QXxwNH{=qt@cfmxgM{R~<8v-YwH=zh^sR#IENiZbtE{uFIkgKJh)m`HE^qe-ES{WB-K(0Yk}M;&+gUWPIbxnS6@MSOLznLTK| zk8Z^-M4$Zeu|YH~;S3YN}JynbDW?#{I{qd-<{5)%4N z@82)=6#Yh@*>3Pl=`e^F2s>0dk!N z9$W~3C;*fnmhrR@&_OrG3Jbyv`JF)?#~^>?W!lru{;Z~ek6*asXn)Yr#ZuYp@DPRg z0lyW+`4-vTqN2)sW+8G)(Une?H4-+%G=JQpC9t-y!qE^p0ICwdl9G~*8yv>J=z^=# zXXy1^4Vr@Kas$(rsnz!7<-u}zFifJ2^X3FCuhV6az#Mz{YKi!LY-ou#D zwWCOrn2+0<+fz8+gv$w^66Z87hghG7cH@xsUBaR|!IXln8(KFC`wP3ZhJC%HJbq65 zLoIGh@JReFf2DBxV=fX**hL6S1r+@n#q zFM>Zr$>kG*-R@iQ);fRYE`Pd!5qc~@>s8Gi){9Q(E79u4vYTY&b#JmTU5QqvBN189l;b&S=+y`6%Z zp08~1av0|1RfmeHr#1xySAQ+LD)Ug>Bk`pIqdum6x)>0+yv6%%(r#^g5) z4G9T>%j`rAAORq6gBD{%Ilm7{R@5iRM{$tq|0K=i$R6 z+VMDtF#~YV3+;~BFcBCd_3z)mU(wdq23SKJLW{(`$3~@ZT>2JB&dF+EvU`)2wMFCG zt?TLoYRzi^Qm?NM2864f5ckFsCnXu4CDtRV`BTegV9XV?iY87@{DTilt-Y0s6JA8R z8McP=b5V-w4l&8ecL7ch{d~acuyQ+l)P1tSt+_YLBtJj;!>6`KFIr1~Wh&uReN=#? zP*x7vI9QhamIJ^#8g<{o!c3zu^PKi@)~iTSgE|-@S4@_cP5w+3l9?^OWt|Vwxc36K zQ1E>{_f~FK98#<`-+c$ZJ^T?bexwWkQwsnI zXppWOIkkE&OZ5qqO08CI62aq+n4J-6%<(BtK-s|^i-a3OKFlU)4)dWF8&LvMtsgOW zaw_SL_Lic4&RnP$%K|)kWXxJTl`i?<6?gYpMh>5^J#T_78YPMBBF3iGuGc>>3%Y{7 zPvt#LWW3s%4o4WEN2^?@clhE6G@j7>@1XGo#@M852YB0EC0&wGvPPmna>II+^@U)L zL!H$py2u&5Qrmz4pR$Il0$T zA0=tS*_$h=y|lFEOkZ`|c8EE83uLE#@(8{RC4VnXKdy0bG91QxkEnY)e;;d$%Mv|) zplhZ*yKzh0sMXeXJ|#yyRJA!3izk&`nsQ~h0d?@@>BC-bY`NGM2_Tr77 zP>I9my0=EhjBAg({TxS5J~%m7`f<2KA(MADt2r?%i!iHjzS=D!Dk?LK4X%Iq{J>z^ z)w4MtanNFX<$D2oX=8709ywtg9*UfSxGg?GK_g4cKoAZ>2+R~10+_7o>gqt$YYfu) z-QSWffJ%c#j}T%%Pd$U918CKW$;r*1TR&FAVW1GTTZ9D;60R%X<>gscv`fO)d9btW z2S^a)(*yn&2YUoLIi3D4Z)4egNE@fCMo>!f^p?d*SR_(47Tq8Vqg^VXIpD%|3?0xxpfb z^nlROiBCWB!gOn5DLo;8#~}(A#;6hn=rio1P?fQN2psZ06hiUa&IN5eE)SSej}$$g zS3rDD;A04h%NaWl)~8WgF0L?u$P6L$>ve~|L$dIE2l|vSNl}%Uo7e8c7Dd01&^s?< zUZrH^NjGI(X_bhk=_a+i;m$T8#Xj9Nt=kN}<_@ghdJVhG`WwVklgE~p{-d8ROZs}o zyZ%zu&4HJl^h?&$Le-L6=3$21^}fx_;pVa0#Yx3m1SuDXo|f7$wm(l}$ZBeljbRB%5~cOf5CRuu}A-)F#OD&eWV`W^RxkC#=@i z#`Q$osl7iyX8JjNH88qI|3)9@4BO?fq&`*KXlf%Qni`?)tyQ|?Dmla*(OCb+dZM6m zd~k;)G4?=_RE2~PWqIK^g}xc}Mw8gpb8 zMooQi@MZMue4kojv$6luu!7>ZjM&RF$5OdV`~mHdPz{Vm3lz@4!kwCw^e!mq5T2Hk#?3>lF1W*Y$zZrgQ9}aI9YMhuh<&#w z%S%fmI`J1~Y&lZ;5T*}sjgR~npz(~v{}yO0eFyr*|6kBp@$?C>^qg>X9El5ba&bZM z5ET^?Qc~%+v756vDdW_fje`jSnKT=Mp_M=717+LV#vD*%Q0%;k$zDV-Yjfe;AC5UW zzL7%0lc>mXNHqsqCcdG#NaaU4z?#?zLNSOSJyQKjQG zmuZ*oLxs@;P++nFh z6%}zfiDpVo$!)1H=YoJ=nce=iO_;wq^dE++FWDJ@OELu%(NB~nZieKxnO>x%=51s0L@e z*-z`9Gu+}$G0l2SvtC~;+1rcBX4o0jxUorb-v9g~jJ$rn%(w64I1;SyR->fRobj*v z!Ru<><<;{~+E;E)2h_Nb9}PV_LT@tMWj%*mJvPm<0JbW9$o!UBMDWgw8vuVKTQZQ1 zW`w)HJN|~Mjo;$!sbJ}N8to5oEk|!5ab!jt?abipugu(;`qE9N^?${CxwyF;9m1&D zJ47U#{mYK{tRRZN|PW0UzcI0Hpo!h$&5scDyy!G_^IVMIkhO z!tiz7R8NL#cQv=`?bQtZ*?`pLu0%^BiuS>#9%*+AqOQ;kmA;YI80qt`O5Q!9e{MAK zT!czE;+>bo`()uX|B{uC3z0OhWk0U6+gX^J)

    hP@a&khtwrO4HCTZ;f~a|=8)t_d0RbC(@K4#}#jo~y1FJa>^4>QpY*$7WSi0qh8hb1B$*z?cI3 zlYE@=?^OtvhR$yYEk#nRVMF>3^CAGy-}3X5a#ovbWQ}*FQDqW-uzq9K0 znOoN*MIr9z#^$Vv!L!bU6iHIan@?45+iYbD{_r{uKSQu_?tIhFU%%e{@W!VLe{#vk zc5QvI;j8BP4_?>qJ6JHe9XeZWOmRLDcCfZKgkc`~Ar%!B-^RpH0K1G9`i-II=q8ep zLCv(5NxaOA0&_Lz%TCwu15hmyln}G1Y*ctV{L5&>TAQg z?-1I!f0o(EtnFgvG<01+h+TeB z5zy%i!TmV*e;zCe_WNAi+z71%D9P3T0B~bG>;A^=N7B+%Fh1_a_ zfgmai4G9s0Z2`zQ_>|Vb@?9qL|8V!7QB|&AmajPy6qKA%5Rk0opb{mB2#OMw93+Ee zHmD>eh=_nB5y=7)B_laX&S^`|AQ?70^m_lNs&7^Gsk&X&-FNi3emP@sR0Q^Z-u28m z*Kh8a+dvE6mDhlyf~5yhhnc{UgDed^ZnN{(uZEf$U#Nk29~-L-73~i)G-v(tPMd)2 zvjE|xok0T)ZEYp^dSY3B!WHdFm8Yok$Lkgmc&$Seyl_6n#==>H)Q3w>fu3RZrG|H2 z4n(x`+1+o0c0<;71V*@vlHr|S-|10uKG_|&Lxpd#vlO(vJN|BDd8B(wQq8j0vSmD$ zizv>cOQ6rN-i+0}s#m48?2<8eewq@VGX6BVp>*J)y0k=}!}D$8{q>TkIa*rzJor1g zR!yK6le5AcZ2W37`LJO^_t2soDAP=Y~iLBG3NKuLKg=JLG31IQCKV=sEMc z(=~_a36X4oxwW7YiSuY!wA=CCw!@Nk3`O3HfRe~3#q+4Z`8dYk@-5lrDE1M zW6@4KgfN3Thi(a#kS_1Kk1Wt4782wPmm^=;fb2X#LjA^vGgV9sd&7d$1J{(qy>y@v zf#irODA=67-TE=PSn4b4n3I%d#(YDbG6t^T>}glWKcW}9#m}A@I_>kH7h`A0|Iewh ze&|u;=`v@$$0T|#Hz$ao2Uyo z_e+@W2feioF_av*ybtzfO-WWsqm|R0Ki{E3|1g_0>Vg-z>fp{>P{lcz&^;|ew@vhk zCjJNEzozw4&87t|&6%<#NVV*pG~`i@4T_vd#&sk??{&G$dg@!yeMA{3l0tw^>`trs%oM(wjVFDL7S>7 z-sE$%b82Q=0#5wt)ylinYwHB0{^sYv3(kQ7Usuqz1XzrE#E^hu1!POS=_&qIE!)!zQW2&VX6&p$P%TL}=+ z^iYxZgc=1%f>i$!{^+5EC;eg&pOk}hrNgjyiK~}xA?MBNy3J$Nyr~gFCXeobS$_lp z?THQDw|6{LB|1$zg-PY2Ms@x)tdL8ajPHKpPRvcLZKZ9IZ7nDB_4d@J>k| zJG?K*;ktH&<`WGfxr|&(Nhv#d1O%AqgJ^q>88UF~H5;{9qT1~724=0vZT+nV=tqgr zzICO6>`DsRjDTDDKNM!yrsJzpBUf@&eB5F4lO0=k>_~NayNFHh5jN> zZs|Kbq32<%9eODg{uktl-ra2BqcXo`a_8!MvLNjp%Edq`%)ouH=(;rf`cjG`+h%l0 zDm`8J_bhMX=C>8fJM~TvG~-w=E(Uf9^wi?Rc0b@BEA8}K9{EcXZ%TQmT=Lv02`?`R z!3MkWm666E?B~@zkL}~1*^1;3e$0C$Kpg6QzLZ>cMfn$lFdgYPYL}92lMx;JO19Ec z@rG8{{Z0dN-mUC?p>kZ~3sElz6Ti)Mc=#aK!~TZEQ5vuCKvFO4Vpek9=6U|UAH|$? z`OB82tl>{{vXuXgd_73U!y1XUu11Ay*4J1@p_C#*L+zMR0ry>LvP`ZX<0eIe zNBY>9m;R?}n50uYy~HVXjvp@$+nx=}n`d}Z3bzyFR$doC3izrdvbmKNq%Pju+Z${& zJ3Q76OnYSU0*xY6W-=@<TIq~t^%j%4NxEpZ~ z*b&B@oSN&B3=AkJ)Z#TZ_1Dn#WC&M;_NQ2^ryLH+M2*^3hd}$o?(QxKr~cur3hzIr zx~j7?J!J${IO0R$NlD4`{-kIXrPfCR0=q}v(BD6KQrhlt_iX#GN-8k;udvz7HHN5B zOYC0_4nF0uGBh493{nx)YDHqP>!ptD7wPHAVZ4Jlxt}IyQpvt&6z1tzi5kDqiz}iB zj6QMUtmrT4*FJG+;oV*IAL?cI(9vxF z51*lt7V>W|>e*YIev^=J^3x~x?Zsp}*}ExZmR61*HtD6kHK_wxe(C<6+Oe^? zzvOStC0F>V!x02sUI(;8lDg;Pzz+dwDG_X%}D_qrA|8!u| z3h-XhxU>$eiXN_KU|>#~a6k%K#`aj@FjzS-8G>x)H7>3?$XjDD?(k}24DuXjCQN@= zw}3yX(NqQ2nZw$cD1P@xHNVI1a~?f+C*Ektz5V|rTHbZ2qmXVS&5(vayM0g7fP3+` z9AWN_=@BQ);_uy$v~cS6Yh{HCDTd!Qj`x4-BrfHUUa-xRTXyY~_Nx#3ej6g!U6?$~ zgyfpu*b&k^(hkn2XI${tULfsLK+7}2k*y-3YTj)9nK{?@!wyIAzSD`3)N^SnR(e_a z=)NS*yf=x72h{M}pp)S(7>gy91fd_&)!D(*|0-<$f}VNAtFT+3|IWabWc0qbW}0SJ zz5Vd-JN=QIt(_TS^5;k>e&^M_V$LN^`tz7sPT?&gUQ-eC-;q&%wKTJI+S2GyddO(6 zfD~eD-!Sfja6}ac{gVQs-pN#R z8grJO%K-f-%f@+!d5^vQ=g_*$%aMc3ECaPS-kMi6pZ(~|(P>Jkh~0GSACK@ZE4wbj zI4bl!oqc(HEC$m{+E-Luc$U%FJUAwl$E(oU{Jm04AL_{6%PyKz6>EzPDft2&mnbu_ zc)5EN<(^G{TyihcwYE(@wYi7*0>Zsg=0UPo@Tn5MX<_@D0{Tu2n~H58+Sa9y*^ZYX z{U7XV3H9wNdCSEBwip*)L{dZ0mk1yAymlZ>;w?#rR8k?OP2q}v_V${Zo6o>98%zwZ zHyMwxvp1-hYUS!%3+u93rDk#-*Bh@|^^c8l6L>ykZ~*RHW|rV+NP)+m4YEZ7TL~QS zeyOj2{gJ}YtGFosCB(>CSgpg`E;|NgW-hCI+U6D&4S1(`iC0Zi^XvTwWU$;!WMUG9 zZ0@+Z86@Z0${88uDY=0s=0gXG@*^GbI_J!v8f_8D1mx5;H-|1P$bl!|BxRQWKbs++ zQvHuJ*;xaH-Zfq%UEFI)q~U*4djjCVHdT)g>QKJ7Ifr64Xzax zjWYw`9}oaY;UFx~9Dx$DP^kEV%}z05tu$2MDmtJWrDs71-R8P}Jy>|c^*Aur;Y=Nk zKfS#=3d{9^0AG)h@)84&!utwln0{0G?55;u;UFr0*ad?6AlIHDB7*Xp=-aPONrva& z{l7AGUf$-7O_u!3LLHMl|34w`+sEorFMYLbw*C$KCa`vLm2gW6$tDerKQ1kh z{as^`ZJPq&tBTIeYx|i#N10v1j-;2|zr8Z^_zUjKT`%s-+^pOJXq)nALt4wx$!>?u zN1`s?RWjYea@H^QOuXL7^^9D>hGID4KkfCr8@x3hBBoTu%WS4bGSK_&VXdl!>i2|a z<2IFVSXvTA`E>XLmSalxKw3$t9ZHEYujU60dv0|%Y?*~q^&2Dr7{oiAq#P_rJMD|bkI zAs@ZRTDsZd;3O|6HwnHl0Q4bY0tbogY#_6bda`_;>&BE&wTl)WmT3OoI$&s-va#hQ zfYAg{fdZgGLL9sB+JyhWfY76Z%3`7O8HGVdkFQmmTR}P#VnV!?+hovx2YU=a+4@|v z2TmizxJwTdO*}z=gD%g=j%>7{jTBwbSF)h5FR@r#eql!6j77b)!h$U_ZS5rv&NpQ) zy9&=H)v(*WDnLYh_nE2Tp3i?RM{c>J_ORdi#*M=7#SDWR*z|`R`C!`JPIGHD762`d zQRl#+6o$NCJc|~}%Jd^6Y?ci>8yk}Vc?B4o^aqTK$v>qpm-WOSwE^YLjey%jR?MJ9 z;>c4d^7DjL2jbU(S%N?~k#aM*5RY{GQg~xG6slm@5>l`urWSY&k)rSJJ4-gX+IEHTdzV}QA4m-2S|%Hs0Lq`I!{CMz`!8)k*gLaEgA3i7?-w& z{Z@d1)u(@HF8l!mEhKWml)C_{o(O{}Or>h=9l^eJudr90;B1P%N`{2Gn&6}as`n`p zx--{asKQR1^hSPcOw7gE?((nyoCRQH?1VD0m!O&g^N~)%^}F&`E+pW8Pffi9#u9+Y zm?b3QfY?-_S{BKrRh%{I;{ZOd{pf*$*+0pJ&CU0#E^Kf?%^@ssbH?kC3J_LWaVC(x zT4j(N3db!hV%Yx}f0=UQ#1}`Kqs<=-q%lRcfD$;@x}|lbA@8Y?H)_ z{O|ee#(Ak<&HgKcZsB4!{Cn`!G$CI4HpGrl>uPW~#OMh0+ps<<2rd-vg+vv(S9uPt z-k$YZpp&dCYDLw~lMiBq!IF}@gU*aRD&;;&1SR4Uwdhe7NyF)@KeaU4}_xQ0i>kz zoRLgqi@&D}LY@?mogwCax;CbX+1;6%X)!zE_o}U>B?zue57gBmYJU{cI*>jfY(7QB zHYN!V)O`iw(q>+BNdH{f})%74|oI-XY)-%;Tv{G^1B z5su;3wukGs4=l(bl>y+|d0w1jiO%-AeH6-tc@}+%BEb}oy$(tG2TOyw6|nIJE)cAz zhh%3XP=v5T{2nc%Gj#GVD#-V~tvIXt`u zB2a(!st=F#gM!co={g8H*Eck%KY222nq&=H+LfzUIb1F}v;R$8y4SWm5GWhKALb*q z^qIjl!(u&6JK~1tqU%D302~G839KEz11AL*1rRQ1xn|hhoavoAcaWuVAX-2yBt8DG z%^Y}g0oVh!vN^!lMiAkVN)Pp^Jcf3UUt+(8L{hIRe=NL<{-?w7HRk5O6IoB(cO!Lq zrU@bLItW`@%X4SYKJd^JcdweiYpu;)>fv}2S8|$LhFjiK=;fUFFIdmJJ;36T`MmjU z$E&k>jP={;j0~2dLYPxn7hS%=EUqV%sHDN7X`AXi-Q3KXCnQ=9VgGxp`xb zcm?T|Hha!QI~D+c)Owp(M9_jpTpqKdB>rR6_PSL7k=(7z8uawupKe4};-|YPcJLW3 zAl1O|PcEi^3+GWVe+mpj|AYj;E-F1B^%%#+eT1Axvcmcmrdu1t^yK7}6udkS^}%hF zc5-7-H~{Vxa55KzvQh9&03WV3`QphFWlG4_%0B=_3y;7?g)d3@R+pOZ1d?V)+EOSe zC?o|?M|~$=w{kwvct*Y$+bcSc3Uy5GEDDM%5_~%pl=mT?Lk61?ZC-8O+VL@ieuA1l zrFx^2_7chLO^VaDTw@+MRd0T&ya_oYf(Fhf}s%n;P zuO&vgoz^3!qPVf|3XMiGKl<@a+2lNao}g3eEEhFT$WdIDr9N@W*uc~$<^ae4YvF{k z{~KDV#ASDUwCJH%hhyTbcZcZlUqLOwH9YMkT;Z1)cwH}evC#wsk?3JuKCQfNczXVg z@a~#i`EkS37cdX6QBoK%t^cZEXfs>LOnSXaT!#~NS*cy&%NKONwz-~xWsp1EKl#JW z<~8Fvb~eFN=;* z$H&QSxvcQvE*V-Td=tZwuky(Ee=Ta_J*8ysm#JOcv9=BtfAPA`#1L8m>$Z_7!Cn^^7xrt{0v+c&@<5gaTVY`^H*v%ZC~}2AeiT6ZolPbBC&cr?ZHSNu zQf8vvlq|~wi+-cQwWg3mVOV0S?{{X=*Y6CEeyno8i?hhXb-$^;i4Yt+dL=II%bz_0 z`R;Cw&r&E~%7Y+y1dkAS2{E5OEq%a4g|vwM)CqXTESW8IC6T7A5EE++SC0C69m;~s zLdu0hTDP1BrlSc!A5Vm??E7$n&u@1|;Klf8VygCq=jD|AhJ6V~BnGi36v_QImLD!}~MclOm#S9hP%=?avYALWO>Vf_=%! zNv}d1Nw&9lnzcXp;RJVGc{VRuZZI=*Yf<0gt=vU(TgH~gbaxN=e!C;TRb=`p=LU1u z2j|T+vvh@@{zqO)lQ#YwyH%H+^3^ZZO7(_;wfqF9!lMb%imE&*socZ+`!>uRV(yMZHMeo7ujCo9@J^}zIx~zx$Zrx&`@(uiSY2vx_DJ3n>ZtvmHsxv zod+XlO+84Ap-n4`CvKV`|9U~{=*=Hr^D5&v2Pkrc`ZVqvD9BM#I?*gG7r6BaUQq~b zn94aR=QEWme~xyE<9T|T+(b?Gz{h)&s@ue_R<0Lp&CHm!Cc@FucjPy6`MY25Fy8Xf zlTI!7SbF~S@an`4Yu)2d_Mh@UQrw=DO7V0oc-@(+_iViRJ3A*Ds0oN4fROn>Zn43^ z6a)ySjj-lfP8@6i1AK7?l$~j~E+Hz+$jAr}jiT1I>2o;0jT6B-2M)5>!{qNg6)vJqRY>$ z#DiNR)SOG}8qkEB@88!4Q{~Z^%|F)FWB4JBnj*waTm_BaS-pqW%i9R$meE$HK@bwD6jQ{mxPn+LG!EPaNX3Se# ztJ2cVDFaC4rsjXBSl0^(0f>K*4Yo8jHRD)>_q=S-9X(IHjT${0fe&G4PaXEQO=N1i z^8(qXFtId%Sr8OqqJhE7)decw0!N~%D&-jATsq+&cg4~eY0JYhG*Gp5tPH%l2O^?! zyXnM14TjLKeFv-?%y^=t$F7Vrsxg?Zwsv58fhtIG``p&t!{3t^a~)mbO$0*>pP--y zuN0*6tUlO63>{Lf2rC~p@XBzz@_0!}Gsy68ygJ=kSHpaaJx!60js@Ra9K5l&ht!I~ z%HBmXvYxbUHG?jQ6{cL*E89*ee~h%zz@p#4zyLz*f*%^lW6mUKJ;EU$X7G^Pz~x{t z1rV?o0r3mNlOr@JoI7_8lD3}DTlWBwy9t4GdkFHw2jNro*R28rfqexoxaB=Gl%mXu{m@g)G9 z6t*#;uOlKdvH@ye+F@`;j1oA+fCm8IgF1iWX~eQW>CBMmLRl^3qBjNhwK)7Cq~aBV zg|I@|lf3EDgxgIlTTsxO?>u&_>`Q4`hKp^O^-b$c%Is?Xs`dRZw%^one(unZd`ZCX zk&9QvS{Pjq#l(E46#Ndkdvjwdr+8=-HyRCFNk0g#*wz&`@^+oSw$AIS_lRCv@{ru! z-+lY-LcqF#S83@qn{ahW#_n{{*xMTA(-Hh!uR`ZS@7YItd@?e&dYu%)d@kc9-sn&R zm8V}9)_IO`siR-rD_?t>kS}SnpSx$aKr2(~{F`F-u&rEWTK_Rgk!B`em%6;RNHkyNWc6O+z zf7hd5%IvR{t3LAaki@8GW8GrOeEm`r(|N9yVAbz81>`xS1Ysmt`7}!7 zJ#psDmE_Dfj9fMr*y=;~i?OvV`&Li+JZyE%)LM3UdP^v)go>$_mLqnuwofET31nrt zc6%(4M;HL@2(Gw+08IGu<>l>HYhITY;1zgRS$P`r1$t$KNmA5PI8$J<35FGI zT@YNBV8~E@*J2EDP$LVAAlP+d)o_`bZDZeR#$%zY$tmI>*PqIG9o%uXm&_ zfZ!(+z><;6yVBD+_bV0&ZzF***!_O2qtiQ))6;2VV_#C*5FWSU{~+W3j=eL|4IrLl zfPSlzDp23wr~5TuAf&bRMrLLQ+=6cdoa>aJpn|pz*Nq-4>+BOBqGK{b--^3!y4@nB zj56EU((zA85zoljO3&)WUyKWAx*;JkrG9NB@C`;6M|j7~`#!$XALgJV5Mj8%Mrdeg z0IF3wO6yUohJFH+NO6Ar`0CH5J4xPt;K26y7~A9mRY#OTS>|c6Cz=3VPQ&GK!@5XcmwNUT3TBl zL_vZs9+r4uDCfUPyc&t+x+L)CZS{%->bVswT=MXclfR4WrQ9xmkg+m{+hu}N3k*?6 zoSgcJ><~c9JWNd5b$ee4UiV>?`JkqQcg{!zX%A5+wqSc(5CF2UdNB-<5F_;}1E6Bp zj*ij-gbGAPY5<}^@O~10)^OpqhowJ&4j+L?@&!ynKRQmk1boh^Nux z(>~rf%rmGMfG-2Vtb+_&`})>rwXj@gIkt$6*(ie-m5rvBtq}MI&o(7F`8aIxJbw-+ z>2vkNmW~cKSm%c?SVBS~8aQ63Ni!Rz6TNn+VXOK#ZVzn+d-_Nv-ZZ_nxj7RT*ISr6 znIZoN;tLWWg)s6Q{Kat5H3uY(Rbc}<-UHUqerz-~G{9LG06O70KTE7PoSO(WZFg-v z3;!;sd~>-K)r+$14U=4~K6%1P&MfQQ>KPp(ZlVl{2SMQ>7f+d<(qzc4=cy<0BKOK( zb78$lp%Je=p1gm%NPPNv&y(7!bIDF-9JdKXwU*JBr;PLNM}%Fpxofi>R=OvvF`P8B ztM-F~;39gw(K6wx&i1p5!?~t{mV|3X>RUG2HicbIDM77uk-BIHdKi*u)&`Cah%|_+ z@7v7FC6t}#Jgy^u=hW1=A$9;WNl2*fn6>15erGtTo~=2xNg({A@}l|LPw)14UIwCe zVgS;3T(4d$F*uOS+)aANfed)Qen;ny|KfL4r!Hw9Yv<#;>AEr!p!$xOxRkh(EyD3; z{|)Ct!C&LA%8G@wXX#a(9odu1G9&Dl4{9cY9jo>RR!RI`Mnul8ls~E6H=wsKkyY*d0Rs;Y@+0!{k7x+OLKMtV%J$BcdpJd4mcV7`5A<+` z$HhTw`cbg{GpllQjz~(%tgI?z;>1N>g9g&qm~8-yf5&3JQN(=n@9A0p{)4o!aV}JS zWYpv6>gul#QE6k6nCXG+X*y}CkhUmk7BP3qBY+0=v5e{(UzY)W1~_t`K0UwHlA%$3 zRdiStw=%K%S;*%0JiV?9?GJBYUdtF5{9ygEyu_27W)>LH8*PJgc1oAh7r1d_kS6U; z611@Y4o}I|K>H^EQfc(Yf-n|!RwD|TsV>GXSo$1Lg3k&!aB#l)%{17&4p zU>+F5=FJiTT7V!nyH&+mLo`~d-BF0 zi1op+<$&OY!1zJ74-nvxMhP_Axg4w=yKyt4aUL|i__4xZ(08#_`&Mo z6|$g_5@+e@&V=nm@P^hl7i!kFlXJGrj`}8ie&&|BpYyWsyCx4$b>M@fD>tu4I(Eh% z*KB$z=gD~6$eY!I-{Py9N|Qt2Ro~d-&JJ2?Pie%jjq1oBRNw5h8=$BXEBf^E=E-3E zo;keF{@@FQ-Lf{#n=O9c?0%Tu65!LT$!;iMM1`NUgL_qAOE9x>)SNn#|0Dkrwz4US z@luWbBwM1ZQ+6lRrR6Rb3kUD|zsT2L@sw4C3)Vd~2~{f#E_Dm_=+fAX7iR~GFM9LE zEhHvW<5tbDS6mknoDP|^l`iyt>lkC3qCZCn2yXWC0?q_w*nWy@T z>g@>~w-e9LEI0(H|JYQo?TkP*){eex?o({Uik!M+XM-1wpG|vCo=+8<|R3K>N`Pa zwhZ#r`-20GI>YnMiL#tC-`d66*pm}$dTSrtFHz$#ijQ*}HoL~vX(q+*mXnn=4KKf? z`9|XY@z3zCE1YmvSqNbCZUzl^be4JvupLbhNU*IsI29!OQkG@WseUB+OJyZJxZUss zL>jsPPJ*yL-o0b`n#&3=c$o}MGU#t#&aDo8kTHWS8202KHzI39S-!PJpEkJr@`}+Fs0~-_aq?j`msj0D7*vqGXU+8F$iBeP|6SN+Aa*9}~ zuNe(#O?A#lnlMEt!rp_MH^+@T8PvwcQu#b~AI^1fH`Fl4Em{7;s{+zybLLoWoiIMl zyJ{_RcQ3t{;73d=WCu+wVBBTo=FTsu|JtbG%B=w1=qlX$5Khg)!v6g&^uRzP>&?TvwcDm@2D@UVj9d8BBf&a*fNnf`EYAZM9qcGarc!T)lFw1 zllnAP^XukUT;yyLBaA@2q1mG07}jPf3@hg=oDMhrRu<+@;kwX-WYV3zDx- zgxyxTdw};%od$!Zy0PA_wCbZtjP#zUs13soQPi^KK?$GQLiAc~Pgfu>YmSKterRd%@Lk7@D4- zs<~os!sE`ti4(7PIiMkt>CbCss((oD$Nzoly^86WZ?M|kOj~+Yj zQmXN)XQin^(dFJ=`3%Q|PVoJi^}Tbre_4J&J{bSvMx8K;0(zlEBc6ktO>neek7pf= z>Xgkh9Qvc*nf5kDR5~=V9TT36nK>*1m8e3j&~_>Gu%>Y+dU~2Y@1`e_JSL2A=nU_D zNS<mH9K0A7qHBrxcV6 zm7Pb;+G?eX==zF0QR!}EIdYDW`;Ino`~paP&!(QT4&r(xgQ}q9OZlYCVUF7wdrwT_4w0&qo|iVwv-PtR=CdX3lh1$ zIAXIk7T6N_?B~$YFx5kITkQ40Jc z+1I3YPouNxc@K$ZA7HPum6y3JqRPtICms!8S=S%rRvApk>xoi{`=aCS&34WUo=7>o zq>hHO%t%2*myOM6^9G%&$BWW$ki?G_ww;6A734Eq91!sX)))}t46=e86*ag>_6JT% zq!|i+;6ZQR)J{xPwH3~_$Gn0DaM<<|OC!KlP2=(7=0c0!$N9!*f!~0P$oI$j|6Zua zEb@8Lsf_iWkJq=K@RfmNEo%8%ggA6oAH`4bitMxX|14y3rrbprd! z>Z_yu^v{%1$Y86kh+u#hym_;q1E(4*WIt7Z7My^-PS_x2R2rtG$;tQlar|&Mh#_c& z@e$@N1$R%ETbK@>aprI%eFxFwi*~|B#welm~4?dc(X9m)8ftR~?l>=rvKiX&`AlDKK5sm9kz$=VA*hQKV&_$4Ku< zFJ__`E`eXRbR!Iar!YvQR*GOEXsObe|zx4?ER3fp`x%iuP%SUXI(QVLfA9P; zg*vU=X|Fh+EjQ;Q2wwYnQxt8ULWOOoySctPZ8RY1?uv7jvsH zoY3}t{qc7Q(y}8LEJ$P=1r8tZhBsl`23A4u!V#fhZM`Z<{G625`hc=$Fx$)2%na#z zm1SXu?RMz&*_qN&QaT4XCWN$u6dXcfS_Isz_A;fE(NsD*7mJJQq9qPAqr`QwI0IIW zyX}D&v+Q4lHuO6mTU&FXu#1A~a{z)uA_F+=T_YoJ64CMTU&F&kgTgw0j(c%RNu7@o zOgj;79yH=*7|@%C=( zlfQ0cWcBHj%>(^}J8}&i4vy&g-PP;yv%;qBX294uMj5qHQWDH6g(qTWM)m&DFfdSi z@uC=Wuyb`NH1wi~coT3IKe%nR+B?R6b;RW59Y%}ou!9)4vH2-1Lc=(01i|vajSv+4 zOy?$0l^C@#@Wj19QOuywq$vAED_S`B7-^VmeBI&U;WRXuz#Y_mqh)kdC)pDYU^XG4 zkB|UkiB#%e{UN-$z9#@`lt_&n37vVUY`h6j3>u}@Ba$?nIIEy+*#-wrE8d8TV zg3v;ZplYDSsIR|&M~UE3ZN6OXyod%!B=A%v!3KpnEdq8;pFD}_m?pP|!$=Rc7b^`Y z5|GL3;FE%AE+5upODaYf?+=449cb&=HJ55c7e~T)&=K;+Wx_2@Lkt=_kdYu+A?!sf z0a7Q;;4wmzC8FhPYiDWK^ z4hgv!Buhjq6b#LM7ccd>VY~IPBV*1%C^&)P5AiuKX@dHxc=ztzuvS#{>b&<(gb)r& zhBR(y36~Em_B`rMb??T|P9KKyVj3}h2I&eDEDFpT=FWq{i}+?{>q#&CO0s*m_Kb1I zFXP`|d@tEvPsQF6t-}kcSYeOtsr|cB|753C3}xePV;td^+pmT#)dpAYty=3GT3o_n zKuYs_+R>XXzuma^4FB}h7`L~6PA*N>U}%^8#h-t(wyW`ipXO~s9xkK$aqeW3nIEb1 z{8GBqlWdEpLmK`Bca(n>{|BAZHtJQ?6>T@x{ATYfZxijLcV?`Qys~+b>Cvh{JKoaS zUPmOC^>SG!M{p@t9sJT%&XhMvS^kFR7DKEDl7YL=mJHuSHos}FSJAdf52j?O-eGc5 zyGAW2$hT(pt><{Z^-ON7LU-y<)7QkO=1widE+oI>V9454cc~)Tn;-YM!f5@=NJgIr zMMLrPWzB@FsK}(5Roh3dGA|~+pM4)}n?lVnK}L#SA4ps;ODMq(^ZLwI)kQmDJwAzz zh?kP)f3NKrI-$dA^wmKWW}23q((K9k5T9EDnVRw?<-=Z;fvV3gVsG+AR|@A8q|LZT zS&W@GphaAW7R*EUV`_{s{SPY^#tX(Nl}z?*haiUTdbs+-4Y*L$!Zvi^aRWQHtz%2`<}U-WC4H+AfIIp#w(&Zof&InV6BXvbJDG0{vj_ zx*VGZ^^~P0&x0p1@+KxU47D%qQtC~X;bE(V1@lusVHMtJ6NvLFGBU3qv1=kWJDUOOp`nH78Q$Fa z3va{BRnfn!$wEUXJr7c{%GeqCnCMtp-!k-X!TT28K+J$2g{(a?&{W|u%K;zptE0n9 zn1aG>Ncg&qRk+?u!oNTWqxu5G6N3mtg}MwVt;GFv;Wq8vnhZAi1b066j-gyDLw!O?t25 zCd?VfVLr?EJSJg>>(e{*)&&v96JBY0WsnFFK;b>1oMv%h;TF&>XldbTq0{FLo;es* ztlZqhU}}J;)Kv2-bEDHN(W?=BTvrH34@3S9?1e7#I_N})B1C&w~SF zQ=3I}knKLCN)cB+i!rP-9=m45+sb#5yE?WG(J;~})a#XcA2d*F&z0d*1T!$`WDFt;#_gFUjdlS(eM55NErYf- z{mt&j?TQY`C=~)jOQ*5VS%(Zu;oFVvL=IYWEVd7ELTk3)3a|{ChwuI$UqTA>(muCu z<@Xg798ZTv&{^c#D!}Ffe@FO;W0B?bz;vI~^Ow(I|7+o93~_P(RPuK_imxg81^YB_ zJF)ed=jRWYoSfLJJ4ts<;|k^mtYdU*u!Wk}i9)E1E&O6jEhn4n#>g~YiISLr#QRslN#!C*!6O@`(afB#%s`h+zBuqJbziRkHM6wCn#1`#U0AawyoDugbN z0f1tup$L$g2;;c5wRUiDFOT15@e_&}M@WF$m;3N^hKaCzXK-TTgim;QWFsLYM6P-E zfv(=Vm-Kspd%|NI>6C=}TMYn*z}r1@J&Ws>TIU6i!*ELi3CSch4i(ui4-Son-D1m< zW6Me^+!lu`TV7tN(TCk#{aYR$f)LYi2_peXOQb1G4Q7q7z|e`4w*ad8dF1NXHLI5| z)4zR_g}P6T2;ys^&PzYwpv*`dFQ@NobLam=>oymSr}JccJ2rIcfmlCKzwdSS!!xwWl#_&2sue7m6C?_2Hd?F_C5PV|KFEbk)_G=PX3L~N0*2PpCp4(`7#?3j6Td6YQKdEWCK?IOt+A8mgOT#n7nayemn-JweAH3)dJX$s`tB5}Kz z7v}~qE$Mp8#lEcgv3X|bf=`TxS<3Q_{vgns{81uwl3tSFuACXZ{3XKEyQZu4!l0~H zT$=cvqIV-M_$JZqBTA)YCIMtd9!BxAdp}14$h;G{`O2C9>SgwRNX?l z(ERJhWB}vlTKW15w^Qyei$E=gVsvGYN(OTc*m-%w;GzM>MBgMpeL`W68tu77Ocrz^ zNVaD&ut>p=2kx!w2_W$%X>taEiUYJAg$&J2a8@(MD=R9HB?&mTnvDfGxwwW)9n(h6 zqzz*D@ycet5mX?-C#A$F)%j<-`Mz8Ao zh;e(nGf+9K4Au{Daj(BRb=7jl7DyM@B9%fEu9OixGT>sIZx+UGW21q%K#ubt;l6p# zxyY2j1~@4COjs{0<+&!O4? z%vaC!gX+P*&dkpS<;#pkrLuC|cN~0v6NqwP7u8T- zVC@(%ArR+%(HTFdmuE9B#-m@M2qjb?2m-6tpKy>!SX7sSf53mnHu30MO%ul@sY+Y) z5{?}s5W8dND+1v_xlQ90-S@pu%0M1&Pxc9GV=3L=g!%u=X30ThclHl^e!+PIBf}D1 zgSPzk#KSl`;V^gqHs>Nw-Dq|v6_;lg=JiqGMy1>lo0~C-=2{Z^|I#c`y!HF;Gmu*V z8xZ#8OZ-c|6Jii*c>+T*c#!0;ng7)1LyQ^*SvRPSdc6r8{ndZ$Df~Vw!)GUXRf6~w z54?^4=9h2Uaj)Aaj)k3{45$^S|K%cm0w;JTcUHeLK~2LxQ3+KHZY_c_1RidI_e{Ff zaHtS`p;`f$v%Gz|-}!~+_V@2TCuV#V?u};rL?>eB11HP*%etc@rP!?t z@}x2RtfzT%PgYU@&51TBSabZDq4jDV_JC}sH`k!+IB#dNB6@7>GJDzrcqQv2aP~|6#(Y+#n&RFWX+fF?{xI4F#wbYY(Z30_krpXeH@B_Vld~_G4{K zU>l(w5fxk|de3+zSd{-%#8I|N8bTuWm}1ylvoCLHtEpF1*9uQGitb^(YtTfyY$mnF zJZj26)tkz?FXf+K&`YSRAbF7Ei*MRu|n2Uuqh8{q>RKQnEu{}3=vyzUXUopGEWau4xnXy6NytxZ=? zMpy@EG^3W*uLES^MNv)748n|H7(BDJB|QGn0>&{U(vb39i1-85=nnW2moKMi95|gN z@Gab{D(ma4{Ijv49))CvXWODQ)YWTXtb85hdn<7;3~>-#=3vRnbAK{m2?>a+1rtdh z%#f3jlb2T{AKVQjcyIp@g6|*FYzYRCA;f9|8>V>HT|IQ?tB zy0lp%y&=HIFY4r4XL&>s-}!m!j_7USq^GJ!zx;4ce78iqENF?ork(TS9ZqRi61Pab zbn!d0&+pDx{CvV)c2*n~NsHR#zqDe3h#4N)R;JQ4@631(%`Ka8Ka;>%{DO*Air#ls zQRiF0lWBOhlPg3p%%cB$%>ObCfb{=GZl5A8Eb=C=J1H~A>WKo$(*i&Hf*(?LY@k^0 zIEB{9qJ8Q*?|n_r2&cT4Se7O&Wv0|3hGZ%Gh)u8Jo*U-PjY{`=O}t4Dl+*Y;5dRPw&-p%kut`>IXvn z{FBh6faC_K1x=ln0hQs5-3UX;@4V3TiMA=_BW$|a+uY(g3)fYk^WT&m z$jz;zBQ+!AETlFeqYrhA0D*NycI*JF0oYkOY3!fl2!{x66P1=LNw&e+*}$ED2Cw_f zHhLaBJ~#Zs71R2_evrSXmc)*_?1`Vh1flbk;$jNm8AD1N%Fh})*E{a@G&ir-H<|rj zle)~9=+_>j@lvr+c2x=t_N1g`JE!3K&h&Lvjo2sh99K32tU#2dVmaaP%f2pJXMw{5Pvx{VhZQU34V zRa@*sYJ*)|TnC56kUdvuIujRf^k+w}wZd(-q*S#>6gM>Fr4o;UY{iDF+pne;j(i1c z#p@IZn;Tl+``7XRkwiZ_@`mpwy^pW&B5LHz8Lq6`G_#|yj04SwPz+C&p_yq@T^Wv2 z>x^lS*J=G-vt?%&__KwB>~ugNp^)1M%)hflL^CkSyng+9n*-N7xRZ5XY86NL>3bah z(P`hl1;cpa;OGbl5{+_3@fX2x6^9$&lj7&pV8YxG5|W}YiTE4uCEMROy?k?#41YUW zwQ6K(o-#N%cnD$&3SkTlFrU=HCGl@!_xjQ>Dj~2*NZ!K+u%?z)7OKrS^JW!P3d8mW ze8;u|0|S`^1l~h3*O~6#y?d}q3VWdK@Et;k=PmEWrw8NW=wK6H4TSpKip_JEFJG>2 zLQ@lv_#0$e1x(waMCTR!eW4?_VCUhvfPetR@Yx{$8YYd8hSx2U<%y4{2T&@U+Hi(7 zg1LO=+>(UUI*+&YT3LNHY-Yh9LiiwXwT3M6SB2SX1Kc?M#KZ(*L6xI%p&;9D0JXQk zvY!(weB#*+@m#R@{=XxNZ-=7V6MWRK;Y@pwwW(gn@tykFE8o(i7&go`<2P${d%|oh zS$rLA@p6x@*0onI``N;XLZmMi9sGpa>4uAwgYe#srsjb&GPE+69-PEA?WW-kEBzT0 zFH7~sXC-5diy8?g9<6^0hn}wfa~8m0y%X-V4$D8eTlb<|Vj3&I(^&>3Br)uW?%a9h z+L|G55CBP;N{YmPH*fF4{o17gYDlZ-#S<3ULz%;sHokvf=|Lyrjy+xz55C(}otBYY z8FBdkVbGrNa9^Q@SHbXa6>`qYJ=LU$6wYDrzd;Ibba4K^Gg-fxX!Vx*KdpxU;8)W- z`oQ9{L+N&c^T)`@6dnVRu|a|Z@yy7QW`yfN>zeT;O4NU2?k&TrY}a*v6%~~-KmiF6 zrBP`C0RaU>x>Ka3yJM&z2nt9^mox~7bSp?J-62SKHzW4-ymQXA=2~m6wfFuX`@^44 z9`n$50%MH(x$g5i&)+%hh8-G!c8NP+t6vX4$}Cvk&#dvR+`)lx4xOyejf{R2bqcq` zMFl}8(t7Pc^%V`^D3ORg?CVZwzzG9o)ePL+JB2y#{^P>p^I!AAjqb;_l{2W_rT*%} zxi~1Eax(MW+1f(prlH~C?vWAb^iT#`IkVY1{5#D+LCjBZ$a8?+D``Opeucy2F z6?A9AJ`;jlGMgjqdeGeH0H`$xBXP?a9q^1yt#{NwB5d>kNb9gGYyepE8!;a5I%w6M z@Y<(_!aY@#)S&L+x;o0G8GdFYJq;lc_~x@`58LI6o4x)zHi}eRoPF8-uKKCaWdCSs zI)}%F#}cAuaLvHKHlS8*zv>d>yKK3Ps$_SfCnYyUTX`GK)Y_Hfqdq>q7bQZD(h z<=Jwnvs1Z6Ya5u`&SSKaTml|1no&{kh4SGEpw2kfRD`}h5~wAcFftUS=^Yc|9`jjQ ziJ9g2fcwQiQ9)RiO962o2F!tw9;xmuVqail(=nx(8650RQMmV250&ytX!i8zK4a!y zD~BThVlrf6UIQ@)_A{>TInE!UdFEnKl{1H*ukStBjXd*MEy1y%Q>S3wfrnsNbhH$B z0pRP_ty>>He%w=FsPhuS{Lk8R^90xDf2BQ}TdJtPOiZNP+oSKtQd8B3O5X-)zo4*; z_LYp8I5?i52m$XVM2G@ZSkf~X zt}ECt_1Qd;b5<6qauLi5((ZD@9~^JW(@Rxo=^Etgu|jLRP=HxKKEBCu8FB?B+e2Ml zIu`L4-hi>!NAhE>QZ@+V$&#zZH8tny=`9k_aG9`&MnJ$`(@J5hrlO9ZGGa{``NR*x zCthd~H#$0c2uj#cjYrwhxk7mQBMhv2`maZlpidqi^C7a-aQRS#bS7-VYin!wb*NTz zJcZ8VZY%_Nu1BI2D9Rz!``E;U0T!IAq@`K^$_alOQ*Z{hV89+fgaRO__qq+PV0ggq zyg=`6cZy6;PdC8*1o6fq?)!IlYZpkZp>$Wbw(5keY|dsyWTYx1pVj7a7`A5WE)&zY zuC5QE^y}{@8X6h^g$^$O=Dmo=ClshCW3$~pkV1m#0Bo+B<+gM%vn;u7Obg@4TsgbH zJW%yi7d6*lxfZ1dLwy+K$F_?3j*~ft(>H5F5RXxDGo|3?MaKnPy6*nALt?I@-!#x?hz|uE^C-JX^*VI3q|EA=+ zZy|A7_M^LzGY1|BN z&kk)putOW&Y3iH;CFDRulk3lrV$hfM_vEekhfx*m%bwf|+6|Jn2KA)&86CHr>PeSI zMqF8stJlIk-^531njRYMaJl|A#E*SAVfs0o3+#u+vxh$!89ITNt5f5yiWw_Usk~kc ziWvkyO6nl}`vKODWxW(jm3}A{NZkJ`xz=~b$q3#-phtd6O?AWU4uYv0*&Ug?0EnNM z>i$EpHgU$h2OPfh+E_NPST;kt6_8TFT!SQj!HlDVk(4CR^O_BiQEP@6Z#38Z*&b2YSgw)iMY8{s;1PpUTP-fe{DdKvS!$n4|#A|9eq(LR0FH z5!%_o4fz&a_)f=1Z`7Z;y@6jD7~Jik3!}SxGi9zWFE0qOpTjK(SftG%wCh*?ai+i& zv0jH028l`x7&D29a#mJ(?sSTfFt~Uz`I&1q#2hcYc?|^@%^Ad{|4Cf`Wan$?GTVC_ zji-z260IHC!Vv(FYsuC6cXjLJcONq=D-3SfWBUtBZpkLD;ek)q&(u>F&b?;3!Q{gw z_jc-K#{4ffb~YiRfn(O=rHaqfPuN+11)Ck2e2UOaWGmr*^yHMv>2c1i8*L^Mb7puR z+sB4U&T9{cZV^vzSLDx!U_Niw4r=Yk?rD+0ZeFL-F`&L)3=m#+PEM*?U#?!fxPoei zp_CJ%gwMQoRY8G4J=9_wPg#rJFpkQ@s9hwx+w{l9Z3BlwLTD$ zj+|D%jqzwqii+}wUU?w$)4{Ug=T8}kYWL7K(G+9O9ui%EjlLnAf2;NDV3UIW=)Ls` zc?SnBz>`2eaX0X=;NkkCAl&b3mtrroyhtUvvVLu1-ndxXz`vi3RcTIm=#8Q-t1d#D z8TqXB$l{TgTu7p}Q1-8^+cxTnUAAjty$`P>)d+J>n9XhFw6|y($w#0&?~~B=cwg+D z_I6KR)L35Jo*h!iS5yDWGFv8`*-VW!EZ0(PWgMmuORXdjmt`kD8#Eo97BC;?o&I>B zY-F%4l2x@X{F@1s^ftxpbpbVRfpa1xb@ws9sPAu}qxWhJ+ok=`-5CkD{nB%~$ zb`@dKEM+3(nTL19hbG;{Trczfx43@7zB6R!9^5&NChuA^Z#e!NuHSiS({o8{@v<%V ziG7#x<23ApGWV&2_*4ZEtS{7;q}I{?+OTR+99;6s;Ial5jlj-Wvmnk!=AMNg(fX#R z7W|~`X=aubixZ5iW5hU)&s0mnFF6@TM&f}*qkGb@TWK959uBUZFYptjJU`=3DBsBg z^x`wRx?G|Z<6!lnX3T69LImE?a~DSNUKSP-yt^97l8D9B(3U?4x#{KQb)Ah}oMseV z0Om7zLpAolJ#}jQHUonn#8c00o!?=Cshj%Q`0WW){jBX#+To}bC> zK1at}@t-ilam8sTOS zeoA=u`iH7KQ;|m&yuS%23Op-{(U&P~oYn(5bS-qpuzw_4Z0LM%mFV6267;pvq?RZH zn?dNVe=&tQCE7J6i>j7!cVB&LeK+8mu!rZpU9Luxe4_93uPMj{zI675e2l#sRv*rU zsfZ}dZ2n}J(VObjt~T)!b$DR!Hb-@*Gk#m4frOKL%G~wVNs~FkWz#l?JK`0b4#d5d zD*`(r?Hwmg=?#Tj%6n1;qK%9jx;MiQM=M;PRYkLr&oTau#Q&h9@DxXDZ+&5xSNQ}# zJXetDEoi_>Drx8-1#rs177*dX)6vmE)bAq>_EIrIe~b*yzL)SF`n0s#_%4&DMt9V>8E;2_K= zl~fzZj29LPfjZXae2iTD0WHw@eS&_G#>Xcb{I1rit{G$*!#gFcKlgJ2sbftyz6W)z zoX=m9+`vgdK=C>tY%k#I>UzO0&Wr)efdkMNs%vV<{&J@WuJG^-^4op`ezh)bz#6#I zp^kN9>j($$%sL$QSSIlS6r_X+o1%^?y=s!Mnq>R<%tT*y(LVWB7;j=>bUGu|NF_trVRVcEO#yfKgrxD`m<0HkTVzMR2wN?+HhRNFdZ*-7$X z)a`R;HY2Xx({t}g!7=UJlZw-WWxB0Lk{_klbh}kud1QJ14&ay74eB$_CM4%N;crYV z#=LjJ>ML)DWCEj&I!$D21D7`uBI)u16}&MXk94g9*xO+_((dkIaWrZ_Fcv<)!h7)os~T*V9gR{b3@N9 zeOmS;91O1u2(l^_9((2M)P7vL8riTPfyGK=cd2(cc6n|)=rTkyN0NFfe+we9o*r!4>DO04-; ziyX5LrAk9XU3aR3E6>`H?3?InvGkOR?CgJMLe)}Te?9S#>w)Ys7koaS3ib0bJNEGm zarT5P%s7y8bDxtqJU?4C>b6p27bHuMp)s$75#W)eq|3ow6^PI)C|pExCUCX094QWf z@*?7E_Z%G^5uM_09hy@qySRB8MT&ehA#n&DF&gkxf|iPZB(fG=QoZ5btf;Oo30@{p zMu7ambafC6Zs&-IX0tOBr@+)=g2Q4E8vB;;Q{6akjLzbq4S8=y!s>?aJO5_ZM~?$2 zI1Gf;)TW_?^?kxPEx}v(5WrkuU}1q62*4m!UWKy@<7Rq6!ez=}Xv&9oEh_zIQj&k} z@`X1Zj~+piF7C^hbM@nx$Dcm^Af)(8_>xXMv9YV`m0`0O6VpLJd_1`~n&X_yzG|Q> z8&tgg4UaErx2gH)(>@^cRgE!0)W`hFnTbs!?w1D!uEg+j1t_Ls(`R569t%61#_jo# z=h_;l4UEYG`4M6NJqKGx7W>y%3jpcxp3TMss;$x8L1 zH<^ST^Y*PH8eRHa|B`R+@-~}}#`_wK47^R2=L7TeDJUr`ae43Zq3cm83@o?Q?`cI6 zf%K37sL02d)c~~M{hub^UwcBm?b`~s8-R3;j>E^6S(_oJ2f&$;X6n8yMXxsOj?=Mw z9mYX@jYLaW^~eU;8*ocyW@5U*#dQTz2td0E@x=$z=`F}e*AERv1F#z^b0i=-Al{3E zkRe$o60?Mx{S^k8;Z#Bb3#_aJ5Z8&ELUnEN3ko7-S_F5izk#t7 z>3M?7>MX$U7E7^^qkkI^@CTM`(px9T%I$n$-a^c?;CY8P9?Luql(1>Iq|T33IDl)i zyQ}LVU^+sA?pCoR;j03ep%`>T=L z<#^N0bkk)vUmA^|Vxg=Ar1US2i%KxTR^G;8_G}VT0x^Bv-E`1b3BMWe(9?0hcy%n{ z=A^v(nL`p`NHKW#+1QPW4`&3<_a(ad+PgJ_?H7QjT ze^?cJ)F=AKgvWzl?o3}(pgyh}Hr$W!^zZM8@bBr;J2&a!SFg z%3uMckoJBFzWMZiB<*}^2R(CmvgzPveOa$L<5@`(1~)m*yxmnnH7|Ux;v=&Zvrhq^ zpEP~5!^^JK$(w054kA#!23@ks`PC;koKDhcKOjGvQ@9X9sbh7`XnvmC{Q zXP80K(F`#k?yg6jq9j5VLVeu*_x89m=n6zcVh-+C)0fyeN`7y}CFmEU9=%M7(?VvtEOg4= zV!htOvc`1i*4z7T<}0T+^Bom;)T9aVDCX3me-w$o_4v$kbsTku-Q6*nqm=A?xuv9@ zE3R8o%y*|L=2J*`ewFg}YfV1ATZ6#XU|(^YuMBg(3+SB?+5QI4Q(7r>$m@xMA4u;k zJjK8W1$p6n+}w}U)uZ5ZNQXK8*80R-bqz8-0*+CwvDrCoVv?CC|Fw*aM7Vj@p$`l% z_V*7{A0eJ#C>s$1(-W+d2!Um!r8NV!vb==aqfmxnWd=DYq*j*GtcL{b*5?8q)m~Cp zw1Yw6w6yePCRV}Dv?r1kPRoKjJ8Um3+0>*93JXua5SN$t)*XxCdU}HAa~`0$YA=Y3 z3r)_yk^va^|Fh5Dl zVoLn<$(4`@|If@0sln#vo!&ASeBL}hflo5RO38 z2h#AfkL;Of;I5A3?Cqw{o`5a-6r39fzoT!n8_MZ9`1nkrh<3aO2iq$RxMw5lQ;;Hm zw70*5xONUHG8#c<3byig=mR?$+1auY$gnfXDP`s0pz&pUK-=aI3_pM7q5z);@_ln( zUj*3fGvt#x4!7r3KYoWcgTHh4`}&N4yRTE_oF~~7l@rnF^HF~Ccb2&xa$4nh?Vg6* zInrvmkdd3)4r#=V-Q9HXp~L&9DTKBYybfgE;Nb-$(hWMglO7%(2-2rJb!)Q|PNoi^ z{{7`CFSDVApItep0hYqxV6umPOz@N=1y&6tB1oVdnnywbgq?n!0xP4w=`!~lY+Yt( zIQd{Fpl@OEPKJTDPOrv25(t2=#Qlkxu~LIE=>5?;CkgevUg#n5^*s#(_um_n#eA-o z+o=2|TjKjf8+$f3g6ghl`y&D(AtGtHi01-rq)ybkcgEKxs8}-|)K+V6`k)<~4zsoo zyk;e_SkG$QYEbt*`X7Y`DYraM(DTv1MiFJqnG?-t#4B4`pH$J!K^kn|WncY!gY6{~ z(b+d`&T-nMilwXh8R1Wi((XKc-0%?3OVVrS^XzqU_fq#43s}1gjPt2fJ!%<$aCvnG z%G=hI)-bN+1&0~l9vgGJ(ryL=V^d<2Mo00jZb>rG3*Tt+N)@Du z6H*zF*L9}0%Cm}gjeGEg^991&PyMLvVWYUAdXhJkcl-jX?x>2E#XEO$Y{ucoaWlQU zxNk;^iT`ZZwI%<6vhzHjVkLXzq6nQ$KK1JfR@U5}zR-U8%6(zG3pQ@0xNBX%j>5Qtq?UCEbgws{joUhEFNr4V>;Ley31aOvY(6D|uW zoh&W=2$?Jpwx5WL<3SJ%r1u9Ajh(fL*u~yVNNN}l=IbULS(s(xJ$%|OwdiGKMGVwq zUf%s@5noD68Gr@_$%T*5-T)1)T>wtm_fpl&$~NPC{!Qu)%3=Ei;OfcZv{5GCUS5*A z6)AO#Y-Sz@jws>iXmla->PmI=$B*uTfm3VGB+|ZYkY>ugaoAmI_VxX7;zLvv{!4#< zeFO3Iph`O`5Fqa9HkaF}mE>U!ua2l#`PS|L>4n zDc#=G&P8`h#l{}#HZg~{c6D{R8ZDNUv??g~E~p7vnPrPVZP&P62TD17?)~8b3!s|d zgPLlX3XdZ&i_3c@cUv;eapOHb^?*P4`3es|B7dPOX#-{rFwG;qwQof5@bExvqs>Y5 zC#IH$C&1n64Y9Hv5u=uifUPG$KI(`$4Eo@}F$N9uLM)BAd&mx}zn_eNfB?Bp!D(g? zpP7S&a~)xxLKsaSFpI16>->n_iI`g8hsSyi;`FGDY#tfWx}#h910je&$+#Qbm8x{> zAiDW`ZRwtBJ95EFmZlvq^ioX6JZRf<5{ZtDg?nN>ygq(^rE4p+*zTN`T|w;N%2p^$b4#;V(-F*(@wBo)6NRyqi+It%4Tk*!13nhja|D=5^#x zu1lwG)LmuU$b4wdW%1;k>SBUDUV8jPdOEcP4#THQ~%RG-QGI# zp>YBCUP4?CZ+5INvzpyT8IBE1y=~0-H23#uv)@S0+=4_`4_v0K48qMV>M zKEKrbZSvKSh?V@@XpGPZ_T~yzIVDYNN4H_Og7P#-D-(WlDqDtW>5ZR6X4L@_o zLW}E;Hdp`9-xqQdSmZLfBH|_h<=oW?&Wgr|w>g;$G85-}yA5sc-6JL3y3%(4DQltw zMceW9N`crG#Ndp(Z1Fv9qVc!+4zR1+uDMuRXT* zcS+lSgbuOqS(%&jgIfnUcyH0uCTxnJ#~dJ};*b&+~@8j7JEWfR1Jy}`jkT>FuPiQ$Wd7QP8WQI^Q$ z{QRJTb&sF1PN7*@r{;cM>aMH12EmpSGuL&%b_2GpP+;xC7t&olJbYMp<#_Z*(}=z` z&9{|db@&;s(0C_``phxU+JN)1xTJ(xC_jH6r<+q*IqHci!7DB8jS<`z01&>RHO)h} z3VR$wB(L4vPlHpZN-<@gwTq!g2>HhFZ%BHh-_=y`oh$YgOt?YJ7q)LHm6eo$qXP^V zjX5?hE-nU`^I#84gVPp<+1>NUy@RV3@;jXg6(WR(l}mLnfr1!O@mhn8frBFyRt@QI zz8#+jVvb*CW~RZPO_-a&N)M4Y+E}cpcMyanW2Z>Nb9R@(rz8`9Kh*1Z4}8RsE;0aJ z05&vPq_~C;Ht>W2!3lW@k*?b>Uv4lnXOA9r{`{$OP~~;Z3!_g4Bya$X52xQFeSJFQ zaaLK$!mi;0OH;p_DA@KO!wVeY*8O)sB)xoAYN=UeL8;1~gnv8f;h-aMy3IsLzIq8s zfyp0v`~{ zZ8I0yY<9M{!*(SUp*uuHNy0`IFp`TTh9s4!<)7Y;#v({xv^S=53g+#PV)-!irPvXm zZ}%OndtHJ9;Wh8kJva=#o=LzZMdR1ko5pLv#_2EVF@|Q|tY0~gizWKzcNQx8!$;n0 zZ8^`L-<`h0O~k!9yez(*U;-s(?>|4@do=vPG~WNB8T9tri3oK$e%{?Vw6|nZ2PS>w zmUdF_G$`acc{wxTD}*VA|8)^i^^Zk>oqsq;*T3aJ?0|N221e7mWWra@V8};mSl}rG zTY()d=u?7bY(v1czV;mK~M#; z(Eerz?)sIVvHA@j50VDlkIpQ9AZfAsR}v5{$w$AiaDday`ON#rPixjm zWP_>r>*4MA=Jhp&8k;?x2KYR6_48bb=$3_*hn>CFnBis+O-)epjYB@E_d&WE#aSww zJL~J1ZwMA=ba;4osCl0D5%$d zKdU!Ea6C#u>oKqpZ-O*udaA?LWGBWs(V=wK)_RJ+&6`==Q$eO0^}sl@UB!7lu%lk@ zc#tPBgp59zEWC&v|K$qG#9?_`TZ~!wR{S%2gP++P(X#g%oKx6ST2rEfmTOZ=bYDFE z7IG3E9-j>JQ-fGd5$ULlp8utdYc#`2UlDuTuG61pHN9+9+MPQUPrVP^?7;jTfY)rANGG z4qYIh7?PXI3heV2^QG{=!vPO4;YNT5s<$zwP0)Zh0d#VN00{cX_tDWf;El45HA8jg z)29T0p+K~8P@IauU6_9V>sR5~okcfbmr*+vVG^LtQ+hFka`!HS%mJ~oGD)MM z(FUXIMPz*plIQpD-)qE#Rku*Eij^m0((cGM zveaKlcqL$D)l%=2uyjQU<}@_Paj$LBtK#|IwMBVZWg__0AFS+(itCBD=0VM3<#ne% zy#mP*rpHRR7Vl9<@#(a9^TqLM#!X?q1Oc^u_}o#@ivQ+4L$*xKPlhSIdFZOT>EH#j zOR|q;#nN5jQ==t=jXEr^nI15-t0bq`e!qS3(`7lX!S9#2bcF_|KsbMlV?x5yki;C4rGqCOumFJA86j*>eJM1!V4Z=D?X|57Dm04;fvn>JWZjg5 z+i`NJ@ZyCF-ZvQ;{b*5y_2XWV23MYHk4D2xLQ2-CsN;Dxbir$F?1`!-3h06zec73X zrHQ^JyN^sdWDNSUP|SyBX4&aqmC{_z%_BrUTI*kW8>z}2pw6S#Ht9pv*Efybv_i~&VexOj*DX3ysK=<7F+FXrcW7TM}^?Co)> z60d`NT~WdB?(QDg$Or<uvtUVD`J%bgav#9m&nPp z-XfKY06GRXZM2{Z!dd^UQ7HBYy2m=ejd||Eg(-NGWCBV(h6_iILxyQ!xo|qKXdwSU z1XVjlavFfexB2-gXlZG|Kyf$|E)U-DAHf!1z`TLru?#t$@B!0F0 z5%u(MNWHZC^eOl2ZL{diW6f1EAt*L=K6A-{%=zhxJ6r8Zfh|;a6c10~oWX_5+3ywc z&Ke|V2ju&ml|{VSzA^cCCPLzVeC}mrwlc5({q2i5s-VK->xdP(vOMOIuEkTqebKWL z(dNrt@=xO!vLl|T@usPcDQ<0NWL|4EHbyJyWbI(T{h}&8KC?w~mzH?EvUSjnPY{sq zfvu)_%ZqGap*q6;k}O=Yu18Yw-$%>$k(frg8(Er`iqNORvlbj?-!Z;5|fm~kCDlUzKDZ9_?3ye zq%HeHjhs(YoVRbEkBGRXpyr(TBs|!DdBA01QL}4E<8#C_L&Ni3&<}LxglPxc$-Xb8 zi7%^;SqO<%--hT(sj3d5UKf>-H2?Ny;MC;${@sIY-Jj6Sueg|~xO4#Lve|VGp1*HA zbjR7Xw9Luso=RVauEWeMCK~Yu(fj;DZ!|os+jp$2G7a=Eef#dii{3A4jE*^zoNnBe zXDQ-{%cp-mAL06Ev-LG7NNl`_IcpC2h(?yvAz@wnRsDQCFA2iuFiGN2d#_ zRDp1eG_=5ae|xi;mi5qMF+B>YuvzNM2BxbpuW?tTXTmJ*pEYFJlUdo>ZVQYQYh~Vf zU_vST?M?tC;z08hq{81>pP+>gC1U%8>QfNXuR+JcYYhIa%jW@$w9+LDI|Znh`2*=_ zK%GNB2eR->5OiTdBZw6MP=k05?ch-U@*!1n7A)B!xOljbZ6cOzn+air`q?L8VQwxZ zD?3~Nh5+sW$PA6BP2lLNaNWEKN{}=kCpsp7YW-&o95uZM&_9jTzJl?O!@CarVo1CQ zq%b9PXg=4W>TJQzUESez#DLu;sOYAiHSyLPx(1c(t536EVdviAX5v?Ty~w28=hRo_ z2*i=k(OU;w){P`vy@IeS(FI(hT zkWf%zyxEhzs$uH3jb&fGC&4ljo)6z2OSnAX^~n0{Q@J8AqBVMNllKzKk1N4{Q*&wF zt&2V8cgkZ2u~xZ%OeVEBlA_f4R#a5U=$IDgP)vYYn`GH-C5m^Kr#QO$3_he>Dm~hI zL+{}w?#P3Ep{LE6|Amd?)%2CjjgO|ssFUNL3vTCD2Cg@j4S$RjEl;Qkt14SOF*hW* zh3mOn8udqxKQ#o^*{A0{^)7zd^Ilt02;EST@P&Q}{Xfid2^Z#<`a{m24>)<_?p=}(Ntcy&cz*we{yZ4RVcA02)ihj$ z6Sw@FHw7LUox+OSgCSez^Q-vEuJA1ATZjyKX@3u+>mj7ISAB zNbb-S@Zjst=!g+$#E!X%1ATp61$rWp=%b?}`}q#)w)S?!eBCSTUWEw2svimjovPm? zp|CM{_{2=QbaVeKrwggYY3FpQVj-4b>*Ul38xUCH8CY75SySzn4;H`*2tkwql^iXqq|knIS8t^2=w>AEs_FVz}*Kr z91CyRnkjlX0c#8qLF#8hLopcdF$i$hTuD#JoT3d3*nc;_wbWkRI`S6&RIhyP#9@iu zE0HrwahgxXUYrV0zRW>o@OF^+ZsZu*mFN+*3&dm!JG8Tc(N!1pX!PU;{MaOLrrhvA zzF#*d(=&|%PN#?z^$nT#U0HHKxrb)OLPCeYv6 zPvx-Xyup0)3G?UW(fqM7J7v3@8`l3?ipBqDDYonMkuaVQz5y8GVr)$JG~%J%SCrBF z+jtu7eCDs4`fY@;a&co&Jj@XVD$`XW`TtSoU8g^>g?gCiB-Q!=j%p; z<~ipDHu1`qmdo!8ox;Zxzoo=XcKLjrOl;F^jKh5%GnP9NO)|o6V$3Uo^b%Y8bE<1n zBYH$fdmESH6CWh(P!t6uJxIuzGx;r7mGzbQ*1xN%<;~~82HyGaYN~txCt&zpfA;Jd ztaHP`>Hys}u%3~|3Lh=d|MuD+cg8))&&inrjU#Y=fc>!S@%Gl%3rout9(cA-#KHTe zcVGZ%1wtyotgNga9Iq5zZ~@jM9O`0VUVxoqSlDIos}>CThRI9E1-yIrqtk{!QPH3& zM1+l&tAEh&`SXXs$^z>RFdvkmJSE_Y{V_EqCDQ%`115+f8}QIC0ssY=dyriaBO^Ze zI1)Y9_#4Rrm9j}$S!eCy-?iiJnaZ7o`*w1HO=Ha&NNEN2!KT4|A(LN~QYtDZ9p>IHOia9+Zf0a!s(>{rfFROSZp|t5+_5S*@GhmJR8vlT5&_TaH&o0!uJ-X~`U%E@ZpQE{Q0t z8)y_+`T0!)16K(M-ucJkJW4`-TO%GkvKgPOEG7uw1^^HV0BT@81ejc&EfVvt0D~xW zS;ABR+GFI^%P=_m1P2o%$tBRo;X@jhkT7mBv&@2nE+wd3@_|ip@7UOT$aDZWr6%9o z*u;e8>RX$M+FyvT+R`$ux;h?o07VTADRXlcM4BKNI0a+CdNjzfFiSK8@>=@YGa9%) zg0?cTo#rNq_^*H~3s9zS5C+fA%2KHOq)i4|E0lj3frA;I^u)BnpY8v4j= zWkJ}ogmUhGzw;gpznoJ=m-k8^$tT@y_Omak3{z#U+vOKp+n{ae&yf~x?YKzUuS;%j zZg=kmmCG&J2T?n$>|bm9YZ1HuRO35XR$aWHojiEyQfNmcYg}vV@LljTQ^N}Z$1>v4 z5v4d)^D#P_9QNiz)ow*Ml3u=45IF;i^l5jB`m=*q>chW$hv>_{`r0r`zLWKE(vEJj z`K7b^_1`4Fp@7}2L^)($c~CkE)<)%Z%fA`hzdPhI26ysb*qxStL1Dphblgoi!As?o z_uEupnnB($U%b<+dK=ety2xZ`gQhdHBvBYSMLvdNy}o*_!jIXO;??UsM)Dv0&By=l zZ=O7(vK}Wi2;6%zN2gz>jUB_m3kH|veGSi4NgH}_w4G>bx+6V!bMK!tO%Ai3G{9Gg z9bYWhgVz$|%b*)c4@1C;_4`l1?rTp_b{DQADipNEJvT8KV4Vh3+*78!n>=K^p%r(+ zxlWqi=Q@%R?BpM{zt6WxWn33QC(Oj~TO{#ouQ5xN%VX0&1(;>Z#^%6F&4I1MAMqaU zi4;}r9_`}FtBluTRvPXO~iEN54&=C z)@{x9Y&|uq?yq5OT9fovtq49Ve#qKOq zA9tVoBjL=bcyb4GLX#^$zktH}!k)=w`X^1?QMXdBn3C(|bde~|y&Vn5pN{UIpP6N4 zZzkD=Yw-6aJmhkC=Da=mm!UZ)sxx;j@s{5&qreh&5KZ4RJqQa>%kfIG{g4uRvqm2y z^ zvt@G)VKPy7iVtKnL&DkUmS%_RGyod>b8Z8Q+&vztOi^R5!|@+#Zf zy9c^$+i&hY%e5srciu=0P2z7E?pE6^ud&^WA|y1IJ$4p5sq zV8Yzo+f#c0t`aY%uY-L$^WRQ75Ysw)-Ks=sEZ)^Fm1#8*vOYw8Se;hu^ZALlWvx*CQ%mq?x-~p6&{<{=AU&vf{+zrdE^(UFRd%da{l@g55IT=}5 zGB*J*O2YOtbgC>kSgwHCM_vSjP;pC+o9t}@aNb8{`3u=t*@q8RBY4Xl^JWP85@}sa zw>6TKj>`q9OJVls^KGiac0=IOQuQf!1JsJ*f#M#mQDmc>-L6D&&*TJgJC(1} zN_JcFKUtPz!{TavgfnV0jPl#lIMc&k+gvE${7w|F=d8w$&A54Zb}3R_K#vqO-Uc|n zggv~vx&{^OEm|y+*CeQS2bV*6r9(-d2t+i@5?%W`@hd}iAj4I}a{3;UVDY9u)_?Gm z9BecT8Vin3Mb_5K>D?bcQdU$h+s(~Eqi$Cx=F#Mpl$JsF;vZL2IFby)-MUp8{TC@?W{6*jQ&_q|@0FsH$GnhJms;LL*lGg^XS5O_2z z>=8#Q+p}tNC{u#+hhISS^W9OsB_fgpo?=jNVb6{rY+<+DI0aN?DrjSRu-m7Mh8{;m zqyhM!;&0Pzi{64HR?HER;Ye{2b)qdSKi+`SOE2-cxy}3mFK>8Z(Xrtx0Z2TQ|Fxkh-L`zW#{H9AGJ9B|JX2Xh5DZi(;lv_&#kS4VW>6O zY#kofd;*LG=X>60*Jmh<6Wn7-cgq;J)+g`9ogvr)LO0#q+$*qT!}702f=_mcuJwU3OA z(|gW-|F0ftG7zp|%mvcqHOStxLC77^^Q*Tz1X)5vD(&m^*3|0~T1hs*41pjr9DooD z{wuiNCgJ~Z+A454!eamun~{KQ10k6TX{?r0f7LNNZ=ODPVvQv;dTyJ%65jlNjE6 zVA}`I5Mo9LMe8f<>)s$V-v2{i^tb;jU$o@^#uwdv1rzu3p^M_Pah0mF=D9!!5vWmB5@kj6Y3NUy4tn^z}xBKtkdbIncM;zQWi8)ur0gJ-PU)8Slg!18=< zKR6>G>56vb3cu#e9E)9E0>!< zP9+pQ!{1swP*&(i!!%};H+dGVf4wB?JjImGOKcjbTvar!7 z#fCV2LyGS4#8s#0rkZgyXMcA!k)07xAhd-#xIH`t}r0#L7^3W`t%)CV}F4qF2t=mpvCp`j13GU@GM7Wn)H2G4ozh?S1~%g0>nk)BCT1wydhExv;Ra z-ZkFW7yhRK7X_=VPn7~y;vz_~UEQ*==Npjk)S6{zwL$yGJc3G{seCO+?CWG`H%Ii;G@`CC5#C->o z^T~cCyQjy-)%9Pzkn}{0<>XXiuf*4Q-G{|hXlv|w;@`QhU>@X3OSgiY1kDNfb)>hV zfc^oV-N1?4w*z3mrmR95lJe&UB+bOwgnt_YCP2CH%el!((d&&5n}4#MU}yKSj@$-^ zF0GSgn^z`WN`D|BWR89%|~NC;z?Cx@!FI zvY2ipwkR)e`@Dqd8pz46Zf>xMY5~jp2@N>v<~tGqL|G;|>-x`XcWE7+0dz}6$HlSg z?z&Ak5kYP1eQ)&rjEsyEh-n&|J<)$49=X|y=}|sfmBLOSro!Q23NSNs^Y9?8pRgMB znAyr!s?B+iL#!#+FOiwBY2$h?T?iCoBS5{Y%J)2}mTeAqrfBZ;g3sUw8 zWjQOOW%Aj?gMED;U;u{pEreXcZ|{r5-Vt)i=G0?2h$F0x4!Cq1Ct^InBcHkH|K<$@ zV*G$&Vu1GET~bAe!C>u7JD}G!-P%{d4O{{r_4#vXvi}#3>Gdlyk0h49s&Er>ce(UQ zc52mrPdCl=_sd5|$TZrJ#m9LDyW(6Q*}U!I?)%H=%nY{a@yf~#YkT1~IxAyJ?8Ff( zFVA|$A2mFu!6hbswfbKPe1-8Q*)!HFX76WZ+KIPSow5V<7}3)9Mx0Z424j4c(}lz0 zv(^2^gWUroegS2zc^UR7p)k!>wH!qj;4=j%3t$iQED&Dq}IvsR$bS?so_9q5ecqqwA# zpL1j32g&}}zl-a4RU(uJN5sk#gutfAEv+4+Zy{;#76b0!=TDo+KHg@q^_(L8y=95J zn;f@WVx+3RS#!WU`jzusS&_Q^J}>n*Di2Al-ch5o{@pQV)1A%rn4@-i&rpX1WacZ> zd)c;S`tR=Q8~^66F6w3um5PQ<43uObZraHkBK&I$3vYiX$uXA}7jHlk$zk%_D`)iw zL6kg!u$U>Sa<~0Qw`G!W0@Ahj{QD_ne1q0%8iK>{u&^vZkp8ZAgNRxWq?aI$4z3Uq z+MmJS5zDu zZ?`19nmE;$ZJYlL6aC=>`3W%Aoj7Esd#pFTDhniT*et22q@A>i7djRJvm#3(9{x|T zcz0n>501xauQpY4o?lwhVu_Sj{TDKPe)AY7B-1EE*f6EM6z1lX<2jjdy6r3v%Ks-X z_FD1ZJBF5t)n3BT%% zdV9=3(xh;&|5Qo6~FDU*TU)>z^5sTbf z&B4O9GZD)70G(G4qS|NyYGwvHMiB-s5Kn1$LJtv0IDevh=c+bHmSh+H-U%KXS31mpyL`8jGT`Iz4!^ z6**Q56mshnJ1dyyU;MPWb)Sx0NKojI`FooQxBxGL)&t@HpjY81pImw(t*<-bJUbU0 zN1e!aeXHprl5D>PW^2Qf5cc>`L#J&_-5P6bRGsi@UaixaaZE*&>=2iOEk3qjwK{#W z_{r!C3oCJtm!)k-%NMqp=lVUlj6`hu1=3i`>U~EQfX<7t+MV+xna&YTQ=3O1Z-Vddg?1R68aJ*nq?cpC&)@K|TaB}hw2wTRQ9U@r|i6C~8blal1YHwzaMSm~W# zv}gf~oB|Um+>()(Wf5ykWii{M&ClO9DmxON}soKoRVS#Ww zB5zT@QrANTmESkDv=Wczn1(#Q0Z;=ZaoD2%t^Gb_`4k1FKAO7)YZFgZ;qTehF8YV} z8o?fIaO9$c9PZHPOrcl20Ywy6`0USJ!5SsNVn^r2EPiQDj0CPMHEsa6?T8zWUpCMHPdtd^D* z6r@ap50Ubh2zN}9V^kv3#As^)Og&+BU{itSR9Jzi?&6YXd(*smVsIxRq?_Hg2-a{| ze0IZf7ecwfUr2_(8xcVYa$4Wj&$LFE@h68OOoue45QfuT%w+gOYXUnN3L$rH6lPfu zQpm{wvO>yDoip7ek=Cf49Y^?$KarQu%*l~neQPa?0329Y0%1bE2_7=QXEkkFgke|z z;#2kaN5U!=!nh9sn#CC-17#G)9JMiVaYZk>KM{wBu67(S+_2?)u{r8?DfCIGnAwiO zl+Tn$&HE05_{C!ty)cvU(Km`dwPy(r9nU!pI~jfcx!^RrP}Uk!`BUIR>9uo5b_4=V z0)!Y(CiX#XDZRwtYE`VpE**i;*^9G1Nv+M^wV8|e$tgD{6hn1#@=V-%7N4mHkG__) z&(4TN%^6+5>t&B#SlDtJdXfJ$PSbNvU2C=`*9#o!?_tCj;g(=M`ASg#XovgGv$2QP zYjP>~PLCj@ZZIapJ?7#C@ySpG%m+_MUdy2yE$}p1_zU9ljo$D1q`QnO6`>9UQvcOg z2>#8KbEj#pitYJ3x^hp9U*1?hF{WO=?04;-$#l$+A|G!N&a8-sqFM zT)GnI`>(%ipQZ2d+Nta?oMz<6)$7##eV7#b=kpH5*z}>}J?^Q6@+X#5SI1aK2TLbI z*{%->9tW>I&(CZQwYxG*!+AlsyJ4ah>E{Nzif`bRa+G1wgtW=e^0%*E1u2lbwCpUJ zl~;AH7amBw9}?4u-U@!e#U(%YD?E#aHTR{*olYOAiI$d29>;I}|5|h`+<}A@FkF>B z$R|s}fWN%4a7R>FROF01J}fzA;KM&OH1r^B6Uu8D`qV;WpT@RoB{T+nxDrop_bFk1 zjJ+bB5?bX_$==poy+TVR-?(`pXuRrE(MHrnoy_nn&L4IL_GL4tK5RcDev?FYp~N+* ztE``qllk)Y2?yx>e9rmpw3nb)WK~Q-%m|n3_H#d+%YCg8trO+A;e=3034?84A~|`% zOK!ZE=UVJC(~Wz$jz9Fy&7y*EJ&T_b*WUer%q?JwLV>Ox~b)Ddsd+tsTiI@(TY z@{w?6Jmx4ZFxkJd$Hy_-Q_Li0X}MYW#i(%p;T^ez)?1SE#2As;F%lAGE^Me&d#jIHVEOD>+^ zh6#scHb9U9-oAa_pQ8%5!uo~=Rc~Yh1Z?*?5|Vk)(Lnpc5%=i)YQ~i_F?kw z9T|}ZUkn&mO<_R;t7TR8;FT2=goZ6+<5+0~Fyw-J_z3MdbvnGfN(6!mZl_OwR;uv# zMTciA%TLd6!@Jqm(+d|^va9gWO)^?6UvyUlKks%aU@pQb>m~#E{wd*`v0QsJ)ojYqik&yBPb{+ zl2JfFf+86SrJ$hXC`eXNl7i$+i3msr0g)^sIU^aQl$>);B{@luT!iADulr88Gkxb@ z_g^!v)vL9|Mzrev&U^OW&(^=^)SwMdZ;+C&0~ZVK4iO`HPiuPP!CxFiFXjhHKB*}e z$N@*fvN#lOFMt~OiCns&`jL$-O?tYA`QUpNO1`;-16L7^1v9|wwO{wZnHc&(m#3zyoTi!O?S1)RfVIW z%h6T(x1JQdgJA(4ZSZ`Tn7UkQ-Ycc&S=D^fx#Fz7&Vm14^>axJ!UsoOjE)Z68vXh| z9pBv6vvdhP+eXcZ$JfuVz8>=hv!fkn>vT&M2WWgfqah8(lE z&5Tde+aI85~Pe`$_UF0{V2=-LH{f3vrZ9Dc$O&GnA(W!%&H`NlpZJvyT=^# zj;`Ft2XQYNzkB}qx^Yr_w_5LgI~9I-t_Ncrm8kU7Gh&)|WoW<9a&HODEY@dk>St6L zN{6jiXa|f1rYmHmuq2=23Z)48M%N|K|Cj^wBbTtOV1g+9Rx(-T{wxWdm(I_!c^kni z6}Ni39i;Saxok(0H$HDAz0{}jCh>N}Ovx{PPz!8HEzS23RB8Vcwo469vSOPZZvH7b zuXi>B92ZPYZ|7n}r0$eQf4}=FI=|D9fIJxAI^s##n3Mgy&ugPn&`3yH#=lR*E%bdb zp=4fnk73us3F_ftH4CRLJDo_G{QiMd<$kG0s)iMXn-7L7#f07rgmqj~9p%KJ&ym*G z|9HbS6vfwMR2uckx>!Xb$~*GiqJQC#5a;_V9S1U2hDn_h;wi4^Yyws$rhZc5NyStJ z6WSaYA;7$~4g>5(8k)>mmarzUHL4yWmu?D9&Cgm_*Vopra&iX1OBgoi!l2**84o<& zKHv*WTS~7IUmD1r12IvGCCoIu3Sx}lsz6FD5uPo)!}4J>Ui@;X8Wx5X=Z-#iF+&}) z?vk;x@wDk%Q&d@Erg?j*)xMCeiKx2z zqG53ED#z|#m4re&NprITBiX`LvISsZwPFFL;IuR50McX)pbinAH)7%CjbK=O=T&F$ zbVAZ>QS02fmy|q4{0j?9jqUgoN-J;JZ{nlO2ezNSTJHGu(-PL<(JhfE!yj+1aNk~l z>Nr7%@$irJyl#6Z{rpH?A375`JHO~1&`&Ddq~wVTFDM9eP&=I@<>sB9PGf4idW^{X zyo*nFw_|$(dTO>ZvG{Se&e8igUADl$XPlg;E?!s@ID3}(qCcrwIR4Y^irYK|Ok#(G zws!LQC8Se(TbBYiZoz~Gkp}R>RAaFTFtYIgbERs3rDFdB9-jQ+;j}4vYo|H6+~BJ? zpQ^w$J6z;O?NySuR{i4#xSR+%IV;_lG?IJ!W-_73xq$%(p;Jn6SD~XF^bz8W$>8a> z7d$%LYcr^QA1v&!!Vgu97kvvuTb@5;_N6WgmM%M5SU%+VF;U|Kbwxi~TLa)W^9=&5 zeb+Y$nwpw2FhJe_xr^z}9jz^Gg+s--eXq|d%wC@WFTytA7e`xmM!!xs4h!^j-4`! z(ayUtO;b@V?u>MNy6uTT*$4Do0s(3Vp8n7#BW4zs;|7+NitwC*vLrzJBRkd846Z*| z6WHc*!SaQa3Iplk7{me~d0K;r|0RmTM~LPQ*9lkXLIYs$ouRD|+t)7z~&xJ2u7y=VW8)HwZ;@s~i6NJVdD~Whh-~MH>VznZI z&jZq=s}7YH7ktycEBR+Bd+V^TcI$q{;zA{Ly>?vtREIjLpEI|~SKEbhIBBo5jcNPA zz@uA#tJBR)#cX%WSHBC+X7*m9Rg8f74!SA-S3Gor}1@Ro( zjam_2l@Ee&7il+irW4{h)4@cv?+!{=PsSCT#)nCO3PokbXzFin2o#(iFcI!);D&mZc9UAMc+ zlHd$qFP@#K8l_7uQ_u`7tgdZ&SN%ysy@F);axEO%4>`V4-od&%c*E072sI_wym5o? zaypGPJ;h<#qnw{k9vgHK!C#1Xr@Yo3juKt9KXnM6!~tPPq_T~x^P=xVoTT&d zV1`E#mpL~}p~98OOM(dv?}Y@!ngRS3=3BQEw?o?iQh>#`8mfl>y8XV2bxcpL*vGWFciz0Z&EUYo1*4h}QQWpLx1 z@6^3%nFvs4H@8fZF)pq{O3~f(+pDBW#R934C(I^je*^uMH~Px5Kn<&Lr@n`4iRr-;5i+!2>jMkdd42_ZL6Pc z%cgeQ_SZ2i8{gneC;+@5lpVGrM6I12W9aR15XDr)9GI)P!x;)A zQf$oB8#7W#IY2Z;fX37-uD4h{}T-$DNC+4^)-Ea*M) zOQ|edBkG5u=hn~K<{p|&;;)9vBqN9nx^gN?N^Z+>Q6wd2Zk|+Dh8W$`&WRF05(GrZ zkdTl!;LL_&zp(ko1|q@3qknw@TI!I0!#^1dYw+j-mdC^aZUfwv$aNM9Y2SgL7oH)2 zpUjMFT$3*M!|?6^@lIhX0pLWyM*Gp!bhb=K7>%)AAcL}Ra#$I2!KzQU8LPV2b5ZxZ zKE#rd90x>ifGrH7m%d5b@i8gG%#gYZ#I^x;Y$(m3oU)t4-oxPx>^qr*tcv?Hu!%=Wzndpm&@x^0DBtLo^E?LC_*?#~17x|xC`njrmlBN}7PW6X@ zR`IKH{Uc8nZ#7&oGch-*vG_s<-W`y%2j%mU_FRjitympwZi3b5>^ONAkJ-2k__((s zmu{ex6!5ILLB#?M8^I0U8utt_;niaO>GZPCe4TNDcl>ubZhQ8hhe0f9o7f|)caUZHP+T_F(l!1c8#Rj%=8!!Jo6RIySFPrh}(x3aH)!lT!6 zxBu&ber|Al{ft8ikWMEmeny60QMs^67FH!17h5E}Te+H~^CqPE5lrvJ`2ujMro6*; zUdLO(+k}SA!70i)h74~mBzSY|S8rJAFjWaX%ON)@x5h~Rpzf%gJWh!19q#O(->ntR z{>JVwl^Tf?(RQ`39v397-&V(#yH#a;*eiaVB3z-=(=8E}6?j9TJvKW_iK>J^;Dw%% z^0VWo8^oKi2g6vKP^^-){j2jRzuf%mB4MWOH_cX#ee7?wwVFZqd>HW(Lx2Bp@if|K zvMT|{xI=VAN#ELV?;3UFy~nT4zp@st$$F<-7Azu}y&v*;?3;Hx-0=MT{NQB;!RLyx z=>vF6fcpB>*th|-rr@ZkClySZ-lPoe(C@-09zElLw7lx-(!uxwLjf|Gfa(gAC`V=r zX$D)oi_0G1ppgbeNGLfh<`${r_zQD#(uY^=?8+LA+Jx=|RlL$Xefl(bWRYzw($g*? zLc_$wG&86X7WnZaNq>Kh8vfNQNfV2YX#v{k+F|E2DyICgg-7OEw!ZQl_NA zxWZxqB`xRQ{X}(3vKgC82ZvN#R4mS;9`!! z-86lPR9{D90P|A-a=D&6ZbpO}%O8lT=&yUjXaMoDT0`HhZ{sS5V?bXGj`~M<=t&HTdEbl*s!k-UdCKEs0 zRD}pUO!-qWmQ`g*xB`?QfaW+h+JP4{uq8h>v+ zJKEbHQ)Qu9nVF#@^9zrKoiW~tb?*=I!H$~-#;XG=>ES?SHH4D$)Ku2Py&Jg#kO2ke zrIJbIaR;yHne^YOJg@_W@gzth=c+hc{6F|cPUU*s=XuLlgdX`x{>U|Rd`9TTJ zd^*S3Bi%e{cjTE2S_vgtmbn)3C&DPV?L{pqQ>Q?8{ggT>kH&LrrsY#`>GX=OBt=G} z#M2UIkN9IhTY8#&?~-Yj*kY1;nNXN~Gc%|jzF&Xo6%kp2Ty5`2c-}6z1ezonZ!Kmj z+cTWp^SbEX^IvBa&op!nDV53Qm3xJ_U4x~VL2WP-x4qzOtL+vOOXc`BBU-!4%s6bA zd2j33yWgbO7;nD~A-q=DU;N>lfV3fQhRd1<+g@uKrtc#X@;h~qJnB-@uQtyvGSiFj zy|uv!2WCuAItl!SVFQe{|3m6*c_9(yGr2>Gx|B^c={c1yufC$t{jJ;Fm`%0u65H@4 z7$i+~+#O^M?=dxXuuQ>mwWeP~g=={T>$VU+jz@hfG{n^EimJZdj(yA)Dpa$s;%oQ5 zvNHbCKL*=CGWe{GHB1b54(G|oPe;z_3{zQEJS(?B-IVvA47-@r+21`03$yL}K_{=~ z<^&C~1{GXqdq=GL0wQcT0vui&b>}DaO>+13J=a4!e9`H}yfzJMIV-YB?%B#EB)D*c zEIwAk{uLK7`3M7{9ZrhtsgL`Yyu|qFrk(}kmv7Zrl4RItU}^hRF|G-Aw14>c4G-Wh z`4mgV6A)Z*(@J+3Ki^Ptnsj1!x9O{V)V+hd9Jyv_kEm~daIa^P z@Y(;wGhB7cU+na{mUTCs7v#~Upt|nL6(2zSz=jCkikYMTQE+H%DjTW-o3H}tMF^WF zIr*fBNQJ#L<*V_^D5PniCQdgl89O}e2OaU?hz6xNEGp`el+-Dp5h2sw7i8KKL1@uz zmx!Rk%n=^(&@GFo#Ox064W7=jKskrkJ!Gwcu$s;zaYJ85TgaZ%@AK!t`_lfv;4Eq8 z&NJ6QeeI@ZQ4oc5)eDe5UR_BJ$C~%EAlbFHvB3mucI1i$@KgA_9x=eQqwU|oUfL@#?zl>jk}ai2?$)cXQq6u7&_#>Rs5Q<00q=YVZi%LSB;44fovXJIY^ z*Fy%yUO9)p#>A@hq21#u-6m|`!7L!9p*iRyoSOvVdV(A>{=*6Jm?e82nZ)6a*-#snr&m_6*b63{t@(gcWdifO-X+MLI;U zjt((L4$`>)M}5g98}(WA9#TQunt;OS?zWHTLg*Gi@_vgqI62jt`o2zFzHNdCwrt?bu>)zdTrP!0F`y^NX#eGU$fo<%x|D_XJ)Le5JH>g1&(@Z(W|FO1K=E zTTYQk+@-S-6AKE6Y1|G`p7*0aOm7Za|M+d;+MsLpU+3%4Cx)+X{%(NErK6DS|UfgBde6{JKRzgwg z#0}fxM1f*hFHFEA&Rg>dR+;wb3U@$4eZZWsgCdAT`Q1I7!H4p4Wqe$I7ihFyIXR#F z77p=OHRfsRDw8}?6yB++=?y>|>HY5Fp&S?pbu+Br_?_;kCoj%nTONF|h>I&g%r%6t z?peD+w0GRkLyV3++%aK4hKzwA4doYC4dH2NX`zS~q9f2{0iXb;MtH{_w}6oANGU6uOf<(BHsF6i z|IKS#S2Bkk<4GUWuMYLAe4T-pF8U08Mj^t*z70EWW_MZl``Ub}wv`KQ4jm->W**$*T(s=`tO# z9tQ9Q)J)cc|rrF=xZJV}2?vZ^62XHP?ZZ>N2D$Fml!Q+>wBE)zWWnfQT$0kqMg z2{`PKb^@_+cm4^5as5^M1BwfdpE}F77!aw*?7u}S`ckH5O2CCu3&OIX_yzwjvXBbZ zjllmbyjVmAeGHb(By33Y)~RC9Lz=g^NC6q{LFA^c4tA7w_ot}OVjxsHWp z9koeZ(@1mQc{1k-N)jvwnJhmV_IA8qoHqM9qY^j`A4j>_eziL?wMS>zb?(+5DnT{$ z{;Tz*?8Ly$%L^G;P_UB^hPCFk<{8aDbZL!bSnbBMV@@YCz;(H>ujm?A7-Pi4zQvWN z-N6NJ&nfRvP96pcrpv0hWF6pRQY$}cYReK6i@8M`z3MG4HlfAGtlgeBH#f}4Y$1*( z6jtJ#J=kjAj;nfD+u(6ca{-8Bw#U!Edc|H+xpyZ=Hd%9NsfW!x>1%qvuA^J1fF&u( zudQjD#!G@Ns|+=s`M0?ZWbIkxd8jN{Qev~il~N|IC+;!65+EU-tmDO~j1IEdmImLz z>T~>PYJ9&{^LDob;-EAJW5VZ*N5`D{vICZCB3s)#N6nHh7t&x z>6Gf~P{3*s&&ocef}N&{%3~Ycy74*6wc{)-hl3S6kS&1AeTpLHu5#D09DT)miX8!a zd*k@-Ho?n?w5d!0$drDJg@+u58)cz~(>N0H`HZfI`i-YSW1a&FUs zoW6bnY@pYGSkB|NXWJgXyh|ts+eKJT9~r!Q)dtDgwA@^cGF`~nK=kV6%a=$X2dHli zi@6oclB$Q!O6y`Pw*45hZj3`;)ha(s0xt*i?(xr!L7^Uibr3Ym-McXl(#=}|?2H$$12dA3tLl;5ipL&{%1BQQ)bu1GyNZ|L% z4SH9LtT6h6Ofp=d94ZdY-lnYPI*7OvpAmgIpO#CD9XFJXuufFZ4W=Job!6%$S9VFH zc#?FkOl>^5;vr^($)}uX6_=4}ebpj7luz*Nz=NFWS4~N_mN&##_id>#cS%2&eh)*m z6#9SEQl?+E)wq?3DA>g1QJZ@CMxOP$y(qEJ``2SjC$)p^MtVlA>mR|qXv`q6gpTzs{+-+O# z`?Au+yeM93VrqU8EcwjN=glORWN-b1-OF5bTv-y?YxoMmfvU>-qFXs)wtLE9I-VQH z=jbh;!Khps{ppvHQ9#hgGiX`!^~J^Vb93;+V6W?{L5C9V-@A|CAjFxrI>w7yKb%?p zA5%*x9kJauR#rdZ$q3t;96fdFXxOQ@BgM<-&YidlD11dlMO!;Nr%7MdLD-aQmsre9 z1*0DOn?sfc*4nSvKJQhRNm%WZtizlFMJ_hr=zyL1F(^Sgb@?)BgF+nEAq8xO-;A2b zz>f(fKhOhU0F($Dn{OfR4znj#nv<+=>6CURR_KgN$jb}t?LDR|*Vfv*?YKts8_NKV zAPPS2V8^KkJ+cwTVR&mG5^MkC=SRP%`Hr!so~^ADPE=})Fw|+gULUycdvIVGDw9c& zmLZc`DeAGleuAC7{kFxZI1w?Xp|dk6C55MG6D}IZPI`McPfaSsY7&@C{8~HVd5Wu{ zwl->aH#I%IssFQ^LQUOsc55f%iHU?vcLr_)Ajq1-Vt@CQRof+5S^WqXH@9oq*@6un z>+APrWT!wyX{h~*T3P8ZG5Td~9SsWh8Q=k@Mb8fozJt@uReHK0M~0xh|4 zpnpzEssaBEz-J5UR|x(Oy(`yPVRzma=b5tMKQe=c7$8!S1Cc>qz_=lH@zav5DKv)!kZ!4pQnZ3@HbQ69#~@5)u}^7RfP}3(T;|)CBj* z5lc*RhKw*e;&0ugByi4t1%_usAY1|Ec?~xwIF|tJ-bV0y=Hp|VOZW%*BqACgv*e$qo zObXqGb)v&16q|!_tiQN)XUCKo&*AbE<&2~BM1{6HsSK+3RJXv%0rYo;1=8eYd$c&`-cx#moZ_UM z)HF-;>q#lmin&JLc#qh-ax9h)IA3oVywu-Hy5&bhgZy;$N=l{F@EyM5bej497Xf0J zMx#SN*QoXY7us6(Hhcm zR<^VbBYEKKf(_uGYT{e4HbhtkkiKco&t;+E;-bsS_DMe&8TRfRfkSN|O|vk#<(}j&B8o)D!mhT4 zMgyeEcA*pv2v=PeVU^cw1895KL9#Y>n@XaxuoiQAPmh{*#b4egQ`8 z);N(!xD#C-OP*tXj(+@WNy$aN7B z?dNN_IDjHBlleB6_R)cj3HGW73ySFKrlv<*gTuq)#R1dS!o$Oh8<}9+vuKm&TR75l z{Zpq9T)dirUSY3%^CRi)KS~O0!A_msU5uM^N{a=H58umO{nK!oCysD|tsF{X^CysPuM2&Pw%FCS?~pJEFP`==#+`XS@bzEcWfPJSt0 zSgOgZZ`%r}t&n9|Tr<)?b1{{P^3T*6FwqI~>bemZ4UFhbTefH{pQ(&EmP>lo2z}?W-v!@y zCP+rCA9BC#Uwo`WkzxY<0L`oS7Eb^8QF4mR`D!?b31{~N-uKfi&!8zclN22pbmDEw zvP1t^Aksk1#7|~XVXw_}UflEF_cM}JX0Ov~Y#77+tP;O+zbp-XEscN?E&hXm*X9F@ zSY4EFkJ4uYse@2^Q{5_M*}=pFwwGzFPV2UNR54d0n$kYV2;Z?F17okX(dd0q`8G=Q zZiv>PgZH_qq%2vT$YK<92mF*25_jh@Ref;*<<7&;#Z?&>on=M!aE3=uR=5Ep74S77 z-xu-W1r)EgJnQhN{1|JC?N(#Ew2Q-~>;29Jz`V{)z|pGA%t1E|;?V{T9r%mjZnD^6 z%mWuF!oMPM(0d>)4dDe4CMBU$5!xvJObL$cfW@e(wbclMsPGIjFgAA34?eTt`n2M* zrKKf2E+NRy%FBxYwLlGhx5;nxPr88FtsRKm147|E6xo3^iNr&x62ot`anXuUJW%SC z`jS)^K=n;qIM>45XduV^VKTp5tRwVy5JmAX5hY2l9uMDnGBZ8>Yi>>&yuZ2Mx2d6E z2tMI)&{M&3wmVCsAu37%V60=x%1_uX@j%ez=toV@h9YE0ntl(JQBrL^?})!gpr)Rj zZyZeOc^dN*M2GtiW(=N9HalR?-(?8{=if!j-D8Sk5puS+w|R|U7(Acp`01%gAVU~g zQ6U1Z;G~QUKiF0yYT?@2?yIN2sI9EpAxgq+SpNXN0J#|ZApD_m=1fPE>G6%(N4;=L zcUTOd-46Mya48H53Sr0>V>1VpbTksrllOK3msi`Hu8QkG<0YZw78al@7Y}>*+@i9g z$XRE}$qHr!4|j8dz87M@L(sfztWG zQ4h`DKzltVr(PexFnzn4Dk>6ym4#J(#-9=n2POKa$+@{5J6Lq^ea3r?p&?;mJxm2# zwFikDJMHU0WqDp7MArhIGhZkq5D|%iQ5Q-;yW#KyW1OtUUf0k;R!Ry&GfG4HT>DD< zH+`6f0hFgU;|K&p2n_;@=1W1r2N+V6;8V-U5CLz^jCgy@8T0$aekn{t7r-0>`>j5V zd3V+7X(p)vK;yoF#{}r?3f2WLmQF(}Jd_a>K#~*Cz>Uq#5-efqZrpuz0R8a(_Kg>Y zs%KUjD!s6mgfFRZs?l&7d?PBx)NhgQI)x)am>T# zd^WaU%J&~M0DBX@rAO!XsJW+muJt}22+ik78i+`OUDG1Z65F8O)%galdCDd3&5guD z9m{KOPIuNH;|%+JI5#QphRdQ^81`}TDN_a}wP@)F*6GF3GmlbTbcHR5>6?DiS_M3Q$q zNj>x)!HXS+JmgXv0^Y(-MUVZvAgzINa%IH#b8@G7yXY?wV7t94 zN#xVgf)yNN#XbQN8k_Tq^8cDUxjbK%KV*5G_k~XFj~`NO0U}0_u^Ank1H2Z9(bqWT zoKE{jMWqhszkmGr*p5#8>CmeGu=qaU7KXFLElpi~Uvmo_tw!&Z23icTiQ0Hv8^D(Wl*Cj_g4iCcnoo$9Z86@Xwn` z!mkC9hgDp^@+Nl2n(ZJHv776x%k|##pdSySl=Jpk48ui zM-&Sh2HQSWayf}REsFE)bvY+R@b013Gu#G-evy0rGZFZPz>nS+I+In`EuA<0T5nxxTxVFg)$`gLbM~^Z?13pn?&k9 zrSeF3^M^m9hb685W(Q&$z#pD{p3(g49Fgkd9t*Mh+rY;=(sDX ziULC~AQOEcyOs|ZYM`x%RDRWbh%t6f4Shb|KP zy*ro&6aq!egF~?WO&z@5U^8mIL%lu24R7Gi+g(3r6mM>xUG>b;+wNSD9;)K;!)w3W z5g8X7uBtlA;E74T3|_O!U&0|>+a$e3MP}Q_pPJQ61RuN4N^ATWrSbCIxhHlLqO0er z#@lm)9@c{FTkv zjkL_nDPPw3&)M1MNl8foT;vFAeu6v#U})|k^z8C)HF19=8vm8z>W7AJR{{kaeF{K(-5jZ zAlfuo@Z3M7${V@CK^Yzt&Vthg0+|^Z8EXF0FnuBwL&ot=-pD-~aNX;uqYYH^ZP~9t z4Y@)=Ar3&9f*QJ;RaLnc=(t`qhVXALi=2{f_#-5O)%mOk?-y_^z+&bifLX!i;CX6n z^bs`$MNMzIv6X%N+tj&@)+p6CkppBA`w*ZwaVn>w{bs;DPrT2($!)?@oX4RL{I9Hs z2jRs_SB~T?EI9Xfd;pMc9dS|iU)6x5tl!L3R1DUK&VKbXf@ZzOFzwQw_OTKY3V6QQ z%L`4VRnZCk{dY1ldDS`~ebzS$qQJt4AerTAvgo1h4Fv^qeg$;vr!l#xbq??|TR|Z0 zyW1e$LWm~kY?z|>)7DS7Gpe3Kh+v275F~;#M8mLjqtw&8^=Gr^d1lkm9fRDSCV0AX3+-4Tp`rJnpb?pjkCIhps1*V1h4f2+W-5AgumM(;Jfo&I+)@A)F9=VQ3#kb# ze0<^13;~fme)}W9Q6Rh>fG8RM9?XY}iZ=|^`dmi<)MbWTQ>31=Z$SYM1obln+=m7S zVX6BD9&0cRgUcAxFl_0tU*bZiclVcjQdN1nol7v6I#=@*UvX#1d<8yKyua+FWfaIW zt>P59!g$yoOuNWlIhPsV?E5+?;A`2V(wo-TEoJH92>s)PO$EM|Gr@zob|>wP*ZM0# zJvyveQd&(5Fzs zFY#OX#E;Ip3r=CN2a4-w@#3GnInJxmW*Yp;@RkNEgXyQfqUPC<{# ze^a1>a`$dGvh#txZUBv-!g8G`(EUJse`cCKYUcu6CU*AW)vu7hp#$&?t*B#OT+Bah zsG}1g*wewOTORI9&iWxTa!0AxCopjRVI>&oARGHujU5bHCm;t40aX(V3ySX5Q!D>7 z+CYSNX-0-EBp=vrDSrMkNjp`)oRqib0`UqHGj>(k-1Kx>B?FT*(VHu^z9-*MkdP>H z!_g)u7wa302J}-7>aC4cl(&D2+Vna_bqz}6P~6~$go^4wcYpud$Yk^l@I|oi?CnWt zX@G+8AMT=U|)KnDIQ!5Absr+4ofvImj_kZJ2E|&BZeW3yt*xw>=8yPF$GWgcd zAc>QGCp*Kh{PC(yj; z$Kc^&KgzYvafv5KAB(le4~`=CI(VEdMQo=E4mu>ORGh6oj7P&EVFdq`Q@<(}c#nwW zQ2Z+9F>YjJBrG--X%_;lYMhql@0GnG!K!()LpTTF>;;GMJM#fGg+s`KAh<)RKyE$|L-~R#C3NGUjQ`sd2}t^MFAdwu6(# zv9rWd9|Cc}z=q4%0&IcpwCNc5@W0P*k?$^VOON21rW(OP@(BXS?d|Psmook=L3j@b zJP~0>khUHG-k}~6n5!`zht)|*Nw9hT0?7_V)OQiQ4m?H?ws@5uKfblKl^KfH0apQE z#}nvol@3a)*8q1Rytv^d1H7*>_apnhDzV?2$B&Bz_Um2CoOnDRv!H69b~fwm&&FjpSEedqVpPho~MV~onw%Jc-?nJ=#`uN`AJVF1>vM{n<&G)V`f zSKO%5c5QOJM%Z-h>=g}tW=lhQNi3D~fY83?g>6Gh{z!8nUYuM-d)&b?&M2pudDq{d zNOePx+Co6dAvvfZ>G|mH+9{GATE1$oLr$yr)>l`Ti=n~zO2)2Lb#QE;u)k5lyK6Dt zzbfi9mhSY=^d-Jj?`HknVd)}ds*EO>6Efa=V4~1_ndc-`3r{C+PfhRGC35Kq&z9Pu z6Jmy90WSkX1k+6KQ=Av?h*}&bDc)r;D=;sYzxCk!R&P=91q-x>o~oD|^{|tO)t|eHjQi&F%T{=W zlhB}XzoJwHY0#*QplZJvo6V*nQbJHXtMlxM3{L|i>xQ@fh%SNPqWMJ)jXx376e6|4 z@C(e|oz@)TZs?0UYLl@H+dfz(l498ROUI&HFzdPNB|i+tNy$vJJx3SWwJsA$Z7;J@ z_NK;O6gX_m`0XSOHd;>vrV8Ft>zF(k1z=T{Us7TW>~pBDG^4$C;sT)UpbKT>08d<0 zRD}3PF-M21NP{@?)OA?RenUV+Ox)Di_@kj=F5k-H82DRNw6&2U40u!{jT-<10E2Ka zqHnA##@(p{068#7b_l163VV;SO9#EcG)v#KtZrJs`@2Kv9+)VIxPe5BG&$>GOx*gN zNd~Xh;J~(rcCI2K7v<$upqqDKaPXWoOXhu+3dyGv@Ph!57Aq7?6+0dj7WS&Dd~jUl zp{lE66?Wo*JTI12RFocBS|M2r{uLj1(!&e6)P|K@X}yNDBQ)e!R@L_MH&u}mwY{#< zpLV(M|A(EVtzFT`J$-dMDmpqhTH1PXZawqN#Ti;Rw;qxWSk^+(uUNr)gv`l1cbwxE zQ@Ob8C@D3~F7J%v|8P%R21ir(eQNFQF3=cC7`MzQ(q7cs z+Dn{E8WYsK`tWGD`RMbXCk|au(hOGc1}Hl(KRnZo)E9v{56X=jpq*krU9*%WJrkP`(Awdy$fI3GkfDB!8HT zcOe0b-qv$un@lq@z6;p>99cv1SHp7{4$M+Hr}-l zuVPF;{zvL|(vb+Yn5vlQ(fp(Ka$2(jv)6u?+m9`urF|iE!RqGQrz1XzNtu$hWoM30 zsY`qCUE&MDzq|uy$l3kd0k^S%KkX~I=4RtnQyKPFlx)x2o!yltxH!l7Ug*ltPvnNf z;i~9ty=4L-S(2LCy8A`4c?DEm7cWkrymN z@YVa+_h%H(O53X4m$f5yqS+|S!F+jguRTV1_tl2^H%}Rw!gNfPm$*W{!e+t{rp>KK zsq=FcwZD3MzSJWo2(Eg&omXzW6irNNeZBedb#DQ0t)1bTg0*~ah;=L+x=^y2Vy%sCC^_4ySv&9 zzE+TrR_G5HVupXI+wfYxNHNbQjFM>2q%u7)+@u_BZ9VC^AI{UBh-&$MYQt==jQ$pM zR1N_hDFe$`6$vl-u&}Ti@EMz0Sk#Y?$M>qq?-u~#bliP^SwN&>b91wI!T)b?(U&h0 zhK%`;Iu8g4*j;bA1-ZnB$tqwMrS}&A&k&@gPovRji_y}13JN4pq6vGVGc)E*kWvj$ zNoR%y6$BB^C#y6O->Uq4yA0*J%MGnOaA}2}j=>6t+~Kpy(R_kopC~1jIv-C?G6ygm zl{Mo_1YZAoL&NjrvTADlv+>H9);O04t0@wKjMXQw=9vH`!_`$3Y0gEIh001=V2~^> z67Bxxbr2Q1!fUB_?Dp+4wg~I0U5Sm8q_=YCKJl{p3=X==$?rpPO$HOm>Sz-z$h-kc zknl-x75amnKE+hn%y(X$wx%jC_YR=my2)#tLViV~Hdc6Ob9t#F-;kUD9ziuVo=7<_ z)boI;4rs$adwMqJygam-nM)0fS%Co}TU+<&eUJiJ!8Fg~V&hWtUMH$e6%=kRTJJ;e z$KdosRyA1!l4QNFr(Z&)c2oN^>uaEB4nTv?5?6BX}Vf7Xb*+X zc-=B&2d{b~l?xto<)35`K8p`3-yAt)5^T%n%&7Bs98vwga; ztxbJk18l+PXoW%`;K$C+UJBi)PzMTEM&S5o=Nn)z7gC|co3_a3hYzG;@Ll9cx&fXA zpjLn_h*?axV#^bLI65}=q|)&=s2PAoV~lL!*ZZ_cRAMoPRJ(^h{91ewUU%R`wr5X3 zgkf;Og#f|L-Cg`N3GICG{uRX80)+gmF~j>J1(su>a8le|06 zaQyp+-Cai>US3$=_$#4~9ZN(LZ4$uS<|U+z;AfyP_~wyH zsC)0^@nGUFmiJ!;%`Oqal=myYK51lzZ^bfT{{8!FmZS?2dmAIOKKs@ViRc3&LRms7 zG=KC{5${Cvwdtt#+DAQ~mipZo2JmyAxcf=y`j@TmM8ig_*7PME)hNgEXwI+OS=6E@ zj>+#)Qd?V3s@eP3Kl?ng@52IW+>09T^Aa`0CPy!~wOf7kj^rEe~y2ens zWJb_=lSD#N9ov{+xY;d4k#C88&o;BAM+_GItQCmdHi4e(uPnIZNAY3CRp;IsT<8g&y|OuI}K zg2l&Jtnopv?f2so5B9B@_pdXbWL0+0&{%1>L-WWQYR@M-qV&|Th~)zb0oJ9%`LEop>FrE8NXt;ZV|!rjX=Fo~<@p4Y}- z)C|n4hR5SItoN_tUik_yUxme0<;~#uEUe`M0~bHnSE*FSLxtu+Rm0l_{EPinTPVqc zhf!p8M$*Rdbn}nwZO?@siX6H9G(PS8LCBOkGRZJ+V4ZN7u$8Nnmd>-WKPd5WKj#j0 zYjT>foQ<`qAYSf@pePq7PP;wXqVH!dUV)Yh(L=IIkn9D_-{UOJd+;(2b~oC6bPEe$7)TMfC!oL+?E;mNpP^W2aSiwZ zFhNPc`5wk1{|uB!gtN#*n!n2sd*`w&w1OHAtTHhjwG^5nB0_*q18;4;9_v+uxH~$` zd#Ep8+^dJ2*hxs9?(X491q7G~`UI=PWv+wcV{sRA@Z#mmEOnfR$13;J>PSuP{ft|( zvK{p4F7j>aFmz6BbbM#*$72Z7LI+ zu+G~o32zv^-XAgcvsW_Rk;zTUl{A;ap9ubP^OI<4Zd^kJX#bfC4- z^@8HKVWlg%1C@;~BBaTa7UPnF=MidaKDcjZlw@gBgbj>x4{MYMlp?QLp1-yq@iJN_ z`c5{`xjI^F0)|JmGI%o59%g)=a3*wq;rwdSP8}Vbf!P>@T~g=t|aK(vzZ~@OZby)8BYjFt*_wum3YPGI^3eFiZ{ANh zg=9!k2NR@?Gu8y)RDzz2mqP_!a2Y{ZV}PrK8>h=9M;DiGW_|1_a#7V^9{9h0{o0@{ znjhAvl5G02$nCGS^=nQJ3A~)(w&1Wbs(gzccFAPGOXKYjtwF9OF!bTZR^3ybUo22R zdp;8w(WjImzn?hv_g4Q3>(kw}^P8(Ede+oOrKO{~pAoeX9`iyXA|M?~faoCUJv#P? zn!4O4E$v%rY3q*`^RpYXmwzpPm!K0je@u}i^{RecJp7uZyR=U1yL+A=kHMCU7{)8B zO%tI953nB+7&-*8?|6(F&5FodIy*mC&a}$p5%Q#VLX?v6^U{`m}~v? zhbbpV+0l`+_UE-iRkw$Xp=&_HKvGLv-%;=XMR2t@$4GVX#UL#+w=jXyky^S%-P z{CVw?NJU7({>N+Zwglpc4Pcys)!`CxxNv^EC@(Jr{(i?DuEaw%F}w1GoS2@q=QM@C|f5f-A9 zp|L2JX@4uP2cVdI(U~!%gAaRLQ=jmM&Ktk)&=#MkUQ}^Qq;~S8^^)s)TAI`42@8L= z?*k0q)rjw%w{CAk`RwyJz++gUX@bnVinp-l;AHu+=Q~DI`NM>gG?Lv3@|HBk^`0Ia zr>*VVpTL1N@-C)4>Y2gwH|`8>m*k1|0+*z>>dXR#^vu7f-O+zgw?Gt#^%v);cF4Tt zsm~zyXX2Sm6`N^E!2VP5L0URDx{Plb=ot6%G|ZAnF7ip{aP6CPJy&cjZ7f#GhL-nS z)DDwU7YFDLli0r#Mb_OMvo{~2OdnUdWoh>8)t<1C^+^wbL++k|22I7_l!PnQt3KKJ zxAg;K8e3IaLj?FI%=+=Q+rg3tnp3>t>8zOKoT6m;T>@U>tn-0+JltO&-86oNX60EO z?SH>fo`5oLX~9($YEJFA39b)OU8>eUkbb>3dtRKQw9A5OrzSP{@?Lql(Lsi&M{2Om zq;(TXQx+T1rOCapif{L`1A{`G9t8Ne9ldgh$#eb?G+ zzGv^V&N_R4Iewb{a1Mqtp8LM8e`XqL_8KrN97WOa5;KHkT(*Qfw&d&^kp%_%P0ig9 zwtR-y0tw;~^7Bifp5f*RKPe+8r_%5d8Iy;jb?g?h#3w;01Kt$9c(Ax&v2@`}9eg53|=HzMh0)21v=gK%% z?Cy%3?O|ke`JLC}r_4tLzNijnvfqY!Td5p$<-)fkB2~Xidf1XZWA+qva4{PG^uox~0LP`W(4y>TA5s4&%kwfDbHhpPs zaq$z(1)%uSEKjv6%LA1@Xlrko^rRCvoF8n&UOBdD-p(c5Im9I-Bm^lg#A-YQM-d3& ztY(@nB1Z!RE5Te-J2*)FCMt@$shQiJeT>sp5klzS0UL*hhldc$KoKKx%cJgC*PI#1 z>^zMWd?H56Hk5P2Nhoyw`DYAWs$x83GDjhm^1V|_xpxej$l(Ti2bwej50fJ&5YJU& z(56USmRpbgn!gBW{hJ^GKO;}gPlSt$OIksJ8QS7u zB7 zbn?QC=;)ByCC5w-6`1en`Dv^vehHs_z;mwJ-Y2hbC$l;0!SBpuMbUV={`h|Z!4KyA z+v%b8Pb`~dZcXJ8R8AE7Tt_M=nw~{YEZ6-0w8_(!^YgTj_i~=&cBEXc?sF^BmuNhq zTg6qsRf^VUOicXqM@O{GpILL{-?$d37Zl=Ho43O8hGwVeV^-F_jFk`Vl9U99kTxVN@{`lr4v13^}%e&$J$9-LGz}JjP)}hjP`V z(mxGT^iNV|4!I59v>P{WAnN;8`J!*I+0%wN+ueqbPr>Vn8oK~u0+{>Zk^~8?i;D{# z%^ZcOi3vNv+pyn1e)?1zXl8IBGqJGjHBJ{7N4PKcut6I_+v-R;J!M`>3h^U;tJRe6 zJoKeM&a$L=@5wLC&r3miHs_D^Q*il!R2ORQRNE`rZwm;Z0hv~5DE>y)*~0{Qs+*r* z>udc28T|r5NJN9*oI!7wE`{!HYl_Rt3PHck9!JPyO_$&hmZVab1J|0`sgta%tS74a z`_-o0AR#sh>?(rxcTQe|p7G0P&$ca^jtL7ES<9x-LrBn`0KESFs}I!rMYDD@GRu7Z z{e_8%_24}hsTOUi>x>oF-n&SE3&qv}*)A!M-}?J&r=g8yWohy7!Zm(Y6uZNqkO*p1 zSOv=EF}vv*N`Ak<{e%n^E4xLEc;~3=AQXZV9xLRZ0#RVW2B3tR7z;uc54eM2g17?( zo8{Kexwv*}j3ocLc-Y4)|0nKX@jvkm->F!HSX~wW9hN}K1!+5i$Pvga^9LZT zNDJH8&H`drplvrH-4YsfU7p{$vfeX*{a=Kz`|In4j9lOe?oY}0=Gk2X2)l=Gu!w6n zORx*PejG*)aWW{5Yiz9EKBgRBR9K}6WG0We=1%tJ>%z|bDEgVKmG~XD5anC+`uvU#cnQOg#^!)%{rpx%)=ArvJ+;hny(f(@`IyG)YdM);P2yReX zI#>3OT_(wWn{df%X)AZnwhKc6B+~r=_trq+C&}{B(woy%@Z>&?`r_D=;PLh(A-=~&NC>81gU6%o@Urww6#a|lu+RNhZA8PbTe?`L)LX5NS@^6d>>VYBa9bom<+%S zjw?m|kJ!VI^c?DtLoob6&WeW|b5eG;FVI?9;Zj}Oyu?4-^6M%O5500X#%04}@nbDqiI6>zNJO*CGTT}% zEhk4eJG**W;O?1HP0!rit4R_S%`K4&bw4lOHdNqoJw#hC58&*$BJrib50zNY5yPmy zzrR1euyC+dQ+1P+RP*aMvfIwP*8m+CmrT2@E1K=;($=P|Uh*i@@~MWCZRzpSqWQ*e zLvubsMZ?wMELcuH#>Rg9M9?}HtvOn^(Cu@<+wKTz!^%>yLW`Sg989jFmK$eZ=xurX zwW{sq`J)>kA@CL#mmW|ANZrrCfD{nvFyOB|If(wFnRyz#4~Rbx%ssmVhut*GrZ64= zzysbs;0hqtGDg5wCTktU?H8!1e!+-n0f7rZQ$rQCI785rO_4=3TbK-l@bqxzLhIk3 z(DH{T|2&o8Bt&8=Vqz};)t7+K4p(;~*5T*a1r^j46+)op)<|<7_)h@z;-(;@j=6cR zh9W@3FQDb@LEh;8<5<(}E(@cxq zpmE_1tV=$ZI8#ASKRm2CZB<%NsH`;sST?vJp=zgx=L*QPAe>)eW~&;X{x$ToLi94Y zH|DC|p4Ld|?yCfWmX|?uc#*U3^7~~q`xKzxVmc<~wmAL{b6yd6SVjh3JR+*ct|SkACNm1I$_n9=ObNVM@E#NH zvAQfTchEj?hC@xQ06nZ&p%761uHkiQW-zw7nS@SmPdC@13e#aZ9L}A8Z%0ytyy732 z^J3pamXmMeUceH}p*48l4WkWk@_p=u`LMc%1_-PA1>rg$&>;FfR!+bN55bGA*;e=p zVH2o-=OJWZiEYC8^=tV0-~GBrJ5Rt4%;N=D#n}H*Qwk^?$fL#5@jK2rvZ4=>21yvM{iz+67=p7g@Fy0bB>L6HXx9 zhiA@gpPiFV+1%^Qys^mxSAv_Zr+=ePVs;4$sEXKdw(?Obp0>H|4FY zy^G(17iH<%uw&l)cE#bD1TE!HL@mEnZz1@i#qli{+)4;~-Pk6zWJ9}P-}39W@1O|5 zBigh}=m)GA!6Ww&jDU}SE^!j61QO=Eq-%?YYG&ps zCAL?*?CG!nI8;ZVu>}_dzn)As*7WzU)^6VZ??bHVZjN;&C>MlGyeieLtX zJ};!J8d!&hMaIeijY^gZ7Z98O6_*1H>s4r70Ny{d7EV-2Sy@8JI*^OH>kX9!`LHJW z-K(2)f4!)31Gc_^kz*RF1087H@q=oed-vk5^6UznJh{{}!O)2%xwu0HC%onBGqwGD z?qBn%PNr9qq^huY!tmLEo#6$>E~g3N3BAseRn`p-s=2~7uq!oUYLZ)E5lJOVR&V0N?YXL!`D$`<9)LS=!EUT4H&hI(**etx8{ zL1PRRKnhVAJ2A_3-ej-FDz%PLK|!5l-FW}Ru>EHPv?9lL#hu_hlc=pW7}lAoT0e}| ziT=2AutSbX3nYp6Pndpvwt#?Y)WV4og_&b%4(FbszD24`5@k=ps>b4JCg@mKeV?P)>iI?5~QH_ z5~Dru93@R4$=Fuu*FY(CtDqkudvBqO$UY zcu(+7XcVE~jl%@1VxXcFq#{g1-*_IxjV)HP7m z8)Z`03x(f2Cb6Ucy^y$VlbB(0RzqKqSoS?<&o7>M$gt?*nDs_6PwV+sV*=gHeh3i+ zFE)_tz$SyKH~PyLhQG(cV^2_1QRM?Lj#>8-xf0Rbi*n!Ylym_{9|(%^Ae;|2N60Ha z@vrdmseGdl$(uSBtSHz%TUz9xs2au?5J*@7xxX{14Rs{ok-4|k!sP-o9i;ec?0D5a z(=a^u>XRp<>QB!Z3>MN4j!aB09THetl6syW6UAbtfRXy((>#LP2B0sjuik%}?qDDR zxO*fv87w=K5N81riLX{OGVm?;-LY%fj9SCirWZZwtph_|@aNV(`h);ve&+< z*1Z07=hD*WpKWeUK~B^oPok$eb}y)?H6E+k`veC=M1=bl!9yYQv9=BkcfAI;8klNd z+r)fK(qmFm%D};^Od^5mR=V*~6=Bdxe# zvwmQJLP%&T{_qPg1;y#Ri^Zn>9Q6Tz4AEc%2r0v!g3O{|nC)iUVw<5EBJ$Sr`)Skm z*3qp;2OdP1OHK%uU$}hve*QPYPBg|Lctz3TYaZ;dkl|l2qD41&PlrCVGHKmQ3zge6 z7UZIJ&V677ADO`bX3sAy)Q2t{xyU;nko{=u;N(<>c0wE$P+keTcyKA%eMbFDq&#bA z(NJGs91sUchC~$qW;OIo?uOG`w{BelmKh>I^sa{;Yh+=FRJhkMVrmT2EV?UgRnH4yJWl{KJuzuMXms2$>X zcCdFMP5h8Bs2CyVa3Bs5p)9B%)bn3>2%rF7S5{YVe1}VT=Iq(4Ae{#^J*QUWV46$; zM0;=?di(k^L3BHmen>vRK-N5xS0>9@A#QBU1`>X1ba7G9&w&9-n0rA)NOeo}kj$3u zF4PMieP@BK`VSmkU@u?*!O^8x{%1J)`9VDXx!L$y*GR#Bd(AXBPeBbj_#AU;V>6Pj^Bn_rHmsIDn()qL!H=H{SAMTs)7_2Y7XA{{AyY|~T?2igM@PKzb~Ou2eJ;`!v}kUdLbnhu#78Mz;Uxr@X5 zanAP{C=Er&7yR%0&q<7FqLk4~?78h_lbgOxk-h2z>9&VDV~(@|y>t+Yk-GM|^0RZa zwRONOace`0yGn}MM8QDHvz>6e@|ezs%KJKM5&3q+Ke!kI6?3&Frm`GqQ;WEHX0t5G zA{`fTx>A`nTU&WcnNs5<)j=_2T{plTgb{3&T@x_f%IrG}=In)`>E_tqcL zhB;5vc-Ylm{PZ$2vyQU6SUv3Dv;b5)X1v3Lg8|QLF?B%cJ=Pk*Y`uVTWS5;@HKf}e zM9N6ay|L2oIRJ8Bw#9hEo(~-gLeH^Khw{7Jp7SDL`ZKYDm)Ne$rUH<^xp@~NZEISi zb)XDU5z0*@9R2(Z-hda+`Gi8eHqrd}Jm1@^51#0muik&$bt*Gc!0l{XFE_OsNJKxq ztf>)J#R?mBJif!l+cl_x-I|QT?dtY<&!91Rt7^{yD2 zuQmbsS7N*L@riL(X5C%xFvbYpk>Xac+4StlN=sh_$u?}p;0Kp=aNvX9Z&r6VHfESL zTTdBDf|wOixGx+|Nf-YKEP{7TUMvSb0zBF>P%X8pz3?uH&4A_N9o21LY|A#7h{{~Rm!;_j{1zi1@Vc| z`=^A@RJ};Y4aV{A2o5;=@^s7Vr_=XWqf4VgrU+FLt++^UT=*gd$PplCTiLPl@yhV2 z#<4iX*-W{2s54Ilxz|!AUY@p9TVxf~sP{W>F@CF6jB&WDoz#mJe{DGQ9xfCjcG_nDS9Eoe+#PoZrzI0+)(`5I8oM=m^HH2>tC)KqOS`RZL< z{wxR6AG-JLrBFA&KLqY}gfX$M;mc~@f_O7Ayvv5^MVPX89s#(|MLdEcYwxwozaH=U=`|hmn=H>85sD}r){&z2=p3jWJk!0khVNaNYj19Q5xzI$;Vv1`f^yrLXg7UTVQNW3*p0?q_h=y~rkigP z=@kb486F>yR2uh}Ui?ewe))Lg@09K*{$AIaS$qW&sqr5qgatkX80vbZl*cA$&+uYo$oOts~OI%LdMz%*f6o+n>=Eu}D z(G!jOVU7CstT&F}HzeB&*8OZN3#A{!!<}SgNY}LLlS}rQSQI57#_;fiSHo$*kxsnH z%gd`jhPpi@|qh4WE)d*ND z8>*i#zmFfu$&3ZxXDV&L*^2}Kt500YFNB=Lf z1@=0$;OfcB%Bt<^+T?AvL>)|3`~a}^{5|R&x2>U78O|%~B?%Lb?8jxH$q7sxxuqW; zCo%zffYbu0KrMg^&3!ZTo4-hYCCe#Py9DxM%8!Z&)D)2(Y5{feU+&!P>Z`1-4OY_U5e}zKo>^lp_lMu! z#qe4Kh}OSAs}52uP~4~I)xQYaN>l1ex05^h@9B#N6Y8d z{wWrvk>Qq_X=Q558$?9vd{yr$)mnLpOf{lPmDI8-na*X3_2+mBM6L2OpBtBv=T2-u z&AxP{UFbHPc=^&_j$_VWu6~a!&GNPhF$;^r&*qw7X%>IeWLMLDDNLMm8%f8|&(N7Z7kB?mg%XdJl^X4D+BfaJ(@%4Pz2b6Lxc>0tIP9s&pM3s=)m!xERut>>(JPI~1H9t-b8ot+6 z=OVK|LB-~6i&6R6wu+__OZ*ePd)c6~NAmn(83AoX7?Q8At?ds7805f$dxf`x(YqtZZK%Ad{0G8xAA5Im?%cG?sK&cTK415(qUirm~&0D zIY-64sP(c3-rB28R2L0kCrc_?ZC$@%#~u-kEHmWYBn6zaqQdoaB*Aku=#fP;_= z;~Kg{Rw4qM=2bA+yJHW>pluPt>S1I1DE>uZ1dhS~5;tL82Q8xz6D%0^M`<1?J{uuc zN<<_c-jY#RZqmUN=Reqq7E?($CZ*t*P_d;Q(53s6>JGM4Ym+vE#r|l;j z7HD0f!gRhi`B?-7DXX?l+e5RGW(qfP%LzYVEYG7~qJ8&ma9WF_OH0rUpD2DR8$5so zg`wW|L^m1AHGPH;bmH;(B$~Oo`a7lZPO)_MxB(MZs@Jd)5sddfuU2c0y*W;tD!FO; z!f`R6kTov*&=mS_A+K9X$e8@Xy!?Mq#3!5sFaqY?byymJhH!WRpgaWxz7l_b zS=D?RsB|%Utni?G_K3VU-=(*s^3%@ZiMsi53X3Z;797|=T%_Jf-AqF4|9mLc|Rw-7c03p z^$uPXuMbZnZD+p%Ur)im+V0&=C_wk-qj|f+H`I{oUf5F@NFDoWTaZmp(eGztU-~oK zy}wh<@3~fEIdz$I0^;ko=(In+TA4RZkM;ep@euicr<~6v{vQS4^5q-+%Q4}_1l`>`$L1cNqhgr{=&??n4udOOa83-g$TMmKwX z5xO3CI_|!{zD4K7|Hwah{*!;m{jR9CtZbi*sLQt3jvGo)w}ss|sv+Sq0+Ues`f0q5 z;)FW^0^(;y=e^)*B!bb71pCkzKBvUBf?txhfjB7aE9}x3`UET+xSe0S_Jz4k9DjJS zd=-3>R7LLOYQ>t@wo>HYiL4D16H33sIYBE#3%FSzZjDqBrwiQWu9g8(Mwb4 znCo3@$tpVOee5}Z@*;d7Xvi0i>P%9f1ZS*ObQwGl|Q!)qtEKf(E$1#Q4${z$}0s-{Gdvxzf#C3~1AQK{yM> z?7K}fM0bnFUwMRfISY3kUGcTq84$Sh@shH8a(bc|GBoOpvRD1?32kM3@37jkz#_2< zf^6MauYCU$WY79=RvPUwPC>XU;=h7~KdMqqgYrKW`ip!>IU-00R{L->bHU}-nyH#~ zhM0I3>YHbvEeK-RJ&_i)R^BmiMT$Lteid2AdV9gH2U7hW^&gOxp#NtIip25ZXhr6l z*1{t73uFEZr{4=hunp8uu~^Re&wgv@-@EAIs&kcdO%EV{Q8hJsEv?f8gsPi4&~yuJ zZ8RbxB9IYqfH_>F;=3F z@aR<6*T(~TCoWO%l&g6FYJKgh;bFg8ueH@5Y;4k?5g;U|pqtrUAtE|=Gb5|~p|muE zRVjc;^a8zjKsLPd_$gXYfFWR|;@`X}BNGgzA&QHC%JfIE<>|Y-0A5RdEug+HS{2R2 z!ugPaKUMW$s=?hqBSYvCXC+hw-~!t)Tp62qysySE0UdG^AgBP$>znP;6QpD$@qw39 zGD1ACatK$qMq`n%uv#yR@s`M$;WAtCtZm?8;P3V&&CoY2Y_Hu;R>Ew>kN8m1zHx1q z2ga!}mYXZ8i;L#a%jtFhswBI7bw@{_*n7O^1_lp7;hZGlHwn>Z74?{HyTi=ZElwdJ ztz&zz9w7q6pFfRhF<;~VVj_Z}L8a)x-}g^00oqrrA)y3zdweh#Syb#$NCwcY+^XQ& z>9#xCDCt`h+cXWq)`~}Oj*N^TMaWR<4P{R|1FQX%l$7gW!6Boep#eHXR!b{ZU>Wc` zNchz=HI;=#CisA8H1Bsx(r3)tqtOFL*yRdG^)Lg>KK8|!{ZoB1Tyh*7-t~kV9G{)p zzZhv`M25IZpt=pSQ&hN5j^7tY1^+Y0sL&W)}ex}w%*j)(X33?+dz1KWTW4{Cd3V*INu?Hk~Ny)pI>wf#9VG9M!6AWJGeGxPE4#r}R} zB_%NlPxL(txClX5l@I2y<&~AE6;9mX+Xc5(*j7JqAL|ehv(`w~U#J$$ z8Acs#%7VbW0fDy{58?7adQzY@0Lo&KGHCd`17TAOt-5^R70&d77rxEIQ{N!@XKTRB zjBS~_>HF{K1tkwSpUt;>?fvPO^D_$0o~YIK+^<~n$xY{&r{c9d35<`qGg6&thp`8~S| zaECIx%H6W$5reb1`GhR>=9<|{=iK$=u>!Z4Xh;8Wboi4A`NPjIbj{vWh6!7F$7m-x zD_KjAe4MQ4ieB96Rf%~#21NniDY54;L=L7Wt{p-dv)bTG>hm=&Z}{Pg=t=Isr*e>~ zoCTS=ob&Tbx-=#}YdWr_2d(nvZm!B%+eK%`F7E8zq7^IUB;~`Q1}S=uxM5U*?Tzd8=%^XNiWHUb#UWRvAa$!2yeey(KqaFBM=643k}C zD>NCN&v@u3-{G%ac6y$g7my!EM|v*gteP^{5om!)v0RR}MJIkt3%z*{^+hckN$l?K zbd*&DIwH(;Snsm%zWSNUmlOk()X&XyKZ-LmW)>3~hZXl8{kV9cv2@c)VVGU%(A7|z zBl`V=1Jm&fyX4awW^UTi#`n(XTsPgewUs{3+s26#4!+*>beenIK12r+;(_Tqd+r?K zH#mVLzaUa}m;Kk=DEJsLLedPQK4p~=u9&qX=|#DFQX z5wPl$c#Ff%tOo}>a2;K1IB{`JSyr6dKL%2vB0${M7UH|Vo0Sa>-~js?eOZ89LS9}W z+U@(R-!(`@09Z~DH6ZvBW?!bb+1bXv$y$QSWlbg4P(3r-or_<-5C=yT<7X6-zG-Tb z4Gp{Y_(`~wqa%Tm5=%1ZiQ*g1%Gk#d_=JkEhGKu6n1V`i+_c+HnhD%@U~ zfXCoH7{hm@j=Q^eoF(f~U|Sm>9~Xrx>gWmXQjyFJ)kKGNLLX4eut3KcqolvCp5Eyy zFbTlFTtEAd)J{q19xW{`4NEfA<5U4n_4|h-7dX;E>Mx>dS`K$w zG|YG=rlvzN_z>R>8}0*epMe6A`P#LIOD-Df>fp?7WZiK;Huv z5~LOabfc0`!~*8jSJ$Eg3S1PqR(9h3=ht?(lXy0_nC?8U!C{vB{08#JtxByhPT+e0 z141JJMCsWoZ8|BB*~;x!oOJJ{L6mzTZR4Gjk+X^yv9YYETT@M=8s$i{z`WRa@)JLnEV zfFj(55fxAEs}An2-g!5Xhn5djm{e?7-5$lYNqNg(g``A}A;Poh8)pbb2z!4~y_NLR zWM{FpWCxq$XQ$6ECbRFpjx32vFw9P?qztf4QPUqHYz;?Km~ff0?PtE39Qj!yo<(oj zddw72sMdYv_oXjTkgWJX42X;+RzXdP(K$yxd8^0P#YA80j5Epj*NR4`Gb+>XUWkPb zgU&Vvyj+@(SSRrsLpm|m5b6BK>v)^Tx zgjl9Eqx%n<2o^P>B4=k6Y?aCU3Mf)b$}Drg{0Or&6%*mC-?4jgm{XD%R~G(~;qqEo zzlvhKCxz|l5zm(98wxVhjU|Q-f(bkJ9Gi=#zj8BX8@@!pYHHJuiY||G4)mkt8WGw@ zZ4>4+zb=1ABI%|-=^ei>ut)7qPf?Rr&5*NnB`YQ zW1yzV;Anp%a}2VWR0!kCiD-Cy9k*tS9E)ThJ-PswHLwwrOG}4!l*K37;)G<)%x;2X zK}A&+3DmQ(xeGYqNWsDH?rsstcZ73E7tX8|w5j=%%5!GAx{15aW@a3BB5^K7q~m+q z+SxHft0R(tS5m?UAR|Ie_`CN0hMqJ%Ltd4cv1&0uGoL`A0F7XIwN#3#R7x03XmxGo zXP!LS^X<~s-ZD1Nh1zut<~QNBYYqHvF4StdE;3MXEI)m8+1&E>-+_HWK_O{yr@!lq zOR1#Jn95d+28X1g;#z)6-lIlfFO~vhczFAt>*{8NjUCQRDNUnpg88)V57R;@W?=!X zI<#Q{{Yc0*rl#$f(#khz?LItZ3bU55urQy10N6q916f>DIVrDvP#EieFJq>g8LOq_ zKB81PJNwcNb6^Nu_wU6|?QrPavYwR#LmIfuwX(jJmX<=m#8*ftL2@x51h+f!9xQHF z8q}S>zmhXrStdZ_;(i6b z8R>>84zv=8S*vno_-N=TJ$A2%<7$sd%>Np+UiVsVSS~aTCoAd#?FF`4EZ-?EMXojl zHPpN6Ia>P7@6#CyIGOTYqw@jtfge96pAq+Zc(?!Vb%H{Yf71d$V~OOFKp^k0UoI>m zZ~RW?D10?mt~_aR?#lLS)(O-HvL7s;X9aVbu?gsI9v8!-sb;-EL(!d6%5^ zbI|J_sv2r*Js0e@ui)<3YJR>KEBta+%HR%r%3}p_`N^)H$jbpyAB#P%O$E@Z5+1IO zC%VMgdJJ{-sfko_8Qo%OP-J#(A|kCf5iZyg&CbX!|zTrO;L7 zCZn{`8yj?0OP7M(c^#jy!o-e~&O?mEu>V;m&^m_rd7%%he>S=SK6$l`jUU%gSO{|V zh4gq9mN{=$9Z0nY`6><`9)uc7f}0xvW#~a7TrmB0Nyp{wAGq~9;PFpG7aVARlE0UX zq1;6l78XEziDc`5>%ZwtisoHjurtWNUdMZ(=R?W$GSBMB$XIu8Rah#e7uL(*-`@y7 zb1zXtO8{Qz*te!%zYqm0varHDj`++W3EI-mE;W}U-U77)R_~M0xl2YH>v8|8-k9*t zrF%arbaZE1D7tkn;k~vzC@fs)0c~tLa8{QRDH=?QQ*$GFz}8Jl51iLJlC?0A)rr%*4@l4j-Qc z!q1s`zJE0CempYH=Q{x8{%T4hKY`fqU1Hdj0Et) z#SO}@y)(#^ll6US3}Q^BWM!3OHxDe}(CV2og+Mj%HoyrD1T7e{LEQFurV=(*B(Vj2 zE%Ng6aG+&sT}@|mah~b6!)zpnD@Grge!?mxu^pq1S1dFuT_Qo*7#ti7o05rj65sXf zvZ`$``*h{$h#=)~>FMK;78@EG8W~ zpY{bqhz&OBXaA0{w;%*TjN5QYfV?^Im%a(7oUJFkrODY@PbsFyI~$>)VNW&adwo`;-z0yL1dBYl6axI&2`gwbPgz8a3c?fpAtH(w|1eXEd; z+cDym@N=f&+ShrX(VBZr&p;v|s{u}no|k>WC~lp=fJ2aV7&fXsFe)3VOPPrXRl?TqWBOYE{9Go~CXbPCyoY|S4HNV9EKs50DTe@FhcZZ*_ z9^^{iM>>KJ$mj8(R`~#WZw8Nq0B zbhfZ-`Hf2V03ySbu>_9xv~S*QEMJ)OpBqU9^jJT;FR{7!=IxnuJp-f0jdbr<7yW8P z_x41%f5USR_CFHpCkf4G&Zz(LrHX(cgG=m5@1YslKW*-a!^fiTqx=UCHk8;4*`vgY-B2i6rIi)27-kfMP_p>u( z)5_xuRgl^OovMipI5=e|i?o=ey+364K8+!bo$UFjoC8GxO*1VeT%qx-X+~-!XRn?O zjla=9s71EofkIcSn$uQ3RMI3VjkY@3=qTd#j*}3eP6pr0z8~fM>)0C!wlK4w_jnr` zJEdk;4O=;*zV)txox%19kKNDn)AwJ6-n2~E!{!-p&X?Dy)LhEsc&MiAt`mW$X7 zZ67uL;i(9h`K!gj^Jj+w@(uS_Cp29S7$Pl1hA-;nT5FNel9EJT_71A8*rXApnL1R{ zVQ}-iGWlvrtb@DnV#IHbfe!;6dtu=GA}VxZf{prM@31Q1>X4_dP#yK*?cEO;a+&yt zt^(wHZ&Xj8871=_vNj*U8Pe6&Td!-B1k|-nLC|O4@OF}de@)|Pdh@i5qNEqSgyaVN z*ggyjuCwU4=gRp==;yf}9<5cK2xgX>p)yp9Tbp}WUuaan)3mj zEX}*_UiOnOyJ;pQB!ooRgMk09sDF?|J2;dywL|^~ztc7=1l!+$hDUH$xKU_XfYix6&aB6O)@nadGV=WY!=* zQ)Ragnx~hGMPG;0+|r7mVGQe@|BdDpD~crb5o-r{9S%^3(NJ0OWZ^u`?n%HK1N{6@ zix3#K7+x?|YV8**NIOv8KbPO*Y7a%c`~g)}sC)Sb*r72@Js4-9ac;>HotC!L50Gsn zbkvbS3d%E!3J)R!TD!)g#n=RSHu?avIQlbjJkNj`nluJSE^39-`LPkc0 z_2x}8uJbD%Yd500#=e`;Ncvw!B)M$LsexdYGR>>#YZ?Phv$Ue3hQ!2OzD1 z*9GrAAemEZYXLs8{qF9^Vh1z=HtB<8mo7B`N2{V2&a<+01@yV9o0mcF1ljB$D5L;n zWBL20ag)2@_5=HH#e}JqJU=(r7{q~rK7vDce>y}_1q;bB$L5P}&~66V@aK8myY5yR zeffs|R=%D0MpP})9V=2_5k|A@Pjp6sRz3{ z-}33r@;bH1qH31rmpZJ{Ji-SlY;0_$v|>ITZmn*An!i@~;?na;ll!t}gk8@bC%<4L zD7`=Pa@N>$=oD|;!}!`mTZ_fKzK{wYn)qH}4Vv3SK3F%KZQFI7wSc!pCl~Tk8BGH- zhN(Acj51%4d zbja0`v*{6jL%KG;3(QLN-0e9p{)tGK$$>ET}gsrYMSlQ0~FOKctP|m zx&$@u4-YM1fpL5XCAQ-f%x%no56TX96>=;%-|={7s7R-L><$7qFtfkVOCOnT9jXa_ zsC^viFv&7XMJ{e9e=dt7Ztl3cmQPzFI~O3V1DldF&mKIWz(z^A%K9{|NNC+GPIuVHP1Qv@9_M41aLh!%V(Oh~Q$hO)$HshPEa+D_#dW?+1=y^tM$C-=JI~>t~p^!on8y0#I1f{X>+5Lfa*P>#4Q0T;Y$g-cXsffMh$w$O)J2`08pKJP(ut zHms46O_ywIYQ8@=TanZl)bRCNp}Ob59l?*o6-%7ru?0ZEA0j7~7_Uj7nkX_(7t9w6sR9 zO&Qwz=B>==M5Bf5mMNjk7ZQXEKq~}wWDOv6>8%d{uj&R+R7D4|)bbO8KB?QTE-rko z=(_;O%`Yw@%`q*lt-Z4kp&@#b)BS)DQmE#@1wu=(HZdV4FMkeF9U&VgbM}drN9vJQ z$!oElJs6_Eb#aY{=gAVf6sVn(Sj-*@4pTjV>4EXe7>3{m1;E92cXxY$8c#|hBm|g$z;kdCMiO&3>yT)jvAT}F zN52&=%IoJe-5+IYF!_2Bw1}W@W{7AKyolAhr{>UQ@l>R(^`4}xt85mZ+}{ukXZ}Gj zWIj5q*dKav#JkTmo^vvEnKRKw#~4A}w@%Iiq3|d~yL+OHd)Yv0eO34I3+2FY+1r+; zQ#SAJ`w*L^21wJB$;HTbboN)@sS5Ty^5lT5?N+Q`EAP7Is`t`mwO5jZFIM#ws(Drv z)T#di2LVUxpBzMeDT{(|Ca22O_o|hk?b@H(ryV+*qf5P6*wXX5*?nV!HgE1(d4CAI z6dcqvP4sQaBeGJs-pNVIE!y|UQTN1km*dY4M=XmP8Z?rJQBQUCj6M{-y-FJCn?A~s ztUCH+YwL;VmAR&7e#LPPM zOXac}2KB#b2;QZ~?n{EKRn z8YD@IovIquJ_rVP7ZnUg4?#po>J8jj1Nd#c#Tk;v&fXIWQ=8HL;gBzH55caBi?4ir zUk%w5=bzQ>NZ{qSVfBrQis3m%qTT4-kAeV4MkIYeHMh3P94{{loK)+F=Hb#Wk67od zdVAGAeiD$<9;Hj}k~zVCol^-og{My$W@gyLAJQfyj62K8T{eF8BPCp0Pme00s!rTd zYdAxh@C0p4Yy;#BS+%r|NJ)K`wYB|TkA|L;{}y85OH}&rAs=YyUV0>$mYjTno?a19 zJjnVn0H25kRY@89@lbKM3SyH`K)G2L@w`@5Gh_@3Weky~8+ds>_`LIBAjdD1CdW*@ zpH@zPfV@1wcO8J<(`C3jmARpOJUnn=`G?f)BuGGk)3p@F(X@v4Rci2oxy$b?0pkF$ zK17FUqTFcTK&(EJ=ns7;xtetBAKi&JWIi zNR+4pn$`}6-;vtXZ=-T^Gpnet^5$`A&>Z{d&ID!m9#UrU!dz% z_N3Wk&?Y~!FY+&_HJEmd^Xg_|;kE~oG$vIbXiIxZK5o%R=JZhMyfyjCjThr(Z};=| z`pdKztri~@vVJ8fSbQDZlw$SxQrZ{8RIdy#Hs&^&(?Q-@XPxQ?0GEE0LXg}=)q zU>()+l`WKw=o_n@9qXwj$L?UDT1uvKm4V|XO1y3G@{#bmX~olSPZuefYlS_9otnFyaVDP zwX{@uq(?j{+k-9FJSK}(jjH$rkG}Rx%f}3o^+L5nCv#BNL25d(cJkaJ6eIe|cFfS) z+dKUJ)$-?s)oN^tj?u+GS^+K}1bMT#O)d?a9=WQYiO2IP=H}Y?mHP}NS-#J( zez7CE&}a;269M7*7lynz*Rz}@j5+fT?(+64Yf!smmji->r=Swf@4A}sHHP~KsSW@% zod6lN$uVq|MBd_?L&n^=X^JRO&6upk-=*DR2E&j00yc5_N9Nq7o@DX+X@1JhwNtyI zrlun%DM`1bQw<%N%QH`Qh>CH#Z4FiKrKZ97Ly$%pyNLL3UGi%ZE^=pR&e=}1zBvL7(RN8rJN=w`AV8$+Q zv^QBq@DF@Wtaa07Y5XSXJF=3(Nm7E+cOS-a+CX zGP-fqLN;zuJYkUO$Pe?8*!!c@Cs7-9acg-lepBnhTzY6xYO%j_c7U>D_iXkq#cI*b zyVJbsarLA#MP_~v{Ti8MPJieoX8}BkYWvp}*nI(r(jV}D-h@fas;%0?Cq*l#7aWQB zCX{9_K|#RQSm0Qd#~kN4YBYXbr-tfaOKQ1cAmrZlr-BEM{)WytB4eSisBxaad=Y%Q zrf+MEJ=EVn1NyUldm}!z)Q8pv8g~tutJNJW_>6-A@qiryf*or*JM()lipINa zo?WZh(~h?Aw10RM-~&vZi>vEd$a06kXK>ZSNBX0`KSfu6Fg$Ok{s@%EK_UVPJl+Ax zDQ#wna>u#>)CxHpkg%K&t7I67OsSDMWPC@BkDr6`%-b(pv{Nt^|37E(|A4cIi;FWk zy3WQ1^B5s$Gt$ipA#1}WbSf+L$TU+2ehQ+ZZ%p(KV0nV5VGwnwB5C^IT=$0ZYA6uM zS8su9qKJ6x>7XnY;tHUh%ip5%z!Mk?prj)OY}pxtHgSs~rvV79R41^lt2m+Z^Of`* z9=YkPf#?xQoed2YhxmmE*9Y>-nAJOa^+e!6RY{vJ%!xnz)ztL3s2e6MWEq20Y

    W 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 literal 0 HcmV?d00001 diff --git a/docs/img/screenshot_thumb.png b/docs/img/screenshot_thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..f7a18d767ae4a12a9c2d368c73bbc339156c00c5 GIT binary patch literal 124784 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 @W++kQ+q8J&*fHIKYY+E`oq1xPoFa=51uEf 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⋙eOJ58Abl%G!YoxpC1M3i^wtMb4&0luS##Wq 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