diff --git a/.github/skills/dev/pr-reviews/fetch-review-threads/SKILL.md b/.github/skills/dev/pr-reviews/fetch-review-threads/SKILL.md index 012aadb20..9c196a47f 100644 --- a/.github/skills/dev/pr-reviews/fetch-review-threads/SKILL.md +++ b/.github/skills/dev/pr-reviews/fetch-review-threads/SKILL.md @@ -8,9 +8,12 @@ metadata: # Fetching PR Review Threads +This is a component skill within the **process-copilot-suggestions** workflow. Use this skill before resolving review feedback. Its purpose is to collect the unresolved review thread IDs and enough context to decide whether each thread should stay open or be closed. +**Part of larger workflow**: See **process-copilot-suggestions** for the full end-to-end process. + ## Preferred Sources Use one of these approaches: diff --git a/.github/skills/dev/pr-reviews/process-copilot-suggestions/SKILL.md b/.github/skills/dev/pr-reviews/process-copilot-suggestions/SKILL.md new file mode 100644 index 000000000..7b75de8a1 --- /dev/null +++ b/.github/skills/dev/pr-reviews/process-copilot-suggestions/SKILL.md @@ -0,0 +1,172 @@ +--- +name: process-copilot-suggestions +description: End-to-end workflow for processing and resolving all Copilot code review suggestions on a pull request in torrust-tracker. Use when asked to handle PR review feedback, process all copilot suggestions, audit and resolve review comments, or manage copilot-generated review threads. Triggers on "process copilot suggestions", "handle all PR feedback", "resolve copilot review", "audit PR suggestions", or "close all copilot comments". +metadata: + author: torrust + version: "1.0" +--- + +# Processing Copilot PR Suggestions + +This is the primary workflow for handling all Copilot code review suggestions on a pull request. +It combines decision-making, implementation, tracking, and resolution into a structured end-to-end process. + +## Overview + +Copilot generates suggestions that fall into two categories: + +- **action** — Code or documentation changes needed; implement, validate, commit +- **no-action** — Already handled, false positive, or intentionally declined; explain reasoning and mark resolved + +## Prerequisites + +- Target PR number +- Write access to branch (to apply fixes and push) +- Access to GitHub CLI (`gh`) +- Ability to run linters and tests locally + +## Full Workflow + +### 1. Setup Tracking File + +Copy the template to create a tracker for this PR: + +```bash +cp docs/pr-reviews/COPILOT-SUGGESTIONS-TEMPLATE.md \ + docs/pr-reviews/pr--copilot-suggestions.md +``` + +Open the tracker file and fill in: + +- `` and `` at the top +- Placeholder columns in the Suggestions table + +### 2. Fetch All Review Threads + +Use the **fetch-review-threads** skill or the helper script: + +```bash +contrib/dev-tools/github-api-scripts/get-pr-review-threads.sh +``` + +This saves all review threads (resolved, unresolved, outdated) to `/tmp/pr_threads_.json`. + +### 3. Populate the Tracker + +Extract unresolved threads from the JSON: + +```bash +contrib/dev-tools/github-api-scripts/list-unresolved-threads.sh /tmp/pr_threads_.json +``` + +Add one row per thread to your tracker file with: + +- Thread ID +- File path +- Comment URL +- Brief summary of the suggestion + +### 4. Analyze and Decide + +For each suggestion, decide: + +- **action** — The suggestion identifies a real fix needed: + - Apply the code/doc change + - Run `linter all` and targeted tests + - Commit with clear message + - Update tracker with `action` status +- **no-action** — The suggestion is already handled or not needed: + - Document the reason (e.g., "outdated after later commits", "false positive verified by tests") + - Update tracker with `no-action` status and rationale + +**Key principle**: Do not resolve a thread just because a suggestion exists. Only resolve when the concern is genuinely addressed or explicitly declined with documented reasoning. + +### 5. Implement Fixes + +For each `action` item: + +1. Read the suggestion carefully +2. Apply the minimal fix +3. Validate: + + ```bash + linter all # Full lint gate + cargo test -p # Targeted tests + ``` + +4. Commit with GPG signature: + + ```bash + git add + git commit -S -m "chore(review): " + ``` + +5. Update tracker with `action` status + +### 6. Batch Resolve All Threads + +After all decisions are made and `action` items are committed: + +```bash +contrib/dev-tools/github-api-scripts/get-pr-review-threads.sh +contrib/dev-tools/github-api-scripts/resolve-all-unresolved-threads.sh /tmp/pr_threads_.json +``` + +This resolves all unresolved threads (both `action` and `no-action` categories). + +### 7. Final Documentation + +Update the tracker file with completion notes: + +- Add timestamps to the Processing Log +- Mark all threads as `resolved` in the Thread State column +- Commit the tracker and all helper scripts as final documentation + +```bash +git add docs/pr-reviews/pr--copilot-suggestions.md +git add contrib/dev-tools/github-api-scripts/ +git commit -S -m "docs(review): document PR # copilot suggestions audit" +``` + +## Decision Matrix + +| Suggestion Type | Has Fix? | Tests Pass? | Decision | Action | +| ----------------------------------------- | -------- | ----------- | --------- | ------------------------- | +| Clear code bug | Yes | Yes | action | Apply + commit + resolve | +| Outdated (already fixed in later commits) | N/A | N/A | no-action | Document reason + resolve | +| False positive (verified by tests) | N/A | Pass | no-action | Document why + resolve | +| Good suggestion but low priority | No | N/A | no-action | Document reason + resolve | +| Docs improvement | Yes | Yes | action | Apply + commit + resolve | + +## Helper Scripts Reference + +Located in `contrib/dev-tools/github-api-scripts/`: + +- **get-pr-review-threads.sh** — Fetch all threads for a PR +- **list-unresolved-threads.sh** — Filter to unresolved threads only +- **resolve-all-unresolved-threads.sh** — Resolve all unresolved threads via GraphQL + +See `contrib/dev-tools/github-api-scripts/README.md` for details. + +## Related Skills + +- **fetch-review-threads** — Deep dive on collecting thread metadata +- **resolve-review-threads** — Deep dive on resolving threads via GraphQL + +Both are integrated into this workflow automatically. + +## Example + +See `docs/pr-reviews/pr-1733-copilot-suggestions.md` for a complete worked example +with all 26 Copilot suggestions processed, decided, and resolved. + +## Completion Checklist + +- [ ] Tracker file created from template with PR number and URL +- [ ] All review threads fetched and added to tracker table +- [ ] Each thread categorized as `action` or `no-action` with rationale +- [ ] All `action` items implemented, validated, and committed +- [ ] All threads resolved in GitHub (via batch script or one-by-one) +- [ ] Tracker file updated with Processing Log and Thread State column +- [ ] Tracker and helper scripts committed as documentation +- [ ] No uncommitted changes remain diff --git a/.github/skills/dev/pr-reviews/resolve-review-threads/SKILL.md b/.github/skills/dev/pr-reviews/resolve-review-threads/SKILL.md index 6033a7ccd..8ca2cd2f3 100644 --- a/.github/skills/dev/pr-reviews/resolve-review-threads/SKILL.md +++ b/.github/skills/dev/pr-reviews/resolve-review-threads/SKILL.md @@ -8,9 +8,12 @@ metadata: # Resolving PR Review Threads +This is a component skill within the **process-copilot-suggestions** workflow. Use this skill after the requested code or documentation changes are already implemented, validated, committed, and pushed. +**Part of larger workflow**: See **process-copilot-suggestions** for the full end-to-end process. + ## Preconditions - The feedback has actually been addressed in the branch. diff --git a/Cargo.lock b/Cargo.lock index 6f67c8958..c125b3164 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,32 +136,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "aquatic_peer_id" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0732a73df221dcb25713849c6ebaf57b85355f669716652a7466f688cc06f25" -dependencies = [ - "compact_str", - "hex", - "quickcheck", - "regex", - "serde", - "zerocopy 0.7.35", -] - -[[package]] -name = "aquatic_udp_protocol" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0af90e5162f5fcbde33524128f08dc52a779f32512d5f8692eadd4b55c89389e" -dependencies = [ - "aquatic_peer_id", - "byteorder", - "either", - "zerocopy 0.7.35", -] - [[package]] name = "arc-swap" version = "1.9.1" @@ -598,7 +572,6 @@ dependencies = [ name = "bittorrent-http-tracker-core" version = "3.0.0-develop" dependencies = [ - "aquatic_udp_protocol", "bittorrent-http-tracker-protocol", "bittorrent-primitives", "bittorrent-tracker-core", @@ -625,9 +598,9 @@ dependencies = [ name = "bittorrent-http-tracker-protocol" version = "3.0.0-develop" dependencies = [ - "aquatic_udp_protocol", "bittorrent-primitives", "bittorrent-tracker-core", + "bittorrent-udp-tracker-protocol", "derive_more", "multimap", "percent-encoding", @@ -641,26 +614,37 @@ dependencies = [ "torrust-tracker-primitives", ] +[[package]] +name = "bittorrent-peer-id" +version = "3.0.0-develop" +dependencies = [ + "compact_str", + "hex", + "pretty_assertions", + "quickcheck", + "regex", + "serde", + "zerocopy", +] + [[package]] name = "bittorrent-primitives" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc1bd0462f0af0b57abd5f5f8f32b904ba0a17cc8be1714db160a054552f242" +checksum = "6b47ab263cb9c3bc8be80e312f81c1ee94d3af3d9ee066b81abc06f8fc851023" dependencies = [ - "aquatic_udp_protocol", "binascii", "serde", "serde_json", "thiserror 1.0.69", - "zerocopy 0.7.35", ] [[package]] name = "bittorrent-tracker-client" version = "3.0.0-develop" dependencies = [ - "aquatic_udp_protocol", "bittorrent-primitives", + "bittorrent-udp-tracker-protocol", "derive_more", "hyper", "percent-encoding", @@ -675,7 +659,7 @@ dependencies = [ "torrust-tracker-located-error", "torrust-tracker-primitives", "tracing", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -683,7 +667,6 @@ name = "bittorrent-tracker-core" version = "3.0.0-develop" dependencies = [ "anyhow", - "aquatic_udp_protocol", "async-trait", "bittorrent-primitives", "chrono", @@ -716,7 +699,6 @@ dependencies = [ name = "bittorrent-udp-tracker-core" version = "3.0.0-develop" dependencies = [ - "aquatic_udp_protocol", "bittorrent-primitives", "bittorrent-tracker-core", "bittorrent-udp-tracker-protocol", @@ -740,16 +722,20 @@ dependencies = [ "torrust-tracker-swarm-coordination-registry", "torrust-tracker-test-helpers", "tracing", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] name = "bittorrent-udp-tracker-protocol" version = "3.0.0-develop" dependencies = [ - "aquatic_udp_protocol", - "torrust-tracker-clock", - "torrust-tracker-primitives", + "bittorrent-peer-id", + "byteorder", + "either", + "pretty_assertions", + "quickcheck", + "quickcheck_macros", + "zerocopy", ] [[package]] @@ -1102,13 +1088,14 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ "castaway", "cfg-if", "itoa", + "rustversion", "ryu", "static_assertions", ] @@ -2103,7 +2090,7 @@ checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", - "zerocopy 0.8.48", + "zerocopy", ] [[package]] @@ -2162,12 +2149,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hex-literal" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" - [[package]] name = "hkdf" version = "0.12.4" @@ -3510,7 +3491,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.48", + "zerocopy", ] [[package]] @@ -3655,6 +3636,17 @@ dependencies = [ "rand 0.10.1", ] +[[package]] +name = "quickcheck_macros" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a28b8493dd664c8b171dd944da82d933f7d456b829bfb236738e1fe06c5ba4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "quinn" version = "0.11.9" @@ -5317,7 +5309,6 @@ dependencies = [ name = "torrust-axum-http-tracker-server" version = "3.0.0-develop" dependencies = [ - "aquatic_udp_protocol", "axum", "axum-client-ip", "axum-server", @@ -5325,6 +5316,7 @@ dependencies = [ "bittorrent-http-tracker-protocol", "bittorrent-primitives", "bittorrent-tracker-core", + "bittorrent-udp-tracker-protocol", "derive_more", "futures", "hyper", @@ -5350,14 +5342,13 @@ dependencies = [ "tower-http", "tracing", "uuid", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] name = "torrust-axum-rest-tracker-api-server" version = "3.0.0-develop" dependencies = [ - "aquatic_udp_protocol", "axum", "axum-extra", "axum-server", @@ -5506,12 +5497,11 @@ name = "torrust-tracker-client" version = "3.0.0-develop" dependencies = [ "anyhow", - "aquatic_udp_protocol", "bittorrent-primitives", "bittorrent-tracker-client", + "bittorrent-udp-tracker-protocol", "clap", "futures", - "hex-literal", "hyper", "reqwest", "serde", @@ -5603,8 +5593,8 @@ dependencies = [ name = "torrust-tracker-primitives" version = "3.0.0-develop" dependencies = [ - "aquatic_udp_protocol", "binascii", + "bittorrent-peer-id", "bittorrent-primitives", "derive_more", "rstest 0.25.0", @@ -5614,14 +5604,12 @@ dependencies = [ "thiserror 2.0.18", "torrust-tracker-configuration", "url", - "zerocopy 0.7.35", ] [[package]] name = "torrust-tracker-swarm-coordination-registry" version = "3.0.0-develop" dependencies = [ - "aquatic_udp_protocol", "async-std", "bittorrent-primitives", "chrono", @@ -5658,7 +5646,6 @@ dependencies = [ name = "torrust-tracker-torrent-repository-benchmarking" version = "3.0.0-develop" dependencies = [ - "aquatic_udp_protocol", "async-std", "bittorrent-primitives", "criterion 0.8.2", @@ -5671,18 +5658,17 @@ dependencies = [ "torrust-tracker-clock", "torrust-tracker-configuration", "torrust-tracker-primitives", - "zerocopy 0.7.35", ] [[package]] name = "torrust-udp-tracker-server" version = "3.0.0-develop" dependencies = [ - "aquatic_udp_protocol", "bittorrent-primitives", "bittorrent-tracker-client", "bittorrent-tracker-core", "bittorrent-udp-tracker-core", + "bittorrent-udp-tracker-protocol", "derive_more", "futures", "futures-util", @@ -5705,7 +5691,7 @@ dependencies = [ "tracing", "url", "uuid", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -6680,34 +6666,13 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive 0.7.35", -] - [[package]] name = "zerocopy" version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ - "zerocopy-derive 0.8.48", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "zerocopy-derive", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d47630dfc..17eb6c12b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,14 +69,17 @@ tracing = "0" tracing-subscriber = { version = "0", features = [ "json" ] } [dev-dependencies] -bittorrent-primitives = "0.1.0" +bittorrent-primitives = "0.2.0" bittorrent-tracker-client = { version = "3.0.0-develop", path = "packages/tracker-client" } local-ip-address = "0" mockall = "0" torrust-tracker-test-helpers = { version = "3.0.0-develop", path = "packages/test-helpers" } [workspace] -members = [ "console/tracker-client", "packages/torrent-repository-benchmarking" ] +members = [ + "console/tracker-client", + "packages/torrent-repository-benchmarking", +] [profile.dev] debug = 1 diff --git a/console/tracker-client/Cargo.toml b/console/tracker-client/Cargo.toml index 8c12227e9..7df682dbf 100644 --- a/console/tracker-client/Cargo.toml +++ b/console/tracker-client/Cargo.toml @@ -16,12 +16,11 @@ version.workspace = true [dependencies] anyhow = "1" -aquatic_udp_protocol = "0" -bittorrent-primitives = "0.1.0" +bittorrent-udp-tracker-protocol = { version = "3.0.0-develop", path = "../../packages/udp-protocol" } +bittorrent-primitives = "0.2.0" bittorrent-tracker-client = { version = "3.0.0-develop", path = "../../packages/tracker-client" } clap = { version = "4", features = [ "derive", "env" ] } futures = "0" -hex-literal = "1" hyper = "1" reqwest = { version = "0", features = [ "json" ] } serde = { version = "1", features = [ "derive" ] } diff --git a/console/tracker-client/src/console/clients/checker/checks/udp.rs b/console/tracker-client/src/console/clients/checker/checks/udp.rs index 611afafc4..407e6fca1 100644 --- a/console/tracker-client/src/console/clients/checker/checks/udp.rs +++ b/console/tracker-client/src/console/clients/checker/checks/udp.rs @@ -1,8 +1,9 @@ use std::net::SocketAddr; +use std::str::FromStr; use std::time::Duration; -use aquatic_udp_protocol::TransactionId; -use hex_literal::hex; +use bittorrent_primitives::info_hash::InfoHash as TorrustInfoHash; +use bittorrent_udp_tracker_protocol::TransactionId; use serde::Serialize; use url::Url; @@ -29,8 +30,7 @@ pub async fn run(udp_trackers: Vec, timeout: Duration) -> Vec, timeout: Duration) -> Vec, timeout: Duration) -> Vec "$OUTPUT_FILE" + +echo "Review threads saved to $OUTPUT_FILE" diff --git a/contrib/dev-tools/github-api-scripts/list-unresolved-threads.sh b/contrib/dev-tools/github-api-scripts/list-unresolved-threads.sh new file mode 100755 index 000000000..24dea8580 --- /dev/null +++ b/contrib/dev-tools/github-api-scripts/list-unresolved-threads.sh @@ -0,0 +1,4 @@ +#!/bin/bash +THREADS_FILE=${1:-/tmp/pr_threads_1733.json} + +jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | {id, isOutdated, path, url: .comments.nodes[0].url}' "$THREADS_FILE" diff --git a/contrib/dev-tools/github-api-scripts/resolve-all-unresolved-threads.sh b/contrib/dev-tools/github-api-scripts/resolve-all-unresolved-threads.sh new file mode 100755 index 000000000..00aacfb9c --- /dev/null +++ b/contrib/dev-tools/github-api-scripts/resolve-all-unresolved-threads.sh @@ -0,0 +1,6 @@ +#!/bin/bash +THREADS_FILE=${1:-/tmp/pr_threads_1733.json} + +jq -r '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | .id' "$THREADS_FILE" | while read -r id; do + gh api graphql -f query="mutation(\$id:ID!){resolveReviewThread(input:{threadId:\$id}){thread{id isResolved}}}" -F id="$id" && echo "resolved: $id" +done diff --git a/docs/issues/1732-replace-aquatic-udp-protocol/ISSUE.md b/docs/issues/1732-replace-aquatic-udp-protocol/ISSUE.md new file mode 100644 index 000000000..2705dbccc --- /dev/null +++ b/docs/issues/1732-replace-aquatic-udp-protocol/ISSUE.md @@ -0,0 +1,373 @@ +# Replace `aquatic_udp_protocol` with an In-House UDP Protocol Crate + +## Overview + +The Torrust Tracker currently depends on +[`aquatic_udp_protocol`](https://crates.io/crates/aquatic_udp_protocol) (from the +[`aquatic`](https://github.com/greatest-ape/aquatic) project) for BitTorrent UDP tracker +protocol types, serialization, and deserialization (BEP 15). + +The upstream project has been inactive since February 2025. An open issue +([aquatic#224](https://github.com/greatest-ape/aquatic/issues/224)) requesting a `zerocopy` 0.8 +upgrade has received no response. We contributed a PR +([aquatic#235](https://github.com/greatest-ape/aquatic/pull/235)) to apply the fix ourselves, +but it has also remained unreviewed. This `zerocopy` version mismatch currently blocks +[torrust/torrust-tracker#1682](https://github.com/torrust/torrust-tracker/pull/1682) — a +recurring dependabot PR that cannot be merged. + +With **13 packages** in this workspace directly depending on `aquatic_udp_protocol`, continuing +to rely on an apparently unmaintained external crate is a maintenance and security risk. + +The proposal is to own the UDP protocol implementation inside this workspace: + +1. Copy the current `aquatic_udp_protocol` source into a new internal package + (`packages/aquatic-udp-protocol`) under the terms of its Apache 2.0 license. +2. Remove everything we do not use. +3. Apply the `zerocopy` 0.8 migration from our unmerged PR. +4. Migrate `packages/udp-protocol` to own all protocol types, absorbing the internal fork. +5. Remove the interim fork once the migration is complete. +6. Progressively redesign the types so they fit the Torrust Tracker domain model — while + keeping the public surface backward-compatible throughout the transition. + +## Background + +### Why `aquatic_udp_protocol`? + +It provides a complete, correct implementation of the BEP 15 UDP tracker wire protocol. +The crate is small (~785 SLoC, 4 source files: `common.rs`, `lib.rs`, `request.rs`, +`response.rs`), making an in-house replacement feasible. + +### License + +`aquatic_udp_protocol` is published under **Apache 2.0**, which is fully compatible with the +Torrust Tracker's AGPL-3.0 license. Apache 2.0 permits copying, modification, and +redistribution provided that: + +- The original copyright notice is preserved. +- A `NOTICE` file is included (if the original has one — the aquatic repo does not have one). +- Modifications are clearly marked. + +We must include the Apache 2.0 `LICENSE` file in each new package and attribute the original +author in the `README.md`. + +### No publishing required + +The internal fork packages (`packages/aquatic-peer-id`, `packages/aquatic-udp-protocol`) are +**never published to crates.io**. All dependent packages reference them via Cargo path +dependencies (`path = "../aquatic-peer-id"`, `path = "../aquatic-udp-protocol"`), which are +resolved locally by Cargo. The crate names are kept identical to the upstream ones +(`aquatic_peer_id`, `aquatic_udp_protocol`) so that all existing `use` statements in the +codebase compile without changes. Once Step 4 is complete and the packages are removed from the +workspace, the path dependencies are removed along with them. + +### Types currently used across the workspace + +The following distinct types are imported from `aquatic_udp_protocol` in 26 source files across +13 packages: + +| Category | Types | +| ------------------- | --------------------------------------------------------------------------------------- | +| Request types | `Request`, `ConnectRequest`, `AnnounceRequest`, `ScrapeRequest` | +| Response types | `Response`, `ConnectResponse`, `AnnounceResponse`, `ScrapeResponse`, `ErrorResponse` | +| Identifiers | `TransactionId`, `ConnectionId`, `InfoHash`, `PeerId` | +| Announce parameters | `AnnounceEvent`, `AnnounceActionPlaceholder`, `Port`, `PeerKey` | +| Counters | `NumberOfBytes`, `NumberOfPeers`, `NumberOfDownloads` | +| Scrape statistics | `TorrentScrapeStatistics` | +| Address types | `Ipv4AddrBytes`, `Ipv6AddrBytes` | +| Modules | `aquatic_udp_protocol::common` | + +### Packages to update + +| Package | Path | +| --------------------------------- | ------------------------------------------ | +| `bittorrent-udp-protocol` | `packages/udp-protocol` | +| `bittorrent-http-protocol` | `packages/http-protocol` | +| `bittorrent-udp-tracker-core` | `packages/udp-tracker-core` | +| `bittorrent-tracker-core` | `packages/tracker-core` | +| `bittorrent-http-tracker-core` | `packages/http-tracker-core` | +| `bittorrent-tracker-primitives` | `packages/primitives` | +| `axum-http-tracker-server` | `packages/axum-http-tracker-server` | +| `axum-rest-tracker-api-server` | `packages/axum-rest-tracker-api-server` | +| `swarm-coordination-registry` | `packages/swarm-coordination-registry` | +| `torrent-repository-benchmarking` | `packages/torrent-repository-benchmarking` | +| `bittorrent-tracker-client` | `packages/tracker-client` | +| `tracker-client` (console) | `console/tracker-client` | +| `udp-tracker-server` | `packages/udp-tracker-server` | + +## Goals + +- [x] Remove the external `aquatic_udp_protocol` dependency from the entire workspace. +- [x] Own the BEP 15 implementation in an internal package that we fully control. +- [x] Apply the `zerocopy` 0.8 migration (unblocking + [torrust/torrust-tracker#1682](https://github.com/torrust/torrust-tracker/pull/1682)). +- [x] Keep all existing tests green throughout the migration. +- [x] Pass `linter all` and `cargo machete` with zero warnings after every step. + +## Implementation Plan + +### Step 1: Create `packages/aquatic-udp-protocol` (internal fork) + +#### Step 1a: Add the internal fork packages to the workspace + +- [x] Copy the `aquatic_udp_protocol` 0.9.0 source (4 files) into a new workspace package + `packages/aquatic-udp-protocol`. Also copied `aquatic_peer_id` 0.9.0 into + `packages/aquatic-peer-id` (needed because `PeerClient` is used in the workspace). +- [x] Add the Apache 2.0 `LICENSE` file to each fork package. The upstream aquatic repo has no + `NOTICE` file and no per-file copyright headers, so none need to be copied. Each source + file carries an inline attribution header naming the original author (Joakim Frostegård / + greatest-ape), linking to the source crate version on crates.io, and stating the Apache + 2.0 license. +- [x] Add a `README.md` to each fork package explaining it is a temporary internal fork. +- [x] Register both packages in the workspace `Cargo.toml`. + +#### Step 1b: Switch all dependent packages to the internal fork + +- [x] Point all 13 packages at the internal fork instead of the crates.io version + (`aquatic_udp_protocol = { path = "../aquatic-udp-protocol" }`). +- [x] Verify the build compiles and all tests pass. + +### Step 2: Strip unused items from the internal fork + +Analysis documented in [step-2-analysis.md](step-2-analysis.md). + +- [x] Identify and remove any code paths, feature flags, or types from the fork that no + package in this workspace uses. +- [x] Confirm no regressions. + +After a thorough search of all 26 source files across 13 packages, no unused public types, +functions, or feature-enabled code paths were found that could be safely removed. Every public +type is used by at least one workspace package. The only internal-only item (`AnnounceEventBytes`) +is structurally required for zero-copy deserialization and cannot be removed. No changes to the +fork source were needed. + +### Step 3: Apply the `zerocopy` 0.8 migration + +Analysis of the transitive dependency problem documented in +[step-3-bittorrent-primitives-problem.md](step-3-bittorrent-primitives-problem.md). + +- [x] Update `zerocopy` to `0.8` in `packages/aquatic-udp-protocol/Cargo.toml` and + `packages/aquatic-peer-id/Cargo.toml`. +- [x] Apply the API migration from our PR + ([aquatic#235](https://github.com/greatest-ape/aquatic/pull/235)) to all four fork source + files (`common.rs`, `request.rs`, `response.rs`, `lib.rs` of `aquatic-peer-id`). +- [x] Update `zerocopy` to `0.8` in `packages/primitives/Cargo.toml` and fix the one + `read_from` → `read_from_bytes` call site in `src/peer.rs`. +- [x] Create an internal fork of `bittorrent-primitives` at `packages/bittorrent-primitives/` + to fix the transitive API breakage (see + [step-3-bittorrent-primitives-problem.md](step-3-bittorrent-primitives-problem.md)). + Add it to `[patch.crates-io]` and to workspace `members`. +- [x] Ensure the build is clean under the workspace `rustflags` (`-D warnings`, etc.) — + `cargo check --workspace` passes with no errors or warnings. + +### Step 4: Absorb the internal forks into their permanent homes + +#### Architectural context + +Three types currently defined in `packages/aquatic-udp-protocol` are **domain types**, not +protocol wire types: + +| Type | Current location | Correct home | +| --------------- | ------------------------------- | --------------------- | +| `PeerId` | `aquatic-peer-id` (re-exported) | `packages/primitives` | +| `PeerClient` | `aquatic-peer-id` | `packages/primitives` | +| `AnnounceEvent` | `aquatic-udp-protocol` | `packages/primitives` | +| `NumberOfBytes` | `aquatic-udp-protocol` | `packages/primitives` | + +These types ended up in the protocol package only because BEP 15 was where they first appeared. +In practice they are used across protocols without any UDP-specific wire format: + +- `PeerId([u8; 20])` — identifies a peer; used in both UDP and HTTP trackers. +- `AnnounceEvent` — a pure domain enum (`Started` / `Stopped` / `Completed` / `None`); carries + no wire-format information. +- `NumberOfBytes` — represents transfer statistics (`uploaded`, `downloaded`, `left`) inside the + domain `Peer` struct. The current definition `NumberOfBytes(pub I64)` uses a zerocopy + network-endian wrapper `I64` only because `AnnounceRequest` needs to derive `FromBytes` / + `IntoBytes`. That zerocopy detail has no place in a domain type. + +The `Peer` struct in `packages/primitives/src/peer.rs` is a domain type, yet it currently +depends on protocol wire-format types for three of its fields. That is the root of the +architectural problem: the **dependency direction is inverted**. + +The correct layering is: + +```text +packages/bittorrent-primitives — InfoHash (standalone BitTorrent primitive) + ↑ +packages/primitives — PeerId, PeerClient, AnnounceEvent, NumberOfBytes(i64), Peer + ↑ +packages/udp-protocol — wire types (AnnounceRequest, …), converts I64 ↔ NumberOfBytes + ↑ +packages/udp-tracker-core — handles the UDP request/response lifecycle +``` + +`packages/primitives` must depend on **nothing** in the protocol layer. UDP protocol packages +must depend **downward** on `primitives` to re-use domain types in conversions. + +#### The circular dependency problem + +There is a dependency cycle that prevents a direct migration in a single step: + +```text +udp-protocol → primitives (via peer_builder.rs: constructs torrust_tracker_primitives::Peer) +primitives → aquatic-udp-protocol (for PeerId, AnnounceEvent, NumberOfBytes) +``` + +After Step 4a moves all aquatic types into `udp-protocol`, `packages/primitives` would need to +import those types from `udp-protocol` — but `udp-protocol` already depends on `primitives`. +That would create a **direct circular dependency**: `udp-protocol → primitives → udp-protocol`. + +#### Breaking the cycle: define domain types natively first (Step 4b) + +The cleanest fix avoids the cycle entirely by making `packages/primitives` self-contained: +define `PeerId`, `PeerClient`, `AnnounceEvent`, and `NumberOfBytes` natively in `primitives` +instead of importing them from any protocol package. Once that is done, `primitives` has no +dependency on any protocol package — the cycle never forms — and the correct dependency +direction is established in a single move. + +**`NumberOfBytes` representation change**: the domain type becomes `NumberOfBytes(pub i64)` (plain +Rust `i64`, host byte order). The wire-format type `NumberOfBytes(I64)` (big-endian zerocopy) is +retained inside `packages/udp-protocol` only, renamed or clearly scoped as a wire-format type. +The conversion in `peer_builder.rs` calls `.0.get()` to extract the `i64` from the wire `I64`. + +**Required step order:** + +1. **Step 4b** (domain types to `primitives`): Define `PeerId`, `PeerClient`, `AnnounceEvent`, + and `NumberOfBytes(i64)` natively in `packages/primitives`. Remove the + `bittorrent_udp_tracker_protocol` / `aquatic-peer-id` dependencies from + `packages/primitives/Cargo.toml`. This step severs the architectural inversion and eliminates + the cycle root cause. + +2. **Step 4a-prep** (move `peer_builder`): `peer_builder.rs` is a domain-adapter, not a + protocol-parsing concern. Move it from `packages/udp-protocol` to `packages/udp-tracker-core`. + Remove `torrust-tracker-primitives` from `packages/udp-protocol/Cargo.toml`. After this, the + dependency graph has no cycle and no architectural inversion. + +3. **Step 4a** (absorb aquatic fork): With the clean dependency graph in place, inline the + aquatic fork source files into `packages/udp-protocol` and remove the fork packages. + +4. **Step 4c** (standalone `InfoHash`): Make `bittorrent-primitives::InfoHash` self-contained + by replacing the `aquatic_udp_protocol::InfoHash` inner field with a plain `[u8; 20]`. + +#### Step 4b: Define domain types natively in `packages/primitives` + +- [x] Copy `PeerId([u8; 20])` and `PeerClient` from `packages/aquatic-peer-id/src/lib.rs` into + a new file `packages/primitives/src/peer_id.rs`. Add an inline attribution comment + crediting the original `aquatic_peer_id` 0.9.0. +- [x] Define `AnnounceEvent { Started, Stopped, Completed, None }` natively in + `packages/primitives/src/` (e.g., `announce_event.rs` or alongside `peer.rs`). +- [x] Define `NumberOfBytes(pub i64)` natively in `packages/primitives/src/`. Implement + `NumberOfBytes::new(v: i64) -> Self` to match the existing call sites. +- [x] Update `packages/primitives/src/peer.rs` to import `PeerId`, `AnnounceEvent`, and + `NumberOfBytes` from the local crate rather than from `bittorrent_udp_tracker_protocol`. +- [x] Remove `bittorrent_udp_tracker_protocol` from `packages/primitives/Cargo.toml`. +- [x] Update `packages/udp-protocol/src/peer_builder.rs` to convert the wire `NumberOfBytes(I64)` + to the domain `primitives::NumberOfBytes(i64)` using `.0.get()`. +- [x] Update all affected packages, tests, benches, and adapters to use the new primitives + domain types where they actually model tracker-domain state (`Peer`, HTTP announce parsing, + REST resources, benchmarking fixtures, and tracker-core test helpers). +- [x] Keep compatibility explicit at the protocol/domain boundary instead of re-exporting the + domain types from `packages/udp-protocol`. Re-exporting `PeerId` / `AnnounceEvent` from the + protocol crate would shadow the real wire types and break code that still needs the BEP 15 + representation. The current boundary is handled by explicit conversions in adapters such as + `peer_builder.rs`. +- [x] Verify `cargo check --workspace` and `linter all` pass with no errors. + +#### Step 4a-prep: Move `peer_builder` to `packages/udp-tracker-core` + +- [x] Copy `packages/udp-protocol/src/peer_builder.rs` into + `packages/udp-tracker-core/src/peer_builder.rs` (or a suitable submodule). +- [x] Remove `pub mod peer_builder;` from `packages/udp-protocol/src/lib.rs`. +- [x] Update `packages/udp-tracker-core/src/services/announce.rs` to import `peer_builder` + from the local module instead of `bittorrent_udp_tracker_protocol::peer_builder`. +- [x] Remove `torrust-tracker-primitives` from `packages/udp-protocol/Cargo.toml` + (it is no longer needed once `peer_builder` is gone). +- [x] Verify `cargo check --workspace` and `linter all` pass with no errors. + +#### Step 4a: Migrate UDP protocol types to `packages/udp-protocol` + +- [x] Move all BEP 15 protocol types (`Request`, `Response`, common types) from + `packages/aquatic-udp-protocol` into `packages/udp-protocol/src/`. + Add an inline attribution comment to each migrated source file crediting the original + `aquatic_udp_protocol` 0.9.0 as the starting point. +- [x] Retain a wire-format `NumberOfBytes` type (or inline `I64` fields) inside `udp-protocol` + to keep zero-copy deserialization of `AnnounceRequest`. Do not expose it as a public + re-export; the public API uses `primitives::NumberOfBytes`. +- [x] Inline the remaining `aquatic_peer_id` fork code needed by the protocol layer into + `packages/udp-protocol/src/peer_id.rs` so the in-house crate is self-contained. +- [x] Update all packages that import from `aquatic_udp_protocol` to import from + `bittorrent-udp-tracker-protocol` instead. `packages/primitives` is now safe to migrate + (its own domain types are native; no cycle can form). +- [x] Remove `aquatic_udp_protocol` from every `Cargo.toml`. +- [x] Remove the no-longer-needed dependency edge from `packages/udp-protocol` to the clock crate. + That dead edge became visible after moving `peer_builder` and would otherwise reintroduce a + package cycle through `clock -> primitives -> bittorrent-primitives -> udp-protocol`. +- [x] Remove both interim forks (`packages/aquatic-udp-protocol` and `packages/aquatic-peer-id`) + from the workspace `Cargo.toml` once no package depends on them. +- [x] Verify `cargo check --workspace` and `linter all` pass with no errors. +- [x] Verify `cargo test --doc --workspace` passes after updating doc tests to use + domain types where required. +- [x] Verify `contrib/dev-tools/git/hooks/pre-commit.sh` passes end-to-end. + +#### Step 4c: Consolidate `InfoHash` into `bittorrent-primitives` + +The internal fork at `packages/bittorrent-primitives/` currently delegates `InfoHash` storage to +`aquatic_udp_protocol::InfoHash`. After Step 4a removes the `aquatic_udp_protocol` dependency from +all other packages, this is the last remaining use of that type from the fork. + +- [x] Replace the `data: aquatic_udp_protocol::InfoHash` field with a plain `[u8; 20]` array + directly inside `bittorrent-primitives::InfoHash`. +- [x] Remove the `aquatic_udp_protocol` dependency from `packages/bittorrent-primitives/Cargo.toml`. +- [x] Update all impls in `src/info_hash.rs` that previously delegated to + `aquatic_udp_protocol::InfoHash` to operate on the inner `[u8; 20]` directly. +- [x] Ensure all existing tests in `bittorrent-primitives` pass. +- [x] Publish a new version of `bittorrent-primitives` to crates.io once the crate is + self-contained (no external protocol dependencies). +- [x] Remove the `packages/bittorrent-primitives/` fork and the `[patch.crates-io]` entry once + the published version is available. + +> **Note on step ordering**: Step 4c is independent of Steps 4b and 4a-prep. It can be done in +> parallel or in any order relative to those steps. Step 4c only unblocks removal of the +> `bittorrent-primitives` fork from `[patch.crates-io]`. + +### Step 5: Post-Migration Refactor and Cleanup (pre-merge) + +Now that the aquatic dependency has been fully removed, Step 5 is the umbrella phase for +follow-up refactors before merging the PR: improving module organization, removing duplication, +clarifying ownership boundaries, and cleaning up protocol/domain structure while preserving +behavior. + +- [ ] Keep API and wire-format behavior stable while refactoring internals. +- [ ] Review each type and assess whether a domain-specific redesign is warranted. +- [ ] Introduce new types iteratively — keeping the existing API intact until each replacement + is complete. +- [ ] Remove duplication and simplify module boundaries where it improves maintainability. +- [ ] Track protocol-module refactor work in + [step-5-udp-protocol-module-refactor-plan.md](step-5-udp-protocol-module-refactor-plan.md). +- [ ] Document design decisions in an ADR if any significant trade-offs arise. + +## Acceptance Criteria + +- [x] `aquatic_udp_protocol` and `aquatic_peer_id` are removed as dependencies/imports from + workspace packages (`Cargo.toml` and Rust code imports). +- [x] All workspace tests pass (`cargo test --workspace`). +- [x] `linter all` exits with code `0`. +- [x] `cargo machete` reports no unused dependencies. +- [x] The `zerocopy` version across the workspace is `0.8`. +- [x] Both interim forks (`packages/aquatic-udp-protocol` and `packages/aquatic-peer-id`) have been + removed from the workspace members by the end of Step 4a. The fork directories still exist + on disk and will be physically deleted as a follow-up cleanup. +- [x] `PeerId`, `PeerClient`, `AnnounceEvent`, and `NumberOfBytes` live natively in + `packages/primitives` (no protocol dep). +- [x] `packages/primitives` has no dependency on any UDP or HTTP protocol package. +- [x] UDP wire-format protocol types live in `packages/udp-protocol`. +- [x] `bittorrent-primitives::InfoHash` is self-contained with a plain `[u8; 20]` inner field. + +## References + +- Upstream crate: +- Upstream repository: +- Upstream `zerocopy` upgrade issue: +- Our unmerged upgrade PR: +- Dependabot PR (blocked): +- BEP 15 specification: +- Apache 2.0 license: diff --git a/docs/issues/1732-replace-aquatic-udp-protocol/step-2-analysis.md b/docs/issues/1732-replace-aquatic-udp-protocol/step-2-analysis.md new file mode 100644 index 000000000..231e977b1 --- /dev/null +++ b/docs/issues/1732-replace-aquatic-udp-protocol/step-2-analysis.md @@ -0,0 +1,97 @@ +# Step 2 Analysis: Unused Code in Internal Forks + +## Objective + +Identify and remove any code paths, feature flags, or types from the internal forks +(`packages/aquatic-peer-id`, `packages/aquatic-udp-protocol`) that no package in this workspace +uses. + +## Approach + +For each public item exported by the two fork packages, we searched the entire workspace for +import or use sites outside the fork packages themselves. + +## Findings + +### `packages/aquatic-udp-protocol` + +#### Public types used outside the fork + +All of the following types are referenced by at least one workspace package outside of the fork: + +| Type | Used by | +| --------------------------- | --------------------------------------------------------------------------------------------------- | +| `Request` | `udp-tracker-core`, `udp-tracker-server`, `tracker-client` (console), `bittorrent-tracker-client` | +| `ConnectRequest` | `udp-tracker-core`, `udp-tracker-server`, `tracker-client` (console), `bittorrent-tracker-client` | +| `AnnounceRequest` | `udp-tracker-core`, `http-tracker-core`, `tracker-core`, `axum-rest-tracker-api-server`, and others | +| `ScrapeRequest` | `udp-tracker-core`, `udp-tracker-server`, `tracker-client` (console), `bittorrent-tracker-client` | +| `Response` | `udp-tracker-core`, `udp-tracker-server`, `tracker-client` (console), `bittorrent-tracker-client` | +| `ConnectResponse` | `udp-tracker-core`, `udp-tracker-server`, `tracker-client` (console), `bittorrent-tracker-client` | +| `AnnounceResponse` | `udp-tracker-core`, `udp-tracker-server`, `tracker-client` (console), `bittorrent-tracker-client` | +| `ScrapeResponse` | `udp-tracker-core`, `udp-tracker-server`, `tracker-client` (console), `bittorrent-tracker-client` | +| `ErrorResponse` | `udp-tracker-core`, `udp-tracker-server`, `tracker-client` (console), `bittorrent-tracker-client` | +| `TransactionId` | `udp-tracker-core`, `udp-tracker-server`, `tracker-client` (console), `bittorrent-tracker-client` | +| `ConnectionId` | `udp-tracker-core`, `udp-tracker-server`, `tracker-client` (console), `bittorrent-tracker-client` | +| `InfoHash` | `udp-protocol`, `udp-tracker-core`, `tracker-core`, `swarm-coordination-registry`, and others | +| `PeerId` (re-export) | `udp-protocol`, `udp-tracker-core`, `tracker-core`, and others (via `aquatic_peer_id`) | +| `AnnounceEvent` | `udp-tracker-core`, `http-tracker-core`, `tracker-core`, `axum-rest-tracker-api-server`, and others | +| `AnnounceActionPlaceholder` | `udp-tracker-core`, `udp-tracker-server` | +| `Port` | `udp-tracker-core`, `udp-tracker-server`, `tracker-client` (console), and others | +| `PeerKey` | `udp-tracker-core`, `udp-tracker-server` | +| `NumberOfBytes` | `udp-tracker-core`, `udp-tracker-server`, `tracker-client` (console), and others | +| `NumberOfPeers` | `udp-tracker-core`, `udp-tracker-server`, `tracker-client` (console), and others | +| `NumberOfDownloads` | `udp-tracker-core`, `udp-tracker-server`, and others | +| `TorrentScrapeStatistics` | `udp-tracker-core`, `udp-tracker-server`, `tracker-client` (console), and others | +| `Ipv4AddrBytes` | `udp-tracker-core`, `udp-tracker-server` | +| `Ipv6AddrBytes` | `udp-tracker-core`, `udp-tracker-server` | +| `RequestParseError` | `udp-tracker-core`, `udp-tracker-server` | +| `ResponsePeer` | `udp-tracker-core`, `udp-tracker-server` | + +#### Internal-only types + +`AnnounceEventBytes` is not exported from the fork's public API and has no uses outside the fork. +It exists solely as an intermediate wire-format representation inside `AnnounceRequest` +(a `#[repr(C, packed)]` struct used during zero-copy deserialization). Removing it would break +the deserialization logic for `AnnounceRequest`. It cannot be removed. + +#### Feature flags + +The upstream crate has no optional feature flags. No feature stripping is possible. + +#### Conclusion + +Every public type exported by `packages/aquatic-udp-protocol` is used by at least one other +workspace package. The only internal-only item (`AnnounceEventBytes`) is structurally required +and cannot be removed. **There is no dead code to strip.** + +--- + +### `packages/aquatic-peer-id` + +#### Public types used outside the fork + +| Type | Used by | +| ------------ | ------------------------------------------------------------------------------------------------------------ | +| `PeerId` | Re-exported through `aquatic-udp-protocol`; used by `udp-protocol`, `tracker-core`, `primitives`, and others | +| `PeerClient` | `udp-tracker-core` | + +#### Feature flags + +The upstream crate exposed an optional `quickcheck` feature (for property-based testing helpers). +At the time of this analysis in the original migration, the feature was retained to preserve +upstream test-oriented behavior rather than to optimize release dependency footprint. + +#### Conclusion + +Both public types (`PeerId`, `PeerClient`) are actively used in the workspace. **There is no dead +code to strip.** + +--- + +## Overall Conclusion + +After a thorough search of all 26 source files across 13 packages that depend on the two forks, +**no unused public types, functions, or feature-enabled code paths were found** that could be +safely removed at this stage. Step 2 is complete with no changes to the fork source. + +The migration continues at Step 3: upgrading `zerocopy` from 0.7 to 0.8. diff --git a/docs/issues/1732-replace-aquatic-udp-protocol/step-3-bittorrent-primitives-problem.md b/docs/issues/1732-replace-aquatic-udp-protocol/step-3-bittorrent-primitives-problem.md new file mode 100644 index 000000000..a083417a1 --- /dev/null +++ b/docs/issues/1732-replace-aquatic-udp-protocol/step-3-bittorrent-primitives-problem.md @@ -0,0 +1,189 @@ +# Step 3: `bittorrent-primitives` Transitive Dependency Problem + +## Problem + +During Step 3 (zerocopy 0.8 migration), `cargo check --workspace` fails with: + +```text +error[E0599]: no associated function or constant named `read_from` found for struct +`aquatic_udp_protocol::InfoHash` in the current scope + --> /home/josecelano/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ + bittorrent-primitives-0.1.0/src/info_hash.rs:155:52 +note: there are multiple different versions of crate `zerocopy` in the dependency graph +``` + +The root cause is that the crates.io package `bittorrent-primitives 0.1.0` depends on +`aquatic_udp_protocol = "0.9.0"` and calls the zerocopy 0.7 API (`read_from`) on +`aquatic_udp_protocol::InfoHash`. After our `[patch.crates-io]` entry substitutes our internal +fork (zerocopy 0.8) for `aquatic_udp_protocol`, that call becomes invalid. + +```toml +# bittorrent-primitives 0.1.0 (crates.io) — relevant deps +[dependencies] +aquatic_udp_protocol = "0.9.0" +zerocopy = { version = "0.7", features = ["derive"] } +``` + +```rust +// bittorrent-primitives 0.1.0 — src/info_hash.rs, line 155 +pub fn from_bytes(bytes: &[u8]) -> Self { + let data = aquatic_udp_protocol::InfoHash::read_from(bytes) // ← zerocopy 0.7 API + .expect("it should have the exact amount of bytes"); + Self { data } +} +``` + +In zerocopy 0.8, `read_from` was renamed to `read_from_bytes` and its return type changed from +`Option` to `Result`. The `expect` call must also be updated accordingly. + +## Scope + +11 workspace packages depend on `bittorrent-primitives`: + +| Package | Published on crates.io | +| ------------------------------------------------- | ---------------------- | +| `torrust-axum-http-tracker-server` | No | +| `torrust-axum-rest-tracker-api-server` | No | +| `bittorrent-http-tracker-protocol` | No | +| `bittorrent-http-tracker-core` | No | +| `torrust-tracker-primitives` | **Yes** | +| `torrust-tracker-swarm-coordination-registry` | No | +| `torrust-tracker-torrent-repository-benchmarking` | No | +| `bittorrent-tracker-client` | No | +| `bittorrent-tracker-core` | No | +| `bittorrent-udp-tracker-core` | No | +| `torrust-udp-tracker-server` | No | + +Also, the root workspace crate (`torrust-tracker`) has `bittorrent-primitives = "0.1.0"` in +its `[dev-dependencies]`. + +Of these, only `torrust-tracker-primitives` is already published on crates.io. All others are +unpublished workspace packages with no backward-compatibility constraints on crates.io. + +## Relationship Between the Crates + +```text +bittorrent-primitives (crates.io 0.1.0) + └── aquatic_udp_protocol = "0.9.0" ← patched by our workspace to the internal fork + └── zerocopy = "0.8" ← our fork uses 0.8 + └── zerocopy = "0.7" ← crates.io version still calls 0.7 API +``` + +The workspace `[patch.crates-io]` already replaces `aquatic_udp_protocol` with our fork, but +the patched `bittorrent-primitives` source code itself still uses the zerocopy 0.7 call. Cargo's +patch mechanism substitutes the library, but cannot rewrite the call sites in the dependent +crate's source. + +## Solution + +Create an internal fork of `bittorrent-primitives` at `packages/bittorrent-primitives/`, apply +the two required changes, and add it to `[patch.crates-io]`: + +### Changes required in the fork + +1. **`Cargo.toml`**: Change `aquatic_udp_protocol = "0.9.0"` to + `aquatic_udp_protocol = { path = "../aquatic-udp-protocol" }` and bump + `zerocopy` from `"0.7"` to `"0.8"`. + +2. **`src/info_hash.rs`**: Update `from_bytes` to use the zerocopy 0.8 API: + + ```rust + // Before (zerocopy 0.7) + use zerocopy::FromBytes; + // ... + let data = aquatic_udp_protocol::InfoHash::read_from(bytes) + .expect("it should have the exact amount of bytes"); + + // After (zerocopy 0.8) + use zerocopy::FromBytes as _; + // ... + let data = aquatic_udp_protocol::InfoHash::read_from_bytes(bytes) + .expect("it should have the exact amount of bytes"); + ``` + +### Root Cargo.toml changes + +Add to `[workspace.members]`: + +```toml +"packages/bittorrent-primitives", +``` + +Add to `[patch.crates-io]`: + +```toml +bittorrent-primitives = { path = "packages/bittorrent-primitives" } +``` + +The existing `bittorrent-primitives = "0.1.0"` entry in `[workspace.dependencies]` stays +unchanged; the patch transparently replaces the resolved crate for all workspace members. + +### Publishing considerations + +The fork is marked `publish = false` because it is a temporary internal patch — not a version +intended for crates.io. When Step 4 is complete and all direct uses of +`aquatic_udp_protocol::InfoHash` are replaced by the type from `packages/udp-protocol`, the +`bittorrent-primitives` fork will need to be updated again (or, if `bittorrent-primitives` is +kept long-term as a published crate, a new version should be released that depends on the +published `bittorrent-udp-tracker-protocol` crate instead of `aquatic_udp_protocol`). + +## Future Work + +### Update `bittorrent-primitives` dependency after Step 4c + +Once Step 4c consolidates `InfoHash` directly into `bittorrent-primitives`, the crate will no +longer depend on `aquatic_udp_protocol` at all. At that point a new version of +`bittorrent-primitives` can be published to crates.io (bumping from `0.1.0`) with the +self-contained implementation. The workspace `[patch.crates-io]` entry for +`bittorrent-primitives` and the fork in `packages/bittorrent-primitives/` can then both be +removed. + +### Consolidate `InfoHash` into `bittorrent-primitives` (Step 4c) + +The `bittorrent-primitives` crate currently wraps `aquatic_udp_protocol::InfoHash` inside its +own `InfoHash` newtype: + +```rust +// packages/bittorrent-primitives/src/info_hash.rs +pub struct InfoHash { + data: aquatic_udp_protocol::InfoHash, +} +``` + +Once Step 4a migrates the `aquatic_udp_protocol::InfoHash` bytes type into +`packages/udp-protocol` (as `bittorrent-udp-tracker-protocol`), the natural next move is to +eliminate the wrapping layer entirely: the raw `[u8; 20]` storage — and all the serialization, +formatting, and conversion logic — should live directly inside `bittorrent-primitives` with no +dependency on any UDP protocol crate at all. + +This would give `bittorrent-primitives` a fully self-contained `InfoHash` type that any +BitTorrent project can use without pulling in UDP protocol machinery. + +This is tracked as **Step 4c** in the issue spec. + +### Re-evaluate the boundary between `bittorrent-primitives` and `torrust-tracker-primitives` + +The current separation is ad-hoc: + +- `bittorrent-primitives` (external crate) — originally scoped to bare BitTorrent types + (`InfoHash`). Despite its name it currently lives in a separate repository and is published + independently. +- `torrust-tracker-primitives` (`packages/primitives`) — a tracker-scoped library that already + contains peer-related logic (`src/peer.rs`: `Peer`, `PeerId` usage, `PeerRole`, `PeerAnnouncement`, + `PeerClient`), plus tracker-domain types (`DurationSinceUnixEpoch`, stats, etc.). + +A cleaner long-term split would be: + +| Crate | Should contain | +| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `bittorrent-primitives` | Types reusable across **any** BitTorrent application or protocol: `InfoHash`, `PeerId`, `PeerClient`, announce/scrape value objects (`AnnounceEvent`, `NumberOfBytes`, `Port`, …) | +| `torrust-tracker-primitives` | Types **specific** to the Torrust Tracker domain: `Peer`, `PeerRole`, `PeerAnnouncement`, tracker stats, `DurationSinceUnixEpoch`, etc. | + +Concretely this means `packages/primitives/src/peer.rs` — and the peer-related logic that +currently re-exports or wraps `aquatic_udp_protocol::PeerId` — should eventually move into +`bittorrent-primitives`. This would make `InfoHash` and peer identity types available to any +BitTorrent project, not just the Torrust Tracker. + +This boundary review is **out of scope for the current issue** (issue 1732 is focused on +removing `aquatic_udp_protocol`). It should be tracked as a separate issue once Step 4 is +complete and the peer/protocol types have settled into their new homes. diff --git a/docs/issues/1732-replace-aquatic-udp-protocol/step-5-udp-protocol-module-refactor-plan.md b/docs/issues/1732-replace-aquatic-udp-protocol/step-5-udp-protocol-module-refactor-plan.md new file mode 100644 index 000000000..a4b14c738 --- /dev/null +++ b/docs/issues/1732-replace-aquatic-udp-protocol/step-5-udp-protocol-module-refactor-plan.md @@ -0,0 +1,331 @@ +# Step 5: UDP Protocol Module Refactor Plan + +## Goal + +Refactor `packages/udp-protocol/src` so module boundaries reflect BEP 15 actions and shared +wire primitives are isolated. Keep behavior and external API stable during the migration. + +## Scope + +In scope: + +- Reorganize internal modules in `packages/udp-protocol/src` +- Split action-specific types and logic into `connect`, `announce`, and `scrape` +- Keep shared protocol-wide wire types in `common` +- Preserve compatibility through `pub use` exports in `lib.rs` +- Keep all workspace users building without behavior changes + +Out of scope: + +- Redesigning protocol semantics +- Changing wire format +- Cross-crate public API breaks in one step + +## Current Layout + +Current source files: + +- `common.rs` +- `request.rs` +- `response.rs` +- `peer_id.rs` +- `lib.rs` + +Current problem: + +- Request and response logic are grouped by message direction, not by BEP 15 action. +- Action-specific types are split across files, which makes ownership harder to follow. + +## Target Layout + +Planned source files: + +- `common.rs` (shared wire primitives only) +- `connect.rs` (connect request and response) +- `announce.rs` (announce request and response) +- `scrape.rs` (scrape request and response) +- `request.rs` (kept as stable wrapper/orchestration entrypoint) +- `response.rs` (kept as stable wrapper/orchestration entrypoint) +- `peer_id.rs` +- `lib.rs` + +## Final Module Map (Implemented) + +- `common.rs`: shared wire primitives and helpers (`ConnectionId`, `TransactionId`, `InfoHash`, + `NumberOfBytes`, `Port`, `PeerKey`, `NumberOfPeers`, `NumberOfDownloads`, + `Ipv4AddrBytes`/`Ipv6AddrBytes`, `ResponsePeer`, read helpers, `invalid_data`) +- `connect.rs`: connect action request/response types +- `announce.rs`: announce action request/response types and announce-only wire helpers + (`AnnounceInterval`, `AnnounceActionPlaceholder`, `AnnounceEvent*`) +- `scrape.rs`: scrape action request/response types and scrape statistics +- `request.rs`: stable top-level request wrapper/orchestration +- `response.rs`: stable top-level response wrapper/orchestration (including `ErrorResponse`) +- `lib.rs`: compatibility-preserving re-exports + +## Type Ownership Rules + +`common.rs` owns protocol-wide shared types and helpers: + +- `ConnectionId` +- `TransactionId` +- `InfoHash` +- `NumberOfBytes` +- `Port` +- `PeerKey` +- `NumberOfPeers` +- `NumberOfDownloads` +- `Ipv4AddrBytes`, `Ipv6AddrBytes`, `ResponsePeer` +- read helpers and shared error helper (`invalid_data`) + +`announce.rs` owns announce-only types and wire conversions: + +- `AnnounceRequest` +- `AnnounceResponse*` +- `AnnounceInterval` +- `AnnounceActionPlaceholder` +- `AnnounceEvent`, `AnnounceEventBytes` + +Current note: + +- `InfoHash` and `NumberOfBytes` are intentionally retained in `common.rs` for now. +- These types mirror equivalents in other packages and can be unified in a separate future task. + +`connect.rs` owns connect-only types: + +- `ConnectRequest` +- `ConnectResponse` + +`scrape.rs` owns scrape-only types: + +- `ScrapeRequest` +- `ScrapeResponse` +- `TorrentScrapeStatistics` + +`request.rs` and `response.rs` are intentionally retained: + +- `Request` and `Response` enums stay as top-level wrappers +- top-level parse/write orchestration stays there +- concrete type implementations are delegated to action modules +- `ErrorResponse` remains in `response.rs` as the top-level error wrapper type + +## Constraints + +- Preserve all existing tests and behavior. +- Keep re-export compatibility from `lib.rs` during migration. +- Avoid changing call sites outside `udp-protocol` until compatibility exports are in place. + +## Implementation Decisions (Agreed) + +- Start migration with the `connect` action types first. +- Keep `request.rs` and `response.rs` as stable wrapper/orchestration modules. +- Use one signed commit per action (`connect`, `announce`, `scrape`). + +## Execution Plan + +### Phase 0: Baseline and Safety Net + +- [ ] Record baseline: + - [x] `cargo check --workspace` + - [ ] `cargo test --workspace` + - [x] `linter all` +- [x] Capture current public exports in `lib.rs` +- [x] Capture current import usage in workspace (`rg` search) + +Exit criteria: + +- [x] Baseline green and recorded in issue comments/notes + +### Phase 1: Introduce New Action Modules + +- [x] Create `connect.rs`, `announce.rs`, `scrape.rs` +- [x] Keep `Request`/`Response` enums and top-level parse/write wrappers in + `request.rs`/`response.rs` +- [x] Move concrete action-specific type implementations from + `request.rs` and `response.rs` into action modules without behavior changes +- [x] Re-export moved types from `lib.rs` to preserve public API for workspace consumers +- [x] Ensure `lib.rs` re-exports old symbols and new module symbols + +Exit criteria: + +- [x] `cargo check --workspace` passes +- [x] `cargo test --workspace` passes + +### Phase 2: Normalize `common.rs` + +- [x] Move action-specific types out of `common.rs` +- [x] Keep only shared wire primitives and generic helpers in `common.rs` +- [x] Ensure no announce/scrape-specific parsing logic remains in `common.rs` + +Exit criteria: + +- [x] `common.rs` content matches ownership rules +- [x] All tests still pass + +### Phase 3: Compatibility and Call Site Stability + +- [x] Verify existing imports in dependent crates still compile via re-exports +- [x] Update internal imports to use new module boundaries where beneficial +- [x] Keep `request.rs` and `response.rs` as stable wrapper/orchestration modules + +Exit criteria: + +- [x] Zero workspace build regressions +- [x] No behavior changes in protocol encode/decode tests + +### Phase 4: Optional Cleanup + +- [x] Keep wrappers and evaluate only internal simplification (not removal) +- [x] Remove dead internal aliases/helpers if any remain after migration +- [x] Update docs with final module map + +Exit criteria: + +- [x] Final module structure agreed and documented +- [x] Lints/tests/checks green + +## Tracking Checklist + +### Deliverables + +- [x] New action modules implemented +- [x] `common.rs` narrowed to shared primitives +- [x] Compatibility exports preserved +- [x] Docs updated + +### Type-by-Type Progress Tracker + +Use this checklist to track migration one type at a time. + +Status legend: `pending` | `moved` | `re-exported` | `consumers-updated` | `validated` + +- [x] `ConnectRequest` + - [x] moved + - [x] re-exported from `lib.rs` + - [x] consumers updated + - [x] validated (`cargo check --workspace`, `linter all`) +- [x] `ConnectResponse` + - [x] moved + - [x] re-exported from `lib.rs` + - [x] consumers updated + - [x] validated (`cargo check --workspace`, `linter all`) +- [x] `AnnounceRequest` + - [x] moved + - [x] re-exported from `lib.rs` + - [x] consumers updated + - [x] validated (`cargo check --workspace`, `linter all`) +- [x] `AnnounceActionPlaceholder` + - [x] moved + - [x] re-exported from `lib.rs` + - [x] consumers updated + - [x] validated (`cargo check --workspace`, `linter all`) +- [x] `AnnounceEvent` + - [x] moved + - [x] re-exported from `lib.rs` + - [x] consumers updated + - [x] validated (`cargo check --workspace`, `linter all`) +- [x] `AnnounceEventBytes` + - [x] moved + - [x] re-exported from `lib.rs` + - [x] consumers updated + - [x] validated (`cargo check --workspace`, `linter all`) +- [x] `ScrapeRequest` + - [x] moved + - [x] re-exported from `lib.rs` + - [x] consumers updated + - [x] validated (`cargo check --workspace`, `linter all`) +- [x] `AnnounceResponse` / `AnnounceResponse` + - [x] moved + - [x] re-exported from `lib.rs` + - [x] consumers updated + - [x] validated (`cargo check --workspace`, `linter all`) +- [x] `AnnounceResponseFixedData` + - [x] moved + - [x] re-exported from `lib.rs` + - [x] consumers updated + - [x] validated (`cargo check --workspace`, `linter all`) +- [x] `AnnounceInterval` + - [x] moved + - [x] re-exported from `lib.rs` + - [x] consumers updated + - [x] validated (`cargo check --workspace`, `linter all`) +- [x] `ScrapeResponse` + - [x] moved + - [x] re-exported from `lib.rs` + - [x] consumers updated + - [x] validated (`cargo check --workspace`, `linter all`) +- [x] `TorrentScrapeStatistics` + - [x] moved + - [x] re-exported from `lib.rs` + - [x] consumers updated + - [x] validated (`cargo check --workspace`, `linter all`) +- [x] `ErrorResponse` + - [x] retained in `response.rs` by design + - [x] re-exported from `lib.rs` + - [x] consumers unchanged + - [x] validated (`cargo check --workspace`, `linter all`) + +### Per-Type Migration Workflow (Implementation Strategy) + +For each type, execute this sequence before starting the next one: + +1. Move one type to its target module. +2. Add/adjust `pub use` re-export in `lib.rs`. +3. Update consumers/imports. +4. Run validation gate for that single move: + - `cargo check --workspace` + - `linter all` +5. Mark the type row/checklist as validated. + +### Validation Gate (must be green) + +- [x] `cargo check --workspace` +- [x] `cargo test --workspace` +- [x] `cargo test --doc --workspace` +- [x] `linter all` + +Additionally, run `linter all` at the end of every per-type move, not only at the end of the +full refactor. + +## Risk Register + +### Risk 1: Re-export breakage + +Impact: high + +Mitigation: + +- Keep `lib.rs` compatibility exports during transition +- Validate downstream crates with full workspace build + +### Risk 2: Silent protocol behavior regressions + +Impact: high + +Mitigation: + +- Keep existing encode/decode tests unchanged +- Add focused tests if code moves require it + +### Risk 3: Mixed ownership of types + +Impact: medium + +Mitigation: + +- Apply and enforce ownership rules in this plan +- Review each moved type before merge + +## Review Checklist + +- [x] Module boundaries are action-oriented and coherent +- [x] Shared types remain in `common.rs` +- [x] No wire format behavior changes introduced +- [x] No unnecessary cross-module coupling +- [x] Public API compatibility preserved during migration + +## Suggested Commit Slicing + +1. [x] `refactor(udp-protocol): move connect types to connect module` +2. [x] `refactor(udp-protocol): move announce types to announce module` +3. [x] `refactor(udp-protocol): move scrape types to scrape module` +4. [x] `docs(issue-1732): document final udp-protocol module layout` diff --git a/docs/issues/1732-replace-aquatic-udp-protocol/step-6-primitives-module-refactor-plan.md b/docs/issues/1732-replace-aquatic-udp-protocol/step-6-primitives-module-refactor-plan.md new file mode 100644 index 000000000..332d9fae3 --- /dev/null +++ b/docs/issues/1732-replace-aquatic-udp-protocol/step-6-primitives-module-refactor-plan.md @@ -0,0 +1,288 @@ +# Step 6: Primitives Module Refactor Plan + +## Goal + +Refactor `packages/primitives/src` so announce-related and scrape-related primitives live in +separate modules with clearer ownership boundaries, while preserving compatibility for existing +workspace consumers during the migration. + +## Scope + +In scope: + +- Split `packages/primitives/src/core.rs` into action-oriented modules +- Introduce `announce.rs` and `scrape.rs` under `packages/primitives/src` +- Move `AnnounceData` into `announce.rs` +- Move `ScrapeData` into `scrape.rs` +- Move `packages/primitives/src/announce_event.rs` logic into `announce.rs` +- Preserve existing public API during migration through compatibility re-exports +- Keep all current workspace consumers building without behavior changes + +Out of scope: + +- Renaming public data structures +- Redesigning tracker-core announce/scrape domain semantics +- Large cross-package cleanup of shared primitive types +- Removing compatibility exports in the first step + +## Current Layout + +Current source files involved: + +- `core.rs` +- `announce_event.rs` +- `lib.rs` + +Current problem: + +- `core.rs` mixes announce and scrape concerns in a single module. +- `announce_event.rs` is announce-specific but lives outside the announce area. +- Many workspace consumers currently import `AnnounceData` and `ScrapeData` from + `torrust_tracker_primitives::core`, so ownership is unclear and future cleanup is harder. + +## Target Layout + +Planned source files: + +- `announce.rs` (`AnnounceData`, `AnnounceEvent`) +- `scrape.rs` (`ScrapeData`) +- `lib.rs` (re-exports and module declarations) + +## Final Module Map (Implemented) + +- `announce.rs`: owns `AnnounceData` and `AnnounceEvent` +- `scrape.rs`: owns `ScrapeData` +- `lib.rs`: root exports for `AnnounceData`, `AnnounceEvent`, and `ScrapeData` + +## Final Module Intent + +`announce.rs` owns announce-only primitives: + +- `AnnounceData` +- `AnnounceEvent` + +`scrape.rs` owns scrape-only primitives: + +- `ScrapeData` + +`lib.rs` preserves root-level compatibility and exposes the new module structure. + +## Migration Strategy + +Follow the same strategy used for the `udp-protocol` refactor: + +- move one type at a time +- re-export moved types from `lib.rs` immediately +- preserve compatibility before updating consumers +- validate after each type move before starting the next one +- use one signed commit per logical slice + +This allows internal reorganization without breaking current or future consumers while the +module layout evolves. + +## Constraints + +- Preserve all current behavior. +- Keep `torrust_tracker_primitives::core::AnnounceData` and + `torrust_tracker_primitives::core::ScrapeData` working during the migration. +- Keep `torrust_tracker_primitives::AnnounceEvent` working during the migration. +- Avoid unnecessary churn outside `packages/primitives` until compatibility exports are in place. + +## Current Consumer Notes + +Known current import patterns in the workspace: + +- `torrust_tracker_primitives::core::AnnounceData` +- `torrust_tracker_primitives::core::ScrapeData` +- `torrust_tracker_primitives::AnnounceEvent` + +This means the refactor should prioritize compatibility re-exports before call-site cleanup. + +## Implementation Decisions (Proposed) + +- Introduce `announce.rs` and `scrape.rs` first as empty/new target modules. +- Move one type at a time instead of moving all announce or scrape types in a single step. +- Re-export moved types from `lib.rs` immediately after each move. +- Keep `core.rs` as a stable compatibility wrapper during the refactor. +- Prefer delaying consumer import cleanup until after compatibility is in place. +- Use one signed commit per logical slice. + +## Execution Plan + +### Phase 0: Baseline and Safety Net + +- [x] Record baseline: + - [x] `cargo check --workspace` + - [x] `cargo test --workspace` + - [x] `linter all` +- [x] Capture current `packages/primitives/src/lib.rs` exports +- [x] Capture current workspace import usage (`rg`) + +Exit criteria: + +- [x] Baseline recorded and green + +### Phase 1: Introduce Action-Oriented Primitive Modules + +- [x] Create `packages/primitives/src/announce.rs` +- [x] Create `packages/primitives/src/scrape.rs` +- [x] Update `lib.rs` to declare and re-export the new modules + +Exit criteria: + +- [x] `cargo check --workspace` passes +- [x] `linter all` passes + +### Phase 2: Preserve Compatibility + +- [x] Convert `core.rs` into a compatibility wrapper module +- [x] Re-export `AnnounceData` and `ScrapeData` from `core.rs` +- [x] Preserve `torrust_tracker_primitives::AnnounceEvent` via `lib.rs` re-export +- [x] Verify existing consumers still compile unchanged + +Exit criteria: + +- [x] Existing import paths continue to work +- [x] No workspace build regressions + +### Phase 3: Type-by-Type Migration + +- [x] Move `AnnounceData` into `announce.rs` +- [x] Re-export `AnnounceData` from `lib.rs` +- [x] Validate after the `AnnounceData` move +- [x] Move `AnnounceEvent` from `announce_event.rs` into `announce.rs` +- [x] Preserve root `AnnounceEvent` re-export from `lib.rs` +- [x] Validate after the `AnnounceEvent` move +- [x] Move `ScrapeData` into `scrape.rs` +- [x] Re-export `ScrapeData` from `lib.rs` +- [x] Validate after the `ScrapeData` move + +Exit criteria: + +- [x] Each moved type remains available through compatibility exports +- [x] Each per-type move passes validation before the next move starts + +### Phase 4: Optional Consumer Cleanup + +- [x] Decide whether internal consumers should migrate from `core::*` to `announce::*` / `scrape::*` +- [x] Update internal imports only where it improves clarity +- [x] Remove `packages/primitives/src/core.rs` and `packages/primitives/src/announce_event.rs` + +Exit criteria: + +- [x] New ownership boundaries are clear +- [x] Compatibility strategy is documented + +### Phase 5: Final Documentation + +- [x] Document final module map +- [x] Record any follow-up work for eventual compatibility wrapper removal + +Exit criteria: + +- [x] Final module structure documented +- [x] Remaining follow-up work explicitly listed + +## Tracking Checklist + +### Deliverables + +- [x] `announce.rs` added +- [x] `scrape.rs` added +- [x] `AnnounceData` moved +- [x] `ScrapeData` moved +- [x] `AnnounceEvent` moved +- [x] compatibility wrapper modules removed +- [x] `lib.rs` updated +- [x] Docs updated + +### Type-by-Type Progress Tracker + +- [x] `AnnounceData` + - [x] moved to `announce.rs` + - [x] re-exported from `lib.rs` + - [x] compatibility preserved + - [x] consumers validated + - [x] validated (`cargo check --workspace`, `linter all`) +- [x] `ScrapeData` + - [x] moved to `scrape.rs` + - [x] re-exported from `lib.rs` + - [x] compatibility preserved + - [x] consumers validated + - [x] validated (`cargo check --workspace`, `linter all`) +- [x] `AnnounceEvent` + - [x] moved to `announce.rs` + - [x] re-exported from `lib.rs` + - [x] root re-export preserved + - [x] consumers validated + - [x] validated (`cargo check --workspace`, `linter all`) + +### Per-Type Migration Workflow + +For each type, execute this sequence before starting the next one: + +1. Move one type to its target module. +2. Add or adjust the `pub use` re-export in `lib.rs`. +3. Preserve compatibility exports before touching consumers. +4. Run validation gate for that single move: + - `cargo check --workspace` + - `linter all` +5. Mark the type row/checklist as validated. + +## Validation Gate + +- [x] `cargo check --workspace` +- [x] `cargo test --workspace` +- [x] `cargo test --doc --workspace` +- [x] `linter all` + +## Risk Register + +### Risk 1: Breaking `core::*` imports + +Impact: high + +Mitigation: + +- Keep `core.rs` as a compatibility wrapper first +- Validate all current consumers with workspace-wide checks + +### Risk 2: Incomplete announce ownership move + +Impact: medium + +Mitigation: + +- Keep announce-related primitives co-located by the end of the refactor +- Still move one type at a time so validation remains narrow and reversible + +### Risk 3: Over-scoping the refactor + +Impact: medium + +Mitigation: + +- Limit this task to module boundaries and compatibility +- Defer deeper domain redesign or wrapper removal to future work + +## Review Checklist + +- [x] Announce-related primitives are co-located +- [x] Scrape-related primitives are isolated +- [x] Compatibility exports preserve current consumers +- [x] No unnecessary behavior changes introduced +- [x] Follow-up cleanup work is documented + +## Suggested Commit Slicing + +1. [x] `refactor(primitives): add announce and scrape modules` +2. [x] `refactor(primitives): move AnnounceData to announce module` +3. [x] `refactor(primitives): move AnnounceEvent to announce module` +4. [x] `refactor(primitives): move ScrapeData to scrape module` +5. [x] `refactor(primitives): keep core module as compatibility wrapper` +6. [x] `docs(issue-1732): document final primitives module layout` + +## Follow-Up Work + +- Consider whether future public API cleanup should move external consumers from root exports to + module-oriented imports, but do not do that as part of this refactor. diff --git a/docs/issues/1732-replace-aquatic-udp-protocol/step-7-peer-id-extraction-plan.md b/docs/issues/1732-replace-aquatic-udp-protocol/step-7-peer-id-extraction-plan.md new file mode 100644 index 000000000..4d4682817 --- /dev/null +++ b/docs/issues/1732-replace-aquatic-udp-protocol/step-7-peer-id-extraction-plan.md @@ -0,0 +1,204 @@ +# Step 7: PeerId Extraction Plan + +## Goal + +Remove duplicated `PeerId` / `PeerClient` implementations by extracting them into an in-house +shared crate at `packages/peer-id`, while preserving correct dependency direction: + +- `bittorrent-udp-tracker-protocol` must not depend on `torrust-tracker-primitives` +- both crates consume `bittorrent-peer-id` via local path dependencies + +## Context + +Aquatic previously kept this logic in a dedicated `peer_id` crate. +During in-house migration, that logic ended up duplicated in: + +- `packages/udp-protocol/src/peer_id.rs` +- `packages/primitives/src/peer_id.rs` + +This plan restores the standalone shared-crate approach in-house. + +## Scope + +In scope: + +- Create local workspace package `packages/peer-id` +- Move shared `PeerId` / `PeerClient` logic into that package +- Migrate `packages/udp-protocol` to consume it +- Migrate `packages/primitives` to consume it +- Keep public API compatibility for existing consumers +- Add a final internal module split step in `packages/peer-id` (`PeerId` and `PeerClient` modules) + +Out of scope: + +- Large API redesign of peer-id semantics +- Inverting crate dependency direction +- Folding protocol and domain crates together + +## Implementation Shape + +Default: + +- canonical `PeerId` / `PeerClient` in `packages/peer-id` +- optional features for integrations (`serde`, `quickcheck`, `zerocopy`) + +Fallback (if needed): + +- keep thin local wrappers in consumers, but centralize parsing/client-identification logic in + `packages/peer-id` + +## Workspace Membership Note + +`packages/peer-id` is consumed through local path dependencies. +Cargo workspace membership is auto-discovered in this repository setup, so explicit addition in +`[workspace].members` is not required. + +## Execution Plan + +### Phase 0: Baseline and Safety Net + +- [ ] Record baseline: + - [ ] `cargo check --workspace` + - [ ] `cargo test --workspace` + - [ ] `cargo test --doc --workspace` + - [ ] `linter all` +- [ ] Capture current exports of both peer-id implementations +- [ ] Capture current consumers of both `PeerId` types + +Exit criteria: + +- [ ] Baseline recorded and green + +### Phase 1: Create Extraction Target + +- [x] Create new in-house crate at `packages/peer-id` +- [x] Add crate metadata and README +- [x] Add root module with exports (`PeerId`, `PeerClient`) +- [x] Wire local path dependencies from consumer crates +- [x] Seed crate contents from Aquatic-derived logic and in-house behavior + +Exit criteria: + +- [x] New crate exists and builds +- [x] Workspace resolution works through path dependencies +- [ ] No existing consumers changed yet + +### Phase 2: Move Shared Logic + +- [x] Move shared `PeerClient` detection/parsing logic into `packages/peer-id` +- [x] Move shared `PeerId` behavior into `packages/peer-id` +- [x] Preserve helper behavior (`first_8_bytes_hex`) +- [x] Add tests in `packages/peer-id` for behavior parity + +Exit criteria: + +- [x] Shared crate owns core logic +- [x] Behavior parity is validated + +### Phase 3: Integrate With `bittorrent-udp-tracker-protocol` + +- [x] Replace local peer-id module usage with `bittorrent-peer-id` +- [x] Preserve wire requirements (`zerocopy` feature) +- [x] Remove duplicated udp-protocol peer-id implementation + +Exit criteria: + +- [x] `bittorrent-udp-tracker-protocol` no longer owns duplicated peer-id logic +- [x] Protocol behavior remains unchanged + +### Phase 4: Integrate With `torrust-tracker-primitives` + +- [x] Replace local peer-id implementation with shared crate compatibility re-exports +- [x] Preserve public API for root exports and module-path imports + +Exit criteria: + +- [x] `torrust-tracker-primitives` compiles unchanged for consumers +- [x] Workspace build remains green + +### Phase 5: Cleanup and Final Documentation + +- [x] Remove leftover duplicated peer-id code +- [x] Document final ownership boundaries in issue docs +- [x] Record any remaining follow-up tasks + +Exit criteria: + +- [x] Duplication removed or reduced to intentional thin compatibility layers +- [x] Final structure documented + +### Phase 6: Final Internal Module Split (Post-Extraction) + +- [x] Split `packages/peer-id` internals into focused modules +- [x] Move `PeerId` type/helpers into dedicated module +- [x] Move `PeerClient` enum/detection logic into dedicated module +- [x] Preserve crate public API through root re-exports +- [x] Update tests to match new internal module boundaries + +Exit criteria: + +- [x] Internal module boundaries are clear and maintainable +- [x] Public API remains unchanged +- [x] Validation gate passes after split + +## Deliverables + +- [x] In-house shared crate created: `packages/peer-id` +- [x] Shared peer-id logic extracted +- [x] `udp-protocol` integrated with shared crate +- [x] `primitives` integrated with shared crate +- [x] Duplicate implementations removed from original locations +- [x] `packages/peer-id` internal module split completed +- [x] Final docs/progress notes updated + +## Validation Gate + +- [x] `cargo check --workspace` +- [x] `cargo test --workspace` +- [x] `cargo test --doc --workspace` +- [x] `linter all` + +## Final Ownership (Implemented) + +- `packages/peer-id`: canonical ownership of `PeerId` and `PeerClient` +- `packages/peer-id/src/peer_id.rs`: `PeerId` type and helpers +- `packages/peer-id/src/peer_client.rs`: `PeerClient` enum and client detection/parsing logic +- `packages/udp-protocol`: consumes `bittorrent-peer-id` (no local duplicated peer-id logic) +- `packages/primitives`: compatibility re-export module preserving existing public API paths + +## Risks + +### Risk 1: Wrong dependency direction + +Impact: high + +Mitigation: + +- Keep `udp-protocol` independent of `torrust-tracker-primitives` +- Depend on `bittorrent-peer-id` from both crates + +### Risk 2: Trait support divergence + +Impact: high + +Mitigation: + +- Keep integration features explicit (`zerocopy`, `serde`, `quickcheck`) +- Validate protocol serialization behavior after every slice + +### Risk 3: API breakage during internal module split + +Impact: medium + +Mitigation: + +- Keep root `pub use` API stable while reorganizing internals +- Run full validation before closing Step 7 + +## Suggested Commit Slicing + +1. `docs(issue-1732): add peer-id extraction plan` +2. `refactor(peer-id): create in-house crate and migrate udp-protocol` +3. `refactor(primitives): integrate extracted peer-id crate` +4. `refactor(peer-id): split peer-id crate into focused internal modules` +5. `docs(issue-1732): document final peer-id ownership` diff --git a/docs/pr-reviews/COPILOT-SUGGESTIONS-TEMPLATE.md b/docs/pr-reviews/COPILOT-SUGGESTIONS-TEMPLATE.md new file mode 100644 index 000000000..f86051673 --- /dev/null +++ b/docs/pr-reviews/COPILOT-SUGGESTIONS-TEMPLATE.md @@ -0,0 +1,37 @@ +# PR # Copilot Suggestions Tracking + +Source: Copilot PR review threads for + +Status legend: + +- `action`: code/docs change applied +- `no-action`: suggestion reviewed; no code change needed +- `resolved`: thread resolved in PR + +## Workflow + +1. Download all review threads (including resolved/outdated state and thread IDs). +2. Add one row per thread in the Suggestions table. +3. Process suggestions one by one: + - decide `action` or `no-action` + - if `action`, apply change and validate + - if needed, commit changes + - resolve the PR thread +4. Set `Thread State` to `resolved` once resolved in PR. + +## Processing Log + +- : Started processing suggestions. +- : Completed processing suggestions. + +## Suggestions + +| # | Thread ID | Path | URL | Suggestion Summary | Decision | Status | Thread State | +|---|---|---|---|---|---|---|---| +| 1 | | | | | | | | + +## Notes + +- Keep this file as an audit log of review handling for the PR. +- Prefer concise decisions with explicit rationale. +- If no code changes are needed, explain why in `Decision`. diff --git a/docs/pr-reviews/README.md b/docs/pr-reviews/README.md new file mode 100644 index 000000000..50acf0b35 --- /dev/null +++ b/docs/pr-reviews/README.md @@ -0,0 +1,26 @@ +# PR Copilot Suggestions Review Workflow + +This directory contains tools and templates for managing GitHub Copilot code review suggestions on pull requests. + +## Files + +- **COPILOT-SUGGESTIONS-TEMPLATE.md** — Reusable template for tracking and processing copilot suggestions on any PR. Copy and customize for each new PR. +- **pr-1733-copilot-suggestions.md** — Example of a completed suggestion review for PR #1733, showing how to document decisions, process suggestions, and track resolutions. + +## Workflow + +1. **Setup** — Copy `COPILOT-SUGGESTIONS-TEMPLATE.md` to a new file named `pr--copilot-suggestions.md`. + +2. **Download threads** — Use `contrib/dev-tools/github-api-scripts/get-pr-review-threads.sh ` to fetch all review threads. + +3. **List and analyze** — Use `list-unresolved-threads.sh` to see unresolved suggestions, then review each one to determine if code/doc changes are needed. + +4. **Apply changes** — For `action` items, apply fixes, validate with linters/tests, and commit. + +5. **Resolve threads** — Use `resolve-all-unresolved-threads.sh` to mark all processed suggestions as resolved in GitHub. + +6. **Document** — Update the tracker file with decisions and thread states, then commit as part of the PR documentation. + +## Example + +See `pr-1733-copilot-suggestions.md` for a complete example where all 26 Copilot suggestions were reviewed, processed, and resolved. diff --git a/docs/pr-reviews/pr-1733-copilot-suggestions.md b/docs/pr-reviews/pr-1733-copilot-suggestions.md new file mode 100644 index 000000000..f7b4623c8 --- /dev/null +++ b/docs/pr-reviews/pr-1733-copilot-suggestions.md @@ -0,0 +1,48 @@ +# PR #1733 Copilot Suggestions Tracking + +Source: Copilot PR review threads for https://github.com/torrust/torrust-tracker/pull/1733 + +Status legend: + +- `action`: code/docs change applied +- `no-action`: suggestion reviewed; no code change needed +- `resolved`: thread resolved in PR + +## Processing Log + +- 2026-05-06: Started processing suggestions (downloaded 26 threads from PR #1733) +- 2026-05-06: Applied code/doc fixes and committed changes +- 2026-05-06: Resolved all 26 threads in PR #1733 + +All suggestions (action and no-action) have been processed and marked resolved. + +## Suggestions + +| # | Thread ID | Path | URL | Decision | Status | Thread State | +| --- | --------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | --------- | ------------ | +| 1 | PRRT_kwDOGp2yqc5_wNtH | Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844085 | Already handled in previous commits; patch section removed during migration cleanup | no-action | resolved | +| 2 | PRRT_kwDOGp2yqc5_wNt2 | packages/udp-tracker-server/Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844149 | Outdated after dependency/version updates in later commits | no-action | resolved | +| 3 | PRRT_kwDOGp2yqc5_wNuR | packages/udp-tracker-core/Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844185 | Outdated after dependency/version updates in later commits | no-action | resolved | +| 4 | PRRT_kwDOGp2yqc5_wNus | packages/udp-protocol/Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844217 | Outdated after dependency/version updates in later commits | no-action | resolved | +| 5 | PRRT_kwDOGp2yqc5_wNvC | packages/tracker-core/Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844246 | Outdated after dependency/version updates in later commits | no-action | resolved | +| 6 | PRRT_kwDOGp2yqc5_wNvd | packages/tracker-client/Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844281 | Outdated after dependency/version updates in later commits | no-action | resolved | +| 7 | PRRT_kwDOGp2yqc5_wNvx | packages/torrent-repository-benchmarking/Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844309 | Outdated after dependency/version updates in later commits | no-action | resolved | +| 8 | PRRT_kwDOGp2yqc5_wNwJ | packages/swarm-coordination-registry/Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844342 | Outdated after dependency/version updates in later commits | no-action | resolved | +| 9 | PRRT_kwDOGp2yqc5_wNwY | packages/primitives/Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844361 | Outdated after dependency/version updates in later commits | no-action | resolved | +| 10 | PRRT_kwDOGp2yqc5_wNwo | packages/http-tracker-core/Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844382 | Outdated after dependency/version updates in later commits | no-action | resolved | +| 11 | PRRT_kwDOGp2yqc5_wNw0 | packages/http-protocol/Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844400 | Outdated after dependency/version updates in later commits | no-action | resolved | +| 12 | PRRT_kwDOGp2yqc5_wNxD | packages/axum-rest-tracker-api-server/Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844422 | Outdated after dependency/version updates in later commits | no-action | resolved | +| 13 | PRRT_kwDOGp2yqc5_wNxQ | packages/axum-http-tracker-server/Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844443 | Outdated after dependency/version updates in later commits | no-action | resolved | +| 14 | PRRT_kwDOGp2yqc5_wNxe | console/tracker-client/Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844467 | Outdated after dependency/version updates in later commits | no-action | resolved | +| 15 | PRRT_kwDOGp2yqc5_wNx0 | docs/issues/1732-replace-aquatic-udp-protocol/step-2-analysis.md | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844493 | Updated wording to remove outdated claim about quickcheck never compiling | action | resolved | +| 16 | PRRT_kwDOGp2yqc5_wNyU | packages/aquatic-peer-id/Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844529 | Already superseded by package replacement/removal in later migration steps | no-action | resolved | +| 17 | PRRT_kwDOGp2yqc5_wNyn | packages/aquatic-udp-protocol/Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3190844551 | Already superseded by package replacement/removal in later migration steps | no-action | resolved | +| 18 | PRRT_kwDOGp2yqc5_96zB | packages/udp-protocol/src/announce.rs | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3195675375 | No change: false positive, compilation verified; current code compiles and tests pass with zerocopy derives | no-action | resolved | +| 19 | PRRT_kwDOGp2yqc5_96z0 | packages/udp-protocol/Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3195675444 | Reduced production footprint: removed default quickcheck feature and limited peer-id features to zerocopy | action | resolved | +| 20 | PRRT_kwDOGp2yqc5_960c | packages/udp-protocol/src/common.rs | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3195675497 | Updated import path to zerocopy::byteorder::network_endian for consistency | action | resolved | +| 21 | PRRT_kwDOGp2yqc5_9607 | packages/udp-tracker-core/src/services/scrape.rs | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3195675538 | Renamed conversion helper to convert_from_wire_info_hashes | action | resolved | +| 22 | PRRT_kwDOGp2yqc5_961X | console/tracker-client/src/console/clients/udp/responses/dto.rs | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3195675569 | Updated outdated Aquatic wording in module docs | action | resolved | +| 23 | PRRT_kwDOGp2yqc5_961r | packages/udp-tracker-server/src/error.rs | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3195675598 | Reworded internal error comment to wire-protocol crate | action | resolved | +| 24 | PRRT_kwDOGp2yqc5_962D | project-words.txt | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3195675636 | Reordered Celano to preserve alphabetical order | action | resolved | +| 25 | PRRT_kwDOGp2yqc5_962d | Cargo.toml | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3195675668 | Already handled by prior PR description update | no-action | resolved | +| 26 | PRRT_kwDOGp2yqc5_9623 | packages/udp-protocol/README.md | https://github.com/torrust/torrust-tracker/pull/1733#discussion_r3195675705 | Added explicit Apache-2.0 license text file and README reference (also applied to peer-id crate) | action | resolved | diff --git a/packages/axum-http-tracker-server/Cargo.toml b/packages/axum-http-tracker-server/Cargo.toml index 88d073527..aea8a849f 100644 --- a/packages/axum-http-tracker-server/Cargo.toml +++ b/packages/axum-http-tracker-server/Cargo.toml @@ -14,13 +14,13 @@ rust-version.workspace = true version.workspace = true [dependencies] -aquatic_udp_protocol = "0" +bittorrent_udp_tracker_protocol = { package = "bittorrent-udp-tracker-protocol", path = "../udp-protocol" } axum = { version = "0", features = [ "macros" ] } axum-client-ip = "0" axum-server = { version = "0", features = [ "tls-rustls-no-provider" ] } bittorrent-http-tracker-core = { version = "3.0.0-develop", path = "../http-tracker-core" } bittorrent-http-tracker-protocol = { version = "3.0.0-develop", path = "../http-protocol" } -bittorrent-primitives = "0.1.0" +bittorrent-primitives = "0.2.0" bittorrent-tracker-core = { version = "3.0.0-develop", path = "../tracker-core" } derive_more = { version = "2", features = [ "as_ref", "constructor", "from" ] } futures = "0" @@ -50,4 +50,4 @@ torrust-tracker-clock = { version = "3.0.0-develop", path = "../clock" } torrust-tracker-events = { version = "3.0.0-develop", path = "../events" } torrust-tracker-test-helpers = { version = "3.0.0-develop", path = "../test-helpers" } uuid = { version = "1", features = [ "v4" ] } -zerocopy = "0.7" +zerocopy = "0.8" diff --git a/packages/axum-http-tracker-server/src/v1/extractors/announce_request.rs b/packages/axum-http-tracker-server/src/v1/extractors/announce_request.rs index 57001a47e..a69da5fb9 100644 --- a/packages/axum-http-tracker-server/src/v1/extractors/announce_request.rs +++ b/packages/axum-http-tracker-server/src/v1/extractors/announce_request.rs @@ -86,10 +86,10 @@ fn extract_announce_from(maybe_raw_query: Option<&str>) -> Result for Peer { peer_addr: value.peer_addr.to_string(), updated: value.updated.as_millis(), updated_milliseconds_ago: value.updated.as_millis(), - uploaded: value.uploaded.0.get(), - downloaded: value.downloaded.0.get(), - left: value.left.0.get(), + uploaded: value.uploaded.0, + downloaded: value.downloaded.0, + left: value.left.0, event: format!("{:?}", value.event), } } diff --git a/packages/axum-rest-tracker-api-server/src/v1/context/torrent/resources/torrent.rs b/packages/axum-rest-tracker-api-server/src/v1/context/torrent/resources/torrent.rs index 1753b60b9..6ed9d500d 100644 --- a/packages/axum-rest-tracker-api-server/src/v1/context/torrent/resources/torrent.rs +++ b/packages/axum-rest-tracker-api-server/src/v1/context/torrent/resources/torrent.rs @@ -96,10 +96,9 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::str::FromStr; - use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; use bittorrent_primitives::info_hash::InfoHash; use bittorrent_tracker_core::torrent::services::{BasicInfo, Info}; - use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch}; + use torrust_tracker_primitives::{peer, AnnounceEvent, DurationSinceUnixEpoch, NumberOfBytes, PeerId}; use super::Torrent; use crate::v1::context::torrent::resources::peer::Peer; diff --git a/packages/http-protocol/Cargo.toml b/packages/http-protocol/Cargo.toml index 78a037b18..3436c1e77 100644 --- a/packages/http-protocol/Cargo.toml +++ b/packages/http-protocol/Cargo.toml @@ -15,8 +15,8 @@ rust-version.workspace = true version.workspace = true [dependencies] -aquatic_udp_protocol = "0" -bittorrent-primitives = "0.1.0" +bittorrent-udp-tracker-protocol = { version = "3.0.0-develop", path = "../udp-protocol" } +bittorrent-primitives = "0.2.0" bittorrent-tracker-core = { version = "3.0.0-develop", path = "../tracker-core" } derive_more = { version = "2", features = [ "as_ref", "constructor", "from" ] } multimap = "0" diff --git a/packages/http-protocol/src/percent_encoding.rs b/packages/http-protocol/src/percent_encoding.rs index e58bf94be..85c1bf96d 100644 --- a/packages/http-protocol/src/percent_encoding.rs +++ b/packages/http-protocol/src/percent_encoding.rs @@ -15,9 +15,8 @@ //! - //! - //! - -use aquatic_udp_protocol::PeerId; use bittorrent_primitives::info_hash::{self, InfoHash}; -use torrust_tracker_primitives::peer; +use torrust_tracker_primitives::{peer, PeerId}; /// Percent decodes a percent encoded infohash. Internally an /// [`InfoHash`] is a 20-byte array. @@ -59,9 +58,9 @@ pub fn percent_decode_info_hash(raw_info_hash: &str) -> Result Result for Event { - fn from(event: aquatic_udp_protocol::request::AnnounceEvent) -> Self { +impl From for Event { + fn from(event: bittorrent_udp_tracker_protocol::AnnounceEvent) -> Self { + match event { + bittorrent_udp_tracker_protocol::AnnounceEvent::Started => Self::Started, + bittorrent_udp_tracker_protocol::AnnounceEvent::Stopped => Self::Stopped, + bittorrent_udp_tracker_protocol::AnnounceEvent::Completed => Self::Completed, + bittorrent_udp_tracker_protocol::AnnounceEvent::None => Self::Empty, + } + } +} + +impl From for Event { + fn from(event: AnnounceEvent) -> Self { match event { AnnounceEvent::Started => Self::Started, AnnounceEvent::Stopped => Self::Stopped, @@ -202,7 +212,7 @@ impl From for Event { } } -impl From for aquatic_udp_protocol::request::AnnounceEvent { +impl From for AnnounceEvent { fn from(event: Event) -> Self { match event { Event::Started => Self::Started, @@ -430,8 +440,8 @@ mod tests { mod announce_request { - use aquatic_udp_protocol::{NumberOfBytes, PeerId}; use bittorrent_primitives::info_hash::InfoHash; + use torrust_tracker_primitives::{NumberOfBytes, PeerId}; use crate::v1::query::Query; use crate::v1::requests::announce::{ diff --git a/packages/http-protocol/src/v1/responses/announce.rs b/packages/http-protocol/src/v1/responses/announce.rs index 7175b019a..a55db6919 100644 --- a/packages/http-protocol/src/v1/responses/announce.rs +++ b/packages/http-protocol/src/v1/responses/announce.rs @@ -6,8 +6,7 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use derive_more::{AsRef, Constructor, From}; use torrust_tracker_contrib_bencode::{ben_bytes, ben_int, ben_list, ben_map, BMutAccess, BencodeMut}; -use torrust_tracker_primitives::core::AnnounceData; -use torrust_tracker_primitives::peer; +use torrust_tracker_primitives::{peer, AnnounceData}; /// An [`Announce`] response, that can be anything that is convertible from [`AnnounceData`]. /// @@ -278,11 +277,10 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::Arc; - use aquatic_udp_protocol::PeerId; use torrust_tracker_configuration::AnnouncePolicy; - use torrust_tracker_primitives::core::AnnounceData; use torrust_tracker_primitives::peer::fixture::PeerBuilder; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; + use torrust_tracker_primitives::{AnnounceData, PeerId}; use crate::v1::responses::announce::{Announce, Compact, Normal}; diff --git a/packages/http-protocol/src/v1/responses/scrape.rs b/packages/http-protocol/src/v1/responses/scrape.rs index 30319bd6b..af44afb04 100644 --- a/packages/http-protocol/src/v1/responses/scrape.rs +++ b/packages/http-protocol/src/v1/responses/scrape.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use torrust_tracker_contrib_bencode::{ben_int, ben_map, BMutAccess}; -use torrust_tracker_primitives::core::ScrapeData; +use torrust_tracker_primitives::ScrapeData; /// The `Scrape` response for the HTTP tracker. /// @@ -12,7 +12,7 @@ use torrust_tracker_primitives::core::ScrapeData; /// use bittorrent_http_tracker_protocol::v1::responses::scrape::Bencoded; /// use bittorrent_primitives::info_hash::InfoHash; /// use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; -/// use torrust_tracker_primitives::core::ScrapeData; +/// use torrust_tracker_primitives::ScrapeData; /// /// let info_hash = InfoHash::from_bytes(&[0x69; 20]); /// let mut scrape_data = ScrapeData::empty(); @@ -84,8 +84,8 @@ mod tests { mod scrape_response { use bittorrent_primitives::info_hash::InfoHash; - use torrust_tracker_primitives::core::ScrapeData; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; + use torrust_tracker_primitives::ScrapeData; use crate::v1::responses::scrape::Bencoded; diff --git a/packages/http-tracker-core/Cargo.toml b/packages/http-tracker-core/Cargo.toml index c419052f9..bf10784d4 100644 --- a/packages/http-tracker-core/Cargo.toml +++ b/packages/http-tracker-core/Cargo.toml @@ -14,9 +14,8 @@ rust-version.workspace = true version.workspace = true [dependencies] -aquatic_udp_protocol = "0" bittorrent-http-tracker-protocol = { version = "3.0.0-develop", path = "../http-protocol" } -bittorrent-primitives = "0.1.0" +bittorrent-primitives = "0.2.0" bittorrent-tracker-core = { version = "3.0.0-develop", path = "../tracker-core" } criterion = { version = "0.5.1", features = [ "async_tokio" ] } futures = "0" diff --git a/packages/http-tracker-core/benches/helpers/util.rs b/packages/http-tracker-core/benches/helpers/util.rs index 4f2f96459..a06a8ce70 100644 --- a/packages/http-tracker-core/benches/helpers/util.rs +++ b/packages/http-tracker-core/benches/helpers/util.rs @@ -1,7 +1,6 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; -use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; use bittorrent_http_tracker_core::event::bus::EventBus; use bittorrent_http_tracker_core::event::sender::Broadcaster; use bittorrent_http_tracker_core::event::Event; @@ -24,7 +23,7 @@ use tokio_util::sync::CancellationToken; use torrust_tracker_configuration::{Configuration, Core}; use torrust_tracker_events::sender::SendError; use torrust_tracker_primitives::peer::Peer; -use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch}; +use torrust_tracker_primitives::{peer, AnnounceEvent, DurationSinceUnixEpoch, NumberOfBytes, PeerId}; use torrust_tracker_test_helpers::configuration; pub struct CoreTrackerServices { diff --git a/packages/http-tracker-core/src/lib.rs b/packages/http-tracker-core/src/lib.rs index 1692a68fa..493dc906e 100644 --- a/packages/http-tracker-core/src/lib.rs +++ b/packages/http-tracker-core/src/lib.rs @@ -22,9 +22,8 @@ pub const HTTP_TRACKER_LOG_TARGET: &str = "HTTP TRACKER"; pub(crate) mod tests { use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; - use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; use bittorrent_primitives::info_hash::InfoHash; - use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch}; + use torrust_tracker_primitives::{peer, AnnounceEvent, DurationSinceUnixEpoch, NumberOfBytes, PeerId}; /// # Panics /// diff --git a/packages/http-tracker-core/src/services/announce.rs b/packages/http-tracker-core/src/services/announce.rs index e6ace18b1..92f2c14fc 100644 --- a/packages/http-tracker-core/src/services/announce.rs +++ b/packages/http-tracker-core/src/services/announce.rs @@ -21,9 +21,9 @@ use bittorrent_tracker_core::authentication::{self, Key}; use bittorrent_tracker_core::error::{AnnounceError, TrackerCoreError, WhitelistError}; use bittorrent_tracker_core::whitelist; use torrust_tracker_configuration::Core; -use torrust_tracker_primitives::core::AnnounceData; use torrust_tracker_primitives::peer::PeerAnnouncement; use torrust_tracker_primitives::service_binding::ServiceBinding; +use torrust_tracker_primitives::AnnounceData; use crate::event; use crate::event::Event; @@ -331,10 +331,9 @@ mod tests { use bittorrent_http_tracker_protocol::v1::services::peer_ip_resolver::{RemoteClientAddr, ResolvedIp}; use mockall::predicate::{self}; use torrust_tracker_configuration::Configuration; - use torrust_tracker_primitives::core::AnnounceData; - use torrust_tracker_primitives::peer; use torrust_tracker_primitives::service_binding::{Protocol, ServiceBinding}; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; + use torrust_tracker_primitives::{peer, AnnounceData}; use torrust_tracker_test_helpers::configuration; use crate::event::test::announce_events_match; diff --git a/packages/http-tracker-core/src/services/scrape.rs b/packages/http-tracker-core/src/services/scrape.rs index 29fd424d3..39055511a 100644 --- a/packages/http-tracker-core/src/services/scrape.rs +++ b/packages/http-tracker-core/src/services/scrape.rs @@ -18,8 +18,8 @@ use bittorrent_tracker_core::authentication::{self, Key}; use bittorrent_tracker_core::error::{ScrapeError, TrackerCoreError, WhitelistError}; use bittorrent_tracker_core::scrape_handler::ScrapeHandler; use torrust_tracker_configuration::Core; -use torrust_tracker_primitives::core::ScrapeData; use torrust_tracker_primitives::service_binding::ServiceBinding; +use torrust_tracker_primitives::ScrapeData; use crate::event::{ConnectionContext, Event}; @@ -169,7 +169,6 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; - use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; use bittorrent_primitives::info_hash::InfoHash; use bittorrent_tracker_core::announce_handler::AnnounceHandler; use bittorrent_tracker_core::authentication::key::repository::in_memory::InMemoryKeyRepository; @@ -184,7 +183,7 @@ mod tests { use mockall::mock; use torrust_tracker_configuration::Configuration; use torrust_tracker_events::sender::SendError; - use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch}; + use torrust_tracker_primitives::{peer, AnnounceEvent, DurationSinceUnixEpoch, NumberOfBytes, PeerId}; use crate::event::Event; use crate::tests::sample_info_hash; @@ -256,9 +255,9 @@ mod tests { use bittorrent_tracker_core::announce_handler::PeersWanted; use mockall::predicate::eq; use torrust_tracker_events::bus::SenderStatus; - use torrust_tracker_primitives::core::ScrapeData; use torrust_tracker_primitives::service_binding::{Protocol, ServiceBinding}; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; + use torrust_tracker_primitives::ScrapeData; use torrust_tracker_test_helpers::configuration; use crate::event::bus::EventBus; @@ -447,8 +446,8 @@ mod tests { use bittorrent_tracker_core::announce_handler::PeersWanted; use mockall::predicate::eq; use torrust_tracker_events::bus::SenderStatus; - use torrust_tracker_primitives::core::ScrapeData; use torrust_tracker_primitives::service_binding::{Protocol, ServiceBinding}; + use torrust_tracker_primitives::ScrapeData; use torrust_tracker_test_helpers::configuration; use crate::event::bus::EventBus; diff --git a/packages/peer-id/Cargo.toml b/packages/peer-id/Cargo.toml new file mode 100644 index 000000000..94121e8b5 --- /dev/null +++ b/packages/peer-id/Cargo.toml @@ -0,0 +1,32 @@ +[package] +description = "Peer ID parsing and client identification primitives for BitTorrent crates." +keywords = [ "bittorrent", "library", "peer-id", "primitives" ] +name = "bittorrent-peer-id" +readme = "README.md" + +authors.workspace = true +documentation.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +publish.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true + +[features] +default = [ "serde" ] +quickcheck = [ "dep:quickcheck" ] +serde = [ "dep:serde" ] +zerocopy = [ "dep:zerocopy" ] + +[dependencies] +compact_str = "0.9" +hex = "0.4" +quickcheck = { version = "1", optional = true } +regex = "1" +serde = { version = "1", features = [ "derive" ], optional = true } +zerocopy = { version = "0.8", features = [ "derive" ], optional = true } + +[dev-dependencies] +pretty_assertions = "1" diff --git a/packages/peer-id/LICENSE b/packages/peer-id/LICENSE new file mode 100644 index 000000000..0ad25db4b --- /dev/null +++ b/packages/peer-id/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/packages/peer-id/LICENSE-APACHE b/packages/peer-id/LICENSE-APACHE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/packages/peer-id/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/peer-id/README.md b/packages/peer-id/README.md new file mode 100644 index 000000000..30d57d55a --- /dev/null +++ b/packages/peer-id/README.md @@ -0,0 +1,38 @@ +# bittorrent-peer-id + +In-house crate for BitTorrent `PeerId` parsing and `PeerClient` identification. + +## Origin and In-House Maintenance + +This crate was originally derived from Aquatic's `peer_id` crate: + +- https://github.com/greatest-ape/aquatic/tree/master/crates/peer_id + +This crate is extracted from previously duplicated in-house implementations in: + +- `packages/primitives/src/peer_id.rs` +- `packages/udp-protocol/src/peer_id.rs` + +It provides a shared implementation that can be consumed by both domain and protocol crates +without introducing inverted dependency directions. + +Torrust keeps this package in-house because upstream maintenance appears inactive and the tracker +still needs dependency updates, security maintenance, and ongoing evolution. + +Relevant upstream context: + +- https://github.com/greatest-ape/aquatic/issues/224 +- https://github.com/greatest-ape/aquatic/pull/235 + +## Licensing and Notices + +The original source is Apache-2.0 licensed. The in-house package keeps the required origin and +change notices in code headers, consistent with the license terms. + +An explicit copy of Apache-2.0 is included at [LICENSE-APACHE](./LICENSE-APACHE). + +## Acknowledgment + +Special thanks to [greatest-ape](https://github.com/greatest-ape) +(Joakim Frostegård) for his contributions to the BitTorrent ecosystem and the original +implementation this crate builds upon. diff --git a/packages/peer-id/src/lib.rs b/packages/peer-id/src/lib.rs new file mode 100644 index 000000000..779b6b6a5 --- /dev/null +++ b/packages/peer-id/src/lib.rs @@ -0,0 +1,9 @@ +//! Peer ID parsing and client identification for `BitTorrent` crates. + +#![allow(clippy::module_name_repetitions)] + +mod peer_client; +mod peer_id; + +pub use self::peer_client::PeerClient; +pub use self::peer_id::PeerId; diff --git a/packages/peer-id/src/peer_client.rs b/packages/peer-id/src/peer_client.rs new file mode 100644 index 000000000..bd05bb505 --- /dev/null +++ b/packages/peer-id/src/peer_client.rs @@ -0,0 +1,244 @@ +// Adapted from aquatic_peer_id 0.9.0 by Joakim Frostegard (greatest-ape). +// Source: https://crates.io/crates/aquatic_peer_id/0.9.0 +// Repository: https://github.com/greatest-ape/aquatic +// License: Apache License, Version 2.0 + +use std::borrow::Cow; +use std::fmt::Display; +use std::sync::OnceLock; + +use compact_str::{format_compact, CompactString}; +use regex::bytes::Regex; + +use crate::peer_id::PeerId; + +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum PeerClient { + BitTorrent(CompactString), + Deluge(CompactString), + LibTorrentRakshasa(CompactString), + LibTorrentRasterbar(CompactString), + QBitTorrent(CompactString), + Transmission(CompactString), + UTorrent(CompactString), + UTorrentEmbedded(CompactString), + UTorrentMac(CompactString), + UTorrentWeb(CompactString), + Vuze(CompactString), + WebTorrent(CompactString), + WebTorrentDesktop(CompactString), + Mainline(CompactString), + OtherWithPrefixAndVersion { prefix: CompactString, version: CompactString }, + OtherWithPrefix(CompactString), + Other, +} + +impl PeerClient { + #[must_use] + pub fn from_prefix_and_version(prefix: &[u8], version: &[u8]) -> Self { + fn three_digits_plus_prerelease(v1: char, v2: char, v3: char, v4: char) -> CompactString { + let prerelease: Cow<'_, str> = match v4 { + 'd' | 'D' => " dev".into(), + 'a' | 'A' => " alpha".into(), + 'b' | 'B' => " beta".into(), + 'r' | 'R' => " rc".into(), + 's' | 'S' => " stable".into(), + other => format_compact!("{}", other).into(), + }; + + format_compact!("{}.{}.{}{}", v1, v2, v3, prerelease) + } + + fn webtorrent(v1: char, v2: char, v3: char, v4: char) -> CompactString { + let major = if v1 == '0' { + format_compact!("{}", v2) + } else { + format_compact!("{}{}", v1, v2) + }; + + let minor = if v3 == '0' { + format_compact!("{}", v4) + } else { + format_compact!("{}{}", v3, v4) + }; + + format_compact!("{}.{}", major, minor) + } + + if let [v1, v2, v3, v4] = version { + let (v1, v2, v3, v4) = (*v1 as char, *v2 as char, *v3 as char, *v4 as char); + + match prefix { + b"AZ" => Self::Vuze(format_compact!("{}.{}.{}.{}", v1, v2, v3, v4)), + b"BT" => Self::BitTorrent(three_digits_plus_prerelease(v1, v2, v3, v4)), + b"DE" => Self::Deluge(three_digits_plus_prerelease(v1, v2, v3, v4)), + b"lt" => Self::LibTorrentRakshasa(format_compact!("{}.{}{}.{}", v1, v2, v3, v4)), + b"LT" => Self::LibTorrentRasterbar(format_compact!("{}.{}{}.{}", v1, v2, v3, v4)), + b"qB" => Self::QBitTorrent(format_compact!("{}.{}.{}", v1, v2, v3)), + b"TR" => { + let v = match (v1, v2, v3, v4) { + ('0', '0', '0', v4) => format_compact!("0.{}", v4), + ('0', '0', v3, v4) => format_compact!("0.{}{}", v3, v4), + _ => format_compact!("{}.{}{}", v1, v2, v3), + }; + + Self::Transmission(v) + } + b"UE" => Self::UTorrentEmbedded(three_digits_plus_prerelease(v1, v2, v3, v4)), + b"UM" => Self::UTorrentMac(three_digits_plus_prerelease(v1, v2, v3, v4)), + b"UT" => Self::UTorrent(three_digits_plus_prerelease(v1, v2, v3, v4)), + b"UW" => Self::UTorrentWeb(three_digits_plus_prerelease(v1, v2, v3, v4)), + b"WD" => Self::WebTorrentDesktop(webtorrent(v1, v2, v3, v4)), + b"WW" => Self::WebTorrent(webtorrent(v1, v2, v3, v4)), + _ => Self::OtherWithPrefixAndVersion { + prefix: CompactString::from_utf8_lossy(prefix), + version: CompactString::from_utf8_lossy(version), + }, + } + } else { + match (prefix, version) { + (b"M", &[major, b'-', minor, b'-', patch, b'-']) => { + Self::Mainline(format_compact!("{}.{}.{}", major as char, minor as char, patch as char)) + } + (b"M", &[major, b'-', minor1, minor2, b'-', patch]) => Self::Mainline(format_compact!( + "{}.{}{}.{}", + major as char, + minor1 as char, + minor2 as char, + patch as char + )), + _ => Self::OtherWithPrefixAndVersion { + prefix: CompactString::from_utf8_lossy(prefix), + version: CompactString::from_utf8_lossy(version), + }, + } + } + } + + /// # Panics + /// + /// Never panics; all `expect` calls compile constant regex patterns that are always valid. + #[must_use] + pub fn from_peer_id(peer_id: &PeerId) -> Self { + static AZ_RE: OnceLock = OnceLock::new(); + static MAINLINE_RE: OnceLock = OnceLock::new(); + static PREFIX_RE: OnceLock = OnceLock::new(); + + if let Some(caps) = AZ_RE + .get_or_init(|| Regex::new(r"^\-(?P[a-zA-Z]{2})(?P[0-9]{3}[0-9a-zA-Z])").expect("compile AZ_RE regex")) + .captures(&peer_id.0) + { + return Self::from_prefix_and_version(&caps["name"], &caps["version"]); + } + + if let Some(caps) = MAINLINE_RE + .get_or_init(|| Regex::new(r"^(?P[a-zA-Z])(?P[0-9\-]{6})\-").expect("compile MAINLINE_RE regex")) + .captures(&peer_id.0) + { + return Self::from_prefix_and_version(&caps["name"], &caps["version"]); + } + + if let Some(caps) = PREFIX_RE + .get_or_init(|| Regex::new(r"^(?P[a-zA-Z0-9\-]+)\-").expect("compile PREFIX_RE regex")) + .captures(&peer_id.0) + { + return Self::OtherWithPrefix(CompactString::from_utf8_lossy(&caps["prefix"])); + } + + Self::Other + } +} + +impl Display for PeerClient { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::BitTorrent(v) => write!(f, "BitTorrent {}", v.as_str()), + Self::Deluge(v) => write!(f, "Deluge {}", v.as_str()), + Self::LibTorrentRakshasa(v) => write!(f, "lt (rakshasa) {}", v.as_str()), + Self::LibTorrentRasterbar(v) => write!(f, "lt (rasterbar) {}", v.as_str()), + Self::QBitTorrent(v) => write!(f, "QBitTorrent {}", v.as_str()), + Self::Transmission(v) => write!(f, "Transmission {}", v.as_str()), + Self::UTorrent(v) => write!(f, "\u{00B5}Torrent {}", v.as_str()), + Self::UTorrentEmbedded(v) => write!(f, "\u{00B5}Torrent Emb. {}", v.as_str()), + Self::UTorrentMac(v) => write!(f, "\u{00B5}Torrent Mac {}", v.as_str()), + Self::UTorrentWeb(v) => write!(f, "\u{00B5}Torrent Web {}", v.as_str()), + Self::Vuze(v) => write!(f, "Vuze {}", v.as_str()), + Self::WebTorrent(v) => write!(f, "WebTorrent {}", v.as_str()), + Self::WebTorrentDesktop(v) => write!(f, "WebTorrent Desktop {}", v.as_str()), + Self::Mainline(v) => write!(f, "Mainline {}", v.as_str()), + Self::OtherWithPrefixAndVersion { prefix, version } => { + write!(f, "Other ({}) ({})", prefix.as_str(), version.as_str()) + } + Self::OtherWithPrefix(prefix) => write!(f, "Other ({})", prefix.as_str()), + Self::Other => f.write_str("Other"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn create_peer_id(bytes: &[u8]) -> PeerId { + let mut peer_id = PeerId([0; 20]); + + let len = bytes.len(); + + peer_id.0[..len].copy_from_slice(bytes); + + peer_id + } + + #[test] + fn test_client_from_peer_id() { + assert_eq!( + PeerClient::from_peer_id(&create_peer_id(b"-lt1234-k/asdh3")), + PeerClient::LibTorrentRakshasa("1.23.4".into()) + ); + assert_eq!( + PeerClient::from_peer_id(&create_peer_id(b"-DE123s-k/asdh3")), + PeerClient::Deluge("1.2.3 stable".into()) + ); + assert_eq!( + PeerClient::from_peer_id(&create_peer_id(b"-DE123r-k/asdh3")), + PeerClient::Deluge("1.2.3 rc".into()) + ); + assert_eq!( + PeerClient::from_peer_id(&create_peer_id(b"-UT123A-k/asdh3")), + PeerClient::UTorrent("1.2.3 alpha".into()) + ); + assert_eq!( + PeerClient::from_peer_id(&create_peer_id(b"-TR0012-k/asdh3")), + PeerClient::Transmission("0.12".into()) + ); + assert_eq!( + PeerClient::from_peer_id(&create_peer_id(b"-TR1212-k/asdh3")), + PeerClient::Transmission("1.21".into()) + ); + assert_eq!( + PeerClient::from_peer_id(&create_peer_id(b"-WW0102-k/asdh3")), + PeerClient::WebTorrent("1.2".into()) + ); + assert_eq!( + PeerClient::from_peer_id(&create_peer_id(b"-WW1302-k/asdh3")), + PeerClient::WebTorrent("13.2".into()) + ); + assert_eq!( + PeerClient::from_peer_id(&create_peer_id(b"-WW1324-k/asdh3")), + PeerClient::WebTorrent("13.24".into()) + ); + assert_eq!( + PeerClient::from_peer_id(&create_peer_id(b"M1-2-3--k/asdh3")), + PeerClient::Mainline("1.2.3".into()) + ); + assert_eq!( + PeerClient::from_peer_id(&create_peer_id(b"M1-23-4-k/asdh3")), + PeerClient::Mainline("1.23.4".into()) + ); + assert_eq!( + PeerClient::from_peer_id(&create_peer_id(b"S3-k/asdh3")), + PeerClient::OtherWithPrefix("S3".into()) + ); + } +} diff --git a/packages/peer-id/src/peer_id.rs b/packages/peer-id/src/peer_id.rs new file mode 100644 index 000000000..cb28a8998 --- /dev/null +++ b/packages/peer-id/src/peer_id.rs @@ -0,0 +1,53 @@ +// Adapted from aquatic_peer_id 0.9.0 by Joakim Frostegard (greatest-ape). +// Source: https://crates.io/crates/aquatic_peer_id/0.9.0 +// Repository: https://github.com/greatest-ape/aquatic +// License: Apache License, Version 2.0 + +use compact_str::CompactString; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::peer_client::PeerClient; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "zerocopy", derive(zerocopy::IntoBytes, zerocopy::FromBytes, zerocopy::Immutable))] +#[repr(transparent)] +pub struct PeerId(pub [u8; 20]); + +impl PeerId { + #[must_use] + pub fn as_bytes(&self) -> &[u8; 20] { + &self.0 + } + + #[must_use] + pub fn client(&self) -> PeerClient { + PeerClient::from_peer_id(self) + } + + /// # Panics + /// + /// Never panics; the expect is unreachable because the buffer is exactly the right size. + #[must_use] + pub fn first_8_bytes_hex(&self) -> CompactString { + let mut buf = [0u8; 16]; + + hex::encode_to_slice(&self.0[..8], &mut buf).expect("PeerId.first_8_bytes_hex buffer too small"); + + CompactString::from_utf8_lossy(&buf) + } +} + +#[cfg(feature = "quickcheck")] +impl quickcheck::Arbitrary for PeerId { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let mut bytes = [0u8; 20]; + + for byte in &mut bytes { + *byte = u8::arbitrary(g); + } + + Self(bytes) + } +} diff --git a/packages/primitives/Cargo.toml b/packages/primitives/Cargo.toml index c9ce64177..d6871d8a3 100644 --- a/packages/primitives/Cargo.toml +++ b/packages/primitives/Cargo.toml @@ -15,9 +15,9 @@ rust-version.workspace = true version.workspace = true [dependencies] -aquatic_udp_protocol = "0" +bittorrent-peer-id = { version = "3.0.0-develop", path = "../peer-id" } binascii = "0" -bittorrent-primitives = "0.1.0" +bittorrent-primitives = "0.2.0" derive_more = { version = "2", features = [ "constructor" ] } serde = { version = "1", features = [ "derive" ] } tdyne-peer-id = "1" @@ -25,7 +25,6 @@ tdyne-peer-id-registry = "0" thiserror = "2" torrust-tracker-configuration = { version = "3.0.0-develop", path = "../configuration" } url = "2.5.4" -zerocopy = "0.7" [dev-dependencies] rstest = "0.25.0" diff --git a/packages/primitives/src/announce.rs b/packages/primitives/src/announce.rs new file mode 100644 index 000000000..2d80ee37f --- /dev/null +++ b/packages/primitives/src/announce.rs @@ -0,0 +1,28 @@ +//! Announce-related primitive types. + +use std::sync::Arc; + +use derive_more::derive::Constructor; +use torrust_tracker_configuration::AnnouncePolicy; + +use crate::peer; +use crate::swarm_metadata::SwarmMetadata; + +/// Structure that holds the data returned by the `announce` request. +#[derive(Clone, Debug, PartialEq, Constructor, Default)] +pub struct AnnounceData { + /// The list of peers that are downloading the same torrent. + /// It excludes the peer that made the request. + pub peers: Vec>, + /// Swarm statistics + pub stats: SwarmMetadata, + pub policy: AnnouncePolicy, +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] +pub enum AnnounceEvent { + Started, + Stopped, + Completed, + None, +} diff --git a/packages/primitives/src/lib.rs b/packages/primitives/src/lib.rs index ec2edda97..59ab7457b 100644 --- a/packages/primitives/src/lib.rs +++ b/packages/primitives/src/lib.rs @@ -4,9 +4,12 @@ //! which is a `BitTorrent` tracker server. These structures are used not only //! by the tracker server crate, but also by other crates in the Torrust //! ecosystem. -pub mod core; +pub mod announce; +pub mod number_of_bytes; pub mod pagination; pub mod peer; +pub mod peer_id; +pub mod scrape; pub mod service_binding; pub mod swarm_metadata; @@ -18,5 +21,10 @@ use bittorrent_primitives::info_hash::InfoHash; /// Duration since the Unix Epoch. pub type DurationSinceUnixEpoch = Duration; +pub use announce::{AnnounceData, AnnounceEvent}; +pub use number_of_bytes::NumberOfBytes; +pub use peer_id::{PeerClient, PeerId}; +pub use scrape::ScrapeData; + pub type NumberOfDownloads = u32; pub type NumberOfDownloadsBTreeMap = BTreeMap; diff --git a/packages/primitives/src/number_of_bytes.rs b/packages/primitives/src/number_of_bytes.rs new file mode 100644 index 000000000..d3069b172 --- /dev/null +++ b/packages/primitives/src/number_of_bytes.rs @@ -0,0 +1,9 @@ +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] +pub struct NumberOfBytes(pub i64); + +impl NumberOfBytes { + #[must_use] + pub const fn new(v: i64) -> Self { + Self(v) + } +} diff --git a/packages/primitives/src/peer.rs b/packages/primitives/src/peer.rs index 473d9003a..c3aa99193 100644 --- a/packages/primitives/src/peer.rs +++ b/packages/primitives/src/peer.rs @@ -3,7 +3,7 @@ //! A sample peer: //! //! ```rust,no_run -//! use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; +//! use torrust_tracker_primitives::{AnnounceEvent, NumberOfBytes, PeerId}; //! use torrust_tracker_primitives::peer; //! use std::net::SocketAddr; //! use std::net::IpAddr; @@ -28,11 +28,9 @@ use std::ops::{Deref, DerefMut}; use std::str::FromStr; use std::sync::Arc; -use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; use serde::Serialize; -use zerocopy::FromBytes as _; -use crate::DurationSinceUnixEpoch; +use crate::{AnnounceEvent, DurationSinceUnixEpoch, NumberOfBytes, PeerId}; pub type PeerAnnouncement = Peer; @@ -92,7 +90,7 @@ pub enum ParsePeerRoleError { /// A sample peer: /// /// ```rust,no_run -/// use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; +/// use torrust_tracker_primitives::{AnnounceEvent, NumberOfBytes, PeerId}; /// use torrust_tracker_primitives::peer; /// use std::net::SocketAddr; /// use std::net::IpAddr; @@ -173,7 +171,7 @@ pub fn ser_announce_event(announce_event: &AnnounceEvent, /// /// If will return an error if the internal serializer was to fail. pub fn ser_number_of_bytes(number_of_bytes: &NumberOfBytes, ser: S) -> Result { - ser.serialize_i64(number_of_bytes.0.get()) + ser.serialize_i64(number_of_bytes.0) } /// Serializes a `PeerId` as a `peer::Id`. @@ -209,7 +207,7 @@ pub trait ReadInfo { impl ReadInfo for Peer { fn is_seeder(&self) -> bool { - self.left.0.get() <= 0 && self.event != AnnounceEvent::Stopped + self.left.0 <= 0 && self.event != AnnounceEvent::Stopped } fn is_leecher(&self) -> bool { @@ -235,7 +233,7 @@ impl ReadInfo for Peer { impl ReadInfo for Arc { fn is_seeder(&self) -> bool { - self.left.0.get() <= 0 && self.event != AnnounceEvent::Stopped + self.left.0 <= 0 && self.event != AnnounceEvent::Stopped } fn is_leecher(&self) -> bool { @@ -262,7 +260,7 @@ impl ReadInfo for Arc { impl Peer { #[must_use] pub fn is_seeder(&self) -> bool { - self.left.0.get() <= 0 && self.event != AnnounceEvent::Stopped + self.left.0 <= 0 && self.event != AnnounceEvent::Stopped } #[must_use] @@ -393,7 +391,9 @@ impl TryFrom> for Id { }); } - let data = PeerId::read_from(&bytes).expect("it should have the correct amount of bytes"); + let mut data = [0_u8; PEER_ID_BYTES_LEN]; + data.copy_from_slice(&bytes); + let data = PeerId(data); Ok(Self { data }) } } @@ -493,10 +493,8 @@ impl FromIterator for Vec

