From 702e03b31eee0bc0b75dbc4a7a1bf23403ee26d3 Mon Sep 17 00:00:00 2001 From: TheDancingDeveloper Date: Wed, 29 Jul 2026 03:02:20 +0000 Subject: [PATCH] feat: prepare SwarmForge release family --- Cargo.lock | 1052 ++++++++--------- Cargo.toml | 24 +- crates/SWARMFORGE-README.md | 32 + crates/librtbit-bencode/Cargo.toml | 12 +- crates/librtbit-buffers/Cargo.toml | 12 +- crates/librtbit-clone-to-owned/Cargo.toml | 12 +- crates/librtbit-core/Cargo.toml | 12 +- crates/librtbit-core/src/announce_port.rs | 75 ++ crates/librtbit-core/src/lib.rs | 2 + crates/librtbit-dht/Cargo.toml | 12 +- crates/librtbit-dht/src/dht.rs | 52 +- crates/librtbit-lsd/Cargo.toml | 12 +- crates/librtbit-lsd/src/lib.rs | 57 +- crates/librtbit-peer-protocol/Cargo.toml | 12 +- crates/librtbit-sha1-wrapper/Cargo.toml | 12 +- crates/librtbit-tracker-comms/Cargo.toml | 12 +- .../src/tracker_comms.rs | 40 +- crates/librtbit-upnp-serve/Cargo.toml | 12 +- crates/librtbit-upnp/Cargo.toml | 12 +- crates/librtbit/Cargo.toml | 12 +- crates/librtbit/src/api.rs | 13 + .../src/http_api/handlers/qbit_compat.rs | 165 ++- crates/librtbit/src/session/mod.rs | 21 +- crates/librtbit/src/session/peer_sources.rs | 14 +- crates/librtbit/src/tests/mock_tracker.rs | 96 ++ crates/rtbit/src/main.rs | 19 + docs/SWARMFORGE-0.1.0-RELEASE.md | 124 ++ fuzz/Cargo.lock | 417 +++---- fuzz/Cargo.toml | 6 +- scripts/publish.sh | 91 +- 30 files changed, 1632 insertions(+), 812 deletions(-) create mode 100644 crates/SWARMFORGE-README.md create mode 100644 crates/librtbit-core/src/announce_port.rs create mode 100644 docs/SWARMFORGE-0.1.0-RELEASE.md mode change 100644 => 100755 scripts/publish.sh diff --git a/Cargo.lock b/Cargo.lock index 3f251b7..ed8ba0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3315,312 +3315,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "librtbit" -version = "0.1.6" -dependencies = [ - "anyhow", - "arc-swap", - "async-backtrace", - "async-compression", - "async-stream", - "async-trait", - "axum", - "axum-extra", - "backon", - "base64 0.22.1", - "bincode", - "bitvec", - "byteorder", - "bytes", - "chrono", - "console-subscriber", - "crc32fast", - "dashmap 6.2.1", - "feed-rs", - "futures", - "governor", - "hex 0.4.3", - "home", - "http", - "intervaltree", - "itertools 0.14.0", - "librqbit-dualstack-sockets", - "librqbit-utp", - "librtbit-bencode", - "librtbit-buffers", - "librtbit-clone-to-owned", - "librtbit-core", - "librtbit-dht", - "librtbit-lsd", - "librtbit-peer-protocol", - "librtbit-sha1-wrapper", - "librtbit-tracker-comms", - "librtbit-upnp", - "librtbit-upnp-serve", - "lru", - "memmap2", - "metrics-exporter-prometheus", - "mime_guess", - "nix", - "notify", - "parking_lot", - "pollster", - "rand 0.10.1", - "regex", - "reqwest", - "rlimit", - "rusqlite", - "serde", - "serde_derive", - "serde_json", - "serde_urlencoded", - "serde_with", - "size_format", - "socket2", - "sqlx", - "tempfile", - "thiserror 2.0.18", - "tokio", - "tokio-socks", - "tokio-stream", - "tokio-test", - "tokio-util", - "tower-http", - "tracing", - "tracing-subscriber", - "url", - "urlencoding", - "utoipa", - "utoipa-axum", - "utoipa-swagger-ui", - "uuid", - "walkdir", - "windows 0.62.2", -] - -[[package]] -name = "librtbit-bencode" -version = "0.1.5" -dependencies = [ - "anyhow", - "arrayvec", - "atoi 3.0.0", - "bytes", - "librtbit-buffers", - "librtbit-clone-to-owned", - "serde", - "serde_derive", - "thiserror 2.0.18", -] - -[[package]] -name = "librtbit-buffers" -version = "0.1.2" -dependencies = [ - "bytes", - "librtbit-clone-to-owned", - "serde", - "serde_derive", -] - -[[package]] -name = "librtbit-clone-to-owned" -version = "0.1.1" -dependencies = [ - "bytes", -] - -[[package]] -name = "librtbit-core" -version = "0.1.5" -dependencies = [ - "anyhow", - "bytes", - "chardetng", - "data-encoding", - "directories", - "encoding_rs", - "hex 0.4.3", - "itertools 0.14.0", - "librtbit-bencode", - "librtbit-buffers", - "librtbit-clone-to-owned", - "librtbit-sha1-wrapper", - "memchr", - "parking_lot", - "rand 0.10.1", - "serde", - "serde_derive", - "serde_json", - "thiserror 2.0.18", - "tokio", - "tokio-util", - "tracing", - "url", -] - -[[package]] -name = "librtbit-dht" -version = "0.1.4" -dependencies = [ - "anyhow", - "aws-lc-rs", - "backon", - "bytes", - "chrono", - "dashmap 6.2.1", - "futures", - "indexmap 2.14.0", - "leaky-bucket", - "librqbit-dualstack-sockets", - "librtbit-bencode", - "librtbit-buffers", - "librtbit-clone-to-owned", - "librtbit-core", - "librtbit-sha1-wrapper", - "parking_lot", - "rand 0.10.1", - "serde", - "serde_derive", - "serde_json", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "librtbit-lsd" -version = "0.1.2" -dependencies = [ - "anyhow", - "atoi 3.0.0", - "bstr", - "futures", - "httparse", - "librqbit-dualstack-sockets", - "librtbit-core", - "librtbit-sha1-wrapper", - "parking_lot", - "rand 0.10.1", - "tokio", - "tokio-util", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "librtbit-peer-protocol" -version = "0.1.4" -dependencies = [ - "anyhow", - "bitvec", - "byteorder", - "bytes", - "criterion", - "itertools 0.14.0", - "librtbit-bencode", - "librtbit-buffers", - "librtbit-clone-to-owned", - "librtbit-core", - "serde", - "serde_derive", - "thiserror 2.0.18", - "tracing", -] - -[[package]] -name = "librtbit-sha1-wrapper" -version = "0.1.2" -dependencies = [ - "assert_cfg", - "aws-lc-rs", - "crypto-hash", - "hex 0.4.3", -] - -[[package]] -name = "librtbit-tracker-comms" -version = "0.1.6" -dependencies = [ - "anyhow", - "async-stream", - "backon", - "byteorder", - "futures", - "itertools 0.14.0", - "librqbit-dualstack-sockets", - "librtbit-bencode", - "librtbit-buffers", - "librtbit-core", - "parking_lot", - "rand 0.10.1", - "reqwest", - "serde", - "serde_derive", - "serde_with", - "tokio", - "tokio-util", - "tracing", - "url", - "urlencoding", -] - -[[package]] -name = "librtbit-upnp" -version = "0.1.2" -dependencies = [ - "anyhow", - "bstr", - "futures", - "httparse", - "librqbit-dualstack-sockets", - "network-interface", - "quick-xml 0.40.1", - "reqwest", - "serde", - "serde_derive", - "socket2", - "tokio", - "tracing", - "tracing-subscriber", - "url", -] - -[[package]] -name = "librtbit-upnp-serve" -version = "0.1.2" -dependencies = [ - "anyhow", - "axum", - "bstr", - "futures", - "gethostname", - "http", - "httparse", - "librqbit-dualstack-sockets", - "librtbit-core", - "librtbit-upnp", - "mime_guess", - "network-interface", - "parking_lot", - "quick-xml 0.40.1", - "rand 0.10.1", - "reqwest", - "serde", - "serde_derive", - "tokio", - "tokio-util", - "tower-http", - "tracing", - "tracing-subscriber", - "url", - "uuid", -] - [[package]] name = "libsqlite3-sys" version = "0.37.0" @@ -5283,9 +4977,6 @@ dependencies = [ "gethostname", "libc", "librqbit-dualstack-sockets", - "librtbit", - "librtbit-core", - "librtbit-upnp-serve", "metrics-exporter-prometheus", "openssl", "parking_lot", @@ -5296,6 +4987,9 @@ dependencies = [ "serde_json", "signal-hook 0.4.4", "size_format", + "swarmforge", + "swarmforge-core", + "swarmforge-upnp-serve", "tokio", "tokio-util", "tracing", @@ -5311,7 +5005,6 @@ dependencies = [ "directories", "gethostname", "librqbit-dualstack-sockets", - "librtbit", "metrics-exporter-prometheus", "open", "parking_lot", @@ -5319,6 +5012,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "swarmforge", "tauri", "tauri-build", "tauri-plugin-notification", @@ -5985,320 +5679,626 @@ dependencies = [ ] [[package]] -name = "socket2" -version = "0.6.4" +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spinning_top" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + +[[package]] +name = "sqlx" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb" +dependencies = [ + "base64 0.22.1", + "bytes", + "cfg-if", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.16.1", + "hashlink", + "indexmap 2.14.0", + "log", + "memchr", + "percent-encoding", + "serde", + "serde_json", + "sha2 0.10.9", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.117", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3" +dependencies = [ + "cfg-if", + "dotenvy", + "either", + "heck 0.5.0", + "hex 0.4.3", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.9", + "sqlx-core", + "sqlx-postgres", + "syn 2.0.117", + "thiserror 2.0.18", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" +dependencies = [ + "bitflags 2.13.0", + "byteorder", + "bytes", + "digest 0.11.3", + "dotenvy", + "either", + "futures-core", + "futures-util", + "generic-array 0.14.9", + "log", + "percent-encoding", + "serde", + "sha1", + "sha2 0.11.0", + "sqlx-core", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "sqlx-postgres" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e" dependencies = [ - "libc", - "windows-sys 0.61.2", + "atoi 2.0.0", + "base64 0.22.1", + "bitflags 2.13.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex 0.4.3", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "rand 0.10.1", + "serde", + "serde_json", + "sha2 0.11.0", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "whoami", ] [[package]] -name = "softbuffer" -version = "0.4.8" +name = "sqlx-sqlite" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ - "bytemuck", - "js-sys", - "ndk", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "objc2-quartz-core", - "raw-window-handle", - "redox_syscall", + "atoi 2.0.0", + "flume", + "form_urlencoded", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "thiserror 2.0.18", "tracing", - "wasm-bindgen", - "web-sys", - "windows-sys 0.61.2", + "url", ] [[package]] -name = "soup3" -version = "0.5.0" +name = "stable_deref_trait" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" -dependencies = [ - "futures-channel", - "gio", - "glib", - "libc", - "soup3-sys", -] +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "soup3-sys" -version = "0.5.0" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "spin" -version = "0.9.8" +name = "string_cache" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ - "lock_api", + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", ] [[package]] -name = "spinning_top" -version = "0.3.0" +name = "string_cache_codegen" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ - "lock_api", + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", ] [[package]] -name = "sqlite-wasm-rs" -version = "0.5.5" +name = "stringprep" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" dependencies = [ - "cc", - "js-sys", - "rsqlite-vfs", - "wasm-bindgen", + "unicode-bidi", + "unicode-normalization", + "unicode-properties", ] [[package]] -name = "sqlx" -version = "0.9.0" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "sqlx-core" -version = "0.9.0" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "swarmforge" +version = "0.1.0" dependencies = [ + "anyhow", + "arc-swap", + "async-backtrace", + "async-compression", + "async-stream", + "async-trait", + "axum", + "axum-extra", + "backon", "base64 0.22.1", + "bincode", + "bitvec", + "byteorder", "bytes", - "cfg-if", - "crc", - "crossbeam-queue", - "either", - "event-listener", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.16.1", - "hashlink", - "indexmap 2.14.0", - "log", - "memchr", - "percent-encoding", + "chrono", + "console-subscriber", + "crc32fast", + "dashmap 6.2.1", + "feed-rs", + "futures", + "governor", + "hex 0.4.3", + "home", + "http", + "intervaltree", + "itertools 0.14.0", + "librqbit-dualstack-sockets", + "librqbit-utp", + "lru", + "memmap2", + "metrics-exporter-prometheus", + "mime_guess", + "nix", + "notify", + "parking_lot", + "pollster", + "rand 0.10.1", + "regex", + "reqwest", + "rlimit", + "rusqlite", "serde", + "serde_derive", "serde_json", - "sha2 0.10.9", - "smallvec", + "serde_urlencoded", + "serde_with", + "size_format", + "socket2", + "sqlx", + "swarmforge-bencode", + "swarmforge-buffers", + "swarmforge-clone-to-owned", + "swarmforge-core", + "swarmforge-dht", + "swarmforge-lsd", + "swarmforge-peer-protocol", + "swarmforge-sha1-wrapper", + "swarmforge-tracker-comms", + "swarmforge-upnp", + "swarmforge-upnp-serve", + "tempfile", "thiserror 2.0.18", "tokio", + "tokio-socks", "tokio-stream", + "tokio-test", + "tokio-util", + "tower-http", "tracing", + "tracing-subscriber", "url", + "urlencoding", + "utoipa", + "utoipa-axum", + "utoipa-swagger-ui", + "uuid", + "walkdir", + "windows 0.62.2", +] + +[[package]] +name = "swarmforge-bencode" +version = "0.1.0" +dependencies = [ + "anyhow", + "arrayvec", + "atoi 3.0.0", + "bytes", + "serde", + "serde_derive", + "swarmforge-buffers", + "swarmforge-clone-to-owned", + "thiserror 2.0.18", ] [[package]] -name = "sqlx-macros" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf" +name = "swarmforge-buffers" +version = "0.1.0" dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 2.0.117", + "bytes", + "serde", + "serde_derive", + "swarmforge-clone-to-owned", ] [[package]] -name = "sqlx-macros-core" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3" +name = "swarmforge-clone-to-owned" +version = "0.1.0" dependencies = [ - "cfg-if", - "dotenvy", - "either", - "heck 0.5.0", + "bytes", +] + +[[package]] +name = "swarmforge-core" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytes", + "chardetng", + "data-encoding", + "directories", + "encoding_rs", "hex 0.4.3", - "proc-macro2", - "quote", + "itertools 0.14.0", + "memchr", + "parking_lot", + "rand 0.10.1", "serde", + "serde_derive", "serde_json", - "sha2 0.10.9", - "sqlx-core", - "sqlx-postgres", - "syn 2.0.117", + "swarmforge-bencode", + "swarmforge-buffers", + "swarmforge-clone-to-owned", + "swarmforge-sha1-wrapper", "thiserror 2.0.18", "tokio", + "tokio-util", + "tracing", "url", ] [[package]] -name = "sqlx-mysql" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" +name = "swarmforge-dht" +version = "0.1.0" dependencies = [ - "bitflags 2.13.0", - "byteorder", + "anyhow", + "aws-lc-rs", + "backon", "bytes", - "digest 0.11.3", - "dotenvy", - "either", - "futures-core", - "futures-util", - "generic-array 0.14.9", - "log", - "percent-encoding", + "chrono", + "dashmap 6.2.1", + "futures", + "indexmap 2.14.0", + "leaky-bucket", + "librqbit-dualstack-sockets", + "parking_lot", + "rand 0.10.1", "serde", - "sha1", - "sha2 0.11.0", - "sqlx-core", + "serde_derive", + "serde_json", + "swarmforge-bencode", + "swarmforge-buffers", + "swarmforge-clone-to-owned", + "swarmforge-core", + "swarmforge-sha1-wrapper", "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", "tracing", + "tracing-subscriber", ] [[package]] -name = "sqlx-postgres" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e" +name = "swarmforge-lsd" +version = "0.1.0" dependencies = [ - "atoi 2.0.0", - "base64 0.22.1", - "bitflags 2.13.0", - "byteorder", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex 0.4.3", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", + "anyhow", + "atoi 3.0.0", + "bstr", + "futures", + "httparse", + "librqbit-dualstack-sockets", + "parking_lot", "rand 0.10.1", - "serde", - "serde_json", - "sha2 0.11.0", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", + "swarmforge-core", + "swarmforge-sha1-wrapper", + "tokio", + "tokio-util", "tracing", - "whoami", + "tracing-subscriber", ] [[package]] -name = "sqlx-sqlite" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" +name = "swarmforge-peer-protocol" +version = "0.1.0" dependencies = [ - "atoi 2.0.0", - "flume", - "form_urlencoded", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", + "anyhow", + "bitvec", + "byteorder", + "bytes", + "criterion", + "itertools 0.14.0", "serde", - "sqlx-core", + "serde_derive", + "swarmforge-bencode", + "swarmforge-buffers", + "swarmforge-clone-to-owned", + "swarmforge-core", "thiserror 2.0.18", "tracing", - "url", ] [[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +name = "swarmforge-sha1-wrapper" +version = "0.1.0" +dependencies = [ + "assert_cfg", + "aws-lc-rs", + "crypto-hash", + "hex 0.4.3", +] [[package]] -name = "string_cache" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +name = "swarmforge-tracker-comms" +version = "0.1.0" dependencies = [ - "new_debug_unreachable", + "anyhow", + "async-stream", + "backon", + "byteorder", + "futures", + "itertools 0.14.0", + "librqbit-dualstack-sockets", "parking_lot", - "phf_shared", - "precomputed-hash", + "rand 0.10.1", + "reqwest", + "serde", + "serde_derive", + "serde_with", + "swarmforge-bencode", + "swarmforge-buffers", + "swarmforge-core", + "tokio", + "tokio-util", + "tracing", + "url", + "urlencoding", ] [[package]] -name = "string_cache_codegen" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +name = "swarmforge-upnp" +version = "0.1.0" dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", + "anyhow", + "bstr", + "futures", + "httparse", + "librqbit-dualstack-sockets", + "network-interface", + "quick-xml 0.40.1", + "reqwest", + "serde", + "serde_derive", + "socket2", + "tokio", + "tracing", + "tracing-subscriber", + "url", ] [[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +name = "swarmforge-upnp-serve" +version = "0.1.0" dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", + "anyhow", + "axum", + "bstr", + "futures", + "gethostname", + "http", + "httparse", + "librqbit-dualstack-sockets", + "mime_guess", + "network-interface", + "parking_lot", + "quick-xml 0.40.1", + "rand 0.10.1", + "reqwest", + "serde", + "serde_derive", + "swarmforge-core", + "swarmforge-upnp", + "tokio", + "tokio-util", + "tower-http", + "tracing", + "tracing-subscriber", + "url", + "uuid", ] -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - [[package]] name = "swift-rs" version = "1.0.7" diff --git a/Cargo.toml b/Cargo.toml index 5ee66d5..4b7d2a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,18 +46,18 @@ debug = true [workspace.dependencies] # Internal crates. Version-plus-path keeps publishing metadata while making this # monorepo's sources authoritative for all local builds. -librtbit = { version = "0.1.6", path = "crates/librtbit", default-features = false } -bencode = { version = "0.1.5", path = "crates/librtbit-bencode", package = "librtbit-bencode", default-features = false } -buffers = { version = "0.1.2", path = "crates/librtbit-buffers", package = "librtbit-buffers" } -clone_to_owned = { version = "0.1.1", path = "crates/librtbit-clone-to-owned", package = "librtbit-clone-to-owned" } -librtbit-core = { version = "0.1.5", path = "crates/librtbit-core", default-features = false } -dht = { version = "0.1.4", path = "crates/librtbit-dht", package = "librtbit-dht", default-features = false } -librtbit-lsd = { version = "0.1.2", path = "crates/librtbit-lsd", default-features = false } -peer_binary_protocol = { version = "0.1.4", path = "crates/librtbit-peer-protocol", package = "librtbit-peer-protocol", default-features = false } -sha1w = { version = "0.1.2", path = "crates/librtbit-sha1-wrapper", package = "librtbit-sha1-wrapper", default-features = false } -tracker_comms = { version = "0.1.6", path = "crates/librtbit-tracker-comms", package = "librtbit-tracker-comms", default-features = false } -librtbit-upnp = { version = "0.1.2", path = "crates/librtbit-upnp" } -upnp-serve = { version = "0.1.2", path = "crates/librtbit-upnp-serve", package = "librtbit-upnp-serve", default-features = false } +librtbit = { version = "0.1.0", path = "crates/librtbit", package = "swarmforge", default-features = false } +bencode = { version = "0.1.0", path = "crates/librtbit-bencode", package = "swarmforge-bencode", default-features = false } +buffers = { version = "0.1.0", path = "crates/librtbit-buffers", package = "swarmforge-buffers" } +clone_to_owned = { version = "0.1.0", path = "crates/librtbit-clone-to-owned", package = "swarmforge-clone-to-owned" } +librtbit-core = { version = "0.1.0", path = "crates/librtbit-core", package = "swarmforge-core", default-features = false } +dht = { version = "0.1.0", path = "crates/librtbit-dht", package = "swarmforge-dht", default-features = false } +librtbit-lsd = { version = "0.1.0", path = "crates/librtbit-lsd", package = "swarmforge-lsd", default-features = false } +peer_binary_protocol = { version = "0.1.0", path = "crates/librtbit-peer-protocol", package = "swarmforge-peer-protocol", default-features = false } +sha1w = { version = "0.1.0", path = "crates/librtbit-sha1-wrapper", package = "swarmforge-sha1-wrapper", default-features = false } +tracker_comms = { version = "0.1.0", path = "crates/librtbit-tracker-comms", package = "swarmforge-tracker-comms", default-features = false } +librtbit-upnp = { version = "0.1.0", path = "crates/librtbit-upnp", package = "swarmforge-upnp" } +upnp-serve = { version = "0.1.0", path = "crates/librtbit-upnp-serve", package = "swarmforge-upnp-serve", default-features = false } # External crates anyhow = "1" diff --git a/crates/SWARMFORGE-README.md b/crates/SWARMFORGE-README.md new file mode 100644 index 0000000..8068fbb --- /dev/null +++ b/crates/SWARMFORGE-README.md @@ -0,0 +1,32 @@ +# SwarmForge + +SwarmForge is the reusable BitTorrent engine and protocol-crate family that +powers [rustTorrent](https://github.com/TheDancingDeveloper-org/rustTorrent). +The public packages are released together from the rustTorrent monorepo. + +The first coordinated release changes Cargo package identities from the +historical `librtbit-*` names to `swarmforge-*`. Rust library names remain +compatible, so existing imports such as `librtbit::Session`, +`librtbit_core::Id20`, and `librtbit_bencode` continue to work when dependencies +use an alias: + +```toml +librtbit = { package = "swarmforge", version = "0.1" } +librtbit-core = { package = "swarmforge-core", version = "0.1" } +bencode = { package = "swarmforge-bencode", version = "0.1" } +``` + +Consumers must migrate every SwarmForge family member in one dependency graph +atomically. Mixing old and new package identities can duplicate shared types and +break trait coherence. The historical repositories and packages remain available +as rollback sources while downstream consumers migrate. + +SwarmForge is derived from [rqbit](https://github.com/ikatson/rqbit) by Igor +Katson. Copyright 2021 Igor Katson. The original Apache-2.0 license applies; see +the rustTorrent repository's [license](https://github.com/TheDancingDeveloper-org/rustTorrent/blob/main/LICENSE) +and +[fork provenance](https://github.com/TheDancingDeveloper-org/rustTorrent/blob/main/docs/FORK_PROVENANCE.md) +for details. + +Source, issues, and release history are maintained in the +[rustTorrent repository](https://github.com/TheDancingDeveloper-org/rustTorrent). diff --git a/crates/librtbit-bencode/Cargo.toml b/crates/librtbit-bencode/Cargo.toml index f012037..1db37c2 100644 --- a/crates/librtbit-bencode/Cargo.toml +++ b/crates/librtbit-bencode/Cargo.toml @@ -1,10 +1,16 @@ [package] -name = "librtbit-bencode" -version = "0.1.5" +name = "swarmforge-bencode" +version = "0.1.0" edition = "2024" description = "Bencode serialization and deserialization using Serde" -license = "MIT" +license = "Apache-2.0" repository = "https://github.com/TheDancingDeveloper-org/rustTorrent" +readme = "../SWARMFORGE-README.md" +keywords = ["bencode", "bittorrent", "swarmforge"] +categories = ["encoding", "network-programming"] + +[lib] +name = "librtbit_bencode" [dependencies] serde = { version = "1", features = ["derive"] } diff --git a/crates/librtbit-buffers/Cargo.toml b/crates/librtbit-buffers/Cargo.toml index f484748..142a522 100644 --- a/crates/librtbit-buffers/Cargo.toml +++ b/crates/librtbit-buffers/Cargo.toml @@ -1,10 +1,16 @@ [package] -name = "librtbit-buffers" -version = "0.1.2" +name = "swarmforge-buffers" +version = "0.1.0" edition = "2024" description = "Utils to work with byte buffers in librtbit source code" -license = "MIT" +license = "Apache-2.0" repository = "https://github.com/TheDancingDeveloper-org/rustTorrent" +readme = "../SWARMFORGE-README.md" +keywords = ["buffers", "bittorrent", "swarmforge"] +categories = ["data-structures", "network-programming"] + +[lib] +name = "librtbit_buffers" [dependencies] serde = "1" diff --git a/crates/librtbit-clone-to-owned/Cargo.toml b/crates/librtbit-clone-to-owned/Cargo.toml index 0c08a24..c470c88 100644 --- a/crates/librtbit-clone-to-owned/Cargo.toml +++ b/crates/librtbit-clone-to-owned/Cargo.toml @@ -1,10 +1,16 @@ [package] -name = "librtbit-clone-to-owned" -version = "0.1.1" +name = "swarmforge-clone-to-owned" +version = "0.1.0" edition = "2024" description = "Util traits to represent something that can be made owned and change type at the same time" -license = "MIT" +license = "Apache-2.0" repository = "https://github.com/TheDancingDeveloper-org/rustTorrent" +readme = "../SWARMFORGE-README.md" +keywords = ["ownership", "bittorrent", "swarmforge"] +categories = ["data-structures"] + +[lib] +name = "librtbit_clone_to_owned" [dependencies] bytes = "1" diff --git a/crates/librtbit-core/Cargo.toml b/crates/librtbit-core/Cargo.toml index efafb41..5b050cf 100644 --- a/crates/librtbit-core/Cargo.toml +++ b/crates/librtbit-core/Cargo.toml @@ -1,10 +1,16 @@ [package] -name = "librtbit-core" -version = "0.1.5" +name = "swarmforge-core" +version = "0.1.0" edition = "2024" description = "Core types and utilities for the rtbit BitTorrent client" -license = "MIT" +license = "Apache-2.0" repository = "https://github.com/TheDancingDeveloper-org/rustTorrent" +readme = "../SWARMFORGE-README.md" +keywords = ["bittorrent", "torrent", "swarmforge"] +categories = ["network-programming", "data-structures"] + +[lib] +name = "librtbit_core" [features] default = ["sha1-crypto-hash"] diff --git a/crates/librtbit-core/src/announce_port.rs b/crates/librtbit-core/src/announce_port.rs new file mode 100644 index 0000000..308f003 --- /dev/null +++ b/crates/librtbit-core/src/announce_port.rs @@ -0,0 +1,75 @@ +use std::{ + num::NonZeroU16, + sync::{ + Arc, + atomic::{AtomicU16, Ordering}, + }, +}; + +/// Runtime-mutable port advertised to BitTorrent peer-discovery services. +/// +/// A zero atomic value represents a disabled announcement. Clones share the +/// same value, allowing tracker, DHT, and LSD workers to observe lease changes +/// without being restarted. +#[derive(Clone, Debug, Default)] +pub struct AnnouncePort(Arc); + +impl AnnouncePort { + pub fn new(port: Option) -> Self { + Self(Arc::new(AtomicU16::new( + port.map(NonZeroU16::get).unwrap_or_default(), + ))) + } + + pub fn get(&self) -> Option { + NonZeroU16::new(self.0.load(Ordering::Acquire)) + } + + pub fn set(&self, port: NonZeroU16) { + self.0.store(port.get(), Ordering::Release); + } +} + +#[cfg(test)] +mod tests { + use super::AnnouncePort; + use std::num::NonZeroU16; + use std::thread; + + #[test] + fn clones_observe_runtime_updates() { + let port = AnnouncePort::new(NonZeroU16::new(4241)); + let worker_port = port.clone(); + + port.set(NonZeroU16::new(51_234).unwrap()); + + assert_eq!(worker_port.get().map(NonZeroU16::get), Some(51_234)); + } + + #[test] + fn concurrent_readers_observe_only_complete_updates() { + let port = AnnouncePort::new(NonZeroU16::new(4241)); + let readers = (0..4) + .map(|_| { + let port = port.clone(); + thread::spawn(move || { + for _ in 0..10_000 { + let observed = port.get().map(NonZeroU16::get).unwrap(); + assert!(matches!(observed, 4241 | 51_234)); + } + }) + }) + .collect::>(); + + for _ in 0..10_000 { + port.set(NonZeroU16::new(51_234).unwrap()); + port.set(NonZeroU16::new(4241).unwrap()); + } + port.set(NonZeroU16::new(51_234).unwrap()); + + for reader in readers { + reader.join().unwrap(); + } + assert_eq!(port.get().map(NonZeroU16::get), Some(51_234)); + } +} diff --git a/crates/librtbit-core/src/lib.rs b/crates/librtbit-core/src/lib.rs index 70218d0..d802354 100644 --- a/crates/librtbit-core/src/lib.rs +++ b/crates/librtbit-core/src/lib.rs @@ -1,3 +1,4 @@ +pub mod announce_port; pub mod compact_ip; pub mod constants; pub mod directories; @@ -9,6 +10,7 @@ pub mod peer_id; pub mod spawn_utils; pub mod speed_estimator; pub mod torrent_metainfo; +pub use announce_port::AnnouncePort; pub use hash_id::{Id20, Id32}; pub use error::Error; diff --git a/crates/librtbit-dht/Cargo.toml b/crates/librtbit-dht/Cargo.toml index b107b55..ba52bb2 100644 --- a/crates/librtbit-dht/Cargo.toml +++ b/crates/librtbit-dht/Cargo.toml @@ -1,10 +1,16 @@ [package] -name = "librtbit-dht" -version = "0.1.4" +name = "swarmforge-dht" +version = "0.1.0" edition = "2024" description = "Kademlia DHT implementation for the rtbit BitTorrent client" -license = "MIT" +license = "Apache-2.0" repository = "https://github.com/TheDancingDeveloper-org/rustTorrent" +readme = "../SWARMFORGE-README.md" +keywords = ["bittorrent", "dht", "swarmforge"] +categories = ["network-programming"] + +[lib] +name = "librtbit_dht" [features] default = ["sha1-crypto-hash"] diff --git a/crates/librtbit-dht/src/dht.rs b/crates/librtbit-dht/src/dht.rs index 4c50ea0..982d2db 100644 --- a/crates/librtbit-dht/src/dht.rs +++ b/crates/librtbit-dht/src/dht.rs @@ -29,6 +29,7 @@ use futures::{ use leaky_bucket::RateLimiter; use librqbit_dualstack_sockets::{BindDevice, UdpSocket}; use librtbit_core::{ + AnnouncePort, compact_ip::{CompactSerialize, CompactSerializeFixedLen}, crate_version, hash_id::Id20, @@ -108,7 +109,16 @@ trait RecursiveRequestCallbacks: Sized + Send + Sync + 'static { struct RecursiveRequestCallbacksGetPeers { // Id20::from_str("00000fffffffffffffffffffffffffffffffffff").unwrap() min_distance_to_announce: Id20, - announce_port: Option, + announce_port: Option, +} + +impl RecursiveRequestCallbacksGetPeers { + fn current_announce_port(&self) -> Option { + self.announce_port + .as_ref() + .and_then(AnnouncePort::get) + .map(std::num::NonZeroU16::get) + } } impl RecursiveRequestCallbacks for RecursiveRequestCallbacksGetPeers { @@ -121,8 +131,8 @@ impl RecursiveRequestCallbacks for RecursiveRequestCallbacksGetPeers { addr: SocketAddr, resp: &crate::Result, ) { - let announce_port = match self.announce_port { - Some(a) => a, + let announce_port = match self.current_announce_port() { + Some(port) => port, None => return, }; let resp = match resp { @@ -157,6 +167,27 @@ impl RecursiveRequestCallbacks for RecursiveRequestCallbacksGetPeers { } } +#[cfg(test)] +mod announce_port_tests { + use super::RecursiveRequestCallbacksGetPeers; + use librtbit_core::{AnnouncePort, Id20}; + use std::{num::NonZeroU16, str::FromStr}; + + #[test] + fn announce_peer_decision_reads_runtime_port() { + let shared = AnnouncePort::new(NonZeroU16::new(4241)); + let callbacks = RecursiveRequestCallbacksGetPeers { + min_distance_to_announce: Id20::from_str("0000ffffffffffffffffffffffffffffffffffff") + .unwrap(), + announce_port: Some(shared.clone()), + }; + + assert_eq!(callbacks.current_announce_port(), Some(4241)); + shared.set(NonZeroU16::new(51_234).unwrap()); + assert_eq!(callbacks.current_announce_port(), Some(51_234)); + } +} + struct RecursiveRequestCallbacksFindNodes {} impl RecursiveRequestCallbacks for RecursiveRequestCallbacksFindNodes { fn on_request_start(&self, req: &RecursiveRequest, target_node: Id20, addr: SocketAddr) { @@ -204,7 +235,7 @@ pub struct RequestPeersStream { } impl RequestPeersStream { - fn new(dht: Arc, info_hash: Id20, announce_port: Option) -> Self { + fn new(dht: Arc, info_hash: Id20, announce_port: Option) -> Self { let (peer_tx, peer_rx) = unbounded_channel(); let make = |is_v4: bool, dht: Arc, peer_tx: UnboundedSender| { let (node_tx, node_rx) = unbounded_channel(); @@ -222,7 +253,7 @@ impl RequestPeersStream { "0000ffffffffffffffffffffffffffffffffffff", ) .unwrap(), - announce_port, + announce_port: announce_port.clone(), }, }); rp.request_peers_forever(node_rx, is_v4) @@ -1438,6 +1469,17 @@ impl DhtState { self: &Arc, info_hash: Id20, announce_port: Option, + ) -> RequestPeersStream { + self.get_peers_with_shared_announce_port( + info_hash, + announce_port.map(|port| AnnouncePort::new(std::num::NonZeroU16::new(port))), + ) + } + + pub fn get_peers_with_shared_announce_port( + self: &Arc, + info_hash: Id20, + announce_port: Option, ) -> RequestPeersStream { RequestPeersStream::new(self.clone(), info_hash, announce_port) } diff --git a/crates/librtbit-lsd/Cargo.toml b/crates/librtbit-lsd/Cargo.toml index edf8568..b34be57 100644 --- a/crates/librtbit-lsd/Cargo.toml +++ b/crates/librtbit-lsd/Cargo.toml @@ -1,10 +1,16 @@ [package] -name = "librtbit-lsd" -version = "0.1.2" +name = "swarmforge-lsd" +version = "0.1.0" edition = "2024" description = "BEP 14 Local Service Discovery for the rtbit BitTorrent client" -license = "MIT" +license = "Apache-2.0" repository = "https://github.com/TheDancingDeveloper-org/rustTorrent" +readme = "../SWARMFORGE-README.md" +keywords = ["bittorrent", "lsd", "swarmforge"] +categories = ["network-programming"] + +[lib] +name = "librtbit_lsd" [dependencies] anyhow = "1" diff --git a/crates/librtbit-lsd/src/lib.rs b/crates/librtbit-lsd/src/lib.rs index 1be387c..5e43c17 100644 --- a/crates/librtbit-lsd/src/lib.rs +++ b/crates/librtbit-lsd/src/lib.rs @@ -12,7 +12,7 @@ use std::{ use anyhow::Context; use futures::Stream; use librqbit_dualstack_sockets::{BindDevice, MulticastUdpSocket}; -use librtbit_core::{Id20, spawn_utils::spawn_with_cancel}; +use librtbit_core::{AnnouncePort, Id20, spawn_utils::spawn_with_cancel}; use parking_lot::RwLock; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}; use tokio_util::sync::CancellationToken; @@ -57,11 +57,20 @@ impl RateLimiter { struct Announce { tx: UnboundedSender, - port: Option, + port: Option, last_reply_ipv4: RateLimiter, last_reply_ipv6: RateLimiter, } +impl Announce { + fn current_port(&self) -> Option { + self.port + .as_ref() + .and_then(AnnouncePort::get) + .map(std::num::NonZeroU16::get) + } +} + struct LocalServiceDiscoveryInner { socket: MulticastUdpSocket, cookie: u32, @@ -174,7 +183,7 @@ cookie: {cookie}\r return_if_none!(announce.tx.send(addr).ok()); - let announce_port = return_if_none!(announce.port); + let announce_port = return_if_none!(announce.current_port()); let rl = if addr.is_ipv4() { &announce.last_reply_ipv4 @@ -215,7 +224,11 @@ cookie: {cookie}\r } } - async fn task_announce_periodically(self, info_hash: Id20, port: u16) -> anyhow::Result<()> { + async fn task_announce_periodically( + self, + info_hash: Id20, + port: AnnouncePort, + ) -> anyhow::Result<()> { let mut interval = tokio::time::interval(Duration::from_secs(60 * 5)); loop { interval.tick().await; @@ -223,7 +236,9 @@ cookie: {cookie}\r self.inner .socket .try_send_mcast_everywhere(&|mopts| { - Some(self.gen_announce_msg(info_hash, port, mopts.mcast_addr().is_ipv6())) + port.get().map(|port| { + self.gen_announce_msg(info_hash, port.get(), mopts.mcast_addr().is_ipv6()) + }) }) .await; } @@ -233,6 +248,17 @@ cookie: {cookie}\r &self, info_hash: Id20, announce_port: Option, + ) -> impl Stream + Send + Sync + 'static { + self.announce_with_shared_port( + info_hash, + announce_port.map(|port| AnnouncePort::new(std::num::NonZeroU16::new(port))), + ) + } + + pub fn announce_with_shared_port( + &self, + info_hash: Id20, + announce_port: Option, ) -> impl Stream + Send + Sync + 'static { // 1. Periodically announce the torrent. // 2. Stream back the results from received messages. @@ -266,7 +292,7 @@ cookie: {cookie}\r info_hash, Announce { tx, - port: announce_port, + port: announce_port.clone(), last_reply_ipv4: Default::default(), last_reply_ipv6: Default::default(), }, @@ -274,8 +300,9 @@ cookie: {cookie}\r if let Some(announce_port) = announce_port { let cancel_token = self.inner.cancel_token.child_token(); + let initial_port = announce_port.get().map(std::num::NonZeroU16::get); spawn_with_cancel( - debug_span!(parent: None, "lsd-announce", ?info_hash, port=announce_port), + debug_span!(parent: None, "lsd-announce", ?info_hash, port=initial_port), "lsd-announce", cancel_token, self.clone() @@ -613,6 +640,22 @@ mod tests { assert_eq!(LSD_IPV6.port(), 6771); } + #[test] + fn lsd_reply_decision_reads_runtime_port() { + let shared = AnnouncePort::new(std::num::NonZeroU16::new(4241)); + let (tx, _rx) = unbounded_channel(); + let announce = Announce { + tx, + port: Some(shared.clone()), + last_reply_ipv4: Default::default(), + last_reply_ipv6: Default::default(), + }; + + assert_eq!(announce.current_port(), Some(4241)); + shared.set(std::num::NonZeroU16::new(51_234).unwrap()); + assert_eq!(announce.current_port(), Some(51_234)); + } + #[test] fn test_lsd_rate_limiter_allows_first_call() { let rl = RateLimiter::default(); diff --git a/crates/librtbit-peer-protocol/Cargo.toml b/crates/librtbit-peer-protocol/Cargo.toml index 001425e..41e00c5 100644 --- a/crates/librtbit-peer-protocol/Cargo.toml +++ b/crates/librtbit-peer-protocol/Cargo.toml @@ -1,10 +1,16 @@ [package] -name = "librtbit-peer-protocol" -version = "0.1.4" +name = "swarmforge-peer-protocol" +version = "0.1.0" edition = "2024" description = "BitTorrent peer wire protocol implementation for the rtbit client" -license = "MIT" +license = "Apache-2.0" repository = "https://github.com/TheDancingDeveloper-org/rustTorrent" +readme = "../SWARMFORGE-README.md" +keywords = ["bittorrent", "protocol", "swarmforge"] +categories = ["network-programming"] + +[lib] +name = "librtbit_peer_protocol" [dependencies] serde = "1" diff --git a/crates/librtbit-sha1-wrapper/Cargo.toml b/crates/librtbit-sha1-wrapper/Cargo.toml index 23246df..aa55352 100644 --- a/crates/librtbit-sha1-wrapper/Cargo.toml +++ b/crates/librtbit-sha1-wrapper/Cargo.toml @@ -1,10 +1,16 @@ [package] -name = "librtbit-sha1-wrapper" -version = "0.1.2" +name = "swarmforge-sha1-wrapper" +version = "0.1.0" edition = "2024" description = "Pluggable SHA1/SHA256 abstraction for the rtbit BitTorrent client" -license = "MIT" +license = "Apache-2.0" repository = "https://github.com/TheDancingDeveloper-org/rustTorrent" +readme = "../SWARMFORGE-README.md" +keywords = ["sha1", "bittorrent", "swarmforge"] +categories = ["cryptography"] + +[lib] +name = "librtbit_sha1_wrapper" [features] default = ["sha1-crypto-hash"] diff --git a/crates/librtbit-tracker-comms/Cargo.toml b/crates/librtbit-tracker-comms/Cargo.toml index 1b18c43..b99e35f 100644 --- a/crates/librtbit-tracker-comms/Cargo.toml +++ b/crates/librtbit-tracker-comms/Cargo.toml @@ -1,10 +1,16 @@ [package] -name = "librtbit-tracker-comms" -version = "0.1.6" +name = "swarmforge-tracker-comms" +version = "0.1.0" edition = "2024" description = "HTTP and UDP tracker communication for the rtbit BitTorrent client" -license = "MIT" +license = "Apache-2.0" repository = "https://github.com/TheDancingDeveloper-org/rustTorrent" +readme = "../SWARMFORGE-README.md" +keywords = ["bittorrent", "tracker", "swarmforge"] +categories = ["network-programming"] + +[lib] +name = "librtbit_tracker_comms" [dependencies] tokio = "1" diff --git a/crates/librtbit-tracker-comms/src/tracker_comms.rs b/crates/librtbit-tracker-comms/src/tracker_comms.rs index b2e65b8..c1cba28 100644 --- a/crates/librtbit-tracker-comms/src/tracker_comms.rs +++ b/crates/librtbit-tracker-comms/src/tracker_comms.rs @@ -25,6 +25,7 @@ use crate::tracker_comms_http; use crate::tracker_comms_udp; use crate::tracker_comms_udp::UdpTrackerClient; use crate::tracker_status::TrackerStatusRegistry; +use librtbit_core::AnnouncePort; use librtbit_core::hash_id::Id20; pub struct TrackerComms { @@ -34,7 +35,7 @@ pub struct TrackerComms { force_tracker_interval: Option, tx: Sender, // This MUST be set as trackers don't work with 0 port. - announce_port: u16, + announce_port: AnnouncePort, reqwest_client: reqwest::Client, key: u32, status: Option>, @@ -152,6 +153,31 @@ impl TrackerComms { reqwest_client: reqwest::Client, udp_client: Option, status: Option>, + ) -> Option> { + Self::start_with_shared_announce_port( + info_hash, + peer_id, + trackers, + stats, + force_interval, + AnnouncePort::new(std::num::NonZeroU16::new(announce_port)), + reqwest_client, + udp_client, + status, + ) + } + + #[allow(clippy::too_many_arguments)] + pub fn start_with_shared_announce_port( + info_hash: Id20, + peer_id: Id20, + trackers: HashSet, + stats: Box, + force_interval: Option, + announce_port: AnnouncePort, + reqwest_client: reqwest::Client, + udp_client: Option, + status: Option>, ) -> Option> { let trackers = trackers .into_iter() @@ -320,7 +346,11 @@ impl TrackerComms { let request = tracker_comms_http::TrackerRequest { info_hash: &self.info_hash, peer_id: &self.peer_id, - port: self.announce_port, + port: self + .announce_port + .get() + .map(std::num::NonZeroU16::get) + .unwrap_or(4240), uploaded: stats.uploaded_bytes, downloaded: stats.downloaded_bytes, left: stats.get_left_to_download_bytes(), @@ -482,7 +512,11 @@ impl TrackerComms { } }, key: self.key, - port: self.announce_port, + port: self + .announce_port + .get() + .map(std::num::NonZeroU16::get) + .unwrap_or(4240), }; match client.announce(addr, request).await { diff --git a/crates/librtbit-upnp-serve/Cargo.toml b/crates/librtbit-upnp-serve/Cargo.toml index 1d8b404..e07fd0c 100644 --- a/crates/librtbit-upnp-serve/Cargo.toml +++ b/crates/librtbit-upnp-serve/Cargo.toml @@ -1,10 +1,16 @@ [package] -name = "librtbit-upnp-serve" -version = "0.1.2" +name = "swarmforge-upnp-serve" +version = "0.1.0" edition = "2024" description = "Simple UPnP MediaServer implementation for the rtbit BitTorrent client" -license = "MIT" +license = "Apache-2.0" repository = "https://github.com/TheDancingDeveloper-org/rustTorrent" +readme = "../SWARMFORGE-README.md" +keywords = ["bittorrent", "upnp", "swarmforge"] +categories = ["network-programming"] + +[lib] +name = "librtbit_upnp_serve" [dependencies] anyhow = "1" diff --git a/crates/librtbit-upnp/Cargo.toml b/crates/librtbit-upnp/Cargo.toml index 497de06..ba49f1a 100644 --- a/crates/librtbit-upnp/Cargo.toml +++ b/crates/librtbit-upnp/Cargo.toml @@ -1,10 +1,16 @@ [package] -name = "librtbit-upnp" -version = "0.1.2" +name = "swarmforge-upnp" +version = "0.1.0" edition = "2024" description = "UPnP port forwarding client for the rtbit BitTorrent client" -license = "MIT" +license = "Apache-2.0" repository = "https://github.com/TheDancingDeveloper-org/rustTorrent" +readme = "../SWARMFORGE-README.md" +keywords = ["bittorrent", "upnp", "swarmforge"] +categories = ["network-programming"] + +[lib] +name = "librtbit_upnp" [dependencies] tracing = "0.1" diff --git a/crates/librtbit/Cargo.toml b/crates/librtbit/Cargo.toml index 1a4218e..909e066 100644 --- a/crates/librtbit/Cargo.toml +++ b/crates/librtbit/Cargo.toml @@ -1,10 +1,16 @@ [package] -name = "librtbit" -version = "0.1.6" +name = "swarmforge" +version = "0.1.0" edition = "2024" description = "Core BitTorrent client library for the rtbit torrent client" -license = "MIT" +license = "Apache-2.0" repository = "https://github.com/TheDancingDeveloper-org/rustTorrent" +readme = "../SWARMFORGE-README.md" +keywords = ["bittorrent", "torrent", "swarmforge"] +categories = ["network-programming"] + +[lib] +name = "librtbit" [features] default = ["default-tls", "http-api-client"] diff --git a/crates/librtbit/src/api.rs b/crates/librtbit/src/api.rs index e0d2c9e..30f5bb2 100644 --- a/crates/librtbit/src/api.rs +++ b/crates/librtbit/src/api.rs @@ -2,6 +2,7 @@ use std::{ collections::{HashMap, HashSet}, marker::PhantomData, net::SocketAddr, + num::NonZeroU16, path::PathBuf, str::FromStr, sync::Arc, @@ -516,6 +517,18 @@ impl Api { self.session.output_folder().to_string_lossy().into_owned() } + pub fn api_listen_port(&self) -> u16 { + self.session.listen_addr().map_or(0, |addr| addr.port()) + } + + pub fn api_announce_port(&self) -> u16 { + self.session.announce_port().unwrap_or_default() + } + + pub fn api_set_announce_port(&self, port: NonZeroU16) { + self.session.set_announce_port(port); + } + pub fn api_set_output_folder(&self, folder: String) { self.session.set_output_folder(PathBuf::from(folder)); } diff --git a/crates/librtbit/src/http_api/handlers/qbit_compat.rs b/crates/librtbit/src/http_api/handlers/qbit_compat.rs index 6ef780c..44deb7c 100644 --- a/crates/librtbit/src/http_api/handlers/qbit_compat.rs +++ b/crates/librtbit/src/http_api/handlers/qbit_compat.rs @@ -6,6 +6,7 @@ use std::{ collections::HashMap, + num::NonZeroU16, sync::Arc, time::{Instant, SystemTime, UNIX_EPOCH}, }; @@ -227,6 +228,19 @@ struct QbitPreferences { queueing_enabled: bool, locale: &'static str, web_ui_port: u16, + listen_port: u16, + announce_port: u16, +} + +#[derive(Deserialize)] +struct SetPreferencesForm { + json: String, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +struct SetPreferences { + announce_port: u16, } // --------------------------------------------------------------------------- @@ -300,7 +314,7 @@ async fn h_app_build_info() -> impl IntoResponse { }) } -async fn h_app_preferences(State(state): State>) -> impl IntoResponse { +async fn h_app_preferences(State(state): State>) -> axum::Json { let save_path = state.api_state.api.api_output_folder(); axum::Json(QbitPreferences { @@ -320,9 +334,44 @@ async fn h_app_preferences(State(state): State>) -> impl IntoResp queueing_enabled: false, locale: "en", web_ui_port: state.api_state.opts.web_ui_port.unwrap_or(3030), + listen_port: state.api_state.api.api_listen_port(), + announce_port: state.api_state.api.api_announce_port(), }) } +async fn h_app_set_preferences( + State(state): State>, + body: Bytes, +) -> impl IntoResponse { + let form = match serde_urlencoded::from_bytes::(&body) { + Ok(form) => form, + Err(error) => { + return ( + StatusCode::BAD_REQUEST, + format!("invalid preferences form: {error}"), + ); + } + }; + let preferences = match serde_json::from_str::(&form.json) { + Ok(preferences) => preferences, + Err(error) => { + return ( + StatusCode::BAD_REQUEST, + format!("invalid preferences JSON: {error}"), + ); + } + }; + let Some(port) = NonZeroU16::new(preferences.announce_port) else { + return ( + StatusCode::BAD_REQUEST, + "announce_port must be between 1 and 65535".to_string(), + ); + }; + + state.api_state.api.api_set_announce_port(port); + (StatusCode::OK, String::new()) +} + // --------------------------------------------------------------------------- // Transfer info // --------------------------------------------------------------------------- @@ -1149,7 +1198,8 @@ pub(crate) fn make_qbit_router(api_state: ApiState) -> Router { .route("/version", get(h_app_version)) .route("/webapiVersion", get(h_app_webapi_version)) .route("/buildInfo", get(h_app_build_info)) - .route("/preferences", get(h_app_preferences)); + .route("/preferences", get(h_app_preferences)) + .route("/setPreferences", post(h_app_set_preferences)); // Torrent endpoints let torrents_router = Router::new() @@ -1213,7 +1263,116 @@ pub(crate) fn make_qbit_router(api_state: ApiState) -> Router { #[cfg(test)] mod tests { - use super::matches_category; + use std::{net::Ipv4Addr, sync::Arc}; + + use axum::response::IntoResponse; + use bytes::Bytes; + use http::StatusCode; + + use crate::{ + Api, ListenerMode, Session, SessionOptions, + http_api::{HttpApi, HttpApiOptions}, + listen::ListenerOptions, + }; + + use super::{ + QbitSessions, QbitState, h_app_preferences, h_app_set_preferences, matches_category, + }; + + async fn qbit_state() -> (Arc, Arc, tempfile::TempDir) { + let output = tempfile::TempDir::with_prefix("qbit_preferences").unwrap(); + let session = Session::new_with_opts( + output.path().to_owned(), + SessionOptions { + disable_dht: true, + disable_local_service_discovery: true, + listen: Some(ListenerOptions { + mode: ListenerMode::TcpOnly, + listen_addr: (Ipv4Addr::LOCALHOST, 0).into(), + announce_port: Some(4241), + ..Default::default() + }), + ..Default::default() + }, + ) + .await + .unwrap(); + let api = Api::new( + session.clone(), + None, + #[cfg(feature = "tracing-subscriber-utils")] + None, + ); + let api_state = Arc::new(HttpApi::new( + api, + Some(HttpApiOptions { + web_ui_port: Some(3031), + ..Default::default() + }), + )); + ( + Arc::new(QbitState { + api_state, + sessions: QbitSessions::new(), + }), + session, + output, + ) + } + + #[tokio::test] + async fn preferences_reports_fixed_listener_and_runtime_announce_port() { + let (state, session, _output) = qbit_state().await; + + let preferences = h_app_preferences(axum::extract::State(state)).await.0; + + assert_eq!( + preferences.listen_port, + session.listen_addr().unwrap().port() + ); + assert_eq!(preferences.announce_port, 4241); + assert_eq!(preferences.web_ui_port, 3031); + } + + #[tokio::test] + async fn set_preferences_updates_port_idempotently_without_rebinding_listener() { + let (state, session, _output) = qbit_state().await; + let listen_addr = session.listen_addr().unwrap(); + let body = Bytes::from_static(b"json=%7B%22announce_port%22%3A51234%7D"); + + for _ in 0..2 { + let response = h_app_set_preferences(axum::extract::State(state.clone()), body.clone()) + .await + .into_response(); + assert_eq!(response.status(), StatusCode::OK); + } + + assert_eq!(session.announce_port(), Some(51_234)); + assert_eq!(session.listen_addr(), Some(listen_addr)); + } + + #[tokio::test] + async fn set_preferences_rejects_invalid_and_unsupported_fields() { + let (state, session, _output) = qbit_state().await; + let invalid_bodies = [ + "json=%7B%22announce_port%22%3A0%7D", + "json=%7B%22announce_port%22%3A65536%7D", + "json=%7B%22save_path%22%3A%22%2Ftmp%22%7D", + "json=not-json", + "announce_port=4241", + ]; + + for body in invalid_bodies { + let response = h_app_set_preferences( + axum::extract::State(state.clone()), + Bytes::copy_from_slice(body.as_bytes()), + ) + .await + .into_response(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST, "body={body}"); + } + assert_eq!(session.announce_port(), Some(4241)); + } #[test] fn category_filter_supports_qbittorrent_special_values() { diff --git a/crates/librtbit/src/session/mod.rs b/crates/librtbit/src/session/mod.rs index 3a9c3b7..b7c6caf 100644 --- a/crates/librtbit/src/session/mod.rs +++ b/crates/librtbit/src/session/mod.rs @@ -20,6 +20,7 @@ use std::{ borrow::Cow, collections::HashSet, net::SocketAddr, + num::NonZeroU16, path::{Component, Path, PathBuf}, sync::{Arc, atomic::AtomicUsize}, time::Duration, @@ -56,7 +57,7 @@ use futures::{FutureExt, StreamExt, future::BoxFuture, stream::FuturesUnordered} use itertools::Itertools; use librqbit_utp::BindDevice; use librtbit_core::{ - crate_version, peer_id::generate_azereus_style, spawn_utils::spawn_with_cancel, + AnnouncePort, crate_version, peer_id::generate_azereus_style, spawn_utils::spawn_with_cancel, torrent_metainfo::ValidatedTorrentMetaV1Info, }; use librtbit_lsd::{LocalServiceDiscovery, LocalServiceDiscoveryOptions}; @@ -103,7 +104,7 @@ pub struct Session { // Network peer_id: Id20, - announce_port: Option, + announce_port: AnnouncePort, listen_addr: Option, dht: Option, pub(crate) connector: Arc, @@ -449,7 +450,12 @@ impl Session { db: RwLock::new(Default::default()), _cancellation_token_drop_guard: token.clone().drop_guard(), cancellation_token: token, - announce_port: listen_result.as_ref().and_then(|l| l.announce_port), + announce_port: AnnouncePort::new( + listen_result + .as_ref() + .and_then(|l| l.announce_port) + .and_then(NonZeroU16::new), + ), listen_addr: listen_result.as_ref().map(|l| l.addr), default_storage_factory: opts.default_storage_factory, reqwest_client, @@ -1339,7 +1345,14 @@ impl Session { } pub fn announce_port(&self) -> Option { - self.announce_port + self.announce_port.get().map(NonZeroU16::get) + } + + /// Update the port advertised by trackers, DHT, and LSD. + /// + /// This deliberately does not rebind the peer listener. + pub fn set_announce_port(&self, port: NonZeroU16) { + self.announce_port.set(port); } pub fn categories(&self) -> &CategoryManager { diff --git a/crates/librtbit/src/session/peer_sources.rs b/crates/librtbit/src/session/peer_sources.rs index 0480695..f6ff6e9 100644 --- a/crates/librtbit/src/session/peer_sources.rs +++ b/crates/librtbit/src/session/peer_sources.rs @@ -61,7 +61,10 @@ impl Session { None } else { self.dht.as_ref().map(|dht| { - dht.get_peers(info_hash, if announce { self.announce_port } else { None }) + dht.get_peers_with_shared_announce_port( + info_hash, + announce.then(|| self.announce_port.clone()), + ) }) }; @@ -69,7 +72,10 @@ impl Session { None } else { self.lsd.as_ref().map(|lsd| { - lsd.announce(info_hash, if announce { self.announce_port } else { None }) + lsd.announce_with_shared_port( + info_hash, + announce.then(|| self.announce_port.clone()), + ) }) }; @@ -84,13 +90,13 @@ impl Session { info_hash, session: self.clone(), }; - let tracker_rx = TrackerComms::start( + let tracker_rx = TrackerComms::start_with_shared_announce_port( info_hash, self.peer_id, trackers.into_iter().collect(), Box::new(tracker_rx_stats), force_tracker_interval, - self.announce_port().unwrap_or(4240), + self.announce_port.clone(), self.reqwest_client.clone(), self.udp_tracker_client.clone(), tracker_status, diff --git a/crates/librtbit/src/tests/mock_tracker.rs b/crates/librtbit/src/tests/mock_tracker.rs index aa83d6d..b9a4172 100644 --- a/crates/librtbit/src/tests/mock_tracker.rs +++ b/crates/librtbit/src/tests/mock_tracker.rs @@ -238,6 +238,102 @@ mod tests { tokio::time::sleep(Duration::from_millis(10)).await; } + #[tokio::test(flavor = "multi_thread")] + async fn live_tracker_worker_observes_announce_port_update() { + use std::num::NonZeroU16; + + let tracker = MockUdpTracker::start().await; + let output = tempfile::TempDir::with_prefix("tracker_port_update").unwrap(); + let source = output.path().join("payload.bin"); + create_new_file_with_random_content(&source, 16 * 1024); + let torrent = create_torrent( + &source, + CreateTorrentOptions { + trackers: vec![tracker.url()], + piece_length: Some(16 * 1024), + ..Default::default() + }, + &BlockingSpawner::new(1), + ) + .await + .unwrap(); + let info_hash = torrent.info_hash(); + let listen_port = available_port(); + let initial_announce_port = available_port(); + let updated_announce_port = available_port(); + let session = Session::new_with_opts( + output.path().to_owned(), + SessionOptions { + disable_dht: true, + disable_local_service_discovery: true, + listen: Some(ListenerOptions { + mode: ListenerMode::TcpOnly, + listen_addr: (Ipv4Addr::LOCALHOST, listen_port).into(), + announce_port: Some(initial_announce_port), + ..Default::default() + }), + ..Default::default() + }, + ) + .await + .unwrap(); + let handle = session + .add_torrent( + AddTorrent::TorrentFileBytes(torrent.as_bytes().unwrap()), + Some(AddTorrentOptions { + overwrite: true, + force_tracker_interval: Some(Duration::from_secs(1)), + ..Default::default() + }), + ) + .await + .unwrap() + .into_handle() + .unwrap(); + + wait_until( + || { + let peers = tracker.peers(info_hash); + if peers + .iter() + .any(|peer| peer.port() == initial_announce_port) + { + Ok(()) + } else { + anyhow::bail!("initial announce port not observed: {peers:?}") + } + }, + Duration::from_secs(10), + ) + .await + .unwrap(); + + session.set_announce_port(NonZeroU16::new(updated_announce_port).unwrap()); + + wait_until( + || { + let peers = tracker.peers(info_hash); + if peers + .iter() + .any(|peer| peer.port() == updated_announce_port) + { + Ok(()) + } else { + anyhow::bail!("updated announce port not observed: {peers:?}") + } + }, + Duration::from_secs(10), + ) + .await + .unwrap(); + assert_eq!(session.listen_addr().unwrap().port(), listen_port); + assert_eq!(session.announce_port(), Some(updated_announce_port)); + + drop(handle); + drop(session); + tracker.shutdown().await; + } + #[tokio::test(flavor = "multi_thread")] async fn leech_discovers_seed_through_tracker() { let tracker = MockUdpTracker::start().await; diff --git a/crates/rtbit/src/main.rs b/crates/rtbit/src/main.rs index 882e629..887f50d 100644 --- a/crates/rtbit/src/main.rs +++ b/crates/rtbit/src/main.rs @@ -1193,6 +1193,25 @@ fn spawn_stats_printer(session: Arc) { #[cfg(test)] mod tests { + use clap::Parser; + + use crate::Opts; + + #[test] + fn announce_port_is_initialized_from_cli_value() { + let opts = Opts::try_parse_from([ + "rtbit", + "--announce-port", + "51234", + "server", + "start", + "/tmp/rtbit-startup-test", + ]) + .unwrap(); + + assert_eq!(opts.announce_port, Some(51_234)); + } + #[cfg(not(target_os = "windows"))] #[test] fn test_parse_umask() { diff --git a/docs/SWARMFORGE-0.1.0-RELEASE.md b/docs/SWARMFORGE-0.1.0-RELEASE.md new file mode 100644 index 0000000..0aaeca5 --- /dev/null +++ b/docs/SWARMFORGE-0.1.0-RELEASE.md @@ -0,0 +1,124 @@ +# SwarmForge 0.1.0 release record + +Release preparation date: 2026-07-29 + +This is the immutable release and rollback record for the first coordinated +`swarmforge-*` package family. It records the old-family boundary before any +new crates.io publication and will be extended with the published package +checksums after the registry accepts version 0.1.0. + +## Compatibility contract + +The first public family release uses one coordinated version, `0.1.0`, for all +12 packages. Cargo package identities change to `swarmforge-*`; Rust library +names and import paths remain compatible. Consumers may continue to use +`librtbit::Session`, `librtbit::Api`, `librtbit_core::*`, and the existing +support-crate import names by declaring Cargo dependency aliases. + +Consumers must migrate every member they use atomically. Mixing old +`librtbit-*` and new `swarmforge-*` package identities can duplicate shared +types and break trait coherence. + +The historical repositories and existing packages are retained, unmodified, +through the rustTorrent production soak and until every downstream consumer +has completed its separately reviewed migration. There is no scheduled removal +or yank date. This repository remains named rustTorrent; the `rtbit` binary, +runtime names, environment variables, persisted state, and HTTP API are not +renamed by this release. + +## Old-family monorepo boundary + +The exact pre-migration rustTorrent source is tagged +`swarmforge-old-family-baseline-20260729`. + +| Field | Value | +| --- | --- | +| Commit | `195ef411135e10f63d5ef370208c0fdb83772c37` | +| Git tree | `7293bfa72ed85796243bde4f7c761ddd5350f19a` | +| `git archive --format=tar` SHA-256 | `2b16f61b697a630c489c2fda5039d7b0972250e19944b33aa9787ccabeaf84f4` | +| Canonical repository ID | `1206343877` | +| Canonical source | `https://github.com/TheDancingDeveloper-org/rustTorrent` | + +The workspace package versions at that boundary were: + +| Old package | Version | +| --- | --- | +| `librtbit` | `0.1.6` | +| `librtbit-core` | `0.1.5` | +| `librtbit-bencode` | `0.1.5` | +| `librtbit-buffers` | `0.1.2` | +| `librtbit-clone-to-owned` | `0.1.1` | +| `librtbit-dht` | `0.1.4` | +| `librtbit-lsd` | `0.1.2` | +| `librtbit-peer-protocol` | `0.1.4` | +| `librtbit-sha1-wrapper` | `0.1.2` | +| `librtbit-tracker-comms` | `0.1.6` | +| `librtbit-upnp` | `0.1.2` | +| `librtbit-upnp-serve` | `0.1.2` | + +## Historical standalone source repositories + +All repositories below were verified on 2026-07-29 with default branch `main` +and `archived=false`. They remain rollback sources. + +| Repository | GitHub repository ID | HEAD commit | +| --- | ---: | --- | +| `librtbit` | `1202603883` | `49675c1d79a32aa90ef3806bed4a888bc56d2d23` | +| `librtbit-bencode` | `1202591019` | `255c93423c6fc86da5d9227457da28c8c1f3fdd6` | +| `librtbit-buffers` | `1202590644` | `60fde393b379face7c0acfe7f654a1a5ae3f3731` | +| `librtbit-clone-to-owned` | `1202588618` | `6944f73c91e9a4cc3be7694f864c5c06012066fc` | +| `librtbit-core` | `1202592291` | `486540bb77448ae50df5e2ca846aa0caeef5314e` | +| `librtbit-dht` | `1202595252` | `05f1aa116e767a8020cd459cd43fdd5907c55ada` | +| `librtbit-lsd` | `1202594813` | `79a7b6d4a1dedc0631bbfab43faa576f71ba7e4a` | +| `librtbit-peer-protocol` | `1202594486` | `f91f07946002b9348d7d8c9378cf3b5206f77160` | +| `librtbit-sha1-wrapper` | `1202588836` | `26062b9c6d4e73ad8b061f366d01056d401e467b` | +| `librtbit-tracker-comms` | `1202599388` | `96580fb94839b71fd2ebbfb11dded5d93c53e808` | +| `librtbit-upnp` | `1202589098` | `ffda20d442aa8deee65e5bedf65139ed7de1bb32` | +| `librtbit-upnp-serve` | `1202601459` | `5e1a16554e968ac1bae6b8050f2c38c02db53251` | + +## Existing public old-package checksums + +These are the newest public crates.io entries observed immediately before the +SwarmForge release. `UNPUBLISHED` means the old identity had no sparse-index +entry; it does not mean the standalone Git repository is absent. + +| Old package | Public version | crates.io checksum | +| --- | --- | --- | +| `librtbit` | UNPUBLISHED | - | +| `librtbit-core` | `0.1.3` | `478c51123ed65a4aa1425d535abba96abaf0990ea9dc82eff4943da253561854` | +| `librtbit-bencode` | `0.1.3` | `95e1f155f053c5a2aee47add5141a1c76dbed5f8c511ced3503f79ba1fbf668f` | +| `librtbit-buffers` | `0.1.1` | `29b8cebbaebfe945a5bcc23621c77037c8c9a451b84c9cdd0dadb80cc16414a3` | +| `librtbit-clone-to-owned` | `0.1.1` | `25ef4d419efb961a6ebc8c5122f835ccfe8b918f16fa83b7001a051039889d9b` | +| `librtbit-dht` | `0.1.1` | `24c85ed4287bf4e99868f4acd646329a12db567ae2c63918421457494e578d23` | +| `librtbit-lsd` | UNPUBLISHED | - | +| `librtbit-peer-protocol` | UNPUBLISHED | - | +| `librtbit-sha1-wrapper` | `0.1.1` | `7de78d6fc1f1523fb0416aa64196f2de444102db6026c5ac7a800fb6c4ee5de4` | +| `librtbit-tracker-comms` | UNPUBLISHED | - | +| `librtbit-upnp` | UNPUBLISHED | - | +| `librtbit-upnp-serve` | UNPUBLISHED | - | + +## Coordinated publication order + +1. `swarmforge-clone-to-owned` +2. `swarmforge-buffers` +3. `swarmforge-sha1-wrapper` +4. `swarmforge-bencode` +5. `swarmforge-core` +6. `swarmforge-peer-protocol` +7. `swarmforge-dht` +8. `swarmforge-lsd` +9. `swarmforge-tracker-comms` +10. `swarmforge-upnp` +11. `swarmforge-upnp-serve` +12. `swarmforge` + +`scripts/publish.sh --execute` dry-runs each package immediately before its +upload and waits until crates.io's sparse index exposes version 0.1.0 before +moving to the next dependency level. A pre-existing package name or version is +a hard failure, not a successful rerun. + +## Published SwarmForge artifacts + +This section is intentionally pending until crates.io has accepted all 12 +immutable packages. Record the exact source commit, source tag, package URLs, +and sparse-index checksums here immediately after publication. diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 94ceb7b..08cb125 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arbitrary" @@ -55,9 +55,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.1" +version = "1.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" +checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" dependencies = [ "aws-lc-sys", "untrusted", @@ -66,9 +66,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.42.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" +checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" dependencies = [ "cc", "cmake", @@ -90,9 +90,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "bitvec" @@ -126,9 +126,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cc" -version = "1.2.67" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "jobserver", @@ -291,13 +291,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -308,9 +308,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "encoding_rs" @@ -329,9 +329,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "find-msvc-tools" @@ -377,9 +377,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" dependencies = [ "futures-channel", "futures-core", @@ -392,9 +392,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", "futures-sink", @@ -402,15 +402,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" dependencies = [ "futures-core", "futures-task", @@ -419,38 +419,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-channel", "futures-core", @@ -708,9 +708,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libfuzzer-sys" @@ -747,124 +747,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "librtbit-bencode" -version = "0.1.5" -dependencies = [ - "anyhow", - "arrayvec", - "atoi", - "bytes", - "librtbit-buffers", - "librtbit-clone-to-owned", - "serde", - "serde_derive", - "thiserror", -] - -[[package]] -name = "librtbit-buffers" -version = "0.1.2" -dependencies = [ - "bytes", - "librtbit-clone-to-owned", - "serde", - "serde_derive", -] - -[[package]] -name = "librtbit-clone-to-owned" -version = "0.1.1" -dependencies = [ - "bytes", -] - -[[package]] -name = "librtbit-core" -version = "0.1.5" -dependencies = [ - "anyhow", - "bytes", - "chardetng", - "data-encoding", - "directories", - "encoding_rs", - "hex 0.4.3", - "itertools", - "librtbit-bencode", - "librtbit-buffers", - "librtbit-clone-to-owned", - "librtbit-sha1-wrapper", - "memchr", - "parking_lot", - "rand", - "serde", - "serde_derive", - "thiserror", - "tokio", - "tokio-util", - "tracing", - "url", -] - -[[package]] -name = "librtbit-dht" -version = "0.1.3" -dependencies = [ - "anyhow", - "aws-lc-rs", - "backon", - "bytes", - "chrono", - "dashmap", - "futures", - "indexmap", - "leaky-bucket", - "librqbit-dualstack-sockets", - "librtbit-bencode", - "librtbit-buffers", - "librtbit-clone-to-owned", - "librtbit-core", - "librtbit-sha1-wrapper", - "parking_lot", - "rand", - "serde", - "serde_derive", - "serde_json", - "thiserror", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", -] - -[[package]] -name = "librtbit-peer-protocol" -version = "0.1.3" -dependencies = [ - "anyhow", - "bitvec", - "byteorder", - "bytes", - "itertools", - "librtbit-bencode", - "librtbit-buffers", - "librtbit-clone-to-owned", - "librtbit-core", - "serde", - "serde_derive", - "thiserror", - "tracing", -] - -[[package]] -name = "librtbit-sha1-wrapper" -version = "0.1.2" -dependencies = [ - "assert_cfg", - "crypto-hash", -] - [[package]] name = "litemap" version = "0.8.2" @@ -894,9 +776,9 @@ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", @@ -952,7 +834,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1025,18 +907,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -1095,9 +977,9 @@ name = "rusttorrent-fuzz" version = "0.0.0" dependencies = [ "libfuzzer-sys", - "librtbit-bencode", - "librtbit-dht", - "librtbit-peer-protocol", + "swarmforge-bencode", + "swarmforge-dht", + "swarmforge-peer-protocol", ] [[package]] @@ -1114,9 +996,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -1124,29 +1006,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -1175,9 +1057,9 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys", @@ -1189,11 +1071,140 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "swarmforge-bencode" +version = "0.1.0" +dependencies = [ + "anyhow", + "arrayvec", + "atoi", + "bytes", + "serde", + "serde_derive", + "swarmforge-buffers", + "swarmforge-clone-to-owned", + "thiserror", +] + +[[package]] +name = "swarmforge-buffers" +version = "0.1.0" +dependencies = [ + "bytes", + "serde", + "serde_derive", + "swarmforge-clone-to-owned", +] + +[[package]] +name = "swarmforge-clone-to-owned" +version = "0.1.0" +dependencies = [ + "bytes", +] + +[[package]] +name = "swarmforge-core" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytes", + "chardetng", + "data-encoding", + "directories", + "encoding_rs", + "hex 0.4.3", + "itertools", + "memchr", + "parking_lot", + "rand", + "serde", + "serde_derive", + "swarmforge-bencode", + "swarmforge-buffers", + "swarmforge-clone-to-owned", + "swarmforge-sha1-wrapper", + "thiserror", + "tokio", + "tokio-util", + "tracing", + "url", +] + +[[package]] +name = "swarmforge-dht" +version = "0.1.0" +dependencies = [ + "anyhow", + "aws-lc-rs", + "backon", + "bytes", + "chrono", + "dashmap", + "futures", + "indexmap", + "leaky-bucket", + "librqbit-dualstack-sockets", + "parking_lot", + "rand", + "serde", + "serde_derive", + "serde_json", + "swarmforge-bencode", + "swarmforge-buffers", + "swarmforge-clone-to-owned", + "swarmforge-core", + "swarmforge-sha1-wrapper", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "swarmforge-peer-protocol" +version = "0.1.0" +dependencies = [ + "anyhow", + "bitvec", + "byteorder", + "bytes", + "itertools", + "serde", + "serde_derive", + "swarmforge-bencode", + "swarmforge-buffers", + "swarmforge-clone-to-owned", + "swarmforge-core", + "thiserror", + "tracing", +] + +[[package]] +name = "swarmforge-sha1-wrapper" +version = "0.1.0" +dependencies = [ + "assert_cfg", + "crypto-hash", +] + [[package]] name = "syn" -version = "2.0.118" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -1208,7 +1219,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1219,22 +1230,22 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -1249,9 +1260,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "libc", "mio", @@ -1263,20 +1274,20 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "tokio-stream" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" dependencies = [ "futures-core", "pin-project-lite", @@ -1286,9 +1297,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", @@ -1316,7 +1327,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1407,7 +1418,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] @@ -1463,7 +1474,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1474,7 +1485,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1544,7 +1555,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -1565,7 +1576,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -1605,11 +1616,11 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "zmij" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd2f034a4bebf216c9e4b7083603e024cf930873fd67830cfb083c9fa33129d9" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index fd2f5db..afd5f15 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -9,9 +9,9 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "0.4" -bencode = { path = "../crates/librtbit-bencode", package = "librtbit-bencode" } -peer_binary_protocol = { path = "../crates/librtbit-peer-protocol", package = "librtbit-peer-protocol" } -dht = { path = "../crates/librtbit-dht", package = "librtbit-dht" } +bencode = { path = "../crates/librtbit-bencode", package = "swarmforge-bencode" } +peer_binary_protocol = { path = "../crates/librtbit-peer-protocol", package = "swarmforge-peer-protocol" } +dht = { path = "../crates/librtbit-dht", package = "swarmforge-dht" } [[bin]] name = "bencode" diff --git a/scripts/publish.sh b/scripts/publish.sh old mode 100644 new mode 100755 index dc1424e..8167fa3 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -1,7 +1,86 @@ -#!/bin/bash +#!/usr/bin/env bash +set -euo pipefail -find crates -maxdepth 1 -type d | grep '/' | while read dir; do - pushd "${dir}" - cargo publish --dry-run - popd -done \ No newline at end of file +publish_flag="--dry-run" +if [[ "${1:-}" == "--execute" ]]; then + publish_flag="" + shift +fi + +version="0.1.0" + +# Keep this list explicit: Cargo package publication must follow dependency +# order, and directory discovery can publish the binary or a leaf before the +# family it depends on. The old librtbit-* repositories and packages remain +# available as rollback assets; this script publishes only the coordinated +# swarmforge family. +packages=( + swarmforge-clone-to-owned + swarmforge-buffers + swarmforge-sha1-wrapper + swarmforge-bencode + swarmforge-core + swarmforge-peer-protocol + swarmforge-dht + swarmforge-lsd + swarmforge-tracker-comms + swarmforge-upnp + swarmforge-upnp-serve + swarmforge +) + +sparse_path() { + local package="$1" + case "${#package}" in + 1) printf '1/%s' "$package" ;; + 2) printf '2/%s' "$package" ;; + 3) printf '3/%s/%s' "${package:0:1}" "$package" ;; + *) printf '%s/%s/%s' "${package:0:2}" "${package:2:2}" "$package" ;; + esac +} + +package_is_visible() { + local package="$1" body + body="$(curl --fail --silent --show-error \ + --user-agent 'cargo swarmforge-release' \ + "https://index.crates.io/$(sparse_path "$package")")" || return 1 + jq --exit-status --arg version "$version" \ + 'select(.vers == $version and (.yanked | not))' \ + >/dev/null <<<"$body" +} + +if [[ -z "$publish_flag" ]]; then + command -v curl >/dev/null + command -v jq >/dev/null + [[ -n "${CARGO_REGISTRY_TOKEN:-}" ]] || { + echo 'CARGO_REGISTRY_TOKEN is required for --execute' >&2 + exit 1 + } + + # Names are checked as one family immediately before the first upload. Any + # existing sparse-index entry means ownership changed and publication stops. + for package in "${packages[@]}"; do + status="$(curl --silent --output /dev/null --write-out '%{http_code}' \ + --user-agent 'cargo swarmforge-release' \ + "https://index.crates.io/$(sparse_path "$package")")" + [[ "$status" == "404" ]] || { + echo "refusing publication: $package sparse-index status is $status, expected 404" >&2 + exit 1 + } + done +fi + +for package in "${packages[@]}"; do + cargo publish --locked --dry-run -p "$package" "$@" + [[ -z "$publish_flag" ]] || continue + + cargo publish --locked -p "$package" "$@" + for _ in {1..60}; do + package_is_visible "$package" && break + sleep 2 + done + package_is_visible "$package" || { + echo "$package $version was uploaded but did not appear in the sparse index" >&2 + exit 1 + } +done