diff --git a/.cargo/audit.toml b/.cargo/audit.toml index 58a5a264..66f2e1fc 100644 --- a/.cargo/audit.toml +++ b/.cargo/audit.toml @@ -9,25 +9,10 @@ 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", - # 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/Cargo.lock b/Cargo.lock index aa03a23d..4b253568 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -254,7 +254,7 @@ dependencies = [ "quinn", "rand 0.10.2", "rcgen", - "reqwest 0.13.4", + "reqwest", "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" @@ -1253,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" @@ -1536,6 +1566,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 +1599,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" @@ -1635,15 +1683,17 @@ dependencies = [ "aggelmata", "apotheke", "bytes", + "ctor", "dashmap", "ego-tree", "futures", "horismos", "jiff", - "quick-xml 0.41.0", + "quick-xml", "regex", - "reqwest 0.13.4", + "reqwest", "rstest", + "rustls", "scraper", "serde", "serde_json", @@ -1697,7 +1747,8 @@ dependencies = [ "apotheke", "dashmap", "horismos", - "reqwest 0.13.4", + "reqwest", + "rustls", "serde", "serde_json", "snafu", @@ -1827,7 +1878,7 @@ checksum = "369995dae0733f1fe5ab0e3f345f6503a5f384179df5d8da333702031a131cf9" dependencies = [ "chrono", "mediatype", - "quick-xml 0.41.0", + "quick-xml", "regex", "serde", "serde_json", @@ -1952,6 +2003,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 +2205,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" @@ -2408,22 +2477,6 @@ dependencies = [ "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", -] - [[package]] name = "hyper-util" version = "0.1.20" @@ -2655,15 +2708,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 +2731,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,8 +2946,9 @@ dependencies = [ "feed-rs", "horismos", "jiff", - "reqwest 0.13.4", + "reqwest", "rstest", + "rustls", "serde", "snafu", "sqlx", @@ -3008,18 +3053,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 +3080,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", "rlimit", "serde", + "serde_derive", "serde_json", "serde_urlencoded", "serde_with", "size_format", + "socket2", + "thiserror 2.0.18", "tokio", "tokio-socks", "tokio-stream", @@ -3060,61 +3112,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 +3185,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 +3267,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 +3284,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", "serde", + "serde_derive", + "serde_with", "tokio", "tokio-util", "tracing", @@ -3205,23 +3313,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", "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" @@ -3233,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" @@ -3394,6 +3540,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 +3594,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 +3641,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 +4103,7 @@ dependencies = [ "kritike", "md5", "mdns-sd", - "quick-xml 0.41.0", + "quick-xml", "rand 0.10.2", "serde", "serde_json", @@ -4253,8 +4404,9 @@ dependencies = [ "apotheke", "horismos", "jiff", - "reqwest 0.13.4", + "reqwest", "rstest", + "rustls", "serde", "serde_json", "snafu", @@ -4282,16 +4434,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 +4471,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 +4606,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" @@ -4585,45 +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-tls", - "hyper-util", - "js-sys", - "log", - "native-tls", - "percent-encoding", - "pin-project-lite", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams 0.4.2", - "web-sys", -] - [[package]] name = "reqwest" version = "0.13.4" @@ -4644,6 +4757,7 @@ dependencies = [ "log", "percent-encoding", "pin-project-lite", + "quinn", "rustls", "rustls-pki-types", "rustls-platform-verifier", @@ -4660,7 +4774,7 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams 0.5.0", + "wasm-streams", "web-sys", ] @@ -4688,11 +4802,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 +4980,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 +5044,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 +5547,8 @@ dependencies = [ name = "skene" version = "0.4.1" dependencies = [ - "reqwest 0.13.4", + "reqwest", + "rustls", "serde", "serde_json", "snafu", @@ -5609,7 +5737,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 +5779,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 +5816,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,8 +6165,9 @@ dependencies = [ "horismos", "jiff", "md5", - "reqwest 0.13.4", + "reqwest", "rstest", + "rustls", "serde", "serde_json", "snafu", @@ -6252,16 +6381,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 +6936,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 +7003,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" @@ -7033,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" 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/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/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 28cf4265..74c7e75d 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, Ipv6Addr, 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,109 @@ 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. + // + // 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, 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 + // 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 { + // 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, + 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, @@ -433,7 +495,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() @@ -614,7 +682,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 @@ -1337,10 +1410,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() } @@ -1351,10 +1436,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() } @@ -1764,26 +1854,43 @@ 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. 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( + leech_dir.clone(), + SessionOptions { + dht: None, + persistence: None, + listen: Some(ListenerOptions { + listen_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port), + mode: ListenerMode::TcpOnly, + enable_upnp_port_forwarding: false, + ..Default::default() + }), + ..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..0722c7de 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); @@ -212,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), diff --git a/deny.toml b/deny.toml index 1f2f283e..74a012f5 100644 --- a/deny.toml +++ b/deny.toml @@ -19,34 +19,14 @@ 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" - [[advisories.ignore]] 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" -[[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", @@ -83,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" }, @@ -220,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/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..118b7195 100644 --- a/docs/download/torrent.md +++ b/docs/download/torrent.md @@ -33,30 +33,47 @@ 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)), + 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::V6(Ipv6Addr::UNSPECIFIED), port), + mode: ListenerMode::TcpOnly, + 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. +- **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..fd40f31a 100644 --- a/osv-scanner.toml +++ b/osv-scanner.toml @@ -6,30 +6,10 @@ 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" - [[IgnoredVulns]] 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" - -[[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"