{ pub mod fixture { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes}; - use super::{Id, Peer, PeerId}; - use crate::DurationSinceUnixEpoch; + use crate::{AnnounceEvent, DurationSinceUnixEpoch, NumberOfBytes}; #[derive(PartialEq, Debug)] @@ -658,9 +656,7 @@ pub mod test { } mod torrent_peer_id { - use aquatic_udp_protocol::PeerId; - - use crate::peer; + use crate::{peer, PeerId}; #[test] #[should_panic = "NotEnoughBytes"] diff --git a/packages/primitives/src/peer_id.rs b/packages/primitives/src/peer_id.rs new file mode 100644 index 000000000..8e8967b79 --- /dev/null +++ b/packages/primitives/src/peer_id.rs @@ -0,0 +1,3 @@ +//! Compatibility re-export for shared peer-id primitives. + +pub use bittorrent_peer_id::{PeerClient, PeerId}; diff --git a/packages/primitives/src/core.rs b/packages/primitives/src/scrape.rs similarity index 80% rename from packages/primitives/src/core.rs rename to packages/primitives/src/scrape.rs index aa2fe6926..e4d952d27 100644 --- a/packages/primitives/src/core.rs +++ b/packages/primitives/src/scrape.rs @@ -1,24 +1,11 @@ +//! Scrape-related primitive types. + use std::collections::HashMap; -use std::sync::Arc; use bittorrent_primitives::info_hash::InfoHash; -use derive_more::derive::Constructor; -use torrust_tracker_configuration::AnnouncePolicy; -use crate::peer; use crate::swarm_metadata::SwarmMetadata; -/// Structure that holds the data returned by the `announce` request. -#[derive(Clone, Debug, PartialEq, Constructor, Default)] -pub struct AnnounceData { - /// The list of peers that are downloading the same torrent. - /// It excludes the peer that made the request. - pub peers: Vec>, - /// Swarm statistics - pub stats: SwarmMetadata, - pub policy: AnnouncePolicy, -} - /// Structure that holds the data returned by the `scrape` request. #[derive(Debug, PartialEq, Default)] pub struct ScrapeData { @@ -59,10 +46,9 @@ impl ScrapeData { #[cfg(test)] mod tests { - use bittorrent_primitives::info_hash::InfoHash; - use crate::core::ScrapeData; + use crate::scrape::ScrapeData; /// # Panics /// diff --git a/packages/swarm-coordination-registry/Cargo.toml b/packages/swarm-coordination-registry/Cargo.toml index f9513d3c4..5a285ab89 100644 --- a/packages/swarm-coordination-registry/Cargo.toml +++ b/packages/swarm-coordination-registry/Cargo.toml @@ -16,8 +16,7 @@ rust-version.workspace = true version.workspace = true [dependencies] -aquatic_udp_protocol = "0" -bittorrent-primitives = "0.1.0" +bittorrent-primitives = "0.2.0" chrono = { version = "0", default-features = false, features = [ "clock" ] } crossbeam-skiplist = "0" futures = "0" diff --git a/packages/swarm-coordination-registry/src/lib.rs b/packages/swarm-coordination-registry/src/lib.rs index eb2721a0c..2ec520aeb 100644 --- a/packages/swarm-coordination-registry/src/lib.rs +++ b/packages/swarm-coordination-registry/src/lib.rs @@ -28,10 +28,9 @@ pub const SWARM_COORDINATION_REGISTRY_LOG_TARGET: &str = "SWARM_COORDINATION_REG pub(crate) mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; use bittorrent_primitives::info_hash::InfoHash; use torrust_tracker_primitives::peer::Peer; - use torrust_tracker_primitives::DurationSinceUnixEpoch; + use torrust_tracker_primitives::{AnnounceEvent, DurationSinceUnixEpoch, NumberOfBytes, PeerId}; /// # Panics /// diff --git a/packages/swarm-coordination-registry/src/statistics/event/handler.rs b/packages/swarm-coordination-registry/src/statistics/event/handler.rs index 1d3f8f32c..77bb0c9db 100644 --- a/packages/swarm-coordination-registry/src/statistics/event/handler.rs +++ b/packages/swarm-coordination-registry/src/statistics/event/handler.rs @@ -175,10 +175,10 @@ pub(crate) fn label_set_for_peer(peer: &Peer) -> LabelSet { mod tests { use std::sync::Arc; - use aquatic_udp_protocol::NumberOfBytes; use torrust_tracker_metrics::label::LabelSet; use torrust_tracker_metrics::metric::MetricName; use torrust_tracker_primitives::peer::{Peer, PeerRole}; + use torrust_tracker_primitives::NumberOfBytes; use crate::statistics::repository::Repository; use crate::tests::{leecher, seeder}; diff --git a/packages/swarm-coordination-registry/src/swarm/coordinator.rs b/packages/swarm-coordination-registry/src/swarm/coordinator.rs index 433ab9d32..8c3bf1ffc 100644 --- a/packages/swarm-coordination-registry/src/swarm/coordinator.rs +++ b/packages/swarm-coordination-registry/src/swarm/coordinator.rs @@ -4,12 +4,11 @@ use std::collections::BTreeMap; use std::net::SocketAddr; use std::sync::Arc; -use aquatic_udp_protocol::AnnounceEvent; use bittorrent_primitives::info_hash::InfoHash; use torrust_tracker_configuration::TrackerPolicy; use torrust_tracker_primitives::peer::{self, Peer, PeerAnnouncement}; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; -use torrust_tracker_primitives::DurationSinceUnixEpoch; +use torrust_tracker_primitives::{AnnounceEvent, DurationSinceUnixEpoch}; use crate::event::sender::Sender; use crate::event::Event; @@ -321,10 +320,9 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; - use aquatic_udp_protocol::PeerId; use torrust_tracker_primitives::peer::fixture::PeerBuilder; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; - use torrust_tracker_primitives::DurationSinceUnixEpoch; + use torrust_tracker_primitives::{DurationSinceUnixEpoch, PeerId}; use crate::swarm::coordinator::Coordinator; use crate::tests::sample_info_hash; @@ -526,7 +524,7 @@ mod tests { swarm.upsert_peer(peer.into()).await; - peer.event = aquatic_udp_protocol::AnnounceEvent::Completed; + peer.event = torrust_tracker_primitives::AnnounceEvent::Completed; swarm.upsert_peer(peer.into()).await; @@ -821,8 +819,8 @@ mod tests { } mod for_changes_in_existing_peers { - use aquatic_udp_protocol::NumberOfBytes; use torrust_tracker_primitives::peer::fixture::PeerBuilder; + use torrust_tracker_primitives::NumberOfBytes; use crate::swarm::coordinator::Coordinator; use crate::tests::sample_info_hash; @@ -875,7 +873,7 @@ mod tests { let downloads = swarm.metadata().downloads(); - peer.event = aquatic_udp_protocol::AnnounceEvent::Completed; + peer.event = torrust_tracker_primitives::AnnounceEvent::Completed; swarm.upsert_peer(peer.into()).await; @@ -892,7 +890,7 @@ mod tests { let downloads = swarm.metadata().downloads(); - peer.event = aquatic_udp_protocol::AnnounceEvent::Completed; + peer.event = torrust_tracker_primitives::AnnounceEvent::Completed; swarm.upsert_peer(peer.into()).await; @@ -907,8 +905,8 @@ mod tests { use std::sync::Arc; - use aquatic_udp_protocol::AnnounceEvent::Started; use torrust_tracker_primitives::peer::fixture::PeerBuilder; + use torrust_tracker_primitives::AnnounceEvent::Started; use torrust_tracker_primitives::DurationSinceUnixEpoch; use crate::event::sender::tests::{expect_event_sequence, MockEventSender}; diff --git a/packages/swarm-coordination-registry/src/swarm/registry.rs b/packages/swarm-coordination-registry/src/swarm/registry.rs index c8e98f307..34575c828 100644 --- a/packages/swarm-coordination-registry/src/swarm/registry.rs +++ b/packages/swarm-coordination-registry/src/swarm/registry.rs @@ -508,7 +508,7 @@ mod tests { use std::sync::Arc; - use aquatic_udp_protocol::PeerId; + use torrust_tracker_primitives::PeerId; use crate::swarm::registry::Registry; use crate::tests::{sample_info_hash, sample_peer}; @@ -613,9 +613,8 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; - use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes}; use torrust_tracker_primitives::peer::Peer; - use torrust_tracker_primitives::DurationSinceUnixEpoch; + use torrust_tracker_primitives::{AnnounceEvent, DurationSinceUnixEpoch, NumberOfBytes}; use crate::swarm::registry::tests::the_swarm_repository::numeric_peer_id; use crate::swarm::registry::Registry; @@ -674,10 +673,9 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; - use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes}; use torrust_tracker_configuration::TORRENT_PEERS_LIMIT; use torrust_tracker_primitives::peer::Peer; - use torrust_tracker_primitives::DurationSinceUnixEpoch; + use torrust_tracker_primitives::{AnnounceEvent, DurationSinceUnixEpoch, NumberOfBytes}; use crate::swarm::registry::tests::the_swarm_repository::numeric_peer_id; use crate::swarm::registry::Registry; diff --git a/packages/torrent-repository-benchmarking/Cargo.toml b/packages/torrent-repository-benchmarking/Cargo.toml index 653ad8102..45bce6316 100644 --- a/packages/torrent-repository-benchmarking/Cargo.toml +++ b/packages/torrent-repository-benchmarking/Cargo.toml @@ -16,8 +16,7 @@ rust-version.workspace = true version.workspace = true [dependencies] -aquatic_udp_protocol = "0" -bittorrent-primitives = "0.1.0" +bittorrent-primitives = "0.2.0" crossbeam-skiplist = "0" dashmap = "6" futures = "0" @@ -26,7 +25,6 @@ tokio = { version = "1", features = [ "macros", "net", "rt-multi-thread", "signa torrust-tracker-clock = { version = "3.0.0-develop", path = "../clock" } torrust-tracker-configuration = { version = "3.0.0-develop", path = "../configuration" } torrust-tracker-primitives = { version = "3.0.0-develop", path = "../primitives" } -zerocopy = "0.7" [dev-dependencies] async-std = { version = "1", features = [ "attributes", "tokio1" ] } diff --git a/packages/torrent-repository-benchmarking/benches/helpers/utils.rs b/packages/torrent-repository-benchmarking/benches/helpers/utils.rs index 16ba0bf7f..0d8d920e2 100644 --- a/packages/torrent-repository-benchmarking/benches/helpers/utils.rs +++ b/packages/torrent-repository-benchmarking/benches/helpers/utils.rs @@ -1,19 +1,17 @@ use std::collections::HashSet; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; -use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; use bittorrent_primitives::info_hash::InfoHash; use torrust_tracker_primitives::peer::Peer; -use torrust_tracker_primitives::DurationSinceUnixEpoch; -use zerocopy::I64; +use torrust_tracker_primitives::{AnnounceEvent, DurationSinceUnixEpoch, NumberOfBytes, PeerId}; pub const DEFAULT_PEER: Peer = Peer { peer_id: PeerId([0; 20]), peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080), updated: DurationSinceUnixEpoch::from_secs(0), - uploaded: NumberOfBytes(I64::ZERO), - downloaded: NumberOfBytes(I64::ZERO), - left: NumberOfBytes(I64::ZERO), + uploaded: NumberOfBytes::new(0), + downloaded: NumberOfBytes::new(0), + left: NumberOfBytes::new(0), event: AnnounceEvent::Started, }; diff --git a/packages/torrent-repository-benchmarking/src/entry/peer_list.rs b/packages/torrent-repository-benchmarking/src/entry/peer_list.rs index 976e89d03..74dd7df10 100644 --- a/packages/torrent-repository-benchmarking/src/entry/peer_list.rs +++ b/packages/torrent-repository-benchmarking/src/entry/peer_list.rs @@ -2,8 +2,7 @@ use std::net::SocketAddr; use std::sync::Arc; -use aquatic_udp_protocol::PeerId; -use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch}; +use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch, PeerId}; // code-review: the current implementation uses the peer Id as the ``BTreeMap`` // key. That would allow adding two identical peers except for the Id. @@ -90,9 +89,8 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; - use aquatic_udp_protocol::PeerId; use torrust_tracker_primitives::peer::fixture::PeerBuilder; - use torrust_tracker_primitives::DurationSinceUnixEpoch; + use torrust_tracker_primitives::{DurationSinceUnixEpoch, PeerId}; use crate::entry::peer_list::PeerList; diff --git a/packages/torrent-repository-benchmarking/src/entry/single.rs b/packages/torrent-repository-benchmarking/src/entry/single.rs index 0f922bd02..d3bafa76c 100644 --- a/packages/torrent-repository-benchmarking/src/entry/single.rs +++ b/packages/torrent-repository-benchmarking/src/entry/single.rs @@ -1,11 +1,10 @@ use std::net::SocketAddr; use std::sync::Arc; -use aquatic_udp_protocol::AnnounceEvent; use torrust_tracker_configuration::TrackerPolicy; use torrust_tracker_primitives::peer::{self}; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; -use torrust_tracker_primitives::DurationSinceUnixEpoch; +use torrust_tracker_primitives::{AnnounceEvent, DurationSinceUnixEpoch}; use super::Entry; use crate::EntrySingle; diff --git a/packages/torrent-repository-benchmarking/tests/entry/mod.rs b/packages/torrent-repository-benchmarking/tests/entry/mod.rs index 86ca891d4..4293cdb57 100644 --- a/packages/torrent-repository-benchmarking/tests/entry/mod.rs +++ b/packages/torrent-repository-benchmarking/tests/entry/mod.rs @@ -1,13 +1,12 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::time::Duration; -use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes}; use rstest::{fixture, rstest}; use torrust_tracker_clock::clock::stopped::Stopped as _; use torrust_tracker_clock::clock::{self, Time as _}; use torrust_tracker_configuration::{TrackerPolicy, TORRENT_PEERS_LIMIT}; -use torrust_tracker_primitives::peer; use torrust_tracker_primitives::peer::Peer; +use torrust_tracker_primitives::{peer, AnnounceEvent, NumberOfBytes}; use torrust_tracker_torrent_repository_benchmarking::{ EntryMutexParkingLot, EntryMutexStd, EntryMutexTokio, EntryRwLockParkingLot, EntrySingle, }; diff --git a/packages/torrent-repository-benchmarking/tests/repository/mod.rs b/packages/torrent-repository-benchmarking/tests/repository/mod.rs index fb0b8fcff..72accbed1 100644 --- a/packages/torrent-repository-benchmarking/tests/repository/mod.rs +++ b/packages/torrent-repository-benchmarking/tests/repository/mod.rs @@ -1,13 +1,12 @@ use std::collections::{BTreeMap, HashSet}; use std::hash::{DefaultHasher, Hash, Hasher}; -use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes}; use bittorrent_primitives::info_hash::InfoHash; use rstest::{fixture, rstest}; use torrust_tracker_configuration::TrackerPolicy; use torrust_tracker_primitives::pagination::Pagination; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; -use torrust_tracker_primitives::NumberOfDownloadsBTreeMap; +use torrust_tracker_primitives::{AnnounceEvent, NumberOfBytes, NumberOfDownloadsBTreeMap}; use torrust_tracker_torrent_repository_benchmarking::entry::Entry as _; use torrust_tracker_torrent_repository_benchmarking::repository::dash_map_mutex_std::XacrimonDashMap; use torrust_tracker_torrent_repository_benchmarking::repository::rw_lock_std::RwLockStd; diff --git a/packages/tracker-client/Cargo.toml b/packages/tracker-client/Cargo.toml index 0cd419471..225d82bf0 100644 --- a/packages/tracker-client/Cargo.toml +++ b/packages/tracker-client/Cargo.toml @@ -15,8 +15,8 @@ rust-version.workspace = true version.workspace = true [dependencies] -aquatic_udp_protocol = "0" -bittorrent-primitives = "0.1.0" +bittorrent-udp-tracker-protocol = { version = "3.0.0-develop", path = "../udp-protocol" } +bittorrent-primitives = "0.2.0" derive_more = { version = "2", features = [ "as_ref", "constructor", "from" ] } hyper = "1" percent-encoding = "2" @@ -31,7 +31,7 @@ torrust-tracker-configuration = { version = "3.0.0-develop", path = "../configur torrust-tracker-located-error = { version = "3.0.0-develop", path = "../located-error" } torrust-tracker-primitives = { version = "3.0.0-develop", path = "../primitives" } tracing = "0" -zerocopy = "0.7" +zerocopy = "0.8" [package.metadata.cargo-machete] ignored = [ "serde_bytes" ] diff --git a/packages/tracker-client/src/http/client/requests/announce.rs b/packages/tracker-client/src/http/client/requests/announce.rs index 87bdbad52..a06438a5c 100644 --- a/packages/tracker-client/src/http/client/requests/announce.rs +++ b/packages/tracker-client/src/http/client/requests/announce.rs @@ -2,8 +2,8 @@ use std::fmt; use std::net::{IpAddr, Ipv4Addr}; use std::str::FromStr; -use aquatic_udp_protocol::PeerId; use bittorrent_primitives::info_hash::InfoHash; +use bittorrent_udp_tracker_protocol::PeerId; use serde_repr::Serialize_repr; use crate::http::{percent_encode_byte_array, ByteArray20}; diff --git a/packages/tracker-client/src/http/client/responses/announce.rs b/packages/tracker-client/src/http/client/responses/announce.rs index 7f2d3611c..f59969ff2 100644 --- a/packages/tracker-client/src/http/client/responses/announce.rs +++ b/packages/tracker-client/src/http/client/responses/announce.rs @@ -2,7 +2,6 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use serde::{Deserialize, Serialize}; use torrust_tracker_primitives::peer; -use zerocopy::AsBytes as _; #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct Announce { diff --git a/packages/tracker-client/src/udp/client.rs b/packages/tracker-client/src/udp/client.rs index 94c882d29..96dffee48 100644 --- a/packages/tracker-client/src/udp/client.rs +++ b/packages/tracker-client/src/udp/client.rs @@ -4,12 +4,12 @@ use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::Arc; use std::time::Duration; -use aquatic_udp_protocol::{ConnectRequest, Request, Response, TransactionId}; +use bittorrent_udp_tracker_protocol::{ConnectRequest, Request, Response, TransactionId}; use tokio::net::UdpSocket; use tokio::time; use torrust_tracker_configuration::DEFAULT_TIMEOUT; use torrust_tracker_primitives::service_binding::ServiceBinding; -use zerocopy::network_endian::I32; +use zerocopy::byteorder::network_endian::I32; use super::Error; use crate::udp::MAX_PACKET_SIZE; diff --git a/packages/tracker-client/src/udp/mod.rs b/packages/tracker-client/src/udp/mod.rs index b9d5f34f6..57924b964 100644 --- a/packages/tracker-client/src/udp/mod.rs +++ b/packages/tracker-client/src/udp/mod.rs @@ -1,7 +1,7 @@ use std::net::SocketAddr; use std::sync::Arc; -use aquatic_udp_protocol::Request; +use bittorrent_udp_tracker_protocol::Request; use thiserror::Error; use torrust_tracker_located_error::DynError; diff --git a/packages/tracker-core/Cargo.toml b/packages/tracker-core/Cargo.toml index 68b4f6bf4..cf2b3fdce 100644 --- a/packages/tracker-core/Cargo.toml +++ b/packages/tracker-core/Cargo.toml @@ -20,8 +20,7 @@ db-compatibility-tests = [ ] [dependencies] anyhow = "1" async-trait = "0" -aquatic_udp_protocol = "0" -bittorrent-primitives = "0.1.0" +bittorrent-primitives = "0.2.0" chrono = { version = "0", default-features = false, features = [ "clock" ] } clap = { version = "4", features = [ "derive" ] } derive_more = { version = "2", features = [ "as_ref", "constructor", "from" ] } @@ -49,6 +48,3 @@ mockall = "0" torrust-rest-tracker-api-client = { version = "3.0.0-develop", path = "../rest-tracker-api-client" } torrust-tracker-test-helpers = { version = "3.0.0-develop", path = "../test-helpers" } url = "2.5.4" - -[package.metadata.cargo-machete] -ignored = [ "async-trait" ] diff --git a/packages/tracker-core/src/announce_handler.rs b/packages/tracker-core/src/announce_handler.rs index 150550f49..df1f107a2 100644 --- a/packages/tracker-core/src/announce_handler.rs +++ b/packages/tracker-core/src/announce_handler.rs @@ -18,7 +18,7 @@ //! use std::net::Ipv4Addr; //! use std::str::FromStr; //! -//! use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; +//! use torrust_tracker_primitives::{AnnounceEvent, NumberOfBytes, PeerId}; //! use torrust_tracker_primitives::DurationSinceUnixEpoch; //! use torrust_tracker_primitives::peer; //! use bittorrent_primitives::info_hash::InfoHash; @@ -95,8 +95,7 @@ use std::sync::Arc; use bittorrent_primitives::info_hash::InfoHash; use torrust_tracker_configuration::{Core, TORRENT_PEERS_LIMIT}; -use torrust_tracker_primitives::core::AnnounceData; -use torrust_tracker_primitives::{peer, NumberOfDownloads}; +use torrust_tracker_primitives::{peer, AnnounceData, NumberOfDownloads}; use super::torrent::repository::in_memory::InMemoryTorrentRepository; use crate::databases; @@ -283,9 +282,8 @@ mod tests { use std::str::FromStr; use std::sync::Arc; - use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; use torrust_tracker_primitives::peer::Peer; - use torrust_tracker_primitives::DurationSinceUnixEpoch; + use torrust_tracker_primitives::{AnnounceEvent, DurationSinceUnixEpoch, NumberOfBytes, PeerId}; use torrust_tracker_test_helpers::configuration; use crate::announce_handler::AnnounceHandler; diff --git a/packages/tracker-core/src/lib.rs b/packages/tracker-core/src/lib.rs index b711cda13..5d963b066 100644 --- a/packages/tracker-core/src/lib.rs +++ b/packages/tracker-core/src/lib.rs @@ -187,8 +187,8 @@ mod tests { use std::net::{IpAddr, Ipv4Addr}; use bittorrent_primitives::info_hash::InfoHash; - use torrust_tracker_primitives::core::ScrapeData; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; + use torrust_tracker_primitives::ScrapeData; use crate::announce_handler::PeersWanted; use crate::test_helpers::tests::{complete_peer, incomplete_peer}; @@ -248,8 +248,8 @@ mod tests { mod handling_a_scrape_request { use bittorrent_primitives::info_hash::InfoHash; - use torrust_tracker_primitives::core::ScrapeData; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; + use torrust_tracker_primitives::ScrapeData; use crate::tests::the_tracker::initialize_handlers_for_listed_tracker; diff --git a/packages/tracker-core/src/peer_tests.rs b/packages/tracker-core/src/peer_tests.rs index b60ca3f6d..07a7ecfd8 100644 --- a/packages/tracker-core/src/peer_tests.rs +++ b/packages/tracker-core/src/peer_tests.rs @@ -2,10 +2,9 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; -use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; use torrust_tracker_clock::clock::stopped::Stopped as _; use torrust_tracker_clock::clock::{self, Time}; -use torrust_tracker_primitives::peer; +use torrust_tracker_primitives::{peer, AnnounceEvent, NumberOfBytes, PeerId}; use crate::CurrentClock; diff --git a/packages/tracker-core/src/scrape_handler.rs b/packages/tracker-core/src/scrape_handler.rs index 9c94a4e50..83ffa912f 100644 --- a/packages/tracker-core/src/scrape_handler.rs +++ b/packages/tracker-core/src/scrape_handler.rs @@ -62,8 +62,8 @@ use std::sync::Arc; use bittorrent_primitives::info_hash::InfoHash; -use torrust_tracker_primitives::core::ScrapeData; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; +use torrust_tracker_primitives::ScrapeData; use super::torrent::repository::in_memory::InMemoryTorrentRepository; use super::whitelist; @@ -131,7 +131,7 @@ mod tests { use std::sync::Arc; use bittorrent_primitives::info_hash::InfoHash; - use torrust_tracker_primitives::core::ScrapeData; + use torrust_tracker_primitives::ScrapeData; use torrust_tracker_test_helpers::configuration; use super::ScrapeHandler; diff --git a/packages/tracker-core/src/test_helpers.rs b/packages/tracker-core/src/test_helpers.rs index 08677363e..cf4095701 100644 --- a/packages/tracker-core/src/test_helpers.rs +++ b/packages/tracker-core/src/test_helpers.rs @@ -5,14 +5,13 @@ pub(crate) mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; - use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; use bittorrent_primitives::info_hash::InfoHash; use rand::RngExt; use torrust_tracker_configuration::Configuration; #[cfg(test)] use torrust_tracker_configuration::Core; use torrust_tracker_primitives::peer::Peer; - use torrust_tracker_primitives::DurationSinceUnixEpoch; + use torrust_tracker_primitives::{AnnounceEvent, DurationSinceUnixEpoch, NumberOfBytes, PeerId}; #[cfg(test)] use torrust_tracker_test_helpers::configuration::ephemeral_sqlite_database; diff --git a/packages/tracker-core/src/torrent/mod.rs b/packages/tracker-core/src/torrent/mod.rs index 01d33b893..fec5d1640 100644 --- a/packages/tracker-core/src/torrent/mod.rs +++ b/packages/tracker-core/src/torrent/mod.rs @@ -104,10 +104,10 @@ //! //! ```rust,no_run //! use std::net::SocketAddr; -//! use aquatic_udp_protocol::PeerId; +//! use torrust_tracker_primitives::PeerId; //! use torrust_tracker_primitives::DurationSinceUnixEpoch; -//! use aquatic_udp_protocol::NumberOfBytes; -//! use aquatic_udp_protocol::AnnounceEvent; +//! use torrust_tracker_primitives::NumberOfBytes; +//! use torrust_tracker_primitives::AnnounceEvent; //! //! pub struct Peer { //! pub peer_id: PeerId, // The peer ID diff --git a/packages/tracker-core/src/torrent/services.rs b/packages/tracker-core/src/torrent/services.rs index 874ad1349..e3a92866f 100644 --- a/packages/tracker-core/src/torrent/services.rs +++ b/packages/tracker-core/src/torrent/services.rs @@ -206,8 +206,7 @@ pub async fn get_torrents( mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; - use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch}; + use torrust_tracker_primitives::{peer, AnnounceEvent, DurationSinceUnixEpoch, NumberOfBytes, PeerId}; fn sample_peer() -> peer::Peer { peer::Peer { diff --git a/packages/tracker-core/tests/common/fixtures.rs b/packages/tracker-core/tests/common/fixtures.rs index ea9c93a65..1a94b68ca 100644 --- a/packages/tracker-core/tests/common/fixtures.rs +++ b/packages/tracker-core/tests/common/fixtures.rs @@ -1,11 +1,10 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::str::FromStr; -use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; use bittorrent_primitives::info_hash::InfoHash; use torrust_tracker_configuration::Core; use torrust_tracker_primitives::peer::Peer; -use torrust_tracker_primitives::DurationSinceUnixEpoch; +use torrust_tracker_primitives::{AnnounceEvent, DurationSinceUnixEpoch, NumberOfBytes, PeerId}; use torrust_tracker_test_helpers::configuration::ephemeral_sqlite_database; /// # Panics diff --git a/packages/tracker-core/tests/common/test_env.rs b/packages/tracker-core/tests/common/test_env.rs index 69136dc50..50c13bfc0 100644 --- a/packages/tracker-core/tests/common/test_env.rs +++ b/packages/tracker-core/tests/common/test_env.rs @@ -1,7 +1,6 @@ use std::net::IpAddr; use std::sync::Arc; -use aquatic_udp_protocol::AnnounceEvent; use bittorrent_primitives::info_hash::InfoHash; use bittorrent_tracker_core::announce_handler::PeersWanted; use bittorrent_tracker_core::container::TrackerCoreContainer; @@ -11,10 +10,9 @@ use tokio_util::sync::CancellationToken; use torrust_tracker_configuration::Core; use torrust_tracker_metrics::label::LabelSet; use torrust_tracker_metrics::metric::MetricName; -use torrust_tracker_primitives::core::{AnnounceData, ScrapeData}; use torrust_tracker_primitives::peer::Peer; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; -use torrust_tracker_primitives::DurationSinceUnixEpoch; +use torrust_tracker_primitives::{AnnounceData, AnnounceEvent, DurationSinceUnixEpoch, ScrapeData}; use torrust_tracker_swarm_coordination_registry::container::SwarmCoordinationRegistryContainer; pub struct TestEnv { diff --git a/packages/tracker-core/tests/integration.rs b/packages/tracker-core/tests/integration.rs index d5f8a6e87..9df1dee89 100644 --- a/packages/tracker-core/tests/integration.rs +++ b/packages/tracker-core/tests/integration.rs @@ -3,8 +3,8 @@ mod common; use common::fixtures::{ephemeral_configuration, remote_client_ip, sample_info_hash, sample_peer}; use common::test_env::TestEnv; use torrust_tracker_configuration::AnnouncePolicy; -use torrust_tracker_primitives::core::AnnounceData; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; +use torrust_tracker_primitives::AnnounceData; #[tokio::test] async fn it_should_handle_the_announce_request() { diff --git a/packages/udp-protocol/Cargo.toml b/packages/udp-protocol/Cargo.toml index 3bcde9a95..c3bd094c3 100644 --- a/packages/udp-protocol/Cargo.toml +++ b/packages/udp-protocol/Cargo.toml @@ -14,7 +14,16 @@ repository.workspace = true rust-version.workspace = true version.workspace = true +[features] +default = [ ] + [dependencies] -aquatic_udp_protocol = "0" -torrust-tracker-clock = { version = "3.0.0-develop", path = "../clock" } -torrust-tracker-primitives = { version = "3.0.0-develop", path = "../primitives" } +bittorrent-peer-id = { version = "3.0.0-develop", path = "../peer-id", features = [ "zerocopy" ] } +byteorder = "1" +either = "1" +zerocopy = { version = "0.8", features = [ "derive" ] } + +[dev-dependencies] +pretty_assertions = "1" +quickcheck = "1" +quickcheck_macros = "1" diff --git a/packages/udp-protocol/LICENSE-APACHE b/packages/udp-protocol/LICENSE-APACHE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/packages/udp-protocol/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/udp-protocol/README.md b/packages/udp-protocol/README.md index 4f63fb675..c2cc44f1b 100644 --- a/packages/udp-protocol/README.md +++ b/packages/udp-protocol/README.md @@ -2,6 +2,33 @@ A library with the primitive types and functions used by BitTorrent UDP trackers. +## Origin and In-House Maintenance + +This crate was originally derived from Aquatic's `udp_protocol` crate: + +- https://github.com/greatest-ape/aquatic/tree/master/crates/udp_protocol + +Torrust keeps an in-house copy because upstream maintenance appears inactive and the tracker +still needs dependency updates, security maintenance, and ongoing protocol-related evolution. + +Relevant upstream context: + +- https://github.com/greatest-ape/aquatic/issues/224 +- https://github.com/greatest-ape/aquatic/pull/235 + +## Licensing and Notices + +The original source is Apache-2.0 licensed. The in-house package keeps the required origin and +change notices in code headers, consistent with the license terms. + +An explicit copy of Apache-2.0 is included at [LICENSE-APACHE](./LICENSE-APACHE). + +## Acknowledgment + +Special thanks to [greatest-ape](https://github.com/greatest-ape) +(Joakim Frostegård) for his contributions to the BitTorrent ecosystem and the original +implementation this crate builds upon. + ## Documentation [Crate documentation](https://docs.rs/bittorrent-udp-protocol). diff --git a/packages/udp-protocol/src/announce.rs b/packages/udp-protocol/src/announce.rs new file mode 100644 index 000000000..b63ca2e94 --- /dev/null +++ b/packages/udp-protocol/src/announce.rs @@ -0,0 +1,125 @@ +// Copied from aquatic_udp_protocol 0.9.0 by Joakim Frostegard (greatest-ape). +// Source: https://crates.io/crates/aquatic_udp_protocol/0.9.0 +// Repository: https://github.com/greatest-ape/aquatic +// License: Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) +// +// This in-house crate started from the aquatic 0.9.0 sources that were previously vendored +// under packages/aquatic-udp-protocol. +use std::io::{self, Write}; + +use byteorder::{NetworkEndian, WriteBytesExt}; +use zerocopy::byteorder::network_endian::I32; +use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes}; + +use super::common::*; + +#[derive(PartialEq, Eq, Clone, Copy, Debug, IntoBytes, FromBytes, Immutable)] +#[repr(C, packed)] +pub struct AnnounceRequest { + pub connection_id: ConnectionId, + pub action_placeholder: AnnounceActionPlaceholder, + pub transaction_id: TransactionId, + pub info_hash: InfoHash, + pub peer_id: PeerId, + pub bytes_downloaded: NumberOfBytes, + pub bytes_left: NumberOfBytes, + pub bytes_uploaded: NumberOfBytes, + pub event: AnnounceEventBytes, + pub ip_address: Ipv4AddrBytes, + pub key: PeerKey, + pub peers_wanted: NumberOfPeers, + pub port: Port, +} + +impl AnnounceRequest { + pub fn write_bytes(&self, bytes: &mut impl Write) -> Result<(), io::Error> { + bytes.write_all(self.as_bytes()) + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug, IntoBytes, FromBytes, Immutable)] +#[repr(transparent)] +pub struct AnnounceActionPlaceholder(pub I32); + +impl Default for AnnounceActionPlaceholder { + fn default() -> Self { + Self(I32::new(1)) + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug, IntoBytes, FromBytes, Immutable)] +#[repr(transparent)] +pub struct AnnounceEventBytes(pub I32); + +impl From for AnnounceEventBytes { + fn from(value: AnnounceEvent) -> Self { + Self(I32::new(match value { + AnnounceEvent::None => 0, + AnnounceEvent::Completed => 1, + AnnounceEvent::Started => 2, + AnnounceEvent::Stopped => 3, + })) + } +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] +pub enum AnnounceEvent { + Started, + Stopped, + Completed, + None, +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, IntoBytes, FromBytes, Immutable)] +#[repr(transparent)] +pub struct AnnounceInterval(pub I32); + +impl AnnounceInterval { + pub fn new(v: i32) -> Self { + Self(I32::new(v)) + } +} + +impl From for AnnounceEvent { + fn from(value: AnnounceEventBytes) -> Self { + match value.0.get() { + 1 => Self::Completed, + 2 => Self::Started, + 3 => Self::Stopped, + _ => Self::None, + } + } +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct AnnounceResponse { + pub fixed: AnnounceResponseFixedData, + pub peers: Vec>, +} + +impl AnnounceResponse { + pub fn empty() -> Self { + Self { + fixed: FromZeros::new_zeroed(), + peers: Default::default(), + } + } + + #[inline] + pub fn write_bytes(&self, bytes: &mut impl Write) -> Result<(), io::Error> { + bytes.write_i32::(1)?; + bytes.write_all(self.fixed.as_bytes())?; + bytes.write_all((*self.peers.as_slice()).as_bytes())?; + + Ok(()) + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug, IntoBytes, FromBytes, Immutable)] +#[repr(C, packed)] +pub struct AnnounceResponseFixedData { + pub transaction_id: TransactionId, + pub announce_interval: AnnounceInterval, + pub leechers: NumberOfPeers, + pub seeders: NumberOfPeers, +} diff --git a/packages/udp-protocol/src/common.rs b/packages/udp-protocol/src/common.rs new file mode 100644 index 000000000..08ccc2493 --- /dev/null +++ b/packages/udp-protocol/src/common.rs @@ -0,0 +1,197 @@ +// Copied from aquatic_udp_protocol 0.9.0 by Joakim Frostegard (greatest-ape). +// Source: https://crates.io/crates/aquatic_udp_protocol/0.9.0 +// Repository: https://github.com/greatest-ape/aquatic +// License: Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) +// +// This in-house crate started from the aquatic 0.9.0 sources that were previously vendored +// under packages/aquatic-udp-protocol. +use std::fmt::Debug; +use std::net::{Ipv4Addr, Ipv6Addr}; +use std::num::NonZeroU16; + +use zerocopy::byteorder::network_endian::{I32, I64, U16, U32}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +pub use crate::{PeerClient, PeerId}; + +pub trait Ip: Clone + Copy + Debug + PartialEq + Eq + std::hash::Hash + IntoBytes + Immutable {} + +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, IntoBytes, FromBytes, Immutable)] +#[repr(transparent)] +// Intentionally kept in `common`: this protocol-level wire type mirrors +// `bittorrent-primitives::InfoHash` and may be unified across packages later. +pub struct InfoHash(pub [u8; 20]); + +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, IntoBytes, FromBytes, Immutable)] +#[repr(transparent)] +pub struct ConnectionId(pub I64); + +impl ConnectionId { + pub fn new(v: i64) -> Self { + Self(I64::new(v)) + } +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, IntoBytes, FromBytes, Immutable)] +#[repr(transparent)] +pub struct TransactionId(pub I32); + +impl TransactionId { + pub fn new(v: i32) -> Self { + Self(I32::new(v)) + } +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, IntoBytes, FromBytes, Immutable)] +#[repr(transparent)] +// Intentionally kept in `common`: this mirrors +// `packages/primitives/src/number_of_bytes.rs` and may be shared across packages later. +pub struct NumberOfBytes(pub I64); + +impl NumberOfBytes { + pub fn new(v: i64) -> Self { + Self(I64::new(v)) + } +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, IntoBytes, FromBytes, Immutable)] +#[repr(transparent)] +pub struct NumberOfPeers(pub I32); + +impl NumberOfPeers { + pub fn new(v: i32) -> Self { + Self(I32::new(v)) + } +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, IntoBytes, FromBytes, Immutable)] +#[repr(transparent)] +pub struct NumberOfDownloads(pub I32); + +impl NumberOfDownloads { + pub fn new(v: i32) -> Self { + Self(I32::new(v)) + } +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, IntoBytes, FromBytes, Immutable)] +#[repr(transparent)] +pub struct Port(pub U16); + +impl Port { + pub fn new(v: NonZeroU16) -> Self { + Self(U16::new(v.into())) + } +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, IntoBytes, FromBytes, Immutable)] +#[repr(transparent)] +pub struct PeerKey(pub I32); + +impl PeerKey { + pub fn new(v: i32) -> Self { + Self(I32::new(v)) + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash, IntoBytes, FromBytes, Immutable)] +#[repr(C, packed)] +pub struct ResponsePeer { + pub ip_address: I, + pub port: Port, +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, IntoBytes, FromBytes, Immutable)] +#[repr(transparent)] +pub struct Ipv4AddrBytes(pub [u8; 4]); + +impl Ip for Ipv4AddrBytes {} + +impl From for Ipv4Addr { + fn from(val: Ipv4AddrBytes) -> Self { + Ipv4Addr::from(val.0) + } +} + +impl From for Ipv4AddrBytes { + fn from(val: Ipv4Addr) -> Self { + Ipv4AddrBytes(val.octets()) + } +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, IntoBytes, FromBytes, Immutable)] +#[repr(transparent)] +pub struct Ipv6AddrBytes(pub [u8; 16]); + +impl Ip for Ipv6AddrBytes {} + +impl From for Ipv6Addr { + fn from(val: Ipv6AddrBytes) -> Self { + Ipv6Addr::from(val.0) + } +} + +impl From for Ipv6AddrBytes { + fn from(val: Ipv6Addr) -> Self { + Ipv6AddrBytes(val.octets()) + } +} + +pub fn read_i32_ne(bytes: &mut impl ::std::io::Read) -> ::std::io::Result { + let mut tmp = [0u8; 4]; + + bytes.read_exact(&mut tmp)?; + + Ok(I32::from_bytes(tmp)) +} + +pub fn read_i64_ne(bytes: &mut impl ::std::io::Read) -> ::std::io::Result { + let mut tmp = [0u8; 8]; + + bytes.read_exact(&mut tmp)?; + + Ok(I64::from_bytes(tmp)) +} + +pub fn read_u16_ne(bytes: &mut impl ::std::io::Read) -> ::std::io::Result { + let mut tmp = [0u8; 2]; + + bytes.read_exact(&mut tmp)?; + + Ok(U16::from_bytes(tmp)) +} + +pub fn read_u32_ne(bytes: &mut impl ::std::io::Read) -> ::std::io::Result { + let mut tmp = [0u8; 4]; + + bytes.read_exact(&mut tmp)?; + + Ok(U32::from_bytes(tmp)) +} + +pub fn invalid_data() -> ::std::io::Error { + ::std::io::Error::new(::std::io::ErrorKind::InvalidData, "invalid data") +} + +#[cfg(test)] +impl quickcheck::Arbitrary for InfoHash { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let mut bytes = [0u8; 20]; + + for byte in bytes.iter_mut() { + *byte = u8::arbitrary(g); + } + + Self(bytes) + } +} + +#[cfg(test)] +impl quickcheck::Arbitrary for ResponsePeer { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + Self { + ip_address: quickcheck::Arbitrary::arbitrary(g), + port: Port(u16::arbitrary(g).into()), + } + } +} diff --git a/packages/udp-protocol/src/connect.rs b/packages/udp-protocol/src/connect.rs new file mode 100644 index 000000000..57e1e35bd --- /dev/null +++ b/packages/udp-protocol/src/connect.rs @@ -0,0 +1,47 @@ +// Copied from aquatic_udp_protocol 0.9.0 by Joakim Frostegard (greatest-ape). +// Source: https://crates.io/crates/aquatic_udp_protocol/0.9.0 +// Repository: https://github.com/greatest-ape/aquatic +// License: Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) +// +// This in-house crate started from the aquatic 0.9.0 sources that were previously vendored +// under packages/aquatic-udp-protocol. +use std::io::{self, Write}; + +use byteorder::{NetworkEndian, WriteBytesExt}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +use super::common::{ConnectionId, TransactionId}; + +pub(crate) const PROTOCOL_IDENTIFIER: i64 = 4_497_486_125_440; + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ConnectRequest { + pub transaction_id: TransactionId, +} + +impl ConnectRequest { + pub fn write_bytes(&self, bytes: &mut impl Write) -> Result<(), io::Error> { + bytes.write_i64::(PROTOCOL_IDENTIFIER)?; + bytes.write_i32::(0)?; + bytes.write_all(self.transaction_id.as_bytes())?; + + Ok(()) + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug, IntoBytes, FromBytes, Immutable)] +#[repr(C, packed)] +pub struct ConnectResponse { + pub transaction_id: TransactionId, + pub connection_id: ConnectionId, +} + +impl ConnectResponse { + #[inline] + pub fn write_bytes(&self, bytes: &mut impl Write) -> Result<(), io::Error> { + bytes.write_i32::(0)?; + bytes.write_all(self.as_bytes())?; + + Ok(()) + } +} diff --git a/packages/udp-protocol/src/lib.rs b/packages/udp-protocol/src/lib.rs index f0983a7ba..b678f59c5 100644 --- a/packages/udp-protocol/src/lib.rs +++ b/packages/udp-protocol/src/lib.rs @@ -1,15 +1,35 @@ -//! Primitive types and functions for `BitTorrent` UDP trackers. -pub mod peer_builder; +// Copied from aquatic_udp_protocol 0.9.0 by Joakim Frostegard (greatest-ape). +// Source: https://crates.io/crates/aquatic_udp_protocol/0.9.0 +// Repository: https://github.com/greatest-ape/aquatic +// License: Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) +// +// This in-house crate started from the aquatic 0.9.0 sources that were previously vendored +// under packages/aquatic-udp-protocol and packages/aquatic-peer-id. +#![allow(clippy::cast_possible_truncation)] +#![allow(clippy::default_trait_access)] +#![allow(clippy::doc_markdown)] +#![allow(clippy::explicit_iter_loop)] +#![allow(clippy::legacy_numeric_constants)] +#![allow(clippy::match_same_arms)] +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::missing_panics_doc)] +#![allow(clippy::must_use_candidate)] +#![allow(clippy::needless_pass_by_value)] +#![allow(clippy::semicolon_if_nothing_returned)] +#![allow(clippy::wildcard_imports)] -use torrust_tracker_clock::clock; +pub mod announce; +pub mod common; +pub mod connect; +pub mod request; +pub mod response; +pub mod scrape; -/// This code needs to be copied into each crate. -/// Working version, for production. -#[cfg(not(test))] -#[allow(dead_code)] -pub(crate) type CurrentClock = clock::Working; +pub use bittorrent_peer_id::{PeerClient, PeerId}; -/// Stopped version, for testing. -#[cfg(test)] -#[allow(dead_code)] -pub(crate) type CurrentClock = clock::Stopped; +pub use self::announce::*; +pub use self::common::*; +pub use self::connect::*; +pub use self::request::*; +pub use self::response::*; +pub use self::scrape::*; diff --git a/packages/udp-protocol/src/peer_builder.rs b/packages/udp-protocol/src/peer_builder.rs deleted file mode 100644 index a42ddfaa5..000000000 --- a/packages/udp-protocol/src/peer_builder.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! Logic to extract the peer info from the announce request. -use std::net::{IpAddr, SocketAddr}; - -use torrust_tracker_clock::clock::Time; -use torrust_tracker_primitives::peer; - -use crate::CurrentClock; - -/// Extracts the [`peer::Peer`] info from the -/// announce request. -/// -/// # Arguments -/// -/// * `peer_ip` - The real IP address of the peer, not the one in the announce request. -#[must_use] -pub fn from_request(announce_request: &aquatic_udp_protocol::AnnounceRequest, peer_ip: &IpAddr) -> peer::Peer { - peer::Peer { - peer_id: announce_request.peer_id, - peer_addr: SocketAddr::new(*peer_ip, announce_request.port.0.into()), - updated: CurrentClock::now(), - uploaded: announce_request.bytes_uploaded, - downloaded: announce_request.bytes_downloaded, - left: announce_request.bytes_left, - event: announce_request.event.into(), - } -} diff --git a/packages/udp-protocol/src/request.rs b/packages/udp-protocol/src/request.rs new file mode 100644 index 000000000..5db1b8085 --- /dev/null +++ b/packages/udp-protocol/src/request.rs @@ -0,0 +1,311 @@ +// Copied from aquatic_udp_protocol 0.9.0 by Joakim Frostegard (greatest-ape). +// Source: https://crates.io/crates/aquatic_udp_protocol/0.9.0 +// Repository: https://github.com/greatest-ape/aquatic +// License: Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) +// +// This in-house crate started from the aquatic 0.9.0 sources that were previously vendored +// under packages/aquatic-udp-protocol. +use std::io::{self, Cursor, Write}; +use std::mem::size_of; + +use either::Either; +use zerocopy::byteorder::network_endian::I32; +use zerocopy::FromBytes; + +use super::announce::AnnounceRequest; +use super::common::*; +use super::connect::{ConnectRequest, PROTOCOL_IDENTIFIER}; +pub use super::scrape::ScrapeRequest; + +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum Request { + Connect(ConnectRequest), + Announce(AnnounceRequest), + Scrape(ScrapeRequest), +} + +impl Request { + pub fn write_bytes(&self, bytes: &mut impl Write) -> Result<(), io::Error> { + match self { + Request::Connect(r) => r.write_bytes(bytes), + Request::Announce(r) => r.write_bytes(bytes), + Request::Scrape(r) => r.write_bytes(bytes), + } + } + + pub fn parse_bytes(bytes: &[u8], max_scrape_torrents: u8) -> Result { + let action = bytes + .get(8..12) + .map(|bytes| I32::from_bytes(bytes.try_into().unwrap())) + .ok_or_else(|| RequestParseError::unsendable_text("Couldn't parse action"))?; + + match action.get() { + 0 => { + let mut bytes = Cursor::new(bytes); + + let protocol_identifier = read_i64_ne(&mut bytes).map_err(RequestParseError::unsendable_io)?; + let _action = read_i32_ne(&mut bytes).map_err(RequestParseError::unsendable_io)?; + let transaction_id = read_i32_ne(&mut bytes) + .map(TransactionId) + .map_err(RequestParseError::unsendable_io)?; + + if protocol_identifier.get() == PROTOCOL_IDENTIFIER { + Ok((ConnectRequest { transaction_id }).into()) + } else { + Err(RequestParseError::unsendable_text("Protocol identifier missing")) + } + } + 1 => { + let request = AnnounceRequest::read_from_prefix(bytes) + .map_err(|_| RequestParseError::unsendable_text("invalid data"))? + .0; + + if request.port.0.get() == 0 { + Err(RequestParseError::sendable_text( + "Port can't be 0", + request.connection_id, + request.transaction_id, + )) + } else if !matches!(request.event.0.get(), 0..=3) { + Err(RequestParseError::sendable_text( + "Invalid announce event", + request.connection_id, + request.transaction_id, + )) + } else { + Ok(Request::Announce(request)) + } + } + 2 => { + let mut bytes = Cursor::new(bytes); + + let connection_id = read_i64_ne(&mut bytes) + .map(ConnectionId) + .map_err(RequestParseError::unsendable_io)?; + let _action = read_i32_ne(&mut bytes).map_err(RequestParseError::unsendable_io)?; + let transaction_id = read_i32_ne(&mut bytes) + .map(TransactionId) + .map_err(RequestParseError::unsendable_io)?; + + let remaining_bytes = { + let position = bytes.position() as usize; + let inner = bytes.into_inner(); + &inner[position..] + }; + + if remaining_bytes.is_empty() { + return Err(RequestParseError::sendable_text( + "Full scrapes are not allowed", + connection_id, + transaction_id, + )); + } + + let chunks = remaining_bytes.chunks_exact(size_of::()); + + if !chunks.remainder().is_empty() { + return Err(RequestParseError::sendable_text( + "Invalid info hash list", + connection_id, + transaction_id, + )); + } + + let info_hashes = chunks + .map(|chunk| { + let mut bytes = [0u8; 20]; + bytes.copy_from_slice(chunk); + InfoHash(bytes) + }) + .collect::>(); + + let info_hashes = Vec::from(&info_hashes[..(max_scrape_torrents as usize).min(info_hashes.len())]); + + Ok((ScrapeRequest { + connection_id, + transaction_id, + info_hashes, + }) + .into()) + } + _ => Err(RequestParseError::unsendable_text("Invalid action")), + } + } +} + +impl From for Request { + fn from(r: ConnectRequest) -> Self { + Self::Connect(r) + } +} + +impl From for Request { + fn from(r: AnnounceRequest) -> Self { + Self::Announce(r) + } +} + +impl From for Request { + fn from(r: ScrapeRequest) -> Self { + Self::Scrape(r) + } +} + +#[derive(Debug)] +pub enum RequestParseError { + Sendable { + connection_id: ConnectionId, + transaction_id: TransactionId, + err: &'static str, + }, + Unsendable { + err: Either, + }, +} + +impl RequestParseError { + pub fn sendable_text(text: &'static str, connection_id: ConnectionId, transaction_id: TransactionId) -> Self { + Self::Sendable { + connection_id, + transaction_id, + err: text, + } + } + pub fn unsendable_io(err: io::Error) -> Self { + Self::Unsendable { err: Either::Left(err) } + } + pub fn unsendable_text(text: &'static str) -> Self { + Self::Unsendable { + err: Either::Right(text), + } + } +} + +#[cfg(test)] +mod tests { + use quickcheck::TestResult; + use quickcheck_macros::quickcheck; + use zerocopy::network_endian::{I32, I64}; + + use super::*; + use crate::announce::{AnnounceActionPlaceholder, AnnounceEvent}; + + impl quickcheck::Arbitrary for AnnounceEvent { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + match (bool::arbitrary(g), bool::arbitrary(g)) { + (false, false) => Self::Started, + (true, false) => Self::Started, + (false, true) => Self::Completed, + (true, true) => Self::None, + } + } + } + + impl quickcheck::Arbitrary for ConnectRequest { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + Self { + transaction_id: TransactionId(I32::new(i32::arbitrary(g))), + } + } + } + + impl quickcheck::Arbitrary for AnnounceRequest { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let mut peer_id_bytes = [0u8; 20]; + + for byte in &mut peer_id_bytes { + *byte = u8::arbitrary(g); + } + + Self { + connection_id: ConnectionId(I64::new(i64::arbitrary(g))), + action_placeholder: AnnounceActionPlaceholder::default(), + transaction_id: TransactionId(I32::new(i32::arbitrary(g))), + info_hash: InfoHash::arbitrary(g), + peer_id: PeerId(peer_id_bytes), + bytes_downloaded: NumberOfBytes(I64::new(i64::arbitrary(g))), + bytes_uploaded: NumberOfBytes(I64::new(i64::arbitrary(g))), + bytes_left: NumberOfBytes(I64::new(i64::arbitrary(g))), + event: AnnounceEvent::arbitrary(g).into(), + ip_address: Ipv4AddrBytes::arbitrary(g), + key: PeerKey::new(i32::arbitrary(g)), + peers_wanted: NumberOfPeers(I32::new(i32::arbitrary(g))), + port: Port::new(quickcheck::Arbitrary::arbitrary(g)), + } + } + } + + impl quickcheck::Arbitrary for ScrapeRequest { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let info_hashes = (0..u8::arbitrary(g)).map(|_| InfoHash::arbitrary(g)).collect(); + + Self { + connection_id: ConnectionId(I64::new(i64::arbitrary(g))), + transaction_id: TransactionId(I32::new(i32::arbitrary(g))), + info_hashes, + } + } + } + + fn same_after_conversion(request: Request) -> bool { + let mut buf = Vec::new(); + + request.clone().write_bytes(&mut buf).unwrap(); + let r2 = Request::parse_bytes(&buf[..], ::std::u8::MAX).unwrap(); + + let success = request == r2; + + if !success { + ::pretty_assertions::assert_eq!(request, r2); + } + + success + } + + #[quickcheck] + fn test_connect_request_convert_identity(request: ConnectRequest) -> bool { + same_after_conversion(request.into()) + } + + #[quickcheck] + fn test_announce_request_convert_identity(request: AnnounceRequest) -> bool { + same_after_conversion(request.into()) + } + + #[quickcheck] + fn test_scrape_request_convert_identity(request: ScrapeRequest) -> TestResult { + if request.info_hashes.is_empty() { + return TestResult::discard(); + } + + TestResult::from_bool(same_after_conversion(request.into())) + } + + #[test] + fn test_various_input_lengths() { + for action in 0i32..4 { + for max_scrape_torrents in 0..3 { + for num_bytes in 0..256 { + let mut request_bytes = ::std::iter::repeat(0).take(num_bytes).collect::>(); + + if let Some(action_bytes) = request_bytes.get_mut(8..12) { + action_bytes.copy_from_slice(&action.to_be_bytes()) + } + + drop(Request::parse_bytes(&request_bytes, max_scrape_torrents)); + } + } + } + } + + #[test] + fn test_scrape_request_with_no_info_hashes() { + let mut request_bytes = Vec::new(); + + request_bytes.extend(0i64.to_be_bytes()); + request_bytes.extend(2i32.to_be_bytes()); + request_bytes.extend(0i32.to_be_bytes()); + + Request::parse_bytes(&request_bytes, 1).unwrap_err(); + } +} diff --git a/packages/udp-protocol/src/response.rs b/packages/udp-protocol/src/response.rs new file mode 100644 index 000000000..55b31700f --- /dev/null +++ b/packages/udp-protocol/src/response.rs @@ -0,0 +1,287 @@ +// Copied from aquatic_udp_protocol 0.9.0 by Joakim Frostegard (greatest-ape). +// Source: https://crates.io/crates/aquatic_udp_protocol/0.9.0 +// Repository: https://github.com/greatest-ape/aquatic +// License: Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) +// +// This in-house crate started from the aquatic 0.9.0 sources that were previously vendored +// under packages/aquatic-udp-protocol. +use std::borrow::Cow; +use std::io::{self, Write}; +use std::mem::size_of; + +use byteorder::{NetworkEndian, WriteBytesExt}; +use zerocopy::{FromBytes, IntoBytes}; + +#[cfg(test)] +use super::announce::AnnounceInterval; +use super::announce::{AnnounceResponse, AnnounceResponseFixedData}; +use super::common::*; +use super::connect::ConnectResponse; +pub use super::scrape::{ScrapeResponse, TorrentScrapeStatistics}; + +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum Response { + Connect(ConnectResponse), + AnnounceIpv4(AnnounceResponse), + AnnounceIpv6(AnnounceResponse), + Scrape(ScrapeResponse), + Error(ErrorResponse), +} + +impl Response { + #[inline] + pub fn write_bytes(&self, bytes: &mut impl Write) -> Result<(), io::Error> { + match self { + Response::Connect(r) => r.write_bytes(bytes), + Response::AnnounceIpv4(r) => r.write_bytes(bytes), + Response::AnnounceIpv6(r) => r.write_bytes(bytes), + Response::Scrape(r) => r.write_bytes(bytes), + Response::Error(r) => r.write_bytes(bytes), + } + } + + #[inline] + pub fn parse_bytes(mut bytes: &[u8], ipv4: bool) -> Result { + let action = read_i32_ne(&mut bytes)?; + + match action.get() { + 0 => Ok(Response::Connect( + ConnectResponse::read_from_prefix(bytes).map_err(|_| invalid_data())?.0, + )), + 1 if ipv4 => { + let fixed = AnnounceResponseFixedData::read_from_prefix(bytes) + .map_err(|_| invalid_data())? + .0; + + let peers = if let Some(bytes) = bytes.get(size_of::()..) { + let chunks = bytes.chunks_exact(size_of::>()); + + if !chunks.remainder().is_empty() { + return Err(invalid_data()); + } + + chunks + .map(|chunk| { + ResponsePeer::::read_from_prefix(chunk) + .map(|(peer, _)| peer) + .map_err(|_| invalid_data()) + }) + .collect::, _>>()? + } else { + Vec::new() + }; + + Ok(Response::AnnounceIpv4(AnnounceResponse { fixed, peers })) + } + 1 if !ipv4 => { + let fixed = AnnounceResponseFixedData::read_from_prefix(bytes) + .map_err(|_| invalid_data())? + .0; + + let peers = if let Some(bytes) = bytes.get(size_of::()..) { + let chunks = bytes.chunks_exact(size_of::>()); + + if !chunks.remainder().is_empty() { + return Err(invalid_data()); + } + + chunks + .map(|chunk| { + ResponsePeer::::read_from_prefix(chunk) + .map(|(peer, _)| peer) + .map_err(|_| invalid_data()) + }) + .collect::, _>>()? + } else { + Vec::new() + }; + + Ok(Response::AnnounceIpv6(AnnounceResponse { fixed, peers })) + } + 2 => { + let transaction_id = read_i32_ne(&mut bytes).map(TransactionId)?; + + let chunks = bytes.chunks_exact(size_of::()); + + if !chunks.remainder().is_empty() { + return Err(invalid_data()); + } + + let torrent_stats = chunks + .map(|chunk| { + TorrentScrapeStatistics::read_from_prefix(chunk) + .map(|(stats, _)| stats) + .map_err(|_| invalid_data()) + }) + .collect::, _>>()?; + + Ok((ScrapeResponse { + transaction_id, + torrent_stats, + }) + .into()) + } + 3 => { + let transaction_id = read_i32_ne(&mut bytes).map(TransactionId)?; + let message = String::from_utf8_lossy(bytes).into_owned().into(); + + Ok((ErrorResponse { transaction_id, message }).into()) + } + _ => Err(invalid_data()), + } + } +} + +impl From for Response { + fn from(r: ConnectResponse) -> Self { + Self::Connect(r) + } +} + +impl From> for Response { + fn from(r: AnnounceResponse) -> Self { + Self::AnnounceIpv4(r) + } +} + +impl From> for Response { + fn from(r: AnnounceResponse) -> Self { + Self::AnnounceIpv6(r) + } +} + +impl From for Response { + fn from(r: ScrapeResponse) -> Self { + Self::Scrape(r) + } +} + +impl From for Response { + fn from(r: ErrorResponse) -> Self { + Self::Error(r) + } +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ErrorResponse { + pub transaction_id: TransactionId, + pub message: Cow<'static, str>, +} + +impl ErrorResponse { + #[inline] + pub fn write_bytes(&self, bytes: &mut impl Write) -> Result<(), io::Error> { + bytes.write_i32::(3)?; + bytes.write_all(self.transaction_id.as_bytes())?; + bytes.write_all(self.message.as_bytes())?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use quickcheck_macros::quickcheck; + use zerocopy::network_endian::{I32, I64}; + + use super::*; + + impl quickcheck::Arbitrary for Ipv4AddrBytes { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + Self([u8::arbitrary(g), u8::arbitrary(g), u8::arbitrary(g), u8::arbitrary(g)]) + } + } + + impl quickcheck::Arbitrary for Ipv6AddrBytes { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let mut bytes = [0; 16]; + + for byte in bytes.iter_mut() { + *byte = u8::arbitrary(g) + } + + Self(bytes) + } + } + + impl quickcheck::Arbitrary for TorrentScrapeStatistics { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + Self { + seeders: NumberOfPeers(I32::new(i32::arbitrary(g))), + completed: NumberOfDownloads(I32::new(i32::arbitrary(g))), + leechers: NumberOfPeers(I32::new(i32::arbitrary(g))), + } + } + } + + impl quickcheck::Arbitrary for ConnectResponse { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + Self { + connection_id: ConnectionId(I64::new(i64::arbitrary(g))), + transaction_id: TransactionId(I32::new(i32::arbitrary(g))), + } + } + } + + impl quickcheck::Arbitrary for AnnounceResponse { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let peers = (0..u8::arbitrary(g)).map(|_| ResponsePeer::arbitrary(g)).collect(); + + Self { + fixed: AnnounceResponseFixedData { + transaction_id: TransactionId(I32::new(i32::arbitrary(g))), + announce_interval: AnnounceInterval(I32::new(i32::arbitrary(g))), + leechers: NumberOfPeers(I32::new(i32::arbitrary(g))), + seeders: NumberOfPeers(I32::new(i32::arbitrary(g))), + }, + peers, + } + } + } + + impl quickcheck::Arbitrary for ScrapeResponse { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let torrent_stats = (0..u8::arbitrary(g)).map(|_| TorrentScrapeStatistics::arbitrary(g)).collect(); + + Self { + transaction_id: TransactionId(I32::new(i32::arbitrary(g))), + torrent_stats, + } + } + } + + fn same_after_conversion(response: Response, ipv4: bool) -> bool { + let mut buf = Vec::new(); + + response.clone().write_bytes(&mut buf).unwrap(); + let r2 = Response::parse_bytes(&buf[..], ipv4).unwrap(); + + let success = response == r2; + + if !success { + ::pretty_assertions::assert_eq!(response, r2); + } + + success + } + + #[quickcheck] + fn test_connect_response_convert_identity(response: ConnectResponse) -> bool { + same_after_conversion(response.into(), true) + } + + #[quickcheck] + fn test_announce_response_ipv4_convert_identity(response: AnnounceResponse) -> bool { + same_after_conversion(response.into(), true) + } + + #[quickcheck] + fn test_announce_response_ipv6_convert_identity(response: AnnounceResponse) -> bool { + same_after_conversion(response.into(), false) + } + + #[quickcheck] + fn test_scrape_response_convert_identity(response: ScrapeResponse) -> bool { + same_after_conversion(response.into(), true) + } +} diff --git a/packages/udp-protocol/src/scrape.rs b/packages/udp-protocol/src/scrape.rs new file mode 100644 index 000000000..9d6342a96 --- /dev/null +++ b/packages/udp-protocol/src/scrape.rs @@ -0,0 +1,56 @@ +// Copied from aquatic_udp_protocol 0.9.0 by Joakim Frostegard (greatest-ape). +// Source: https://crates.io/crates/aquatic_udp_protocol/0.9.0 +// Repository: https://github.com/greatest-ape/aquatic +// License: Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) +// +// This in-house crate started from the aquatic 0.9.0 sources that were previously vendored +// under packages/aquatic-udp-protocol. +use std::io::{self, Write}; + +use byteorder::{NetworkEndian, WriteBytesExt}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +use super::common::*; + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ScrapeRequest { + pub connection_id: ConnectionId, + pub transaction_id: TransactionId, + pub info_hashes: Vec, +} + +impl ScrapeRequest { + pub fn write_bytes(&self, bytes: &mut impl Write) -> Result<(), io::Error> { + bytes.write_all(self.connection_id.as_bytes())?; + bytes.write_i32::(2)?; + bytes.write_all(self.transaction_id.as_bytes())?; + bytes.write_all((*self.info_hashes.as_slice()).as_bytes())?; + + Ok(()) + } +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ScrapeResponse { + pub transaction_id: TransactionId, + pub torrent_stats: Vec, +} + +impl ScrapeResponse { + #[inline] + pub fn write_bytes(&self, bytes: &mut impl Write) -> Result<(), io::Error> { + bytes.write_i32::(2)?; + bytes.write_all(self.transaction_id.as_bytes())?; + bytes.write_all((*self.torrent_stats.as_slice()).as_bytes())?; + + Ok(()) + } +} + +#[derive(PartialEq, Eq, Debug, Copy, Clone, IntoBytes, FromBytes, Immutable)] +#[repr(C, packed)] +pub struct TorrentScrapeStatistics { + pub seeders: NumberOfPeers, + pub completed: NumberOfDownloads, + pub leechers: NumberOfPeers, +} diff --git a/packages/udp-tracker-core/Cargo.toml b/packages/udp-tracker-core/Cargo.toml index 45a74f93c..d44c930aa 100644 --- a/packages/udp-tracker-core/Cargo.toml +++ b/packages/udp-tracker-core/Cargo.toml @@ -14,8 +14,7 @@ rust-version.workspace = true version.workspace = true [dependencies] -aquatic_udp_protocol = "0" -bittorrent-primitives = "0.1.0" +bittorrent-primitives = "0.2.0" bittorrent-tracker-core = { version = "3.0.0-develop", path = "../tracker-core" } bittorrent-udp-tracker-protocol = { version = "3.0.0-develop", path = "../udp-protocol" } bloom = "0.3.2" @@ -36,7 +35,7 @@ torrust-tracker-metrics = { version = "3.0.0-develop", path = "../metrics" } torrust-tracker-primitives = { version = "3.0.0-develop", path = "../primitives" } torrust-tracker-swarm-coordination-registry = { version = "3.0.0-develop", path = "../swarm-coordination-registry" } tracing = "0" -zerocopy = "0.7" +zerocopy = "0.8" [dev-dependencies] mockall = "0" diff --git a/packages/udp-tracker-core/src/connection_cookie.rs b/packages/udp-tracker-core/src/connection_cookie.rs index 2d8e941cd..785f6218c 100644 --- a/packages/udp-tracker-core/src/connection_cookie.rs +++ b/packages/udp-tracker-core/src/connection_cookie.rs @@ -77,11 +77,11 @@ //! - The module leverages existing cryptographic primitives while acknowledging and addressing the limitations imposed by the protocol's specifications. //! -use aquatic_udp_protocol::ConnectionId as Cookie; +use bittorrent_udp_tracker_protocol::ConnectionId as Cookie; use cookie_builder::{assemble, decode, disassemble, encode}; use thiserror::Error; use tracing::instrument; -use zerocopy::AsBytes; +use zerocopy::IntoBytes as _; use crate::crypto::keys::CipherArrayBlowfish; /// Error returned when there was an error with the connection cookie. @@ -118,8 +118,8 @@ pub fn make(fingerprint: u64, issue_at: f64) -> Result u64 { mod cookie_builder { use cipher::{BlockCipherDecrypt, BlockCipherEncrypt}; use tracing::instrument; - use zerocopy::{byteorder, AsBytes as _, NativeEndian}; + use zerocopy::{byteorder, IntoBytes as _, NativeEndian}; pub type CookiePlainText = CipherArrayBlowfish; pub type CookieCipherText = CipherArrayBlowfish; @@ -187,13 +187,13 @@ mod cookie_builder { #[instrument()] pub(super) fn assemble(fingerprint: u64, issue_at: f64) -> CookiePlainText { let issue_at: byteorder::I64 = - *zerocopy::FromBytes::ref_from(&issue_at.to_ne_bytes()).expect("it should be aligned"); + *zerocopy::FromBytes::ref_from_bytes(&issue_at.to_ne_bytes()).expect("it should be aligned"); let fingerprint: byteorder::I64 = - *zerocopy::FromBytes::ref_from(&fingerprint.to_ne_bytes()).expect("it should be aligned"); + *zerocopy::FromBytes::ref_from_bytes(&fingerprint.to_ne_bytes()).expect("it should be aligned"); let cookie = issue_at.get().wrapping_add(fingerprint.get()); let cookie: byteorder::I64 = - *zerocopy::FromBytes::ref_from(&cookie.to_ne_bytes()).expect("it should be aligned"); + *zerocopy::FromBytes::ref_from_bytes(&cookie.to_ne_bytes()).expect("it should be aligned"); CipherArrayBlowfish::try_from(cookie.as_bytes()).expect("it should be the same size") } @@ -201,16 +201,16 @@ mod cookie_builder { #[instrument()] pub(super) fn disassemble(fingerprint: u64, cookie: CookiePlainText) -> f64 { let fingerprint: byteorder::I64 = - *zerocopy::FromBytes::ref_from(&fingerprint.to_ne_bytes()).expect("it should be aligned"); + *zerocopy::FromBytes::ref_from_bytes(&fingerprint.to_ne_bytes()).expect("it should be aligned"); // the array may be not aligned, so we read instead of reference. let cookie: byteorder::I64 = - zerocopy::FromBytes::read_from(cookie.as_bytes()).expect("it should be the same size"); + zerocopy::FromBytes::read_from_bytes(cookie.as_bytes()).expect("it should be the same size"); let issue_time_bytes = cookie.get().wrapping_sub(fingerprint.get()).to_ne_bytes(); let issue_time: byteorder::F64 = - *zerocopy::FromBytes::ref_from(&issue_time_bytes).expect("it should be aligned"); + *zerocopy::FromBytes::ref_from_bytes(&issue_time_bytes).expect("it should be aligned"); issue_time.get() } diff --git a/packages/udp-tracker-core/src/lib.rs b/packages/udp-tracker-core/src/lib.rs index 2c1943853..d6b3da635 100644 --- a/packages/udp-tracker-core/src/lib.rs +++ b/packages/udp-tracker-core/src/lib.rs @@ -2,6 +2,7 @@ pub mod connection_cookie; pub mod container; pub mod crypto; pub mod event; +pub mod peer_builder; pub mod services; pub mod statistics; diff --git a/packages/udp-tracker-core/src/peer_builder.rs b/packages/udp-tracker-core/src/peer_builder.rs new file mode 100644 index 000000000..40cc516bf --- /dev/null +++ b/packages/udp-tracker-core/src/peer_builder.rs @@ -0,0 +1,33 @@ +//! Logic to extract the peer info from the announce request. +use std::net::{IpAddr, SocketAddr}; + +use torrust_tracker_clock::clock::Time; +use torrust_tracker_primitives::peer; + +use crate::CurrentClock; + +/// Extracts the [`peer::Peer`] info from the +/// announce request. +/// +/// # Arguments +/// +/// * `peer_ip` - The real IP address of the peer, not the one in the announce request. +#[must_use] +pub fn from_request(announce_request: &bittorrent_udp_tracker_protocol::AnnounceRequest, peer_ip: &IpAddr) -> peer::Peer { + let wire_event = bittorrent_udp_tracker_protocol::AnnounceEvent::from(announce_request.event); + + peer::Peer { + peer_id: torrust_tracker_primitives::PeerId(announce_request.peer_id.0), + peer_addr: SocketAddr::new(*peer_ip, announce_request.port.0.into()), + updated: CurrentClock::now(), + uploaded: torrust_tracker_primitives::NumberOfBytes::new(announce_request.bytes_uploaded.0.get()), + downloaded: torrust_tracker_primitives::NumberOfBytes::new(announce_request.bytes_downloaded.0.get()), + left: torrust_tracker_primitives::NumberOfBytes::new(announce_request.bytes_left.0.get()), + event: match wire_event { + bittorrent_udp_tracker_protocol::AnnounceEvent::Completed => torrust_tracker_primitives::AnnounceEvent::Completed, + bittorrent_udp_tracker_protocol::AnnounceEvent::Started => torrust_tracker_primitives::AnnounceEvent::Started, + bittorrent_udp_tracker_protocol::AnnounceEvent::Stopped => torrust_tracker_primitives::AnnounceEvent::Stopped, + bittorrent_udp_tracker_protocol::AnnounceEvent::None => torrust_tracker_primitives::AnnounceEvent::None, + }, + } +} diff --git a/packages/udp-tracker-core/src/services/announce.rs b/packages/udp-tracker-core/src/services/announce.rs index a69e91d8a..2871ae11e 100644 --- a/packages/udp-tracker-core/src/services/announce.rs +++ b/packages/udp-tracker-core/src/services/announce.rs @@ -11,18 +11,18 @@ use std::net::SocketAddr; use std::ops::Range; use std::sync::Arc; -use aquatic_udp_protocol::AnnounceRequest; use bittorrent_primitives::info_hash::InfoHash; use bittorrent_tracker_core::announce_handler::{AnnounceHandler, PeersWanted}; use bittorrent_tracker_core::error::{AnnounceError, WhitelistError}; use bittorrent_tracker_core::whitelist; -use bittorrent_udp_tracker_protocol::peer_builder; -use torrust_tracker_primitives::core::AnnounceData; +use bittorrent_udp_tracker_protocol::AnnounceRequest; use torrust_tracker_primitives::peer::PeerAnnouncement; use torrust_tracker_primitives::service_binding::ServiceBinding; +use torrust_tracker_primitives::AnnounceData; use crate::connection_cookie::{check, gen_remote_fingerprint, ConnectionCookieError}; use crate::event::{ConnectionContext, Event}; +use crate::peer_builder; /// The `AnnounceService` is responsible for handling the `announce` requests. /// @@ -66,7 +66,7 @@ impl AnnounceService { ) -> Result { Self::authenticate(client_socket_addr, request, cookie_valid_range)?; - let info_hash = request.info_hash.into(); + let info_hash = InfoHash::from(request.info_hash.0); self.authorize(&info_hash).await?; diff --git a/packages/udp-tracker-core/src/services/connect.rs b/packages/udp-tracker-core/src/services/connect.rs index 6ba36f274..585e6c88c 100644 --- a/packages/udp-tracker-core/src/services/connect.rs +++ b/packages/udp-tracker-core/src/services/connect.rs @@ -3,7 +3,7 @@ //! The service is responsible for handling the `connect` requests. use std::net::SocketAddr; -use aquatic_udp_protocol::ConnectionId; +use bittorrent_udp_tracker_protocol::ConnectionId; use torrust_tracker_primitives::service_binding::ServiceBinding; use crate::connection_cookie::{gen_remote_fingerprint, make}; diff --git a/packages/udp-tracker-core/src/services/scrape.rs b/packages/udp-tracker-core/src/services/scrape.rs index 8551351fb..77fa212e5 100644 --- a/packages/udp-tracker-core/src/services/scrape.rs +++ b/packages/udp-tracker-core/src/services/scrape.rs @@ -11,12 +11,12 @@ use std::net::SocketAddr; use std::ops::Range; use std::sync::Arc; -use aquatic_udp_protocol::ScrapeRequest; use bittorrent_primitives::info_hash::InfoHash; use bittorrent_tracker_core::error::{ScrapeError, WhitelistError}; use bittorrent_tracker_core::scrape_handler::ScrapeHandler; -use torrust_tracker_primitives::core::ScrapeData; +use bittorrent_udp_tracker_protocol::ScrapeRequest; use torrust_tracker_primitives::service_binding::ServiceBinding; +use torrust_tracker_primitives::ScrapeData; use crate::connection_cookie::{check, gen_remote_fingerprint, ConnectionCookieError}; use crate::event::{ConnectionContext, Event}; @@ -56,7 +56,7 @@ impl ScrapeService { let scrape_data = self .scrape_handler - .handle_scrape(&Self::convert_from_aquatic(&request.info_hashes)) + .handle_scrape(&Self::convert_from_wire_info_hashes(&request.info_hashes)) .await?; self.send_event(client_socket_addr, server_service_binding).await; @@ -76,8 +76,8 @@ impl ScrapeService { ) } - fn convert_from_aquatic(aquatic_infohashes: &[aquatic_udp_protocol::common::InfoHash]) -> Vec { - aquatic_infohashes.iter().map(|&x| x.into()).collect() + fn convert_from_wire_info_hashes(wire_info_hashes: &[bittorrent_udp_tracker_protocol::common::InfoHash]) -> Vec { + wire_info_hashes.iter().map(|&x| InfoHash::from(x.0)).collect() } async fn send_event(&self, client_socket_addr: SocketAddr, server_service_binding: ServiceBinding) { diff --git a/packages/udp-tracker-server/Cargo.toml b/packages/udp-tracker-server/Cargo.toml index dc66572d8..a978167cf 100644 --- a/packages/udp-tracker-server/Cargo.toml +++ b/packages/udp-tracker-server/Cargo.toml @@ -14,8 +14,8 @@ rust-version.workspace = true version.workspace = true [dependencies] -aquatic_udp_protocol = "0" -bittorrent-primitives = "0.1.0" +bittorrent_udp_tracker_protocol = { package = "bittorrent-udp-tracker-protocol", path = "../udp-protocol" } +bittorrent-primitives = "0.2.0" bittorrent-tracker-client = { version = "3.0.0-develop", path = "../tracker-client" } bittorrent-tracker-core = { version = "3.0.0-develop", path = "../tracker-core" } bittorrent-udp-tracker-core = { version = "3.0.0-develop", path = "../udp-tracker-core" } @@ -37,7 +37,7 @@ torrust-tracker-swarm-coordination-registry = { version = "3.0.0-develop", path tracing = "0" url = { version = "2", features = [ "serde" ] } uuid = { version = "1", features = [ "v4" ] } -zerocopy = "0.7" +zerocopy = "0.8" [dev-dependencies] local-ip-address = "0" diff --git a/packages/udp-tracker-server/src/error.rs b/packages/udp-tracker-server/src/error.rs index d260ebfd4..bb9bb1d0c 100644 --- a/packages/udp-tracker-server/src/error.rs +++ b/packages/udp-tracker-server/src/error.rs @@ -2,9 +2,9 @@ use std::fmt::Display; use std::panic::Location; -use aquatic_udp_protocol::{ConnectionId, RequestParseError, TransactionId}; use bittorrent_udp_tracker_core::services::announce::UdpAnnounceError; use bittorrent_udp_tracker_core::services::scrape::UdpScrapeError; +use bittorrent_udp_tracker_protocol::{ConnectionId, RequestParseError, TransactionId}; use derive_more::derive::Display; use thiserror::Error; @@ -27,7 +27,7 @@ pub enum Error { #[error("tracker scrape error: {source}")] ScrapeFailed { source: UdpScrapeError }, - /// Error returned from a third-party library (`aquatic_udp_protocol`). + /// Error returned from the wire-protocol crate (`bittorrent_udp_tracker_protocol`). #[error("internal server error: {message}, {location}")] Internal { location: &'static Location<'static>, diff --git a/packages/udp-tracker-server/src/event.rs b/packages/udp-tracker-server/src/event.rs index a7634d58e..3a56fcec3 100644 --- a/packages/udp-tracker-server/src/event.rs +++ b/packages/udp-tracker-server/src/event.rs @@ -2,10 +2,10 @@ use std::fmt; use std::net::SocketAddr; use std::time::Duration; -use aquatic_udp_protocol::AnnounceRequest; use bittorrent_tracker_core::error::{AnnounceError, ScrapeError}; use bittorrent_udp_tracker_core::services::announce::UdpAnnounceError; use bittorrent_udp_tracker_core::services::scrape::UdpScrapeError; +use bittorrent_udp_tracker_protocol::AnnounceRequest; use torrust_tracker_metrics::label::{LabelSet, LabelValue}; use torrust_tracker_metrics::label_name; use torrust_tracker_primitives::service_binding::ServiceBinding; diff --git a/packages/udp-tracker-server/src/handlers/announce.rs b/packages/udp-tracker-server/src/handlers/announce.rs index b74de43a0..794001792 100644 --- a/packages/udp-tracker-server/src/handlers/announce.rs +++ b/packages/udp-tracker-server/src/handlers/announce.rs @@ -3,17 +3,17 @@ use std::net::{IpAddr, SocketAddr}; use std::ops::Range; use std::sync::Arc; -use aquatic_udp_protocol::{ +use bittorrent_primitives::info_hash::InfoHash; +use bittorrent_udp_tracker_core::services::announce::AnnounceService; +use bittorrent_udp_tracker_protocol::{ AnnounceInterval, AnnounceRequest, AnnounceResponse, AnnounceResponseFixedData, Ipv4AddrBytes, Ipv6AddrBytes, NumberOfPeers, Port, Response, ResponsePeer, TransactionId, }; -use bittorrent_primitives::info_hash::InfoHash; -use bittorrent_udp_tracker_core::services::announce::AnnounceService; use torrust_tracker_configuration::Core; -use torrust_tracker_primitives::core::AnnounceData; use torrust_tracker_primitives::service_binding::ServiceBinding; +use torrust_tracker_primitives::AnnounceData; use tracing::{instrument, Level}; -use zerocopy::network_endian::I32; +use zerocopy::byteorder::network_endian::I32; use crate::error::Error; use crate::event::{ConnectionContext, Event, UdpRequestKind}; @@ -135,11 +135,11 @@ pub(crate) mod tests { use std::net::Ipv4Addr; use std::num::NonZeroU16; - use aquatic_udp_protocol::{ + use bittorrent_udp_tracker_core::connection_cookie::make; + use bittorrent_udp_tracker_protocol::{ AnnounceActionPlaceholder, AnnounceEvent, AnnounceRequest, ConnectionId, NumberOfBytes, NumberOfPeers, PeerId as AquaticPeerId, PeerKey, Port, TransactionId, }; - use bittorrent_udp_tracker_core::connection_cookie::make; use crate::handlers::tests::{sample_ipv4_remote_addr_fingerprint, sample_issue_time}; @@ -151,7 +151,7 @@ pub(crate) mod tests { pub fn default() -> AnnounceRequestBuilder { let client_ip = Ipv4Addr::new(126, 0, 0, 1); let client_port = 8080; - let info_hash_aquatic = aquatic_udp_protocol::InfoHash([0u8; 20]); + let info_hash_aquatic = bittorrent_udp_tracker_protocol::InfoHash([0u8; 20]); let default_request = AnnounceRequest { connection_id: make(sample_ipv4_remote_addr_fingerprint(), sample_issue_time()).unwrap(), @@ -178,7 +178,7 @@ pub(crate) mod tests { self } - pub fn with_info_hash(mut self, info_hash: aquatic_udp_protocol::InfoHash) -> Self { + pub fn with_info_hash(mut self, info_hash: bittorrent_udp_tracker_protocol::InfoHash) -> Self { self.request.info_hash = info_hash; self } @@ -209,12 +209,12 @@ pub(crate) mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; - use aquatic_udp_protocol::{ + use bittorrent_tracker_core::torrent::repository::in_memory::InMemoryTorrentRepository; + use bittorrent_udp_tracker_core::connection_cookie::{gen_remote_fingerprint, make}; + use bittorrent_udp_tracker_protocol::{ AnnounceInterval, AnnounceResponse, AnnounceResponseFixedData, InfoHash as AquaticInfoHash, Ipv4AddrBytes, Ipv6AddrBytes, NumberOfPeers, PeerId as AquaticPeerId, Response, ResponsePeer, }; - use bittorrent_tracker_core::torrent::repository::in_memory::InMemoryTorrentRepository; - use bittorrent_udp_tracker_core::connection_cookie::{gen_remote_fingerprint, make}; use mockall::predicate::eq; use torrust_tracker_events::bus::SenderStatus; use torrust_tracker_primitives::peer::fixture::PeerBuilder; @@ -269,7 +269,7 @@ pub(crate) mod tests { .await; let expected_peer = PeerBuilder::default() - .with_peer_id(&peer_id) + .with_peer_id(&torrust_tracker_primitives::PeerId(peer_id.0)) .with_peer_address(SocketAddr::new(IpAddr::V4(client_ip), client_port)) .updated_on(peers[0].updated) .into(); @@ -375,7 +375,7 @@ pub(crate) mod tests { let peer_id = AquaticPeerId([255u8; 20]); let peer_using_ipv6 = PeerBuilder::default() - .with_peer_id(&peer_id) + .with_peer_id(&torrust_tracker_primitives::PeerId(peer_id.0)) .with_peer_address(SocketAddr::new(IpAddr::V6(client_ip_v6), client_port)) .into(); @@ -475,8 +475,8 @@ pub(crate) mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; - use aquatic_udp_protocol::{InfoHash as AquaticInfoHash, PeerId as AquaticPeerId}; use bittorrent_udp_tracker_core::connection_cookie::{gen_remote_fingerprint, make}; + use bittorrent_udp_tracker_protocol::{InfoHash as AquaticInfoHash, PeerId as AquaticPeerId}; use torrust_tracker_primitives::peer::fixture::PeerBuilder; use torrust_tracker_primitives::service_binding::{Protocol, ServiceBinding}; @@ -528,7 +528,7 @@ pub(crate) mod tests { let external_ip_in_tracker_configuration = core_tracker_services.core_config.net.external_ip.unwrap(); let expected_peer = PeerBuilder::default() - .with_peer_id(&peer_id) + .with_peer_id(&torrust_tracker_primitives::PeerId(peer_id.0)) .with_peer_address(SocketAddr::new(external_ip_in_tracker_configuration, client_port)) .updated_on(peers[0].updated) .into(); @@ -544,10 +544,6 @@ pub(crate) mod tests { use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::Arc; - use aquatic_udp_protocol::{ - AnnounceInterval, AnnounceResponse, AnnounceResponseFixedData, InfoHash as AquaticInfoHash, Ipv4AddrBytes, - Ipv6AddrBytes, NumberOfPeers, PeerId as AquaticPeerId, Response, ResponsePeer, - }; use bittorrent_tracker_core::announce_handler::AnnounceHandler; use bittorrent_tracker_core::torrent::repository::in_memory::InMemoryTorrentRepository; use bittorrent_tracker_core::whitelist; @@ -555,6 +551,10 @@ pub(crate) mod tests { use bittorrent_udp_tracker_core::event::bus::EventBus; use bittorrent_udp_tracker_core::event::sender::Broadcaster; use bittorrent_udp_tracker_core::services::announce::AnnounceService; + use bittorrent_udp_tracker_protocol::{ + AnnounceInterval, AnnounceResponse, AnnounceResponseFixedData, InfoHash as AquaticInfoHash, Ipv4AddrBytes, + Ipv6AddrBytes, NumberOfPeers, PeerId as AquaticPeerId, Response, ResponsePeer, + }; use mockall::predicate::eq; use torrust_tracker_configuration::Core; use torrust_tracker_events::bus::SenderStatus; @@ -611,7 +611,7 @@ pub(crate) mod tests { .await; let expected_peer = PeerBuilder::default() - .with_peer_id(&peer_id) + .with_peer_id(&torrust_tracker_primitives::PeerId(peer_id.0)) .with_peer_address(SocketAddr::new(IpAddr::V6(client_ip_v6), client_port)) .updated_on(peers[0].updated) .into(); @@ -720,7 +720,7 @@ pub(crate) mod tests { let peer_id = AquaticPeerId([255u8; 20]); let peer_using_ipv4 = PeerBuilder::default() - .with_peer_id(&peer_id) + .with_peer_id(&torrust_tracker_primitives::PeerId(peer_id.0)) .with_peer_address(SocketAddr::new(IpAddr::V4(client_ip_v4), client_port)) .into(); @@ -843,7 +843,6 @@ pub(crate) mod tests { use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::Arc; - use aquatic_udp_protocol::{InfoHash as AquaticInfoHash, PeerId as AquaticPeerId}; use bittorrent_tracker_core::announce_handler::AnnounceHandler; use bittorrent_tracker_core::databases::setup::initialize_database; use bittorrent_tracker_core::statistics::persisted::downloads::DatabaseDownloadsMetricRepository; @@ -853,6 +852,7 @@ pub(crate) mod tests { use bittorrent_udp_tracker_core::connection_cookie::{gen_remote_fingerprint, make}; use bittorrent_udp_tracker_core::services::announce::AnnounceService; use bittorrent_udp_tracker_core::{self, event as core_event}; + use bittorrent_udp_tracker_protocol::{InfoHash as AquaticInfoHash, PeerId as AquaticPeerId}; use mockall::predicate::{self, eq}; use torrust_tracker_primitives::service_binding::{Protocol, ServiceBinding}; @@ -879,7 +879,7 @@ pub(crate) mod tests { let info_hash = AquaticInfoHash([0u8; 20]); let peer_id = AquaticPeerId([255u8; 20]); let mut announcement = sample_peer(); - announcement.peer_id = peer_id; + announcement.peer_id = torrust_tracker_primitives::PeerId(peer_id.0); announcement.peer_addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0x7e00, 1)), client_port); let client_socket_addr = SocketAddr::new(IpAddr::V6(client_ip_v6), client_port); @@ -916,7 +916,7 @@ pub(crate) mod tests { client_socket_addr, server_service_binding.clone(), ), - info_hash: info_hash.into(), + info_hash: bittorrent_primitives::info_hash::InfoHash::from(info_hash.0), announcement, }; diff --git a/packages/udp-tracker-server/src/handlers/connect.rs b/packages/udp-tracker-server/src/handlers/connect.rs index 961189945..0d69f2472 100644 --- a/packages/udp-tracker-server/src/handlers/connect.rs +++ b/packages/udp-tracker-server/src/handlers/connect.rs @@ -2,8 +2,8 @@ use std::net::SocketAddr; use std::sync::Arc; -use aquatic_udp_protocol::{ConnectRequest, ConnectResponse, ConnectionId, Response}; use bittorrent_udp_tracker_core::services::connect::ConnectService; +use bittorrent_udp_tracker_protocol::{ConnectRequest, ConnectResponse, ConnectionId, Response}; use torrust_tracker_primitives::service_binding::ServiceBinding; use tracing::{instrument, Level}; @@ -56,12 +56,12 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; - use aquatic_udp_protocol::{ConnectRequest, ConnectResponse, Response, TransactionId}; use bittorrent_udp_tracker_core::connection_cookie::make; use bittorrent_udp_tracker_core::event as core_event; use bittorrent_udp_tracker_core::event::bus::EventBus; use bittorrent_udp_tracker_core::event::sender::Broadcaster; use bittorrent_udp_tracker_core::services::connect::ConnectService; + use bittorrent_udp_tracker_protocol::{ConnectRequest, ConnectResponse, Response, TransactionId}; use mockall::predicate::eq; use torrust_tracker_events::bus::SenderStatus; use torrust_tracker_primitives::service_binding::{Protocol, ServiceBinding}; diff --git a/packages/udp-tracker-server/src/handlers/error.rs b/packages/udp-tracker-server/src/handlers/error.rs index 7fb4141b2..a342de938 100644 --- a/packages/udp-tracker-server/src/handlers/error.rs +++ b/packages/udp-tracker-server/src/handlers/error.rs @@ -2,12 +2,12 @@ use std::net::SocketAddr; use std::ops::Range; -use aquatic_udp_protocol::{ErrorResponse, Response, TransactionId}; use bittorrent_udp_tracker_core::{self, UDP_TRACKER_LOG_TARGET}; +use bittorrent_udp_tracker_protocol::{ErrorResponse, Response, TransactionId}; use torrust_tracker_primitives::service_binding::ServiceBinding; use tracing::{instrument, Level}; use uuid::Uuid; -use zerocopy::network_endian::I32; +use zerocopy::byteorder::network_endian::I32; use crate::error::Error; use crate::event::{ConnectionContext, Event, UdpRequestKind}; diff --git a/packages/udp-tracker-server/src/handlers/mod.rs b/packages/udp-tracker-server/src/handlers/mod.rs index acbaed905..4303044d9 100644 --- a/packages/udp-tracker-server/src/handlers/mod.rs +++ b/packages/udp-tracker-server/src/handlers/mod.rs @@ -10,9 +10,9 @@ use std::sync::Arc; use std::time::Instant; use announce::handle_announce; -use aquatic_udp_protocol::{Request, Response, TransactionId}; use bittorrent_tracker_core::MAX_SCRAPE_TORRENTS; use bittorrent_udp_tracker_core::container::UdpTrackerCoreContainer; +use bittorrent_udp_tracker_protocol::{Request, Response, TransactionId}; use connect::handle_connect; use error::handle_error; use scrape::handle_scrape; diff --git a/packages/udp-tracker-server/src/handlers/scrape.rs b/packages/udp-tracker-server/src/handlers/scrape.rs index 8bd86f509..126e25913 100644 --- a/packages/udp-tracker-server/src/handlers/scrape.rs +++ b/packages/udp-tracker-server/src/handlers/scrape.rs @@ -3,15 +3,15 @@ use std::net::SocketAddr; use std::ops::Range; use std::sync::Arc; -use aquatic_udp_protocol::{ - NumberOfDownloads, NumberOfPeers, Response, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, TransactionId, -}; use bittorrent_udp_tracker_core::services::scrape::ScrapeService; use bittorrent_udp_tracker_core::{self}; -use torrust_tracker_primitives::core::ScrapeData; +use bittorrent_udp_tracker_protocol::{ + NumberOfDownloads, NumberOfPeers, Response, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, TransactionId, +}; use torrust_tracker_primitives::service_binding::ServiceBinding; +use torrust_tracker_primitives::ScrapeData; use tracing::{instrument, Level}; -use zerocopy::network_endian::I32; +use zerocopy::byteorder::network_endian::I32; use crate::error::Error; use crate::event::{ConnectionContext, Event, UdpRequestKind}; @@ -89,12 +89,12 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; - use aquatic_udp_protocol::{ + use bittorrent_tracker_core::torrent::repository::in_memory::InMemoryTorrentRepository; + use bittorrent_udp_tracker_core::connection_cookie::{gen_remote_fingerprint, make}; + use bittorrent_udp_tracker_protocol::{ InfoHash, NumberOfDownloads, NumberOfPeers, PeerId, Response, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, TransactionId, }; - use bittorrent_tracker_core::torrent::repository::in_memory::InMemoryTorrentRepository; - use bittorrent_udp_tracker_core::connection_cookie::{gen_remote_fingerprint, make}; use torrust_tracker_events::bus::SenderStatus; use torrust_tracker_primitives::peer::fixture::PeerBuilder; use torrust_tracker_primitives::service_binding::{Protocol, ServiceBinding}; @@ -163,7 +163,7 @@ mod tests { let peer_id = PeerId([255u8; 20]); let peer = PeerBuilder::default() - .with_peer_id(&peer_id) + .with_peer_id(&torrust_tracker_primitives::PeerId(peer_id.0)) .with_peer_address(*remote_addr) .with_bytes_left_to_download(0) .into(); @@ -227,7 +227,7 @@ mod tests { } mod with_a_public_tracker { - use aquatic_udp_protocol::{NumberOfDownloads, NumberOfPeers, TorrentScrapeStatistics}; + use bittorrent_udp_tracker_protocol::{NumberOfDownloads, NumberOfPeers, TorrentScrapeStatistics}; use crate::handlers::scrape::tests::scrape_request::{add_a_sample_seeder_and_scrape, match_scrape_response}; use crate::handlers::tests::initialize_core_tracker_services_for_public_tracker; @@ -254,7 +254,7 @@ mod tests { mod with_a_whitelisted_tracker { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - use aquatic_udp_protocol::{InfoHash, NumberOfDownloads, NumberOfPeers, TorrentScrapeStatistics}; + use bittorrent_udp_tracker_protocol::{InfoHash, NumberOfDownloads, NumberOfPeers, TorrentScrapeStatistics}; use torrust_tracker_primitives::service_binding::{Protocol, ServiceBinding}; use crate::handlers::handle_scrape; diff --git a/packages/udp-tracker-server/src/lib.rs b/packages/udp-tracker-server/src/lib.rs index 58a3830e1..4fe6e7934 100644 --- a/packages/udp-tracker-server/src/lib.rs +++ b/packages/udp-tracker-server/src/lib.rs @@ -24,7 +24,7 @@ //! > **NOTICE**: [BEP-41](https://www.bittorrent.org/beps/bep_0041.html) is not //! > implemented yet. //! -//! > **NOTICE**: we are using the [`aquatic_udp_protocol`](https://crates.io/crates/aquatic_udp_protocol) +//! > **NOTICE**: we are using the [`bittorrent_udp_tracker_protocol`](https://crates.io/crates/bittorrent_udp_tracker_protocol) //! > crate so requests and responses are handled by it. //! //! > **NOTICE**: all values are send in network byte order ([big endian](https://en.wikipedia.org/wiki/Endianness)). @@ -52,8 +52,8 @@ //! is designed to be as simple as possible. It uses a single UDP port and //! supports only three types of requests: `Connect`, `Announce` and `Scrape`. //! -//! Request are parsed from UDP packets using the [`aquatic_udp_protocol`](https://crates.io/crates/aquatic_udp_protocol). -//! And then the response is also build using the [`aquatic_udp_protocol`](https://crates.io/crates/aquatic_udp_protocol) +//! Request are parsed from UDP packets using the [`bittorrent_udp_tracker_protocol`](https://crates.io/crates/bittorrent_udp_tracker_protocol). +//! And then the response is also build using the [`bittorrent_udp_tracker_protocol`](https://crates.io/crates/bittorrent_udp_tracker_protocol) //! and converted to a UDP packet. //! //! ```text @@ -139,12 +139,12 @@ //! //! **Connect request (parsed struct)** //! -//! After parsing the UDP packet, the [`ConnectRequest`](aquatic_udp_protocol::request::ConnectRequest) +//! After parsing the UDP packet, the [`ConnectRequest`](bittorrent_udp_tracker_protocol::request::ConnectRequest) //! request struct will look like this: //! //! Field | Type | Example //! -----------------|----------------------------------------------------------------|------------- -//! `transaction_id` | [`TransactionId`](aquatic_udp_protocol::common::TransactionId) | `1950635409` +//! `transaction_id` | [`TransactionId`](bittorrent_udp_tracker_protocol::common::TransactionId) | `1950635409` //! //! #### Connect Response //! @@ -186,13 +186,13 @@ //! //! **Connect response (struct)** //! -//! Before building the UDP packet, the [`ConnectResponse`](aquatic_udp_protocol::response::ConnectResponse) +//! Before building the UDP packet, the [`ConnectResponse`](bittorrent_udp_tracker_protocol::response::ConnectResponse) //! struct will look like this: //! //! Field | Type | Example //! -----------------|----------------------------------------------------------------|------------------------- -//! `connection_id` | [`ConnectionId`](aquatic_udp_protocol::common::ConnectionId) | `-4226491872051668937` -//! `transaction_id` | [`TransactionId`](aquatic_udp_protocol::common::TransactionId) | `-888840697` +//! `connection_id` | [`ConnectionId`](bittorrent_udp_tracker_protocol::common::ConnectionId) | `-4226491872051668937` +//! `transaction_id` | [`TransactionId`](bittorrent_udp_tracker_protocol::common::TransactionId) | `-888840697` //! //! **Connect specification** //! @@ -321,26 +321,26 @@ //! //! **Announce request (parsed struct)** //! -//! After parsing the UDP packet, the [`AnnounceRequest`](aquatic_udp_protocol::request::AnnounceRequest) +//! After parsing the UDP packet, the [`AnnounceRequest`](bittorrent_udp_tracker_protocol::AnnounceRequest) //! struct will contain the following fields: //! //! Field | Type | Example //! -------------------|---------------------------------------------------------------- |-------------- -//! `connection_id` | [`ConnectionId`](aquatic_udp_protocol::common::ConnectionId) | `-4226491872051668937` -//! `transaction_id` | [`TransactionId`](aquatic_udp_protocol::common::TransactionId) | `-1560718264` -//! `info_hash` | [`InfoHash`](aquatic_udp_protocol::common::InfoHash) | `[3,132,5,72,100,58,242,167,182,58,159,92,188,163,72,188,113,80,202,58]` -//! `peer_id` | [`PeerId`](aquatic_udp_protocol::common::PeerId) | `[45,113,66,52,52,49,48,45,41,83,100,126,100,101,52,120,77,112,54,68]` -//! `bytes_downloaded` | [`NumberOfBytes`](aquatic_udp_protocol::common::NumberOfBytes) | `0` -//! `bytes_uploaded` | [`TransactionId`](aquatic_udp_protocol::common::NumberOfBytes) | `0` -//! `event` | [`AnnounceEvent`](aquatic_udp_protocol::request::AnnounceEvent) | `Started` -//! `ip_address` | [`Ipv4Addr`](aquatic_udp_protocol::common::ConnectionId) | `None` -//! `peers_wanted` | [`NumberOfPeers`](aquatic_udp_protocol::common::NumberOfPeers) | `200` -//! `port` | [`Port`](aquatic_udp_protocol::common::Port) | `17548` +//! `connection_id` | [`ConnectionId`](bittorrent_udp_tracker_protocol::common::ConnectionId) | `-4226491872051668937` +//! `transaction_id` | [`TransactionId`](bittorrent_udp_tracker_protocol::common::TransactionId) | `-1560718264` +//! `info_hash` | [`InfoHash`](bittorrent_udp_tracker_protocol::common::InfoHash) | `[3,132,5,72,100,58,242,167,182,58,159,92,188,163,72,188,113,80,202,58]` +//! `peer_id` | [`PeerId`](bittorrent_udp_tracker_protocol::common::PeerId) | `[45,113,66,52,52,49,48,45,41,83,100,126,100,101,52,120,77,112,54,68]` +//! `bytes_downloaded` | [`NumberOfBytes`](bittorrent_udp_tracker_protocol::common::NumberOfBytes) | `0` +//! `bytes_uploaded` | [`TransactionId`](bittorrent_udp_tracker_protocol::common::NumberOfBytes) | `0` +//! `event` | [`AnnounceEvent`](bittorrent_udp_tracker_protocol::AnnounceEvent) | `Started` +//! `ip_address` | [`Ipv4Addr`](bittorrent_udp_tracker_protocol::common::ConnectionId) | `None` +//! `peers_wanted` | [`NumberOfPeers`](bittorrent_udp_tracker_protocol::common::NumberOfPeers) | `200` +//! `port` | [`Port`](bittorrent_udp_tracker_protocol::common::Port) | `17548` //! //! > **NOTICE**: the `peers_wanted` field is the `num_want` field in the UDP //! > packet. //! -//! We are using a wrapper struct for the aquatic [`AnnounceRequest`](aquatic_udp_protocol::request::AnnounceRequest) +//! We are using a wrapper struct for the aquatic [`AnnounceRequest`](bittorrent_udp_tracker_protocol::AnnounceRequest) //! struct, because we have our internal [`InfoHash`](bittorrent_primitives::info_hash::InfoHash) //! struct. //! @@ -446,16 +446,16 @@ //! //! **Announce response (struct)** //! -//! The [`AnnounceResponse`](aquatic_udp_protocol::response::AnnounceResponse) +//! The [`AnnounceResponse`](bittorrent_udp_tracker_protocol::response::AnnounceResponse) //! struct will have the following fields: //! //! Field | Type | Example //! --------------------|------------------------------------------------------------------------|-------------- -//! `transaction_id` | [`TransactionId`](aquatic_udp_protocol::common::TransactionId) | `-1560718264` -//! `announce_interval` | [`AnnounceInterval`](aquatic_udp_protocol::common::AnnounceInterval) | `120` -//! `leechers` | [`NumberOfPeers`](aquatic_udp_protocol::common::NumberOfPeers) | `0` -//! `seeders` | [`NumberOfPeers`](aquatic_udp_protocol::common::NumberOfPeers) | `1` -//! `peers` | Vector of [`ResponsePeer`](aquatic_udp_protocol::common::ResponsePeer) | `[]` +//! `transaction_id` | [`TransactionId`](bittorrent_udp_tracker_protocol::common::TransactionId) | `-1560718264` +//! `announce_interval` | [`AnnounceInterval`](bittorrent_udp_tracker_protocol::AnnounceInterval) | `120` +//! `leechers` | [`NumberOfPeers`](bittorrent_udp_tracker_protocol::common::NumberOfPeers) | `0` +//! `seeders` | [`NumberOfPeers`](bittorrent_udp_tracker_protocol::common::NumberOfPeers) | `1` +//! `peers` | Vector of [`ResponsePeer`](bittorrent_udp_tracker_protocol::common::ResponsePeer) | `[]` //! //! **Announce specification** //! @@ -530,14 +530,14 @@ //! //! **Scrape request (parsed struct)** //! -//! After parsing the UDP packet, the [`ScrapeRequest`](aquatic_udp_protocol::request::ScrapeRequest) +//! After parsing the UDP packet, the [`ScrapeRequest`](bittorrent_udp_tracker_protocol::request::ScrapeRequest) //! struct will look like this: //! //! Field | Type | Example //! -----------------|----------------------------------------------------------------|---------------------------------------------------------------------------- -//! `connection_id` | [`ConnectionId`](aquatic_udp_protocol::common::ConnectionId) | `-4226491872051668937` -//! `transaction_id` | [`TransactionId`](aquatic_udp_protocol::common::TransactionId) | `-1560718264` -//! `info_hashes` | Vector of [`InfoHash`](aquatic_udp_protocol::common::InfoHash) | `[[3,132,5,72,100,58,242,167,182,58,159,92,188,163,72,188,113,80,202,58]]` +//! `connection_id` | [`ConnectionId`](bittorrent_udp_tracker_protocol::common::ConnectionId) | `-4226491872051668937` +//! `transaction_id` | [`TransactionId`](bittorrent_udp_tracker_protocol::common::TransactionId) | `-1560718264` +//! `info_hashes` | Vector of [`InfoHash`](bittorrent_udp_tracker_protocol::common::InfoHash) | `[[3,132,5,72,100,58,242,167,182,58,159,92,188,163,72,188,113,80,202,58]]` //! //! #### Scrape Response //! @@ -591,13 +591,13 @@ //! //! **Scrape response (struct)** //! -//! Before building the UDP packet, the [`ScrapeResponse`](aquatic_udp_protocol::response::ScrapeResponse) +//! Before building the UDP packet, the [`ScrapeResponse`](bittorrent_udp_tracker_protocol::response::ScrapeResponse) //! struct will look like this: //! //! Field | Type | Example //! -----------------|-------------------------------------------------------------------------------------------------|--------------- -//! `transaction_id` | [`TransactionId`](aquatic_udp_protocol::common::TransactionId) | `-1560718264` -//! `torrent_stats` | Vector of [`TorrentScrapeStatistics`](aquatic_udp_protocol::response::TorrentScrapeStatistics) | `[]` +//! `transaction_id` | [`TransactionId`](bittorrent_udp_tracker_protocol::common::TransactionId) | `-1560718264` +//! `torrent_stats` | Vector of [`TorrentScrapeStatistics`](bittorrent_udp_tracker_protocol::response::TorrentScrapeStatistics) | `[]` //! //! **Scrape specification** //! @@ -679,9 +679,8 @@ pub struct RawRequest { pub(crate) mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; use bittorrent_udp_tracker_core::event::Event; - use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch}; + use torrust_tracker_primitives::{peer, AnnounceEvent, DurationSinceUnixEpoch, NumberOfBytes, PeerId}; pub fn sample_peer() -> peer::Peer { peer::Peer { diff --git a/packages/udp-tracker-server/src/server/processor.rs b/packages/udp-tracker-server/src/server/processor.rs index dd6ba633d..591cbe5aa 100644 --- a/packages/udp-tracker-server/src/server/processor.rs +++ b/packages/udp-tracker-server/src/server/processor.rs @@ -3,9 +3,9 @@ use std::net::SocketAddr; use std::sync::Arc; use std::time::Duration; -use aquatic_udp_protocol::Response; use bittorrent_udp_tracker_core::container::UdpTrackerCoreContainer; use bittorrent_udp_tracker_core::{self}; +use bittorrent_udp_tracker_protocol::Response; use tokio::time::Instant; use torrust_tracker_primitives::service_binding::{Protocol, ServiceBinding}; use tracing::{instrument, Level}; diff --git a/packages/udp-tracker-server/src/statistics/event/handler/error.rs b/packages/udp-tracker-server/src/statistics/event/handler/error.rs index 63e480ca5..80b2c5701 100644 --- a/packages/udp-tracker-server/src/statistics/event/handler/error.rs +++ b/packages/udp-tracker-server/src/statistics/event/handler/error.rs @@ -1,4 +1,4 @@ -use aquatic_udp_protocol::PeerClient; +use bittorrent_udp_tracker_protocol::PeerClient; use torrust_tracker_metrics::label::LabelSet; use torrust_tracker_metrics::{label_name, metric_name}; use torrust_tracker_primitives::DurationSinceUnixEpoch; diff --git a/packages/udp-tracker-server/tests/common/fixtures.rs b/packages/udp-tracker-server/tests/common/fixtures.rs index f4066c67a..38b156dc0 100644 --- a/packages/udp-tracker-server/tests/common/fixtures.rs +++ b/packages/udp-tracker-server/tests/common/fixtures.rs @@ -1,5 +1,5 @@ -use aquatic_udp_protocol::TransactionId; use bittorrent_primitives::info_hash::InfoHash; +use bittorrent_udp_tracker_protocol::TransactionId; use rand::prelude::*; /// Returns a random info hash. diff --git a/packages/udp-tracker-server/tests/server/asserts.rs b/packages/udp-tracker-server/tests/server/asserts.rs index 37c848e06..4ad91963e 100644 --- a/packages/udp-tracker-server/tests/server/asserts.rs +++ b/packages/udp-tracker-server/tests/server/asserts.rs @@ -1,4 +1,4 @@ -use aquatic_udp_protocol::{Response, TransactionId}; +use bittorrent_udp_tracker_protocol::{Response, TransactionId}; pub fn get_error_response_message(response: &Response) -> Option { match response { diff --git a/packages/udp-tracker-server/tests/server/contract.rs b/packages/udp-tracker-server/tests/server/contract.rs index 350f3b8eb..8515fcec3 100644 --- a/packages/udp-tracker-server/tests/server/contract.rs +++ b/packages/udp-tracker-server/tests/server/contract.rs @@ -5,8 +5,8 @@ use core::panic; -use aquatic_udp_protocol::{ConnectRequest, ConnectionId, Response, TransactionId}; use bittorrent_tracker_client::udp::client::UdpTrackerClient; +use bittorrent_udp_tracker_protocol::{ConnectRequest, ConnectionId, Response, TransactionId}; use torrust_tracker_configuration::DEFAULT_TIMEOUT; use torrust_tracker_test_helpers::{configuration, logging}; use torrust_udp_tracker_server::MAX_PACKET_SIZE; @@ -67,8 +67,8 @@ async fn should_return_a_bad_request_response_when_the_client_sends_an_empty_req } mod receiving_a_connection_request { - use aquatic_udp_protocol::{ConnectRequest, TransactionId}; use bittorrent_tracker_client::udp::client::UdpTrackerClient; + use bittorrent_udp_tracker_protocol::{ConnectRequest, TransactionId}; use torrust_tracker_configuration::DEFAULT_TIMEOUT; use torrust_tracker_test_helpers::{configuration, logging}; @@ -108,11 +108,11 @@ mod receiving_a_connection_request { mod receiving_an_announce_request { use std::net::Ipv4Addr; - use aquatic_udp_protocol::{ + use bittorrent_tracker_client::udp::client::UdpTrackerClient; + use bittorrent_udp_tracker_protocol::{ AnnounceActionPlaceholder, AnnounceEvent, AnnounceRequest, ConnectionId, InfoHash, NumberOfBytes, NumberOfPeers, PeerId, PeerKey, Port, TransactionId, }; - use bittorrent_tracker_client::udp::client::UdpTrackerClient; use torrust_tracker_configuration::DEFAULT_TIMEOUT; use torrust_tracker_test_helpers::logging::logs_contains_a_line_with; use torrust_tracker_test_helpers::{configuration, logging}; @@ -136,7 +136,7 @@ mod receiving_an_announce_request { c_id: ConnectionId, info_hash: bittorrent_primitives::info_hash::InfoHash, client: &UdpTrackerClient, - ) -> aquatic_udp_protocol::Response { + ) -> bittorrent_udp_tracker_protocol::Response { let announce_request = build_sample_announce_request(tx_id, c_id, client.client.socket.local_addr().unwrap().port(), info_hash); @@ -303,8 +303,8 @@ mod receiving_an_announce_request { } mod receiving_an_scrape_request { - use aquatic_udp_protocol::{ConnectionId, InfoHash, ScrapeRequest, TransactionId}; use bittorrent_tracker_client::udp::client::UdpTrackerClient; + use bittorrent_udp_tracker_protocol::{ConnectionId, InfoHash, ScrapeRequest, TransactionId}; use torrust_tracker_configuration::DEFAULT_TIMEOUT; use torrust_tracker_test_helpers::{configuration, logging}; diff --git a/project-words.txt b/project-words.txt index 49f4d6f01..c7d9e0557 100644 --- a/project-words.txt +++ b/project-words.txt @@ -10,6 +10,7 @@ alekitto analyse appuser Arvid +asdh ASMS asyn autoclean @@ -40,6 +41,8 @@ CALLSITE camino canonicalize canonicalized +cdylib +Celano certbot chrono Cinstrument @@ -56,7 +59,6 @@ connectionless Containerfile conv curr -cdylib cvar Cyberneering cyclomatic @@ -194,18 +196,20 @@ println programatik proot proto +PRRT PUID qbittorrent QJSF +quickcheck Quickstart Radeon RAII Rakshasa randomised Rasterbar -recognised realpath reannounce +recognised recompiles referer Registar @@ -213,8 +217,8 @@ repomix repr reqs reqwest -reuseaddr rerequests +reuseaddr ringbuf ringsize rlib @@ -254,6 +258,7 @@ Subissue Subissues subkey subsec +substeps supertrait Swatinem Swiftbit @@ -262,7 +267,6 @@ sysmalloc sysret taiki taplo -trunc tdyne Tebibytes tempfile @@ -282,18 +286,20 @@ trackerid Trackon triaging trixie +trunc ttwu typenum udpv Unamed underflows uninit -unrecognised -unrepresentable Uninit unittests unparked Unparker +unrecognised +unrepresentable +unreviewed Unsendable unsync untuple @@ -310,13 +316,13 @@ vtable Vuze wakelist wakeup +webtorrent WEBUI Weidendorfer Werror whitespaces Xacrimon XBTT -zstd Xdebug Xeon Xtorrent @@ -324,3 +330,4 @@ Xunlei xxxxxxxxxxxxxxxxxxxxd yyyyyyyyyyyyyyyyyyyyd zerocopy +zstd