From f8c34444fbac768a58bbedd7b57b7ce43c1e047c Mon Sep 17 00:00:00 2001 From: "glm-5.3-flash" Date: Tue, 22 Sep 2026 12:41:30 +0000 Subject: [PATCH] =?UTF-8?q?rfc1928:=20the=20targeted=20fork=20=E2=80=94=20?= =?UTF-8?q?rename=20+=20convention=20pass=20(ADR-013)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mechanical half of the posture change: src/vendor/ -> src/rfc1928/ (module alksocks::rfc1928), tests/vendor-differential.rs -> tests/rfc1928-differential.rs (git mv — history preserved). - Fork point pinned: the tree as of 5b18d4d (the extraction review's gate commit); provenance permanent in the module docs and the extended LICENSE notice (upstream MIT preserved, fork-point + post-fork modification statement added — extend, never replace) - The lint-allow posture dissolved: all module-level #[allow]s removed and the suppressed smells fixed (u8::max_value() -> u8::MAX, needless lifetimes elided, match_like_matches_macro applied, io::Error::new(Other) -> io::Error::other, the AuthMethod::new self-taking signature renamed into_starting_state, the F-4 ToSocketAddrs string re-worded — it no longer names the cut resolve_dns(), the unused ready! macro and dead AuthenticationMethod::from_u8 removed, the upstream commented-out async-std note folded into the impl doc) - Module docs rewritten: ownership statement (project conventions apply unconditionally), extraction provenance (ADR-011) + ownership (ADR-013) + maintenance posture (behavioral reference, hand-port path) - Differential suite survives the rename untouched (32 tests pin bytes, not upstream text) Verification: cargo test (38+32 green), cargo clippy --all-targets -D warnings, cargo fmt --check, cargo check/clippy --target wasm32-unknown-unknown, cargo test --all-features, cargo doc --no-deps — all pass. --- .../decisions/013-targeted-fork.md | 2 +- src/lib.rs | 8 +-- src/{vendor => rfc1928}/LICENSE | 26 ++++--- src/{vendor => rfc1928}/client.rs | 23 +++--- src/{vendor => rfc1928}/mod.rs | 72 ++++++++----------- src/{vendor => rfc1928}/server.rs | 63 +++++++--------- src/{vendor => rfc1928}/util/mod.rs | 5 +- src/{vendor => rfc1928}/util/stream.rs | 36 ++++------ src/{vendor => rfc1928}/util/target_addr.rs | 67 +++++++---------- ...ifferential.rs => rfc1928-differential.rs} | 41 +++++------ 10 files changed, 151 insertions(+), 192 deletions(-) rename src/{vendor => rfc1928}/LICENSE (53%) rename src/{vendor => rfc1928}/client.rs (97%) rename src/{vendor => rfc1928}/mod.rs (88%) rename src/{vendor => rfc1928}/server.rs (94%) rename src/{vendor => rfc1928}/util/mod.rs (59%) rename src/{vendor => rfc1928}/util/stream.rs (75%) rename src/{vendor => rfc1928}/util/target_addr.rs (84%) rename tests/{vendor-differential.rs => rfc1928-differential.rs} (96%) diff --git a/docs/architecture/decisions/013-targeted-fork.md b/docs/architecture/decisions/013-targeted-fork.md index a57f0c3..03e4f6a 100644 --- a/docs/architecture/decisions/013-targeted-fork.md +++ b/docs/architecture/decisions/013-targeted-fork.md @@ -96,7 +96,7 @@ vendor.md, and the review task. "The tree as of the review commit fork-point statement + the modification note. - **The reference checkout survives as a behavioral reference.** `/workspace/fast-socks5` stays for differential testing of RFC - edge cases; the differential suite (tests/vendor-differential.rs) + edge cases; the differential suite (tests/rfc1928-differential.rs) pins bytes, not upstream text, so it survives the rename untouched. Textual-diff discipline is retired; behavioral differential testing is retained. diff --git a/src/lib.rs b/src/lib.rs index 4e3b125..85d850c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,14 +10,14 @@ //! assembly shapes (`local`), never in the protocol layer. //! //! The RFC wire vocabulary and the explicit typestate live in -//! [`vendor`]: an extraction of fast-socks5 v1.0.0's `T`-generic +//! [`rfc1928`]: a targeted fork of fast-socks5 v1.0.0's `T`-generic //! subset (MIT, third-party upstream) carrying two deliberate changes //! (BIND support, `new_reply` publication) — see -//! `docs/architecture/vendor.md` and ADR-011. +//! `docs/architecture/rfc1928.md`, ADR-013, and ADR-011. //! //! Architecture: `docs/architecture/overview.md` (spec set: -//! wire.md, producer.md, consumer.md, vendor.md). +//! wire.md, producer.md, consumer.md, rfc1928.md). #![forbid(unsafe_code)] -pub mod vendor; +pub mod rfc1928; diff --git a/src/vendor/LICENSE b/src/rfc1928/LICENSE similarity index 53% rename from src/vendor/LICENSE rename to src/rfc1928/LICENSE index a80ef1a..b388591 100644 --- a/src/vendor/LICENSE +++ b/src/rfc1928/LICENSE @@ -4,13 +4,23 @@ https://github.com/dizda/fast-socks5 Copyright (c) 2025 Jonathan Dizdarevic -The files under this `src/vendor/` directory are an extraction of -fast-socks5 v1.0.0 (https://github.com/dizda/fast-socks5), carried -in-tree by alksocks under ADR-011 (docs/architecture/decisions/ -011-inline-vendor.md). Upstream has no per-file license headers; this -bundled notice is the license obligation's carrier. The extraction -cut and the carried changes are recorded in -docs/architecture/vendor.md. +The files under this `src/rfc1928/` directory were extracted from +fast-socks5 v1.0.0 (https://github.com/dizda/fast-socks5) and are +carried in-tree by alksocks (extraction per ADR-011, +docs/architecture/decisions/011-inline-vendor.md; extraction range +git 57b107b..5b18d4d; fork point = the tree as of commit 5b18d4d, +the extraction review's gate commit — ownership per ADR-013, +docs/architecture/decisions/013-targeted-fork.md). Upstream has no +per-file license headers; this bundled notice is the license +obligation's carrier. The extraction cut and the fork's deliberate +modifications are recorded in docs/architecture/rfc1928.md. + +Modifications after the fork point are Copyright (c) 2026 the +alksocks developers, licensed under the crate license +(MIT OR Apache-2.0, per alksocks' Cargo.toml); the upstream MIT +notice above is preserved per its terms. The upstream project is +third-party and unmodified in place — see the ADRs for what was +changed after the fork point. --- @@ -32,4 +42,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/src/vendor/client.rs b/src/rfc1928/client.rs similarity index 97% rename from src/vendor/client.rs rename to src/rfc1928/client.rs index 5ab84a6..72ed1bc 100644 --- a/src/vendor/client.rs +++ b/src/rfc1928/client.rs @@ -1,7 +1,7 @@ -//! Vendored from fast-socks5 v1.0.0 (extraction — see the module doc -//! in `mod.rs`; filled by the client-session extraction task). +//! Forked from fast-socks5 v1.0.0 (upstream `src/client.rs` — +//! extraction; ownership per ADR-013, see the crate root's `mod.rs`). //! -//! Upstream file: `src/client.rs`. Extracted region: the `T`-generic +//! Extracted region: the `T`-generic //! client session — [`Socks5Stream`] with the `use_stream` path //! (the version/method greeting, the method-choice read, the //! password-auth sub-handshake, the request encoder `request_header`, @@ -12,7 +12,8 @@ //! `pump_bidi`-shaped consumers and the front doors can pipe through //! it. //! -//! Not extracted (vendor.md's cut table row 2): `Socks5Datagram` +//! Not forked (rfc1928.md's not-forked inventory row 2): +//! `Socks5Datagram` //! (struct + impl — the kernel `UdpSocket` associate client; the //! crate's associate session is first-party per ADR-003's handle //! swap) and the `Socks5Stream` convenience impl @@ -41,17 +42,11 @@ //! Tests are first-party (upstream's client test module rides the cut //! net region): raw-byte mock servers over tokio duplex streams, the //! pinned byte sequences written directly — keeping the client's wire -//! expectations uncorrelated from the vendored server typestate. -//! -//! Verbatim-code lint posture: the copied regions are upstream -//! v1.0.0 code (minus the cut + adjustments), so newer-clippy style -//! lints on upstream style are allowed at module level rather than -//! churned per-line — the diff vs the reference checkout stays -//! mechanical (review task). +//! expectations uncorrelated from the forked server typestate. -use crate::vendor::util::stream::read_exact; -use crate::vendor::util::target_addr::{read_address, TargetAddr}; -use crate::vendor::{consts, AuthenticationMethod, ReplyError, Result, Socks5Command, SocksError}; +use crate::rfc1928::util::stream::read_exact; +use crate::rfc1928::util::target_addr::{read_address, TargetAddr}; +use crate::rfc1928::{consts, AuthenticationMethod, ReplyError, Result, Socks5Command, SocksError}; use std::io; use std::net::SocketAddr; use std::pin::Pin; diff --git a/src/vendor/mod.rs b/src/rfc1928/mod.rs similarity index 88% rename from src/vendor/mod.rs rename to src/rfc1928/mod.rs index fc5777a..4e9dc40 100644 --- a/src/vendor/mod.rs +++ b/src/rfc1928/mod.rs @@ -1,44 +1,45 @@ -//! The inline vendor: an extraction of fast-socks5 v1.0.0's +//! The RFC 1928 core: a targeted fork of fast-socks5 v1.0.0's //! `T`-generic subset (MIT, third-party upstream — //! github.com/dizda/fast-socks5; `/workspace/fast-socks5` is the -//! read-only reference checkout, kept for differential testing of RFC -//! edge cases). +//! read-only reference checkout, kept for differential testing of +//! RFC edge cases). //! -//! Extracted per ADR-011 §Amendment (docs/architecture/decisions/ -//! 011-inline-vendor.md): only the code already generic over -//! `T: AsyncRead + AsyncWrite + Unpin` rides in-tree — the kernel-net -//! handlers, the `socks4` module, and the legacy `Config`/ -//! `Authentication` layer are **not** copied. The extraction cut, the -//! two carried changes (BIND support, `new_reply` publication), and -//! the extraction-time adjustments (no `anyhow`, no `log`, no -//! `async-trait`) are recorded in `docs/architecture/vendor.md`. +//! Arrived by extraction (ADR-011 §Amendment, docs/architecture/ +//! decisions/011-inline-vendor.md): only the code already generic +//! over `T: AsyncRead + AsyncWrite + Unpin` was copied in-tree — the +//! kernel-net handlers, the `socks4` module, and the legacy `Config`/ +//! `Authentication` layer were **not** copied. The extraction cut, +//! the fork's two carried changes (BIND support, `new_reply` +//! publication), and the extraction-time adjustments (no `anyhow`, +//! no `log`, no `async-trait`) are recorded in +//! `docs/architecture/rfc1928.md`. +//! +//! Ownership (ADR-013, docs/architecture/decisions/013-targeted-fork.md): +//! this is fully owned code — project conventions apply +//! unconditionally (no vendor carve-outs, no verbatim preservation, +//! no lint-allow posture). Fork point: the tree as of the +//! extraction-review commit (`5b18d4d`); extraction provenance in +//! git history (`57b107b..5b18d4d`). A future upstream fix that +//! matters is hand-ported against the reference checkout and guarded +//! by the differential suite. //! //! This file is upstream's `src/lib.rs` vocabulary region (the //! wire-vocabulary extraction task): the `consts`, the command/auth //! enums, the reply-code codec, the UDP datagram codec, and the error -//! vocabulary pruned to the extracted region's consumers. The -//! typestate (upstream `server.rs`, minus the cut regions) lives in +//! vocabulary pruned to the region's consumers. The typestate +//! (upstream `server.rs`, minus the cut regions) lives in //! [`server`]; the client session (upstream `client.rs`, the //! `use_stream` path + the `AsyncRead`/`AsyncWrite` impls) lives in //! [`client`]. //! //! License: upstream is MIT with no per-file headers — the bundled -//! notice in `this module's LICENSE file` (`src/vendor/LICENSE`, v1.0.0 -//! attribution) is the license obligation's carrier. +//! notice (`LICENSE`, this module's directory, v1.0.0 attribution + +//! the fork-point statement) is the license obligation's carrier. //! -//! Maintenance posture: the vendored code is first-class, co-evolves -//! with the wrapper, and is the crate's only RFC 1928 wire vocabulary -//! ("no second codec, ever" — ADR-011). A future upstream release is -//! re-based against deliberately, never absorbed silently. -//! -//! Verbatim-code lint posture: the copied regions are upstream -//! v1.0.0 code (minus the cut + adjustments), so newer-clippy style -//! lints on upstream style are allowed at module level rather than -//! churned per-line — the diff vs the reference checkout stays -//! mechanical (review task). - -#![allow(clippy::needless_lifetimes)] -#![allow(clippy::redundant_closure)] +//! Maintenance posture: this code co-evolves with the wrapper and is +//! the crate's only RFC 1928 wire vocabulary ("no second codec, +//! ever" — ADR-011's rationale, carried). Nothing is absorbed +//! silently from upstream; nothing waits on upstream. pub mod client; pub mod server; @@ -119,7 +120,6 @@ pub enum AuthenticationMethod { Password { username: String, password: String }, } -#[allow(dead_code)] impl AuthenticationMethod { #[inline] #[rustfmt::skip] @@ -130,16 +130,6 @@ impl AuthenticationMethod { consts::SOCKS5_AUTH_METHOD_PASSWORD } } - - #[inline] - #[rustfmt::skip] - fn from_u8(code: u8) -> Option { - match code { - consts::SOCKS5_AUTH_METHOD_NONE => Some(AuthenticationMethod::None), - consts::SOCKS5_AUTH_METHOD_PASSWORD => Some(AuthenticationMethod::Password { username: "test".to_string(), password: "test".to_string()}), - _ => None, - } - } } impl fmt::Display for AuthenticationMethod { @@ -299,9 +289,7 @@ pub fn new_udp_header(target_addr: T) -> Result, UdpHea } /// Parse data from UDP client on raw buffer, return (frag, target_addr, payload). -pub async fn parse_udp_request<'a>( - mut req: &'a [u8], -) -> Result<(u8, TargetAddr, &'a [u8]), UdpHeaderError> { +pub async fn parse_udp_request(mut req: &[u8]) -> Result<(u8, TargetAddr, &[u8]), UdpHeaderError> { let rsv = read_exact!(req, [0u8; 2]).map_err(UdpHeaderError::ReadingError)?; if !rsv.eq(&[0u8; 2]) { diff --git a/src/vendor/server.rs b/src/rfc1928/server.rs similarity index 94% rename from src/vendor/server.rs rename to src/rfc1928/server.rs index f1b05c1..628cbc9 100644 --- a/src/vendor/server.rs +++ b/src/rfc1928/server.rs @@ -1,24 +1,24 @@ -//! Vendored from fast-socks5 v1.0.0 (extraction — see the module doc -//! in `mod.rs`; filled by the server-typestate extraction task). +//! Forked from fast-socks5 v1.0.0 (upstream `src/server.rs` — +//! extraction; ownership per ADR-013, see the crate root's `mod.rs`). //! -//! Upstream file: `src/server.rs`. Extracted region: the explicit +//! Extracted region: the explicit //! typestate (`states`, `Socks5ServerProtocol`, auth //! negotiation with the `AuthMethod` machinery, `read_command`, //! `reply_success`, `reply_error`, `transfer`, `wait_on_tcp`, //! `ErrorContext`), `SocksServerError`, and the published reply //! encoder `new_reply` (carried change #2 — upstream's is private, -//! `fn new_reply` at `server.rs:1380`; in-tree it is `pub` so the +//! `fn new_reply` at `server.rs:1380`; here it is `pub` so the //! BIND reply#2 writer and consumer reply readers reuse the //! 10/22-byte reply form instead of re-encoding it). The cut regions //! (the legacy `Socks5Server`/`Socks5Socket`/`Config`/`Authentication` //! layer, the `run_*` kernel-net handlers, `DnsResolveHelper`, the -//! net-only tests) stay in `/workspace/fast-socks5` — the cut table -//! row 1 in `docs/architecture/vendor.md`. +//! net-only tests) stay in `/workspace/fast-socks5` — the not-forked +//! inventory row 1 in `docs/architecture/rfc1928.md`. //! //! Extraction-time adjustments (mechanical, ADR-011 §Amendment): //! `log` calls dropped; `anyhow::Context` dropped; the -//! `#[macro_export]` on `auth_method_enums!` converted to -//! `pub(crate) use` scoping (same adjustment as `read_exact!`); +//! `#[macro_export]` on `auth_method_enums!` converted to `pub` +//! scoping within this module (same adjustment as `read_exact!`); //! `reply_error`'s `"0.0.0.0:0".parse().unwrap()` replaced with a //! `const` parsed at compile time (no `unwrap` outside tests). //! The upstream `Config`/`Authentication` impls that consumed @@ -30,24 +30,16 @@ //! `reply_success(sock_addr)` carries reply#1's BND.ADDR, and //! reply#2 is written with the published `new_reply`. No BIND code //! lives here; this file only guarantees the surface is present. -//! -//! Verbatim-code lint posture: the copied regions are upstream -//! v1.0.0 code (minus the cut + adjustments), so newer-clippy style -//! lints on upstream style are allowed at module level rather than -//! churned per-line — the diff vs the reference checkout stays -//! mechanical (review task). -#![allow(clippy::wrong_self_convention)] // AuthMethod::new(self, ..) is upstream - -use crate::vendor::consts; -use crate::vendor::util::stream::read_exact; -use crate::vendor::util::stream::ConnectError; -use crate::vendor::util::target_addr::read_address; -use crate::vendor::util::target_addr::TargetAddr; -use crate::vendor::AddrError; -use crate::vendor::ReplyError; -use crate::vendor::Socks5Command; -use crate::vendor::UdpHeaderError; +use crate::rfc1928::consts; +use crate::rfc1928::util::stream::read_exact; +use crate::rfc1928::util::stream::ConnectError; +use crate::rfc1928::util::target_addr::read_address; +use crate::rfc1928::util::target_addr::TargetAddr; +use crate::rfc1928::AddrError; +use crate::rfc1928::ReplyError; +use crate::rfc1928::Socks5Command; +use crate::rfc1928::UdpHeaderError; use std::io; use std::marker::PhantomData; use std::net::Ipv4Addr; @@ -249,7 +241,7 @@ pub trait AuthMethodSuccessState { pub trait AuthMethod: Copy { type StartingState; fn method_id(self) -> u8; - fn new(self, inner: T) -> Self::StartingState; + fn into_starting_state(self, inner: T) -> Self::StartingState; } pub struct NoAuthenticationImpl(T); @@ -278,7 +270,7 @@ impl AuthMethod for NoAuthentication { 0x00 } - fn new(self, inner: T) -> Self::StartingState { + fn into_starting_state(self, inner: T) -> Self::StartingState { NoAuthenticationImpl(inner) } } @@ -384,7 +376,7 @@ impl AuthMethod for PasswordAuthentication { 0x02 } - fn new(self, inner: T) -> Self::StartingState { + fn into_starting_state(self, inner: T) -> Self::StartingState { PasswordAuthenticationImpl::new(inner) } } @@ -428,18 +420,15 @@ macro_rules! auth_method_enums { } } - fn new(self, inner: T) -> Self::StartingState { + fn into_starting_state(self, inner: T) -> Self::StartingState { match self { - $($enum::$method(auth) => $state_enum::$method(auth.new(inner))),+ + $($enum::$method(auth) => $state_enum::$method(auth.into_starting_state(inner))),+ } } } }; } -#[allow(unused_imports)] -pub(crate) use auth_method_enums; - auth_method_enums! { /// The combination of all authentication methods supported by this crate out of the box, /// as an enum appropriate for static dispatch. @@ -503,7 +492,7 @@ impl Socks5ServerProtocol .write_all(&[consts::SOCKS5_VERSION, *client_method_id]) .await .err_when("replying with auth method")?; - return Ok(server_method.new(self.inner)); + return Ok(server_method.into_starting_state(self.inner)); } } } @@ -660,9 +649,9 @@ mod tests { use super::states; use super::Socks5ServerProtocol; use super::SocksServerError; - use crate::vendor::util::target_addr::TargetAddr; - use crate::vendor::ReplyError; - use crate::vendor::Socks5Command; + use crate::rfc1928::util::target_addr::TargetAddr; + use crate::rfc1928::ReplyError; + use crate::rfc1928::Socks5Command; use std::net::SocketAddr; use tokio::io::AsyncReadExt; use tokio::io::AsyncWriteExt; diff --git a/src/vendor/util/mod.rs b/src/rfc1928/util/mod.rs similarity index 59% rename from src/vendor/util/mod.rs rename to src/rfc1928/util/mod.rs index 2e89445..8bba4cb 100644 --- a/src/vendor/util/mod.rs +++ b/src/rfc1928/util/mod.rs @@ -1,5 +1,6 @@ -//! Vendored from fast-socks5 v1.0.0 (extraction — see the module doc -//! in the vendor root's `mod.rs`). +//! The RFC 1928 core's utility modules (upstream `src/util/` — +//! extraction; see the fork statement in the crate root's `mod.rs` +//! and ADR-013 for ownership). //! //! Upstream layout: `stream.rs` (the `read_exact!`/`ready!` macros + //! `ConnectError`) and `target_addr.rs` (`TargetAddr` + the address diff --git a/src/vendor/util/stream.rs b/src/rfc1928/util/stream.rs similarity index 75% rename from src/vendor/util/stream.rs rename to src/rfc1928/util/stream.rs index ff5094b..87d4388 100644 --- a/src/vendor/util/stream.rs +++ b/src/rfc1928/util/stream.rs @@ -1,14 +1,16 @@ -//! Vendored from fast-socks5 v1.0.0 (extraction — see the module doc -//! in the vendor root's `mod.rs`). +//! Forked from fast-socks5 v1.0.0 (upstream `src/util/stream.rs` — +//! extraction; ownership per ADR-013, see the crate root's `mod.rs`). //! //! Upstream carries the `read_exact!`/`ready!` macros, `ConnectError`, -//! and the `tcp_connect*` dialers; only the macros + `ConnectError` -//! ride the extraction (the dialers are the cut kernel-net region — -//! the crate's local backend is first-party tokio code, ADR-008). -//! Extraction-time adjustment: upstream exports both macros at the -//! crate root via `#[macro_export]`; in-tree they are plain -//! `macro_rules!` scoped by `pub(crate) use` so they stay internal to -//! the vendor (implementation detail, not alksocks public API). +//! and the `tcp_connect*` dialers; only the `read_exact!` macro + +//! `ConnectError` rode the extraction (the dialers and the `ready!` +//! macro are the cut kernel-net region — the crate's local backend is +//! first-party tokio code, ADR-008, and nothing in the extracted +//! region used `ready!`). Extraction-time adjustment: upstream +//! exports the macro at the crate root via `#[macro_export]`; here it +//! is a plain `macro_rules!` scoped by `pub(crate) use` so it stays +//! internal to the RFC 1928 core (implementation detail, not alksocks +//! public API). /// Easy to destructure bytes buffers by naming each fields: /// @@ -36,21 +38,9 @@ macro_rules! read_exact { }}; } -#[allow(unused_macros)] -macro_rules! ready { - ($e:expr $(,)?) => { - match $e { - std::task::Poll::Ready(t) => t, - std::task::Poll::Pending => return std::task::Poll::Pending, - } - }; -} - pub(crate) use read_exact; -#[allow(unused_imports)] -pub(crate) use ready; -use crate::vendor::ReplyError; +use crate::rfc1928::ReplyError; use std::io; #[derive(thiserror::Error, Debug)] @@ -86,7 +76,7 @@ impl ConnectError { #[cfg(test)] mod tests { use super::ConnectError; - use crate::vendor::ReplyError; + use crate::rfc1928::ReplyError; use std::io::ErrorKind; #[test] diff --git a/src/vendor/util/target_addr.rs b/src/rfc1928/util/target_addr.rs similarity index 84% rename from src/vendor/util/target_addr.rs rename to src/rfc1928/util/target_addr.rs index b5351a5..90a5eb5 100644 --- a/src/vendor/util/target_addr.rs +++ b/src/rfc1928/util/target_addr.rs @@ -1,29 +1,18 @@ -//! Vendored from fast-socks5 v1.0.0 (extraction — see the module doc -//! in the vendor root's `mod.rs`). +//! Forked from fast-socks5 v1.0.0 (upstream `src/util/target_addr.rs` +//! — extraction; ownership per ADR-013, see the crate root's +//! `mod.rs`). //! //! Upstream carries `TargetAddr`, the address codec (`read_address`), -//! and the `resolve_dns` helper; only the codec rides the extraction +//! and the `resolve_dns` helper; only the codec rode the extraction //! (DNS resolution is the cut net region — `tokio::net::lookup_host` //! is not in the wasm-clean graph). The `resolve_dns` method is cut //! with its two `AddrError` variants (`DNSResolutionFailed`, -//! `NoDNSRecords`); the `ToSocketAddrs` impl's error text referencing -//! `resolve_dns` is preserved verbatim for fidelity. -//! -//! Verbatim-code lint posture: this file is upstream v1.0.0 code -//! (minus the cut region), so newer-clippy style lints on upstream -//! style are allowed at module level rather than churned per-line — -//! the diff vs the reference checkout stays mechanical (review task). +//! `NoDNSRecords`). -#![allow(clippy::match_like_matches_macro)] // upstream style -#![allow(clippy::legacy_numeric_constants)] // u8::max_value() is upstream -#![allow(clippy::io_other_error)] // io::Error::new(Other) is upstream -#![allow(clippy::needless_lifetimes)] -#![allow(clippy::redundant_closure)] - -use crate::vendor::consts; -use crate::vendor::consts::SOCKS5_ADDR_TYPE_IPV4; -use crate::vendor::util::stream::read_exact; -use crate::vendor::ReplyError; +use crate::rfc1928::consts; +use crate::rfc1928::consts::SOCKS5_ADDR_TYPE_IPV4; +use crate::rfc1928::util::stream::read_exact; +use crate::rfc1928::ReplyError; use std::fmt; use std::io; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; @@ -76,10 +65,7 @@ pub enum TargetAddr { impl TargetAddr { pub fn is_ip(&self) -> bool { - match self { - TargetAddr::Ip(_) => true, - _ => false, - } + matches!(self, TargetAddr::Ip(_)) } pub fn is_domain(&self) -> bool { @@ -102,7 +88,7 @@ impl TargetAddr { buf.extend_from_slice(&addr.port().to_be_bytes()); // port } TargetAddr::Domain(ref domain, port) => { - if domain.len() > u8::max_value() as usize { + if domain.len() > u8::MAX as usize { return Err(AddrError::DomainLenTooLong(domain.len())); } buf.extend_from_slice(&[consts::SOCKS5_ADDR_TYPE_DOMAIN_NAME, domain.len() as u8]); @@ -122,17 +108,17 @@ impl TargetAddr { } } -// async-std ToSocketAddrs doesn't supports external trait implementation -// @see https://github.com/async-rs/async-std/issues/539 +/// The blocking `ToSocketAddrs` form: IP targets resolve directly; +/// domain targets cannot be resolved here (domain resolution is the +/// egress's job — ADR-012) and error. impl std::net::ToSocketAddrs for TargetAddr { type Iter = IntoIter; fn to_socket_addrs(&self) -> io::Result> { match *self { TargetAddr::Ip(addr) => Ok(vec![addr].into_iter()), - TargetAddr::Domain(_, _) => Err(io::Error::new( - io::ErrorKind::Other, - "Domain name has to be explicitly resolved, please use TargetAddr::resolve_dns().", + TargetAddr::Domain(_, _) => Err(io::Error::other( + "Domain name cannot be resolved by the blocking ToSocketAddrs interface; resolve it before constructing the target.", )), } } @@ -154,7 +140,7 @@ pub trait ToTargetAddr { fn to_target_addr(&self) -> io::Result; } -impl<'a> ToTargetAddr for (&'a str, u16) { +impl ToTargetAddr for (&str, u16) { fn to_target_addr(&self) -> io::Result { // try to parse as an IP first if let Ok(addr) = self.0.parse::() { @@ -228,18 +214,17 @@ pub async fn read_address( ) -> Result { let addr = match atyp { consts::SOCKS5_ADDR_TYPE_IPV4 => { - Addr::V4(read_exact!(stream, [0u8; 4]).map_err(|err| AddrError::IPv4Unreadable(err))?) + Addr::V4(read_exact!(stream, [0u8; 4]).map_err(AddrError::IPv4Unreadable)?) } consts::SOCKS5_ADDR_TYPE_IPV6 => { - Addr::V6(read_exact!(stream, [0u8; 16]).map_err(|err| AddrError::IPv6Unreadable(err))?) + Addr::V6(read_exact!(stream, [0u8; 16]).map_err(AddrError::IPv6Unreadable)?) } consts::SOCKS5_ADDR_TYPE_DOMAIN_NAME => { - let len = - read_exact!(stream, [0]).map_err(|err| AddrError::DomainLenUnreadable(err))?[0]; + let len = read_exact!(stream, [0]).map_err(AddrError::DomainLenUnreadable)?[0]; let domain = read_exact!(stream, vec![0u8; len as usize]) - .map_err(|err| AddrError::DomainContentUnreadable(err))?; + .map_err(AddrError::DomainContentUnreadable)?; // make sure the bytes are correct utf8 string - let domain = String::from_utf8(domain).map_err(|err| AddrError::Utf8(err))?; + let domain = String::from_utf8(domain).map_err(AddrError::Utf8)?; Addr::Domain(domain) } @@ -247,7 +232,7 @@ pub async fn read_address( }; // Find port number - let port = read_exact!(stream, [0u8; 2]).map_err(|err| AddrError::PortNumberUnreadable(err))?; + let port = read_exact!(stream, [0u8; 2]).map_err(AddrError::PortNumberUnreadable)?; // Convert (u8 * 2) into u16 let port = (port[0] as u16) << 8 | port[1] as u16; @@ -255,10 +240,10 @@ pub async fn read_address( let addr: TargetAddr = match addr { Addr::V4([a, b, c, d]) => (Ipv4Addr::new(a, b, c, d), port) .to_target_addr() - .map_err(|err| AddrError::AddrConversionFailed(err))?, + .map_err(AddrError::AddrConversionFailed)?, Addr::V6(x) => (Ipv6Addr::from(x), port) .to_target_addr() - .map_err(|err| AddrError::AddrConversionFailed(err))?, + .map_err(AddrError::AddrConversionFailed)?, Addr::Domain(domain) => TargetAddr::Domain(domain, port), }; @@ -268,7 +253,7 @@ pub async fn read_address( #[cfg(test)] mod tests { use super::*; - use crate::vendor::consts; + use crate::rfc1928::consts; use tokio::io::AsyncWriteExt; async fn read_target_addr(bytes: &[u8]) -> Result { diff --git a/tests/vendor-differential.rs b/tests/rfc1928-differential.rs similarity index 96% rename from tests/vendor-differential.rs rename to tests/rfc1928-differential.rs index ee2b505..61df6e5 100644 --- a/tests/vendor-differential.rs +++ b/tests/rfc1928-differential.rs @@ -1,13 +1,14 @@ -//! The vendor's differential RFC edge-case suite (the wire form is -//! the point — expectations are pinned as explicit bytes). +//! The RFC 1928 core's differential RFC edge-case suite (the wire +//! form is the point — expectations are pinned as explicit bytes). //! -//! Posture (docs/architecture/vendor.md §Maintenance Posture): -//! `/workspace/fast-socks5` (v1.0.0) is the behavioral reference — -//! consulted while writing these tests; the assertions pin the -//! expected bytes, never the reference checkout itself (build-time -//! dependency-free). Where the extraction deliberately diverges (the -//! pruned error variants, the published `new_reply`), the test pins -//! the in-tree contract and the divergence is recorded in vendor.md. +//! Posture (docs/architecture/rfc1928.md §Maintenance Posture, +//! ADR-013): `/workspace/fast-socks5` (v1.0.0) is the behavioral +//! reference — consulted while writing these tests; the assertions +//! pin the expected bytes, never the reference checkout itself +//! (build-time dependency-free). Where the fork deliberately +//! diverges (the pruned error variants, the published `new_reply`), +//! the test pins the in-tree contract and the divergence is recorded +//! in rfc1928.md. //! //! Differential convention: every non-obvious expectation names the //! upstream behavior it pins (file + line in the v1.0.0 checkout). @@ -16,19 +17,19 @@ //! `local` feature; the suite runs in the default wasm-clean crate's //! native test build. -use alksocks::vendor::client::{Config, Socks5Stream}; -use alksocks::vendor::consts; -use alksocks::vendor::server::{ +use alksocks::rfc1928::client::{Config, Socks5Stream}; +use alksocks::rfc1928::consts; +use alksocks::rfc1928::server::{ new_reply, transfer, wait_on_tcp, Socks5ServerProtocol, SocksServerError, }; -use alksocks::vendor::util::target_addr::{AddrError, TargetAddr}; -use alksocks::vendor::{new_udp_header, parse_udp_request, ReplyError, Socks5Command, SocksError}; +use alksocks::rfc1928::util::target_addr::{AddrError, TargetAddr}; +use alksocks::rfc1928::{new_udp_header, parse_udp_request, ReplyError, Socks5Command, SocksError}; use std::net::SocketAddr; use tokio::io::{AsyncReadExt, AsyncWriteExt}; type ServerTask = tokio::task::JoinHandle>; -/// Drive the vendored server through `accept_no_auth` (negotiation + +/// Drive the forked server through `accept_no_auth` (negotiation + /// the auth-choice write) on a spawned task, mapping the success /// value to `()` so the handle unwraps without a `Debug` bound. fn spawn_no_auth(server: tokio::io::DuplexStream) -> ServerTask<()> { @@ -652,20 +653,20 @@ async fn client_domain_request_encoding_is_len_prefixed() { /// for non-ASSOCIATE commands was /// `anyhow::Error::msg("target addr should be present")` /// (`src/client.rs:285`); in-tree it is the typed -/// `SocksError::MissingTargetAddr` (vendor.md's adjustment list) — +/// `SocksError::MissingTargetAddr` (rfc1928.md's adjustment list) — /// pinned by the wire-vocabulary module's own test /// (`mod.rs` tests: `socks_error_from_variants` keeps the variant /// reachable; the private-API form of this test lives in -/// `src/vendor/client.rs`). +/// `src/rfc1928/client.rs`). #[tokio::test] async fn client_udp_associate_zero_target_fallback_is_10_zero_bytes() { - // The vendored client's `target_addr: None` + UDPAssociate arm + // The forked client's `target_addr: None` + UDPAssociate arm // emits exactly this 10-byte form; reproduced byte-for-byte here // (integration surface) and covered against the real code path in // the client module's in-module test (privacy-scoped). let expected: &[u8] = &[0x05, 0x03, 0x00, 0x01, 0, 0, 0, 0, 0, 0]; let mut addr_field = &expected[4..10]; - let parsed = alksocks::vendor::util::target_addr::read_address(&mut addr_field, 0x01) + let parsed = alksocks::rfc1928::util::target_addr::read_address(&mut addr_field, 0x01) .await .unwrap(); // The zero form decodes to the unspecified IPv4 addr, port 0 — @@ -716,7 +717,7 @@ async fn udp_codec_garbage_reserved_is_rejected() { let err = parse_udp_request(&frame).await.unwrap_err(); assert!(matches!( err, - alksocks::vendor::UdpHeaderError::GarbageInReserved + alksocks::rfc1928::UdpHeaderError::GarbageInReserved )); }