From 8a134acb3330f221a21877bc9c9665a2a6eb67c9 Mon Sep 17 00:00:00 2001 From: forkwright Date: Tue, 25 Aug 2026 11:14:58 -0500 Subject: [PATCH 1/5] deps: migrate ergasia to librqbit 9 librqbit 9 restructured SessionOptions. The reported errors named the DHT fields, but four more shapes moved in the same struct and would have surfaced one compile at a time: listen_port_range and enable_upnp_port_forwarding are gone, peer_opts moved under a connect block, and TorrentStatsState::Initializing became a struct variant carrying `paused`, which breaks two match sites the error list never mentioned. Every shape here was read from the crate source at tag v9.0.1 rather than inferred from the error text. Two behaviours needed deliberate work to survive the bump. v8 hardcoded 0.0.0.0 for both the TCP listener and the DHT socket. v9 defaults to dual-stack [::]. ipv4_only is therefore set explicitly at both levels, so this bump does not silently change what the torrent session binds. Going dual-stack is a real v9 feature and worth taking deliberately -- a dependency bump is the wrong place to opt into it. v9 also dropped the listener's port-range scan, taking a single listen_addr with no retry. The old behaviour is reproduced by retrying Session::new_with_opts per candidate port. The listener bind is the first fallible step in that call, so a failed attempt leaves nothing to unwind. Probing a bind, dropping it, and handing librqbit a bare port would have introduced a TOCTOU window that v8 never had. A doc citation pointing at librqbit-8.1.1 session.rs is repointed at the 9.0.1 lines; the tier logic it cites is unchanged between versions, only moved. Closes #724 --- Cargo.lock | 462 ++++++++++++++++++++++------------ Cargo.toml | 2 +- crates/ergasia/src/session.rs | 176 ++++++++----- crates/ergasia/src/state.rs | 6 +- docs/architecture/cargo.md | 2 +- docs/download/torrent.md | 43 +++- 6 files changed, 452 insertions(+), 239 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa03a23d..4b3ad107 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -254,7 +254,7 @@ dependencies = [ "quinn", "rand 0.10.2", "rcgen", - "reqwest 0.13.4", + "reqwest 0.12.28", "rmcp", "rstest", "rustls", @@ -293,6 +293,12 @@ dependencies = [ "password-hash", ] +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + [[package]] name = "askama" version = "0.16.0" @@ -461,6 +467,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atoi" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a8bbe9949e43a1edaa043038c68703b04774156afdfb62ba2cef5bf93d67be" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic" version = "0.6.1" @@ -508,6 +523,29 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "aws-lc-rs" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + [[package]] name = "axum" version = "0.8.9" @@ -589,14 +627,14 @@ dependencies = [ ] [[package]] -name = "backoff" -version = "0.4.0" +name = "backon" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" dependencies = [ - "getrandom 0.2.17", - "instant", - "rand 0.8.6", + "fastrand", + "gloo-timers", + "tokio", ] [[package]] @@ -632,35 +670,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bincode" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" -dependencies = [ - "bincode_derive", - "serde", - "unty", -] - -[[package]] -name = "bincode_derive" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" -dependencies = [ - "virtue", -] - [[package]] name = "bit-vec" version = "0.9.1" @@ -908,6 +917,17 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "chardetng" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13de944a44b5064ee5d3a5ceccc49a41bfec50f2580e66f82e87703acdb88b53" +dependencies = [ + "cfg-if", + "encoding_rs", + "memchr", +] + [[package]] name = "chrono" version = "0.4.45" @@ -1536,6 +1556,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dontfrag" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117949f5a8b25ba471b4dfea927a07a2f8730c585532a2eb4294e18f5155397" +dependencies = [ + "cfg-if", + "libc", + "tokio", + "windows-sys 0.52.0", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -1557,6 +1589,12 @@ dependencies = [ "dtoa", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -1640,9 +1678,9 @@ dependencies = [ "futures", "horismos", "jiff", - "quick-xml 0.41.0", + "quick-xml", "regex", - "reqwest 0.13.4", + "reqwest 0.12.28", "rstest", "scraper", "serde", @@ -1697,7 +1735,7 @@ dependencies = [ "apotheke", "dashmap", "horismos", - "reqwest 0.13.4", + "reqwest 0.12.28", "serde", "serde_json", "snafu", @@ -1827,7 +1865,7 @@ checksum = "369995dae0733f1fe5ab0e3f345f6503a5f384179df5d8da333702031a131cf9" dependencies = [ "chrono", "mediatype", - "quick-xml 0.41.0", + "quick-xml", "regex", "serde", "serde_json", @@ -1952,6 +1990,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -2148,6 +2192,18 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "goblin" version = "0.8.2" @@ -2406,22 +2462,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", + "webpki-roots", ] [[package]] @@ -2655,15 +2696,6 @@ dependencies = [ "generic-array 0.14.7", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "intervaltree" version = "0.2.7" @@ -2687,9 +2719,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" dependencies = [ "either", ] @@ -2902,7 +2934,7 @@ dependencies = [ "feed-rs", "horismos", "jiff", - "reqwest 0.13.4", + "reqwest 0.12.28", "rstest", "serde", "snafu", @@ -3008,18 +3040,18 @@ dependencies = [ [[package]] name = "librqbit" -version = "8.1.1" +version = "9.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dadca8f521242010a4c846ef5f224c217009c92e272709cdc08ba9cdabe62983" +checksum = "4349c0f35ede576a87cc2881dc6eb286ff7a1c8fc4d1f7fef95b5c5a530a7174" dependencies = [ "anyhow", "arc-swap", "async-compression", "async-stream", "async-trait", - "backoff", - "base64 0.22.1", - "bincode 2.0.1", + "axum-extra", + "backon", + "base64 0.23.1", "bitvec", "byteorder", "bytes", @@ -3035,22 +3067,29 @@ dependencies = [ "librqbit-clone-to-owned", "librqbit-core", "librqbit-dht", + "librqbit-dualstack-sockets", + "librqbit-lsd", "librqbit-peer-protocol", "librqbit-sha1-wrapper", "librqbit-tracker-comms", "librqbit-upnp", + "librqbit-utp", "memmap2", "mime_guess", + "nix", "parking_lot", - "rand 0.9.4", + "rand 0.10.2", "regex", - "reqwest 0.12.28", + "reqwest 0.13.4", "rlimit", "serde", + "serde_derive", "serde_json", "serde_urlencoded", "serde_with", "size_format", + "socket2", + "thiserror 2.0.18", "tokio", "tokio-socks", "tokio-stream", @@ -3060,61 +3099,71 @@ dependencies = [ "urlencoding", "uuid", "walkdir", + "windows", ] [[package]] name = "librqbit-bencode" -version = "3.1.0" +version = "9.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "606dff526ba81e3eca33e2bb28b53afa2bc0b2c41d252333fa44e6c11abb37da" +checksum = "be6410a7434e6c2b148931b6d109b57939cfed04cefa86451197c5f5198edf91" dependencies = [ "anyhow", + "arrayvec", + "atoi 3.1.0", "bytes", "librqbit-buffers", "librqbit-clone-to-owned", - "librqbit-sha1-wrapper", "serde", + "serde_derive", + "thiserror 2.0.18", ] [[package]] name = "librqbit-buffers" -version = "4.2.0" +version = "9.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78c78b907d6171a7191c162b2b60db46d254ebde6a95282b77372af556c1463" +checksum = "b00e4b6780a30d3c204f778ad64b3c917b842f5c54df38455c361761e7d12ce2" dependencies = [ "bytes", "librqbit-clone-to-owned", "serde", + "serde_derive", ] [[package]] name = "librqbit-clone-to-owned" -version = "3.0.1" +version = "9.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd1e66d773ba9c475ff89286dc1d6f9d167cbb898603797467dd0ea6844c445" +checksum = "929ff2b22b1596c3f3689a79c0b84ee8a01a650a2dd0b6eb7322d6a3b9be0e0e" dependencies = [ "bytes", ] [[package]] name = "librqbit-core" -version = "5.0.0" +version = "9.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a02cc6fce6743ad38661ccd6fafc6cf1ae5e0106a9922836b0524dbe752378" +checksum = "f1c33158d5c47ac68771dcf7cca13eb2fa2ee2980efbb6bb5645273e2f08c69b" dependencies = [ "anyhow", - "assert_cfg", "bytes", + "chardetng", "data-encoding", "directories", + "encoding_rs", "hex 0.4.3", "itertools", "librqbit-bencode", "librqbit-buffers", "librqbit-clone-to-owned", + "librqbit-sha1-wrapper", + "memchr", "parking_lot", - "rand 0.9.4", + "rand 0.10.2", "serde", + "serde_derive", + "thiserror 2.0.18", "tokio", "tokio-util", "tracing", @@ -3123,41 +3172,79 @@ dependencies = [ [[package]] name = "librqbit-dht" -version = "5.3.1" +version = "9.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7cc129194337771a86b0399956c4d9bf1cd97c5f24d14a50be38e170f76a54b" +checksum = "575f94f543c16de3088f8aa84175203d65437c8886dc24d383941f408dc26488" dependencies = [ "anyhow", - "backoff", - "byteorder", + "backon", "bytes", "chrono", "dashmap", "futures", - "hex 0.4.3", "indexmap 2.14.0", "leaky-bucket", "librqbit-bencode", + "librqbit-buffers", "librqbit-clone-to-owned", "librqbit-core", + "librqbit-dualstack-sockets", "parking_lot", - "rand 0.9.4", + "rand 0.10.2", "serde", + "serde_derive", "serde_json", + "thiserror 2.0.18", "tokio", "tokio-stream", "tokio-util", "tracing", ] +[[package]] +name = "librqbit-dualstack-sockets" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7189f1ff66ca75055137a38b062ba7df9691d071b7172e5090997c62eb4b4de" +dependencies = [ + "axum", + "backon", + "futures", + "libc", + "network-interface", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "librqbit-lsd" +version = "9.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1d2699a46a9dc83693c5fbbf288a17b77a24b9924815a69bfc97853a47af42" +dependencies = [ + "anyhow", + "atoi 3.1.0", + "bstr", + "futures", + "httparse", + "librqbit-core", + "librqbit-dualstack-sockets", + "parking_lot", + "rand 0.10.2", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "librqbit-peer-protocol" -version = "4.3.0" +version = "9.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a73129497b500505f33d1dc0426319b6a6a208f13fdfaae56224ab8c2346a773" +checksum = "43e4b11349070274a1f77fb432d5e764ed38b310e7cdae8d2c37e0568ceb950e" dependencies = [ "anyhow", - "bincode 1.3.3", "bitvec", "byteorder", "bytes", @@ -3167,13 +3254,16 @@ dependencies = [ "librqbit-clone-to-owned", "librqbit-core", "serde", + "serde_derive", + "thiserror 2.0.18", + "tracing", ] [[package]] name = "librqbit-sha1-wrapper" -version = "4.1.0" +version = "9.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79373a02db73159e4de7ca5d27b6eeae2d540df66c6801db2b01c5513d087524" +checksum = "abf23eeda5a8999809c0be52e36b711d062f1d52e2f3cae42d71b7b012a466c7" dependencies = [ "assert_cfg", "crypto-hash", @@ -3181,21 +3271,26 @@ dependencies = [ [[package]] name = "librqbit-tracker-comms" -version = "3.0.0" +version = "9.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08204944c5be677a5de8e1230e0249fce5c14abef23048e26452c6fb03f1b260" +checksum = "b561cb29abec85eee031c998f9e0c987f82d3e193b087f4d2bdc8f00d825796c" dependencies = [ "anyhow", "async-stream", + "backon", "byteorder", "futures", + "itertools", "librqbit-bencode", "librqbit-buffers", "librqbit-core", + "librqbit-dualstack-sockets", "parking_lot", - "rand 0.9.4", - "reqwest 0.12.28", + "rand 0.10.2", + "reqwest 0.13.4", "serde", + "serde_derive", + "serde_with", "tokio", "tokio-util", "tracing", @@ -3205,23 +3300,49 @@ dependencies = [ [[package]] name = "librqbit-upnp" -version = "1.0.0" +version = "9.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545aad6124c97201055983137e12a19f34acad565120c3cd30596cbd72e8fa86" +checksum = "5b950b4dfe2102d3047f3fac70763c494b63a50f04a00d3dd36102d740e9bf33" dependencies = [ "anyhow", "bstr", "futures", "httparse", + "librqbit-dualstack-sockets", "network-interface", - "quick-xml 0.37.5", - "reqwest 0.12.28", + "quick-xml", + "reqwest 0.13.4", "serde", + "serde_derive", + "socket2", "tokio", "tracing", "url", ] +[[package]] +name = "librqbit-utp" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3bfdc73944bc76cab24d5690a98816770040a654c449edf5ff2b9ba22626aa" +dependencies = [ + "bitvec", + "dontfrag", + "lazy_static", + "libc", + "librqbit-dualstack-sockets", + "metrics", + "parking_lot", + "rand 0.9.4", + "ringbuf", + "rustc-hash", + "socket2", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "libsqlite3-sys" version = "0.30.1" @@ -3394,6 +3515,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "metrics" +version = "0.24.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89550ee9f79e88fef3119de263694973a8adb26c21d75322164fb8c493039fe2" +dependencies = [ + "portable-atomic", + "rapidhash", +] + [[package]] name = "mime" version = "0.3.17" @@ -3438,23 +3569,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "ndk" version = "0.9.0" @@ -3502,6 +3616,18 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -3952,7 +4078,7 @@ dependencies = [ "kritike", "md5", "mdns-sd", - "quick-xml 0.41.0", + "quick-xml", "rand 0.10.2", "serde", "serde_json", @@ -4253,7 +4379,7 @@ dependencies = [ "apotheke", "horismos", "jiff", - "reqwest 0.13.4", + "reqwest 0.12.28", "rstest", "serde", "serde_json", @@ -4282,16 +4408,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "quick-xml" -version = "0.37.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "quick-xml" version = "0.41.0" @@ -4329,6 +4445,7 @@ version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" dependencies = [ + "aws-lc-rs", "bytes", "fastbloom", "getrandom 0.3.4", @@ -4463,6 +4580,15 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rapidhash" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e" +dependencies = [ + "rustversion", +] + [[package]] name = "raw-cpuid" version = "11.6.0" @@ -4599,20 +4725,21 @@ dependencies = [ "http-body", "http-body-util", "hyper", - "hyper-tls", + "hyper-rustls", "hyper-util", "js-sys", "log", - "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-native-tls", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -4622,6 +4749,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams 0.4.2", "web-sys", + "webpki-roots", ] [[package]] @@ -4644,12 +4772,12 @@ dependencies = [ "log", "percent-encoding", "pin-project-lite", + "quinn", "rustls", "rustls-pki-types", "rustls-platform-verifier", "serde", "serde_json", - "serde_urlencoded", "sync_wrapper", "tokio", "tokio-rustls", @@ -4688,11 +4816,22 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ringbuf" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe47b720588c8702e34b5979cb3271a8b1842c7cb6f57408efa70c779363488c" +dependencies = [ + "crossbeam-utils", + "portable-atomic", + "portable-atomic-util", +] + [[package]] name = "rlimit" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7043b63bd0cd1aaa628e476b80e6d4023a3b50eb32789f2728908107bd0c793a" +checksum = "f35ee2729c56bb610f6dba436bf78135f728b7373bdffae2ec815b2d3eb98cc3" dependencies = [ "libc", ] @@ -4855,6 +4994,7 @@ version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ + "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -4918,6 +5058,7 @@ version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -5420,7 +5561,7 @@ dependencies = [ name = "skene" version = "0.4.1" dependencies = [ - "reqwest 0.13.4", + "reqwest 0.12.28", "serde", "serde_json", "snafu", @@ -5609,7 +5750,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ - "atoi", + "atoi 2.0.0", "base64 0.22.1", "bitflags 2.11.0", "byteorder", @@ -5651,7 +5792,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ - "atoi", + "atoi 2.0.0", "base64 0.22.1", "bitflags 2.11.0", "byteorder", @@ -5688,7 +5829,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ - "atoi", + "atoi 2.0.0", "flume 0.11.1", "futures-channel", "futures-core", @@ -6037,7 +6178,7 @@ dependencies = [ "horismos", "jiff", "md5", - "reqwest 0.13.4", + "reqwest 0.12.28", "rstest", "serde", "serde_json", @@ -6252,16 +6393,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.4" @@ -6817,12 +6948,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "unty" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" - [[package]] name = "url" version = "2.5.8" @@ -6890,12 +7015,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "virtue" -version = "0.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" - [[package]] name = "visibility" version = "0.1.1" @@ -7112,6 +7231,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weedle2" version = "5.0.0" diff --git a/Cargo.toml b/Cargo.toml index 16693184..deab8ec3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -178,7 +178,7 @@ toml = "1.1" tokio-cron-scheduler = "0.15" # ── Acquisition ─────────────────────────────────────────────────────────────── -librqbit = "=8.1.1" +librqbit = "=9.0.1" zip = { version = "=8.6.0", default-features = false, features = [ "deflate", ] } diff --git a/crates/ergasia/src/session.rs b/crates/ergasia/src/session.rs index 28cf4265..3d6a2fc5 100644 --- a/crates/ergasia/src/session.rs +++ b/crates/ergasia/src/session.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; @@ -11,7 +12,8 @@ use horismos::{ErgasiaConfig, Section}; use jiff::Timestamp; use librqbit::api::TorrentIdOrHash; use librqbit::{ - AddTorrent, AddTorrentOptions, AddTorrentResponse, ManagedTorrent, Session, SessionOptions, + AddTorrent, AddTorrentOptions, AddTorrentResponse, ConnectionOptions, DhtSessionConfig, + ListenerMode, ListenerOptions, ManagedTorrent, Session, SessionOptions, SessionPersistenceConfig, TorrentStats, TorrentStatsState, }; use serde::{Deserialize, Serialize}; @@ -127,7 +129,7 @@ pub struct TorrentSession { } /// Mirrors librqbit's `get_default_subfolder_for_torrent` name resolution -/// (librqbit-8.1.1 session.rs:988-1030) tier for tier: the info-dict name if +/// (librqbit-9.0.1 session.rs:1175-1216) tier for tier: the info-dict name if /// non-empty, else the magnet display name if non-empty, else the largest /// file's stem. `None` only when all three are unavailable. /// @@ -173,49 +175,88 @@ impl TorrentSession { ..Default::default() }; - let persistence = SessionPersistenceConfig::Json { - folder: Some(PathBuf::from(&config.session_state_path)), - }; - - let opts = SessionOptions { - disable_dht: false, - disable_dht_persistence: false, - // WHY: pin DHT routing-table persistence inside this instance's own - // session_state_path rather than librqbit's global default - // (~/.cache/com.rqbit.dht/dht.json). A shared default races and - // corrupts across concurrent instances — and parallel tests — that - // initialize the persistent DHT at once; instance-local state keeps - // each session self-contained. - dht_config: Some(librqbit::dht::PersistentDhtConfig { - config_filename: Some(PathBuf::from(&config.session_state_path).join("dht.json")), + let listen_port_start = config + .listen_port_range + .first() + .copied() + .unwrap_or_else(|| unreachable!("listen_port_range is [u16; 2]")); + let listen_port_end = config + .listen_port_range + .get(1) + .copied() + .unwrap_or_else(|| unreachable!("listen_port_range is [u16; 2]")); + + // WHY: librqbit 9 replaced SessionOptions::listen_port_range (an + // internal loop that tried each port in the range and bound the + // first free one — librqbit 8 session.rs create_tcp_listener) with a + // single ListenerOptions::listen_addr and no retry left inside the + // crate. The listener bind is the first fallible step in + // Session::new_with_opts — it runs before DHT/persistence are + // touched — so retrying the whole call per candidate port reproduces + // the old range scan exactly, without racing a separate probe bind + // against librqbit's own. + let mut last_err = None; + let mut session = None; + for port in listen_port_start..listen_port_end { + let opts = SessionOptions { + // WHY: librqbit 8 always bound the TCP listener and the DHT + // socket to 0.0.0.0 (IPv4-only, hardcoded); librqbit 9 + // defaults to dual-stack ([::], IPv4+IPv6). Pin both the + // session- and listener-level ipv4_only to restore the old + // bind behaviour explicitly rather than silently picking up + // IPv6. + ipv4_only: true, + dht: Some(DhtSessionConfig { + // WHY: pin DHT routing-table persistence inside this + // instance's own session_state_path rather than + // librqbit's global default + // (~/.cache/com.rqbit.dht/dht.json). A shared default + // races and corrupts across concurrent instances — and + // parallel tests — that initialize the persistent DHT at + // once; instance-local state keeps each session + // self-contained. + persistence: Some(librqbit::dht::DhtPersistenceConfig { + config_filename: Some( + PathBuf::from(&config.session_state_path).join("dht.json"), + ), + ..Default::default() + }), + ..Default::default() + }), + persistence: Some(SessionPersistenceConfig::Json { + folder: Some(PathBuf::from(&config.session_state_path)), + }), + listen: Some(ListenerOptions { + listen_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port), + mode: ListenerMode::TcpOnly, + ipv4_only: true, + enable_upnp_port_forwarding: false, + ..Default::default() + }), + connect: Some(ConnectionOptions { + peer_opts: Some(peer_opts), + ..Default::default() + }), ..Default::default() - }), - persistence: Some(persistence), - listen_port_range: Some( - config - .listen_port_range - .first() - .copied() - .unwrap_or_else(|| unreachable!("listen_port_range is [u16; 2]")) - ..config - .listen_port_range - .get(1) - .copied() - .unwrap_or_else(|| unreachable!("listen_port_range is [u16; 2]")), - ), - enable_upnp_port_forwarding: false, - peer_opts: Some(peer_opts), - ..Default::default() - }; + }; - let session = Session::new_with_opts(config.download_dir.clone(), opts) - .await - .map_err(|e| { - SessionInitSnafu { - error: e.to_string(), + match Session::new_with_opts(config.download_dir.clone(), opts).await { + Ok(s) => { + session = Some(s); + break; } - .build() - })?; + Err(e) => last_err = Some(e), + } + } + + let session = session.ok_or_else(|| { + SessionInitSnafu { + error: last_err.map(|e| e.to_string()).unwrap_or_else(|| { + format!("no free TCP ports in range {listen_port_start}..{listen_port_end}") + }), + } + .build() + })?; let torrent_session = Arc::new(Self { session, @@ -614,7 +655,12 @@ impl TorrentSession { ); break; } - TorrentStatsState::Initializing | TorrentStatsState::Live => {} + // NOTE: librqbit 9 added a `paused` field to `Initializing` + // (whether an initializing torrent is also paused) — ignored + // here, matching librqbit 8's coarser (fieldless) variant: + // the seed monitor has nothing to enforce either way while + // still initializing. + TorrentStatsState::Initializing { .. } | TorrentStatsState::Live => {} } // INVARIANT: `stats.uploaded_bytes` counts the CURRENT live epoch @@ -1764,26 +1810,42 @@ mod tests { recv_completion(&mut event_rx, download_id).await; let seeder_port = seeder .session - .tcp_listen_port() + .announce_port() .expect("seeder must expose a TCP listen port"); // Leech: a bare librqbit session pointed straight at the seeder — no // DHT, no trackers, so the seeder is its only possible source. let leech_dir = dir.path().join("leech"); std::fs::create_dir_all(&leech_dir).unwrap(); - let leech_session = Session::new_with_opts( - leech_dir, - SessionOptions { - disable_dht: true, - disable_dht_persistence: true, - persistence: None, - listen_port_range: Some(25501..25509), - enable_upnp_port_forwarding: false, - ..Default::default() - }, - ) - .await - .unwrap(); + // WHY: librqbit 9 dropped SessionOptions::listen_port_range (see the + // WHY note in with_seed_poll_interval) — this reproduces the same + // scan-the-range-by-retrying-construction loop for the bare leech + // session. + let mut leech_session = None; + for port in 25501u16..25509 { + let attempt = Session::new_with_opts( + leech_dir.clone(), + SessionOptions { + dht: None, + persistence: None, + listen: Some(ListenerOptions { + listen_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port), + mode: ListenerMode::TcpOnly, + ipv4_only: true, + enable_upnp_port_forwarding: false, + ..Default::default() + }), + ipv4_only: true, + ..Default::default() + }, + ) + .await; + if let Ok(s) = attempt { + leech_session = Some(s); + break; + } + } + let leech_session = leech_session.expect("a free TCP port for the leech session"); let response = leech_session .add_torrent( AddTorrent::TorrentFileBytes(torrent_bytes), diff --git a/crates/ergasia/src/state.rs b/crates/ergasia/src/state.rs index 06879956..9deb87c6 100644 --- a/crates/ergasia/src/state.rs +++ b/crates/ergasia/src/state.rs @@ -35,7 +35,11 @@ pub fn map_torrent_stats(stats: &TorrentStats) -> DownloadState { // diagnostic message (torrent_state/mod.rs `stats()`), so this arm // covers both. (TorrentStatsState::Error, _) => DownloadState::Failed, - (TorrentStatsState::Initializing, _) => DownloadState::Initializing, + // NOTE: librqbit 9 added a `paused` field to `Initializing` (whether + // an initializing torrent is also paused) — ignored here, matching + // librqbit 8's coarser (fieldless) variant: harmonia has no + // `Initializing`+paused state of its own to report. + (TorrentStatsState::Initializing { .. }, _) => DownloadState::Initializing, (TorrentStatsState::Live, false) => DownloadState::Downloading, (TorrentStatsState::Live, true) => DownloadState::Seeding, // NOTE: the only production pauser is the seed monitor (#590); diff --git a/docs/architecture/cargo.md b/docs/architecture/cargo.md index 02066183..e7ba8bfd 100644 --- a/docs/architecture/cargo.md +++ b/docs/architecture/cargo.md @@ -355,7 +355,7 @@ tokio-cron-scheduler = "0.15" # ── Acquisition ─────────────────────────────────────────────────────────────── # Pin to exact version — narrow documented embedding surface -librqbit = "=8.1.1" +librqbit = "=9.0.1" zip = "8.2" # sevenz-rust2: verify exact version on crates.io before pinning # sevenz-rust2 = "X.Y.Z" diff --git a/docs/download/torrent.md b/docs/download/torrent.md index cd786526..ffde7f58 100644 --- a/docs/download/torrent.md +++ b/docs/download/torrent.md @@ -33,30 +33,49 @@ pub struct TorrentSession { ### Session initialization -Session is created once at startup with `SessionOptions`: +Session is created once at startup with `SessionOptions`. librqbit 9 has no +port-range concept left in `ListenerOptions` (a single `listen_addr`, no +internal retry), so `TorrentSession` retries the whole `Session::new_with_opts` +call once per candidate port in `config.listen_port_range` — the listener bind +is the first fallible step, before DHT/persistence are touched, so a failed +attempt leaves nothing to unwind: ```rust let opts = SessionOptions { - disable_dht: false, // DHT enabled — default peer discovery - disable_dht_persistence: false, - dht_config: None, // use librqbit defaults - persistence: Some(persistence_factory(&config.session_state_path)), - listen_port_range: Some(config.listen_port_range.clone()), - enable_upnp_port_forwarding: false, - // peer opts: - peer_connect_timeout: Some(config.peer_connect_timeout_seconds), - peer_read_write_timeout: Some(Duration::from_secs(10)), + ipv4_only: true, // restore librqbit 8's IPv4-only bind (v9 defaults dual-stack) + dht: Some(DhtSessionConfig { + persistence: Some(dht::DhtPersistenceConfig { + config_filename: Some(config.session_state_path.join("dht.json")), + ..Default::default() + }), + ..Default::default() + }), + persistence: Some(SessionPersistenceConfig::Json { + folder: Some(config.session_state_path.clone()), + }), + listen: Some(ListenerOptions { + listen_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port), + mode: ListenerMode::TcpOnly, + ipv4_only: true, + enable_upnp_port_forwarding: false, + ..Default::default() + }), + connect: Some(ConnectionOptions { + peer_opts: Some(peer_opts), // built from peer_connect_timeout_seconds + a 10s read/write timeout + ..Default::default() + }), ..Default::default() }; let session = Session::new_with_opts(config.download_dir.clone(), opts).await - .context(SessionInitSnafu)?; + .context(SessionInitSnafu)?; // tried again with the next port on failure ``` Key guarantees: - **DHT enabled**: default peer discovery. PEX enabled by librqbit defaults. - **Fast resume**: `persistence` enabled. librqbit persists piece completion state to `session_state_path`. After restart, torrents resume without re-verifying all pieces. - **Single session**: Ergasia does NOT expose librqbit's built-in HTTP API. All external access to download state goes through Ergasia's own trait surface (`start_download`, `cancel_download`, `get_progress`). -- **Connection limits**: `peer_connect_timeout_seconds` is configurable via `[ergasia]`. Max connections per torrent is not configurable — librqbit 8.1.1 has no such knob, and Horismos does not carry a field for it. +- **Connection limits**: `peer_connect_timeout_seconds` is configurable via `[ergasia]`. Max connections per torrent is not configurable — librqbit has no such knob, and Horismos does not carry a field for it. +- **IPv4-only**: the session, its DHT, and its listener are all pinned to IPv4 (`ipv4_only: true` at both the session and listener level) — librqbit 9 defaults to dual-stack IPv4/IPv6, but nothing in Ergasia's config or the fleet's NAT/port-forwarding setup accounts for an IPv6 listen path yet. --- From 54d0fa3d2c0cbfbf192d18410d38455457c5cb7e Mon Sep 17 00:00:00 2001 From: forkwright Date: Tue, 25 Aug 2026 11:44:47 -0500 Subject: [PATCH 2/5] fix(ergasia,archon): finish the librqbit 9 migration CI surfaced Six more breaks, five reported by CI and one found by sweeping for the same shapes -- the gate aborts at the first failing crate, so its list is what compiled up to the abort rather than what is broken. TorrentMetadata dropped its cached `name` field; the name now comes from `info.name()`, an encoding-aware decode. Used librqbit's own idiom from ManagedTorrent::name rather than inventing a conversion, which also preserves the Option the caller already expects. create_torrent takes a BlockingSpawner explicitly in v9, where v8 built one internally, and v9's BlockingSpawner has no Default. Traced what the spawner is actually used for before choosing a value: the semaphore is acquired once per call and the only dispatched work is block_in_place, gated by runtime flavor -- so capacity is inert for a spawner nothing else shares, and `new(1)` matches an existing in-crate precedent for the same one-off case. The third site was in archon rather than ergasia and would have failed on the next run. The remaining two are the Initializing struct-variant change at a site that CONSTRUCTS the value rather than matching it, so `{ .. }` does not apply. The tuple's neighbouring bool is `finished`, a different concept from the new `paused`, so nothing is double-encoded; the consuming match ignores `paused` unconditionally. Swept the workspace for every migrated type and for bare Initializing, including separate integration-test targets: only ergasia and archon depend on librqbit, and nothing else references these shapes. --- crates/archon/src/serve.rs | 16 ++++++++++---- crates/ergasia/src/session.rs | 41 +++++++++++++++++++++++++++-------- crates/ergasia/src/state.rs | 22 +++++++++++++++++-- 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/crates/archon/src/serve.rs b/crates/archon/src/serve.rs index 3c0e4c0f..15a9524f 100644 --- a/crates/archon/src/serve.rs +++ b/crates/archon/src/serve.rs @@ -3078,10 +3078,18 @@ mod service_adapter_tests { std::fs::create_dir_all(&ergasia_cfg.download_dir).expect("download dir"); let payload_path = ergasia_cfg.download_dir.join("done-when.bin"); std::fs::write(&payload_path, b"#602 done-when fixture payload").expect("payload"); - let created = - librqbit::create_torrent(&payload_path, librqbit::CreateTorrentOptions::default()) - .await - .expect("fixture torrent"); + // WHY: librqbit 9 externalized create_torrent's blocking-work + // spawner (was BlockingSpawner::default(), crate-private, in + // librqbit 8) — see ergasia's session.rs single_file_fixture for the + // full rationale. A fresh BlockingSpawner::new(1) reproduces the old + // behaviour exactly for this uncontended, one-off fixture call. + let created = librqbit::create_torrent( + &payload_path, + librqbit::CreateTorrentOptions::default(), + &librqbit::spawn_utils::BlockingSpawner::new(1), + ) + .await + .expect("fixture torrent"); let torrent_url = serve_torrent_bytes(created.as_bytes().expect("torrent bytes")).await; let (event_tx, mut event_rx) = create_event_bus(64); diff --git a/crates/ergasia/src/session.rs b/crates/ergasia/src/session.rs index 3d6a2fc5..aba68705 100644 --- a/crates/ergasia/src/session.rs +++ b/crates/ergasia/src/session.rs @@ -474,7 +474,13 @@ impl TorrentSession { .with_metadata(|m| { ( m.file_infos.len(), - m.name.clone(), + // WHY: librqbit 9 dropped TorrentMetadata::name (a + // cached Option) in favour of computing it live + // from the validated info dict; ManagedTorrent::name() + // (torrent_state/mod.rs) uses this exact + // `m.info.name().map(Cow::into_owned)` pattern, so this + // mirrors the crate's own idiom rather than inventing one. + m.info.name().map(|n| n.into_owned()), m.file_infos.first().map(|f| f.relative_filename.clone()), m.file_infos .iter() @@ -1383,10 +1389,22 @@ mod tests { std::fs::create_dir_all(download_dir).unwrap(); let file_path = download_dir.join(name); std::fs::write(&file_path, payload).unwrap(); - let created = - librqbit::create_torrent(&file_path, librqbit::CreateTorrentOptions::default()) - .await - .unwrap(); + // WHY: librqbit 9 externalized the blocking-work spawner that + // create_torrent uses internally (was BlockingSpawner::default(), + // crate-private, in librqbit 8). A fresh, uncontended spawner here + // reproduces the old behaviour exactly: create_torrent only ever + // calls spawner.block_in_place(...) (gated on the current runtime + // flavor, same as v8's Default), never the semaphore-limited path, + // so the concurrency figure passed to `new` is inert for a spawner + // nothing else shares — 1 matches librqbit's own one-off precedent + // (dht_utils.rs test: BlockingSpawner::new(1)). + let created = librqbit::create_torrent( + &file_path, + librqbit::CreateTorrentOptions::default(), + &librqbit::spawn_utils::BlockingSpawner::new(1), + ) + .await + .unwrap(); created.as_bytes().unwrap() } @@ -1397,10 +1415,15 @@ mod tests { std::fs::create_dir_all(&content_dir).unwrap(); std::fs::write(content_dir.join("a.bin"), b"first payload").unwrap(); std::fs::write(content_dir.join("b.bin"), b"second payload").unwrap(); - let created = - librqbit::create_torrent(&content_dir, librqbit::CreateTorrentOptions::default()) - .await - .unwrap(); + // WHY: see single_file_fixture's WHY note above — same inert + // per-call spawner. + let created = librqbit::create_torrent( + &content_dir, + librqbit::CreateTorrentOptions::default(), + &librqbit::spawn_utils::BlockingSpawner::new(1), + ) + .await + .unwrap(); created.as_bytes().unwrap() } diff --git a/crates/ergasia/src/state.rs b/crates/ergasia/src/state.rs index 9deb87c6..0722c7de 100644 --- a/crates/ergasia/src/state.rs +++ b/crates/ergasia/src/state.rs @@ -216,8 +216,26 @@ mod tests { Some("bug: torrent in broken \"None\" state"), DownloadState::Failed, ), - (S::Initializing, false, None, DownloadState::Initializing), - (S::Initializing, true, None, DownloadState::Initializing), + // NOTE: the `paused` field here is librqbit 9's addition to + // Initializing (see the WHY note on map_torrent_stats above) — + // it is unrelated to the `finished` bool in the second tuple + // slot (a torrent still initializing is never `finished`; these + // rows exercise `finished` true/false anyway for exhaustive + // coverage of the cross product). map_torrent_stats ignores + // `paused` entirely, so its value here is inert; `false` is the + // representative case. + ( + S::Initializing { paused: false }, + false, + None, + DownloadState::Initializing, + ), + ( + S::Initializing { paused: false }, + true, + None, + DownloadState::Initializing, + ), (S::Live, false, None, DownloadState::Downloading), (S::Live, true, None, DownloadState::Seeding), (S::Paused, true, None, DownloadState::SeedPolicySatisfied), From 19abd8a0ee8cfa2716b49664fd07df91a4b2b153 Mon Sep 17 00:00:00 2001 From: forkwright Date: Tue, 25 Aug 2026 13:36:14 -0500 Subject: [PATCH 3/5] fix(archon,syndesis): pick the rustls provider explicitly, and adopt v9's dual-stack bind CI's test failure traced to a mechanism the assertion did not suggest. librqbit 9 pulls rustls with aws-lc-rs while this workspace declares it with ring; cargo features are additive across a build, so both providers end up active and rustls's auto-select refuses to choose. The panic surfaced as an unrelated lifecycle test failing, because a renderer task that panics makes JoinHandle::is_finished true immediately and the op slot reaps it early. Three call sites used the ambiguous builder. archon's render/tls.rs was the one CI reached; two more in syndesis would have failed next. All now request ring explicitly, matching what those files already do elsewhere. quinn-proto's own helper was unaffected because it already asks for a provider by name. progress.rs drops a conversion that librqbit 9 made a no-op: the peer-stats `live` field narrowed from usize to u32 upstream, so the fallible narrowing has nothing left to narrow. The session now binds dual-stack, taking librqbit 9's own default rather than pinning back to v8's IPv4-only behaviour. That is a deliberate choice rather than a side effect: `ipv4_only` is left unset at both the session and listener level, so the TCP listener, the DHT socket and the outbound connector all follow v9. The RUSTSEC-2025-0012 exception for `backoff` goes with it -- librqbit 9 replaced that dependency with `backon`, so the advisory no longer reaches this graph. --- .cargo/audit.toml | 6 ---- crates/ergasia/src/progress.rs | 8 +++++- crates/ergasia/src/session.rs | 50 ++++++++++++++++++++++++---------- deny.toml | 8 ------ docs/download/torrent.md | 6 ++-- osv-scanner.toml | 8 ------ 6 files changed, 45 insertions(+), 41 deletions(-) diff --git a/.cargo/audit.toml b/.cargo/audit.toml index 58a5a264..f676caa2 100644 --- a/.cargo/audit.toml +++ b/.cargo/audit.toml @@ -9,18 +9,12 @@ ignore = [ # rsa timing side-channel via jsonwebtoken/exousia — no safe upgrade, local-only use. # review-by: 2026-10-01 "RUSTSEC-2023-0071", - # backoff unmaintained — transitive dep via librqbit. - # review-by: 2026-10-01 - "RUSTSEC-2025-0012", # bincode unmaintained — transitive dep via librqbit-peer-protocol. # review-by: 2026-10-01 "RUSTSEC-2025-0141", # crypto-hash unmaintained — transitive dep via librqbit-sha1-wrapper. # review-by: 2026-10-01 "RUSTSEC-2025-0060", - # instant unmaintained — transitive dep via backoff/librqbit, no alternative. - # review-by: 2026-10-01 - "RUSTSEC-2024-0384", # paste unmaintained (dtolnay archived) — transitive dep via lofty/taxis. # review-by: 2026-10-01 "RUSTSEC-2024-0436", diff --git a/crates/ergasia/src/progress.rs b/crates/ergasia/src/progress.rs index c7ccb238..c6dac2bc 100644 --- a/crates/ergasia/src/progress.rs +++ b/crates/ergasia/src/progress.rs @@ -32,10 +32,16 @@ impl DownloadProgress { clippy::cast_sign_loss, reason = "speeds are non-negative and far below u64::MAX" )] + // WHY: librqbit 9's AggregatePeerStats.live is already u32 + // (stat_gen.rs's gen_stats! macro expands `live u32` to a plain + // u32 snapshot field, no atomic-to-narrower-int narrowing left); + // librqbit 8's version was `usize`, which is what the removed + // u32::try_from(...).unwrap_or(u32::MAX) saturating conversion + // was guarding against. No conversion needed or possible now. Some(live) => ( (live.download_speed.mbps * MIB_PER_SECOND_TO_BPS) as u64, (live.upload_speed.mbps * MIB_PER_SECOND_TO_BPS) as u64, - u32::try_from(live.snapshot.peer_stats.live).unwrap_or(u32::MAX), + live.snapshot.peer_stats.live, ), None => (0, 0, 0), }; diff --git a/crates/ergasia/src/session.rs b/crates/ergasia/src/session.rs index aba68705..74c7e75d 100644 --- a/crates/ergasia/src/session.rs +++ b/crates/ergasia/src/session.rs @@ -1,5 +1,5 @@ use std::borrow::Cow; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::net::{IpAddr, Ipv6Addr, SocketAddr}; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; @@ -195,17 +195,32 @@ impl TorrentSession { // touched — so retrying the whole call per candidate port reproduces // the old range scan exactly, without racing a separate probe bind // against librqbit's own. + // + // NOTE: dual-stack listening (below) does not change this. Any bind + // failure — including one specific to dual-stack, e.g. the port + // already held by an IPv6-only socket, or set_only_v6(false) itself + // failing (librqbit-dualstack-sockets socket.rs) — still surfaces as + // a plain Err from the same `.context("error starting listeners")?` + // in Session::new_with_opts, still before DHT/persistence run, so + // this loop retries it exactly like any other bind failure without + // needing to know why it failed. let mut last_err = None; let mut session = None; for port in listen_port_start..listen_port_end { let opts = SessionOptions { - // WHY: librqbit 8 always bound the TCP listener and the DHT - // socket to 0.0.0.0 (IPv4-only, hardcoded); librqbit 9 - // defaults to dual-stack ([::], IPv4+IPv6). Pin both the - // session- and listener-level ipv4_only to restore the old - // bind behaviour explicitly rather than silently picking up - // IPv6. - ipv4_only: true, + // WHY: librqbit 8 always bound the TCP listener, the DHT + // socket, and outbound peer connections to IPv4 only + // (0.0.0.0, hardcoded — v8 had no IPv6 support at all). + // librqbit 9 added real IPv6 support and defaults to + // dual-stack; adopted here rather than pinned back to + // IPv4-only (operator decision). `ipv4_only` is left at its + // default (false) across the board, which fans out to three + // places: DHT binds `[::]` (dht_listen_addr derives the IP + // solely from this flag), the outbound StreamConnector + // becomes dual-stack-capable, and the TCP listener below is + // told to request dual-stack explicitly via its own + // listen_addr (ipv4_only alone does not widen an address we + // hand it — see that field's WHY note). dht: Some(DhtSessionConfig { // WHY: pin DHT routing-table persistence inside this // instance's own session_state_path rather than @@ -227,9 +242,15 @@ impl TorrentSession { folder: Some(PathBuf::from(&config.session_state_path)), }), listen: Some(ListenerOptions { - listen_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port), + // WHY: `[::]`, not `0.0.0.0` — with ipv4_only left at its + // default (false), librqbit-dualstack-sockets only + // widens an IPv6-unspecified address to accept IPv4 too + // (socket.rs: request_dualstack applies exclusively to + // the `SocketAddr::V6(UNSPECIFIED)` branch); handing it + // an explicit IPv4 address here would keep the listener + // IPv4-only regardless of the ipv4_only flag. + listen_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port), mode: ListenerMode::TcpOnly, - ipv4_only: true, enable_upnp_port_forwarding: false, ..Default::default() }), @@ -1843,7 +1864,10 @@ mod tests { // WHY: librqbit 9 dropped SessionOptions::listen_port_range (see the // WHY note in with_seed_poll_interval) — this reproduces the same // scan-the-range-by-retrying-construction loop for the bare leech - // session. + // session. ipv4_only is left at its default (false, dual-stack) for + // the same reason as the production session — the loopback outbound + // connect to the seeder below works the same either way, since + // ipv4_only governs binding, not whether an IPv4 peer is reachable. let mut leech_session = None; for port in 25501u16..25509 { let attempt = Session::new_with_opts( @@ -1852,13 +1876,11 @@ mod tests { dht: None, persistence: None, listen: Some(ListenerOptions { - listen_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port), + listen_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port), mode: ListenerMode::TcpOnly, - ipv4_only: true, enable_upnp_port_forwarding: false, ..Default::default() }), - ipv4_only: true, ..Default::default() }, ) diff --git a/deny.toml b/deny.toml index 1f2f283e..8bec92f0 100644 --- a/deny.toml +++ b/deny.toml @@ -19,10 +19,6 @@ all-features = false id = "RUSTSEC-2023-0071" reason = "rsa timing side-channel via jsonwebtoken/exousia — no safe upgrade, local-only use. review-by: 2026-10-01" -[[advisories.ignore]] -id = "RUSTSEC-2025-0012" -reason = "backoff unmaintained — transitive dep via librqbit. review-by: 2026-10-01" - [[advisories.ignore]] id = "RUSTSEC-2025-0141" reason = "bincode unmaintained — transitive dep via librqbit-peer-protocol. review-by: 2026-10-01" @@ -31,10 +27,6 @@ reason = "bincode unmaintained — transitive dep via librqbit-peer-protocol. re id = "RUSTSEC-2025-0060" reason = "crypto-hash unmaintained — transitive dep via librqbit-sha1-wrapper. review-by: 2026-10-01" -[[advisories.ignore]] -id = "RUSTSEC-2024-0384" -reason = "instant unmaintained — transitive dep via backoff/librqbit, no alternative. review-by: 2026-10-01" - [[advisories.ignore]] id = "RUSTSEC-2024-0436" reason = "paste unmaintained (dtolnay archived) — transitive dep via lofty/taxis. review-by: 2026-10-01" diff --git a/docs/download/torrent.md b/docs/download/torrent.md index ffde7f58..118b7195 100644 --- a/docs/download/torrent.md +++ b/docs/download/torrent.md @@ -42,7 +42,6 @@ attempt leaves nothing to unwind: ```rust let opts = SessionOptions { - ipv4_only: true, // restore librqbit 8's IPv4-only bind (v9 defaults dual-stack) dht: Some(DhtSessionConfig { persistence: Some(dht::DhtPersistenceConfig { config_filename: Some(config.session_state_path.join("dht.json")), @@ -54,9 +53,8 @@ let opts = SessionOptions { folder: Some(config.session_state_path.clone()), }), listen: Some(ListenerOptions { - listen_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port), + listen_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port), mode: ListenerMode::TcpOnly, - ipv4_only: true, enable_upnp_port_forwarding: false, ..Default::default() }), @@ -75,7 +73,7 @@ Key guarantees: - **Fast resume**: `persistence` enabled. librqbit persists piece completion state to `session_state_path`. After restart, torrents resume without re-verifying all pieces. - **Single session**: Ergasia does NOT expose librqbit's built-in HTTP API. All external access to download state goes through Ergasia's own trait surface (`start_download`, `cancel_download`, `get_progress`). - **Connection limits**: `peer_connect_timeout_seconds` is configurable via `[ergasia]`. Max connections per torrent is not configurable — librqbit has no such knob, and Horismos does not carry a field for it. -- **IPv4-only**: the session, its DHT, and its listener are all pinned to IPv4 (`ipv4_only: true` at both the session and listener level) — librqbit 9 defaults to dual-stack IPv4/IPv6, but nothing in Ergasia's config or the fleet's NAT/port-forwarding setup accounts for an IPv6 listen path yet. +- **Dual-stack**: `ipv4_only` is left at librqbit 9's default (`false`). The DHT socket, the outbound peer connector, and the TCP listener all bind `[::]` (IPv4+IPv6) rather than librqbit 8's hardcoded IPv4-only `0.0.0.0`. The listener needs its own explicit `[::]` `listen_addr` for this — `ipv4_only` alone only widens an address that is already IPv6-unspecified (librqbit-dualstack-sockets `socket.rs`); handing it an IPv4 literal would keep the listener IPv4-only regardless of the flag. --- diff --git a/osv-scanner.toml b/osv-scanner.toml index 346371fa..77e841cd 100644 --- a/osv-scanner.toml +++ b/osv-scanner.toml @@ -6,10 +6,6 @@ id = "RUSTSEC-2023-0071" reason = "rsa timing side-channel via jsonwebtoken/exousia — no safe upgrade, local-only use. review-by: 2026-10-01" -[[IgnoredVulns]] -id = "RUSTSEC-2025-0012" -reason = "backoff unmaintained — transitive dep via librqbit. review-by: 2026-10-01" - [[IgnoredVulns]] id = "RUSTSEC-2025-0141" reason = "bincode unmaintained — transitive dep via librqbit-peer-protocol. review-by: 2026-10-01" @@ -18,10 +14,6 @@ reason = "bincode unmaintained — transitive dep via librqbit-peer-protocol. re id = "RUSTSEC-2025-0060" reason = "crypto-hash unmaintained — transitive dep via librqbit-sha1-wrapper. review-by: 2026-10-01" -[[IgnoredVulns]] -id = "RUSTSEC-2024-0384" -reason = "instant unmaintained — transitive dep via backoff/librqbit, no alternative. review-by: 2026-10-01" - [[IgnoredVulns]] id = "RUSTSEC-2024-0436" reason = "paste unmaintained (dtolnay archived) — transitive dep via lofty/taxis. review-by: 2026-10-01" From 923a4d68722af52a759796d3d48a1163c4cf995b Mon Sep 17 00:00:00 2001 From: forkwright Date: Tue, 25 Aug 2026 16:42:04 -0500 Subject: [PATCH 4/5] chore(deny): exempt the one duplicate that is upstream-blocked, drop three dead advisories librqbit 9 widens the graph and cargo-deny's multiple-versions rule refuses it. Three duplicates, and only one is genuinely ours to exempt. atoi is upstream-blocked in both directions: sqlx-sqlite's newest release still pins 2.0 and librqbit 9 pins 3. cargo update cannot close a gap where both sides are already newest and disagree on a major, so it is skipped with the specific crates and versions named. reqwest is deliberately NOT skipped. Our own manifest caps us below 0.13, and lifting that cap is #731's core change -- so this unifies by sequencing rather than by exemption. wasm-streams is purely downstream of reqwest and needs no action of its own. Three advisory exceptions are dead and removed. bincode is entirely absent from this branch's lock where main carried two versions, so RUSTSEC-2025-0141 and its two skip entries go. Checking that turned up the same shape in quick-xml: main had two versions, this branch has one because librqbit-upnp no longer pins the old range, so RUSTSEC-2026-0194 and -0195 go with it. A comment on an unrelated syn entry that referred back to bincode is corrected rather than left dangling. The derived ignore files are regenerated through the tool their own header names as the source of truth, not hand-edited, and check clean against deny.toml. --- .cargo/audit.toml | 9 --------- deny.toml | 43 +++++++++---------------------------------- osv-scanner.toml | 12 ------------ 3 files changed, 9 insertions(+), 55 deletions(-) diff --git a/.cargo/audit.toml b/.cargo/audit.toml index f676caa2..66f2e1fc 100644 --- a/.cargo/audit.toml +++ b/.cargo/audit.toml @@ -9,19 +9,10 @@ ignore = [ # rsa timing side-channel via jsonwebtoken/exousia — no safe upgrade, local-only use. # review-by: 2026-10-01 "RUSTSEC-2023-0071", - # bincode unmaintained — transitive dep via librqbit-peer-protocol. - # review-by: 2026-10-01 - "RUSTSEC-2025-0141", # crypto-hash unmaintained — transitive dep via librqbit-sha1-wrapper. # review-by: 2026-10-01 "RUSTSEC-2025-0060", # paste unmaintained (dtolnay archived) — transitive dep via lofty/taxis. # review-by: 2026-10-01 "RUSTSEC-2024-0436", - # quick-xml < 0.41 quadratic-attribute DoS. The direct indexer-XML parser (eksetasis) is on 0.41; the remaining 0.37.5 is transitively pinned by feed-rs (RSS/podcast) and librqbit-upnp (LAN UPnP), neither of which yet targets 0.41. No safe upgrade until upstream bumps. - # review-by: 2026-10-01 - "RUSTSEC-2026-0194", - # quick-xml < 0.41 NsReader namespace-allocation DoS. Same transitive pin as RUSTSEC-2026-0194: the direct eksetasis parser is on 0.41; feed-rs and librqbit-upnp still require 0.37. No safe upgrade until upstream bumps. - # review-by: 2026-10-01 - "RUSTSEC-2026-0195", ] diff --git a/deny.toml b/deny.toml index 8bec92f0..74a012f5 100644 --- a/deny.toml +++ b/deny.toml @@ -19,10 +19,6 @@ all-features = false id = "RUSTSEC-2023-0071" reason = "rsa timing side-channel via jsonwebtoken/exousia — no safe upgrade, local-only use. review-by: 2026-10-01" -[[advisories.ignore]] -id = "RUSTSEC-2025-0141" -reason = "bincode unmaintained — transitive dep via librqbit-peer-protocol. review-by: 2026-10-01" - [[advisories.ignore]] id = "RUSTSEC-2025-0060" reason = "crypto-hash unmaintained — transitive dep via librqbit-sha1-wrapper. review-by: 2026-10-01" @@ -31,14 +27,6 @@ reason = "crypto-hash unmaintained — transitive dep via librqbit-sha1-wrapper. id = "RUSTSEC-2024-0436" reason = "paste unmaintained (dtolnay archived) — transitive dep via lofty/taxis. review-by: 2026-10-01" -[[advisories.ignore]] -id = "RUSTSEC-2026-0194" -reason = "quick-xml < 0.41 quadratic-attribute DoS. The direct indexer-XML parser (eksetasis) is on 0.41; the remaining 0.37.5 is transitively pinned by feed-rs (RSS/podcast) and librqbit-upnp (LAN UPnP), neither of which yet targets 0.41. No safe upgrade until upstream bumps. review-by: 2026-10-01" - -[[advisories.ignore]] -id = "RUSTSEC-2026-0195" -reason = "quick-xml < 0.41 NsReader namespace-allocation DoS. Same transitive pin as RUSTSEC-2026-0194: the direct eksetasis parser is on 0.41; feed-rs and librqbit-upnp still require 0.37. No safe upgrade until upstream bumps. review-by: 2026-10-01" - [licenses] allow = [ "MIT", @@ -75,32 +63,18 @@ wildcards = "deny" # so "deny" binds on registry deps, per REPO-SETUP.md. allow-wildcard-paths = true skip = [ - # reqwest / wasm-streams: TEMPORARY, sequencing-scoped — not upstream - # blocked. archon bumps its own reqwest to 0.13 here (#731); librqbit - # 8.1.1 (still the pin on this branch) pulls reqwest 0.12 independently. - # librqbit 9 (harmonia#735, landing right after this PR) itself resolves - # to reqwest 0.13.4 — the two branches converge once #735 rebases onto - # this one, at which point both these skips (and wasm-streams, which is - # purely transitive on reqwest's version) become unmatched. Remove them - # then rather than leaving them as permanent exemptions — do not add a - # matching skip to #735's deny.toml; one exemption, removed once, not two. - { name = "reqwest", version = "0.12" }, - { name = "reqwest", version = "0.13" }, - { name = "wasm-streams", version = "0.4" }, - { name = "wasm-streams", version = "0.5" }, - - # bincode: librqbit-peer-protocol pins 1.x; librqbit itself uses 2.x - { name = "bincode", version = "1" }, - { name = "bincode", version = "2" }, + # atoi: sqlx-sqlite pins "2.0" (still true at its newest release, 0.9.0 -- + # verified against upstream's Cargo.toml at that tag); librqbit's + # workspace Cargo.toml pins "3" (its only 9.x release line). Neither + # upstream has a version that closes the gap -- genuinely blocked, not a + # cargo-update-away unification. + { name = "atoi", version = "2" }, + { name = "atoi", version = "3" }, # num-complex: librqbit size formatting still pulls num 0.2; audio DSP uses rustfft/rubato 0.4 { name = "num-complex", version = "0.2" }, { name = "num-complex", version = "0.4" }, - # quick-xml: feed-rs and librqbit-upnp require 0.37; eksetasis indexer uses 0.41 - { name = "quick-xml", version = "0.37" }, - { name = "quick-xml", version = "0.41" }, - # block-buffer: digest 0.10 (transitive) uses 0.10.x; digest 0.11 (sha2 0.11) uses 0.12.x { name = "block-buffer", version = "0.10" }, { name = "block-buffer", version = "0.12" }, @@ -212,7 +186,8 @@ skip = [ # syn: serde_derive 1.0.229 moved to syn 3.x; the rest of the proc-macro # tree (clap_derive, darling, cssparser-macros, async-stream-impl, ...) - # still pins 2.x. Transient ecosystem migration, same class as bincode. + # still pins 2.x. Transient ecosystem migration — expect this to collapse + # to one version as the proc-macro tree catches up. { name = "syn", version = "2" }, { name = "syn", version = "3" }, diff --git a/osv-scanner.toml b/osv-scanner.toml index 77e841cd..fd40f31a 100644 --- a/osv-scanner.toml +++ b/osv-scanner.toml @@ -6,10 +6,6 @@ id = "RUSTSEC-2023-0071" reason = "rsa timing side-channel via jsonwebtoken/exousia — no safe upgrade, local-only use. review-by: 2026-10-01" -[[IgnoredVulns]] -id = "RUSTSEC-2025-0141" -reason = "bincode unmaintained — transitive dep via librqbit-peer-protocol. review-by: 2026-10-01" - [[IgnoredVulns]] id = "RUSTSEC-2025-0060" reason = "crypto-hash unmaintained — transitive dep via librqbit-sha1-wrapper. review-by: 2026-10-01" @@ -17,11 +13,3 @@ reason = "crypto-hash unmaintained — transitive dep via librqbit-sha1-wrapper. [[IgnoredVulns]] id = "RUSTSEC-2024-0436" reason = "paste unmaintained (dtolnay archived) — transitive dep via lofty/taxis. review-by: 2026-10-01" - -[[IgnoredVulns]] -id = "RUSTSEC-2026-0194" -reason = "quick-xml < 0.41 quadratic-attribute DoS. The direct indexer-XML parser (eksetasis) is on 0.41; the remaining 0.37.5 is transitively pinned by feed-rs (RSS/podcast) and librqbit-upnp (LAN UPnP), neither of which yet targets 0.41. No safe upgrade until upstream bumps. review-by: 2026-10-01" - -[[IgnoredVulns]] -id = "RUSTSEC-2026-0195" -reason = "quick-xml < 0.41 NsReader namespace-allocation DoS. Same transitive pin as RUSTSEC-2026-0194: the direct eksetasis parser is on 0.41; feed-rs and librqbit-upnp still require 0.37. No safe upgrade until upstream bumps. review-by: 2026-10-01" From 235c1fc8d06ebbd4a58a1e1dc64f6319a6799b44 Mon Sep 17 00:00:00 2001 From: forkwright Date: Tue, 25 Aug 2026 19:05:33 -0500 Subject: [PATCH 5/5] chore(deps): regenerate the lock on the unified reqwest Rebased onto #731. The prediction it carried holds: with archon on reqwest 0.13 and librqbit 9 resolving to 0.13.4, the two converge -- the lock now has ONE reqwest entry and wasm-streams 0.4.2 drops out entirely. So #731's temporary skips are removed rather than duplicated here, which is what its own comment asked for: one exemption, removed once, not two. bincode goes with them; it is absent from this lock, since librqbit 9 no longer pulls the 1.x line. atoi stays, and it is the only duplicate genuinely exempted: sqlx-sqlite pins 2.0 at its newest release and librqbit 9 pins 3, so no cargo update closes it. The rustls provider fix is not re-applied -- #731 landed the same change in archon and syndesis, and this branch takes main's version of both files. The lock is regenerated by cargo rather than hand-merged through the rebase conflict. Each side had resolved with the other's crate still present, so combining them by hand would have produced a file cargo would not. --- Cargo.lock | 116 +++++++++++++++++++---------------------------------- 1 file changed, 41 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b3ad107..4b253568 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -254,7 +254,7 @@ dependencies = [ "quinn", "rand 0.10.2", "rcgen", - "reqwest 0.12.28", + "reqwest", "rmcp", "rstest", "rustls", @@ -1273,6 +1273,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "ctor" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" +dependencies = [ + "link-section", + "linktime-proc-macro", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -1673,6 +1683,7 @@ dependencies = [ "aggelmata", "apotheke", "bytes", + "ctor", "dashmap", "ego-tree", "futures", @@ -1680,8 +1691,9 @@ dependencies = [ "jiff", "quick-xml", "regex", - "reqwest 0.12.28", + "reqwest", "rstest", + "rustls", "scraper", "serde", "serde_json", @@ -1735,7 +1747,8 @@ dependencies = [ "apotheke", "dashmap", "horismos", - "reqwest 0.12.28", + "reqwest", + "rustls", "serde", "serde_json", "snafu", @@ -2462,7 +2475,6 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", ] [[package]] @@ -2934,8 +2946,9 @@ dependencies = [ "feed-rs", "horismos", "jiff", - "reqwest 0.12.28", + "reqwest", "rstest", + "rustls", "serde", "snafu", "sqlx", @@ -3080,7 +3093,7 @@ dependencies = [ "parking_lot", "rand 0.10.2", "regex", - "reqwest 0.13.4", + "reqwest", "rlimit", "serde", "serde_derive", @@ -3287,7 +3300,7 @@ dependencies = [ "librqbit-dualstack-sockets", "parking_lot", "rand 0.10.2", - "reqwest 0.13.4", + "reqwest", "serde", "serde_derive", "serde_with", @@ -3311,7 +3324,7 @@ dependencies = [ "librqbit-dualstack-sockets", "network-interface", "quick-xml", - "reqwest 0.13.4", + "reqwest", "serde", "serde_derive", "socket2", @@ -3354,6 +3367,18 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "link-section" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c29a617ce3df32c08497bdc1ab6e2376e0b17948ac166a2fbe5977c5954cd9" + +[[package]] +name = "linktime-proc-macro" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e57c38c1e860fd37c604281cdfb1dd2216977fd76a50f85ba2f388ef3219616" + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -4379,8 +4404,9 @@ dependencies = [ "apotheke", "horismos", "jiff", - "reqwest 0.12.28", + "reqwest", "rstest", + "rustls", "serde", "serde_json", "snafu", @@ -4711,47 +4737,6 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams 0.4.2", - "web-sys", - "webpki-roots", -] - [[package]] name = "reqwest" version = "0.13.4" @@ -4778,6 +4763,7 @@ dependencies = [ "rustls-platform-verifier", "serde", "serde_json", + "serde_urlencoded", "sync_wrapper", "tokio", "tokio-rustls", @@ -4788,7 +4774,7 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams 0.5.0", + "wasm-streams", "web-sys", ] @@ -5561,7 +5547,8 @@ dependencies = [ name = "skene" version = "0.4.1" dependencies = [ - "reqwest 0.12.28", + "reqwest", + "rustls", "serde", "serde_json", "snafu", @@ -6178,8 +6165,9 @@ dependencies = [ "horismos", "jiff", "md5", - "reqwest 0.12.28", + "reqwest", "rstest", + "rustls", "serde", "serde_json", "snafu", @@ -7152,19 +7140,6 @@ dependencies = [ "wasmparser", ] -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasm-streams" version = "0.5.0" @@ -7231,15 +7206,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webpki-roots" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "weedle2" version = "5.0.0"