diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..64fb2b6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,96 @@ +# Changelog + +All notable changes to this crate are documented here. The format is +based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and +this crate adheres to [Semantic Versioning](https://semver.org/). + +## [Unreleased] + +## [0.1.0] - 2026-09-27 + +The initial release: SOCKS5 (RFC 1928) as a produced resource on +alkcall channels — the `-D` flavor as a first-class protocol crate, +over alkcall 0.8.0. The defining posture: the protocol never binds a +port; kernel binds exist only as explicit, feature-gated assembly +shapes. Composed against by the alktunnels 0.2.0 graduation (the +direct op + the bind-first/forwarded flow power the composed +providers). + +### Added + +- **The RFC 1928 core (`src/rfc1928/`, ADR-013)** — the targeted fork + of fast-socks5 v1.0.0's `T`-generic subset, extracted in-tree as + fully owned code (MIT notice bundled; extraction per ADR-011): the + wire consts, codecs, errors, the explicit typestate server API + (`Socks5ServerProtocol`, generic over + `T: AsyncRead + AsyncWrite + Unpin`), and the client `use_stream` + path. Two deliberate changes carried from the fork point: BIND + support in the base (ADR-007) and the `new_reply` publication. No + kernel-net code — the default crate is wasm-clean by construction. +- **The wire codec (`wire`, ADR-003)** — the ASSOCIATE datagram frame + (`[len: u16 BE][addr + data]`), framing applied exactly once at + this layer; the `DatagramReader` incremental parser; the + `MAX_INNER_LEN` oversize guard. +- **The wire-stable open-op surface (`params`, ADR-001/006)** — the + `alk/socks5` ALPN, the `channels/socks5/sub` op id, the trivial + `{}` params (the RFC conversation is the data plane; nothing to + negotiate at open time), and the two scope constants + (`socks5:open`, `socks5:bind` — two grants, never implied). The + typed open-error vocabulary rides `error` (the ADR-049 reasons). +- **The three seams (`egress`, ADR-004)** — `DialFn` / + `EgressFactory` / `BindAcceptFactory`: functions, not traits, at + the three I/O planes; ownership rides the handle structurally + (`TargetHandle` / `BindTargetHandle` / `BindAccept`); the egress + readiness contract (`next_reply` `Pending` when nothing is ready) + is load-bearing API. +- **The producer half (`producer`)** — the session-gate establisher + (validates the opener identity; the target is unknown at open + time), the command-dispatch pump handler (CONNECT pass-through / + ASSOCIATE datagram stage / BIND phase model — reply#1 → quiet wait + → exactly one accept → reply#2 → pass-through), the command gate + (`socks5:bind` at BIND dispatch), the identity witness, and both + wiring shapes: the free functions + (`register_socks5_openable` / `socks5_session_establisher` / + `make_socks5_pump_handler` / `serve_socks5_over`) and the builder + (`Socks5Producer::builder()` — dial/egress/accept/bind_policy). + Data planes ride `alkcall::channels::pump_bidi` (ADR-050). +- **The consumer half (`consumer`)** — the typed channels-native + sessions, each owning its channel's teardown (`Drop` aborts + + reaps; no `Clone`): [`Socks5ChannelSession`] (CONNECT — raw + pass-through, the `split`/`into_inner` postures), + [`AssociateSession`] (the framed datagram stage — + `send_datagram`/`recv_datagram`/`split`), [`BindSession`] (the + reply#1/reply#2 reader + the `BindDataPlane` pass-through), and the + token-carrying surface (`TokenClient` + the `*_with_token` + constructor twins — the cred-bearing front door's re-origination). +- **The `local` feature (ADR-008/009)** — the socket-backed seam + providers (`tcp_dial` / `udp_egress` / `bind_accept` — the explicit + bind decisions), plus both doors: the consumer-side front doors + (`FrontDoor` — the NoAuth raw pipe + the RFC 1929 cred-bearing + door with the credential→identity mapping, ADR-005; + `UdpFrontDoor` — one association per bound relay socket, the RFC + codec at the door boundary only) and the producer-side listener + (`ProducerDoor` — vanilla clients dial it, each accepted + connection is served via `serve_socks5_over`; the ADR-014 + idle-first-byte window + max-sessions cap knobs). +- **The `composed` feature (ADR-008/009)** — the alktunnels 0.2.0 + composed providers: `direct_egress` (the direct-op egress — a lazy + destination→tunnel table, `DEFAULT_MAX_TUNNELS` bounded), and + `bindfirst_accept` + `register_forwarded_serving` (the BIND accept + whose listener lives on a bind-first listen resource; the + `CorrelationRegistry` correlates the forwarded op's peer metadata + into `ForwardedSession`s). +- **The idle-timeout posture (ADR-014)** — every phase + teardown-governed, no protocol-layer idle bound; the doors own the + accept-path defense. +- **The suites** — the differential suite against the upstream + reference checkout (`tests/rfc1928-differential.rs`), the + protocol-core integration suite (`tests/core-integration.rs`), and + the assembly-layer integration suite (`tests/assembly-integration.rs`). + +[`Socks5ChannelSession`]: https://docs.rs/alksocks/latest/alksocks/consumer/struct.Socks5ChannelSession.html +[`AssociateSession`]: https://docs.rs/alksocks/latest/alksocks/consumer/struct.AssociateSession.html +[`BindSession`]: https://docs.rs/alksocks/latest/alksocks/consumer/struct.BindSession.html + +[Unreleased]: https://git.alk.dev/alkdev/alksocks/compare/v0.1.0...HEAD +[0.1.0]: https://git.alk.dev/alkdev/alksocks/releases/tag/v0.1.0 \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 03ccd21..b6b2626 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ rust-version = "1.88" license = "MIT OR Apache-2.0" description = "SOCKS5 (RFC 1928) proxy protocol over alkcall channels: targeted-forked fast-socks5 typestate core plus producer/consumer sessions. Producer/consumer protocol crate." repository = "https://git.alk.dev/alkdev/alksocks" +readme = "README.md" keywords = ["socks5", "proxy", "rfc1928", "channels", "alkcall"] categories = ["network-programming", "asynchronous"] exclude = [".opencode/", "AGENTS.md", "docs/reviews/", "docs/research/", "docs/plans/", "tasks/", "docs/sdd_process.md"] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..56f9da5 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,192 @@ + 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), or refer to, the Work. + (Note: 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 the 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 the 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 + + Copyright 2025-2026 Alk Development + + 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. \ No newline at end of file diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..9ee173b --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025-2026 Alk Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..cf9d565 --- /dev/null +++ b/README.md @@ -0,0 +1,247 @@ +# alksocks + +SOCKS5 (RFC 1928) as a produced resource on +[alkcall](https://crates.io/crates/alkcall) channels: the `ssh -D` +(dynamic / SOCKS5 proxy) flavor as a first-class protocol crate — +the sibling of [alktty](https://crates.io/crates/alktty) (terminals) +and [alktunnels](https://crates.io/crates/alktunnels) (`-L`/`-R` +tunnels). The RFC 1928 conversation rides inside a channels data +channel like any other produced resource, and target dials happen on +the producing side. + +The defining posture: **the protocol never binds a port unless +explicitly configured to.** Kernel binds exist only as explicit, +feature-gated assembly shapes on either side — the consumer may +expose the resource on a local port (the front door vanilla clients — +curl, browsers, tun2proxy — point at), and the producer may serve +from a real listener. A producer/consumer protocol crate, no +transport dependencies; the transport is whatever alkcall runs over. + +## Quick start + +### Producer — register the SOCKS5 open op + +```rust +use std::sync::Arc; + +use alkcall::channels::ChannelCore; +use alkcall::registry::registration::OperationRegistry; +use alkcall::core::auth::AuthContext; +use alksocks::producer::Socks5Producer; + +// The three seams (ADR-004): dial (CONNECT), egress (UDP ASSOCIATE), +// accept (BIND). Missing seams = those commands refuse +// RFC-faithfully; behind `local`, alksocks::local::{tcp_dial, +// udp_egress, bind_accept} are the reference socket-backed +// constructors, and the composed providers (feature `composed`) ride +// alktunnels' direct-op/bind-first resources instead. +let producer = Socks5Producer::builder() + .dial(alksocks::local::tcp_dial()) + .egress(alksocks::local::udp_egress()) + .accept(alksocks::local::bind_accept("127.0.0.1".parse()?)) + .build(); + +let core = ChannelCore::new(manager, default_policy()); +let op_registry = Arc::new(OperationRegistry::new()); + +// Registers `channels/socks5/sub` on the `alk/socks5` ALPN: the +// registry's ACL (scope `socks5:open`) gates the open op, the +// session-gate establisher checks the opener identity, and the pump +// handler drives the RFC 1928 state machine in-stream over the +// channel's `BiStream`. Wire `core` + `op_registry` into your +// channels serving stack — this call is the socks5-specific step: +producer.register(&core, &op_registry, AuthContext::anonymous(b"alk/socks5"), None)?; +``` + +### Consumer — open a SOCKS5 channel (CONNECT) + +```rust +use alkcall::channels::client::ChannelClient; +use alksocks::rfc1928::util::target_addr::TargetAddr; +use alksocks::Socks5ChannelSession; + +let client: ChannelClient = /* your alk/channels connection */; + +// One session = one channel = one SOCKS5 session. The RFC handshake +// runs in-stream over the channel; a refused target surfaces the RFC +// reply code (typed errors, no phantom channel — the adopted channel +// is torn down before the error returns). +let mut session = Socks5ChannelSession::connect( + &client, + TargetAddr::Domain { addr: "example.internal".into(), port: 80 }, +).await?; + +let (mut read, mut write) = session.split().expect("pipe"); +// ... raw pass-through: the channel IS the target connection ... +``` + +### Consumer — UDP ASSOCIATE (the framed datagram stage) + +```rust +use alksocks::AssociateSession; +use alksocks::rfc1928::util::target_addr::TargetAddr; + +// One channel per association; datagrams ride `[len: u16 BE] +// [addr + data]` frames in-band (ADR-003) — the RFC codec exists at +// the front doors only. +let mut assoc = AssociateSession::associate(&client).await?; +let dest = TargetAddr::Domain { addr: "dns.internal".into(), port: 53 }; +assoc.send_datagram(&dest, b"query").await?; +let (src, payload) = assoc.recv_datagram().await?.expect("a datagram"); +``` + +### Consumer — BIND (the reply#1/reply#2 reader) + +```rust +use alksocks::BindSession; +use alksocks::rfc1928::util::target_addr::TargetAddr; + +// Requires the `socks5:bind` grant on the producing side (a separate +// capability from `socks5:open` — never implied, ADR-006). The +// session drives the phase model: reply#1 → quiet wait → exactly one +// accept → reply#2 → raw pass-through (ADR-007). +let mut bind = BindSession::bind(&client, TargetAddr::Ip("0.0.0.0:0".parse()?)).await?; +let reply1 = bind.reply1().await?; // the BND.ADDR the producer bound +// ... hand reply1's address to the peer, await the accept + reply#2 ... +``` + +### Consumer — the front door (vanilla clients) + +```rust +use std::sync::Arc; + +use alkcall::channels::client::ChannelClient; +use alksocks::{CredentialTable, FrontDoor, UdpFrontDoor}; + +let client: Arc = /* your alk/channels connection */; + +// The ssh `-D` local-exposure shape: a real `socks5://127.0.0.1:port` +// endpoint relaying each accepted connection into a channel. NoAuth +// raw pipe by default; a `CredentialTable` turns the door into an +// RFC 1929 cred-bearing endpoint whose credentials map to producer +// identities (ADR-005). `UdpFrontDoor` serves one association per +// bound relay socket. +let door = FrontDoor::builder() + .bind("127.0.0.1:1080".parse()?) + .build()?; +door.serve(client).await?; +``` + +### Producer — the local listener shape (the other door) + +```rust +use alksocks::producer::Socks5Producer; + +// Feature `local`: serve a produced SOCKS5 resource from a real +// listener — vanilla clients dial it, each accepted connection is +// served via `serve_socks5_over` (the NoAuth in-stream RFC +// conversation; the seams dial locally). The bind is explicit — the +// builder requires it; the protocol layer never binds. +let producer = Socks5Producer::builder() + .dial(alksocks::local::tcp_dial()) + .build(); +let door = alksocks::ProducerDoor::builder() + .producer(producer) + .bind("127.0.0.1:1081".parse()?) + .build()?; +let guard = door.serve().await?; +``` + +## Architecture + +Two roles, symmetric per the alk convention (avoid "server"/"client": +both sides of a connection can be either; role follows the resource): + +| Role | Via `alk/channels` | +|------|--------------------| +| **Producer** (dials targets, serves the RFC conversation) | [`Socks5Producer`](https://docs.rs/alksocks/latest/alksocks/producer/struct.Socks5Producer.html) / [`register_socks5_openable`](https://docs.rs/alksocks/latest/alksocks/producer/fn.register_socks5_openable.html) — the open op (`channels/socks5/sub`) with the session-gate establisher and the command-dispatch pump; the RFC 1928 typestate core drives negotiation, command read, and replies in-stream over the channel's `BiStream`; CONNECT/BIND data planes ride [`alkcall::channels::pump_bidi`](https://docs.rs/alkcall/latest/alkcall/channels/pump/fn.pump_bidi.html) | +| **Consumer** (wants the proxy) | [`Socks5ChannelSession`](https://docs.rs/alksocks/latest/alksocks/consumer/struct.Socks5ChannelSession.html) (CONNECT) / [`AssociateSession`](https://docs.rs/alksocks/latest/alksocks/consumer/struct.AssociateSession.html) (ASSOCIATE) / [`BindSession`](https://docs.rs/alksocks/latest/alksocks/consumer/struct.BindSession.html) (BIND) — typed channels-native sessions, each owning its channel's teardown | + +One connection hosts many SOCKS5 sessions (one per channel); sessions +are independent. Both roles are callable from either connection side +(producer and consumer are roles, not sides); RFC 1928's own +client/server roles keep their RFC names inside the protocol layer. + +### The RFC conversation is the data plane + +The open op's params are deliberately trivial (`{}`, ADR-001) — +there is nothing to negotiate at open time (the SOCKS5 target is +unknown until the RFC command arrives). Everything else is RFC 1928, +in-stream: + +- **CONNECT**: raw pass-through, 0 B overhead — the channel's halves + are the target connection (the two-pump shape). +- **ASSOCIATE**: the reply's BND.ADDR is a pure RFC handle (the + sentinel in-band); datagrams ride `[len: u16 BE][addr + data]` + frames in-band (ADR-003, the handle swap) to the egress seam. + One channel per association; the association lifetime is the + channel's. +- **BIND**: reply#1 → quiet wait → exactly one accept (the accept + seam) → reply#2 → raw pass-through (ADR-007). + +Authorization is two grants, never implied (ADR-006): the open gate +(`socks5:open`) at the registry, the command gate (`socks5:bind`) at +BIND dispatch. The target policy is the dialer (ADR-012): the +producer's dial/egress/accept closures are the security boundary — +whatever they allow is what the resource reaches. + +### The seams + +The protocol layer is substrate-agnostic by construction (ADR-004): +`DialFn` / `EgressFactory` / `BindAcceptFactory` are *functions, not +traits* at the three I/O planes; ownership rides the handle +structurally. A channels `BiStream`, a `TcpStream`, or an in-process +pipe drops in behind the same seams — no socket type leaks into the +protocol layer. + +### WASM target + +The default crate (no features) compiles to `wasm32-unknown-unknown` +— the protocol layer for a sandboxed TS/Python adapter. The RFC 1928 +core is a targeted fork of +[fast-socks5](https://github.com/dizda/fast-socks5) v1.0.0's +`T`-generic subset, extracted in-tree as fully owned code +(`src/rfc1928/`, MIT notice bundled; ADR-011/013): no kernel-net +code, no `net` feature — the socket I/O rides `local` alone, and the +default build is wasm-clean by construction. + +## Features + +| Feature | Contents | wasm | +|---------|----------|------| +| *(default)* | the RFC 1928 core (`src/rfc1928/` — codecs, typestate, client `use_stream`), the ASSOCIATE wire codec, the open-op spec, the three seams, the producer + consumer session types — protocol only | yes | +| `local` | the socket-backed seam providers ([`tcp_dial`](https://docs.rs/alksocks/latest/alksocks/local/fn.tcp_dial.html) / [`udp_egress`](https://docs.rs/alksocks/latest/alksocks/local/fn.udp_egress.html) / [`bind_accept`](https://docs.rs/alksocks/latest/alksocks/local/fn.bind_accept.html)), the front doors ([`FrontDoor`](https://docs.rs/alksocks/latest/alksocks/door/struct.FrontDoor.html) + [`UdpFrontDoor`](https://docs.rs/alksocks/latest/alksocks/door/struct.UdpFrontDoor.html), the consumer-side exposure; [`ProducerDoor`](https://docs.rs/alksocks/latest/alksocks/local/producer_door/struct.ProducerDoor.html), the producer-side listener) | no | +| `composed` | the alktunnels-composed providers: [`direct_egress`](https://docs.rs/alksocks/latest/alksocks/composed/fn.direct_egress.html) (the direct-op egress, a lazy destination→tunnel table) + [`bindfirst_accept`](https://docs.rs/alksocks/latest/alksocks/composed/fn.bindfirst_accept.html) (the BIND accept whose listener lives on a bind-first listen resource) | yes | + +## Version 0.1 + +The first release rides alkcall 0.8.0 (the establisher reply +projection is not consumed yet — the open op's params are trivial) and +alktunnels 0.2.0 (the direct op + the bind-first/forwarded flow power +the composed providers). Wire-stable surface: the `alk/socks5` ALPN, +the `channels/socks5/sub` open op with `{}` params, and the two scope +constants — all pinned by ADRs 001–014. + +## Verification + +```bash +cargo test # default crate (wasm-clean) +cargo test --features local # + real-socket suites +cargo test --all-features # + the composed suites +cargo clippy --all-targets -- -D warnings +cargo fmt --check +cargo check --target wasm32-unknown-unknown # the wasm-clean guard +``` + +## Documentation + +- [Architecture docs](docs/architecture/README.md) — the + authoritative spec set (in the repository, not the published + package): ADRs (001–014), the wire format, the producer/consumer + contracts, and the fork provenance. +- [API docs](https://docs.rs/alksocks) — full crate documentation on + docs.rs. + +## License + +MIT OR Apache-2.0 \ No newline at end of file