Phase 3 complete: alknet-client crate implemented with: - AlknetClient struct with builder methods (with_quinn, with_tcp_tls, with_iroh, with_socks5_proxy) - ClientDialError enum with 5 variants - dial_quic, dial_tcp_tls, dial_iroh methods - SOCKS5 proxy support (Socks5ProxyConfig, Socks5Credentials, Socks5UdpSocket) - Unit tests (18) + integration test (3) - All feature combos compile and test clean - Workspace build and tests pass
156 lines
7.4 KiB
Markdown
156 lines
7.4 KiB
Markdown
---
|
|
id: client/crate-init
|
|
name: Initialize alknet-client crate with Cargo.toml, dependencies, and module skeleton
|
|
status: completed
|
|
depends_on: [tls/review-tls, endpoint/review-endpoint]
|
|
scope: moderate
|
|
risk: low
|
|
impact: project
|
|
level: implementation
|
|
---
|
|
|
|
## Description
|
|
|
|
Phase 3, Task 1 of the crate extraction (per `docs/research/alknet-crate-extraction/findings.md`).
|
|
Initialize the `alknet-client` crate from scratch. This crate provides the native client dial
|
|
seam — `AlknetClient`, the client-side analogue of `AlknetEndpoint`. A multi-transport dialer
|
|
that takes pre-built transport handles (quinn, TCP+TLS, iroh), dials a remote `AlknetEndpoint`
|
|
on a chosen ALPN, and produces a `Connection` for the protocol take-overs to consume.
|
|
|
|
The dial is the client-side mirror of the server-side accept loop (ADR-083): one type that
|
|
takes pre-built transport handles and produces a `Connection`, with the transport choice as a
|
|
parameter. The protocol take-overs (`CallClient::spawn_dispatch`, `ChannelClient::from_connection`)
|
|
are unchanged — they consume the `Connection` and do not know `AlknetClient` produced it.
|
|
|
|
### Crate setup
|
|
|
|
Create `crates/alknet-client/` with:
|
|
|
|
- `Cargo.toml` — package metadata, dependencies, feature flags
|
|
- `src/lib.rs` — crate root with module declarations and re-exports
|
|
- Module skeleton files for:
|
|
- `src/error.rs` — `ClientDialError` enum (5 variants: `TlsConfig`, `Connect`, `Handshake`, `NoTransport`, `Proxy`)
|
|
- `src/client.rs` — `AlknetClient` struct, `new`, builder methods (`with_quinn`, `with_tcp_tls`, `with_iroh`, `with_socks5_proxy`)
|
|
- `src/dial/quinn.rs` — `dial_quic` implementation (feature-gated on `quinn`)
|
|
- `src/dial/tcp_tls.rs` — `dial_tcp_tls` implementation (feature-gated on `tcp`)
|
|
- `src/dial/iroh.rs` — `dial_iroh` implementation (feature-gated on `iroh`)
|
|
- `src/dial/mod.rs` — dial module declarations
|
|
- `src/socks5.rs` — `Socks5ProxyConfig`, `Socks5Credentials`, `Socks5UdpSocket`, HTTP-to-SOCKS5 bridge (feature-gated on `socks5`)
|
|
|
|
### Dependencies
|
|
|
|
Per the findings (Phase 3) and the architecture spec:
|
|
|
|
| Crate | Purpose |
|
|
|-------|---------|
|
|
| `alknet-core` | `Connection`, `ConnectionCredentials`, `RemoteIdentity`, `Ed25519SecretKey`, types (workspace path) |
|
|
| `alknet-tls` | `TlsClientConfig` — for quinn + tcp dials (workspace path) |
|
|
| `quinn` 0.11 | QUIC transport (optional, feature-gated) |
|
|
| `tokio-rustls` 0.26 | TCP+TLS transport (optional, feature-gated) |
|
|
| `iroh` 1.0 | Iroh transport (optional, feature-gated) |
|
|
| `fast-socks5` 1 | SOCKS5 client (optional, feature-gated — ADR-090) |
|
|
| `tokio` 1 (full) | Async runtime, TcpStream, spawn |
|
|
| `thiserror` 2 | `ClientDialError` |
|
|
|
|
`alknet-client` depends on `alknet-tls` (for `TlsClientConfig`) and `alknet-core` (for
|
|
`Connection`, `ConnectionCredentials`, `RemoteIdentity`, and types). It does **not** depend
|
|
on `alknet-call` or `alknet-channels-call` — the dial is below the protocol.
|
|
|
|
### Feature flags
|
|
|
|
```toml
|
|
[features]
|
|
default = []
|
|
quinn = ["dep:quinn", "alknet-tls/quinn", "alknet-core/quinn"] # dial_quic
|
|
tcp = ["dep:tokio-rustls", "alknet-tls/tcp"] # dial_tcp_tls
|
|
iroh = ["dep:iroh", "alknet-core/iroh"] # dial_iroh
|
|
socks5 = ["dep:fast-socks5"] # proxied dial paths (ADR-090)
|
|
```
|
|
|
|
The `quinn` and `tcp` features pull the corresponding features on `alknet-tls` (for
|
|
`TlsClientConfig::for_quinn` / `for_tcp_tls`). The `quinn` and `iroh` features also pull
|
|
the corresponding features on `alknet-core` — `dial_quic` produces a `Connection` via
|
|
`Connection::from_quinn_with_alpn` and `dial_iroh` via `Connection::from_iroh`, both of
|
|
which live in `alknet-core`'s `types.rs` behind core's `quinn` / `iroh` features.
|
|
|
|
The `socks5` feature (ADR-090) is independent of the transport features — it enables the
|
|
proxy code path that `dial_quic` (UDP ASSOCIATE) and `dial_tcp_tls` (CONNECT) use when a
|
|
proxy is configured. The `fast-socks5` dep is behind `socks5`, so deployments that don't
|
|
use a proxy don't pay the dep.
|
|
|
|
### Workspace Cargo.toml
|
|
|
|
Add `crates/alknet-client` to the workspace `members` list in the root `Cargo.toml`.
|
|
|
|
### Module skeleton
|
|
|
|
```rust
|
|
// src/lib.rs
|
|
//! alknet-client: Native client dial seam — multi-transport dialer that
|
|
//! produces `Connection`s for protocol take-overs.
|
|
//!
|
|
//! `AlknetClient` is the client-side analogue of `AlknetEndpoint`: a
|
|
//! multi-transport dialer that takes pre-built transport handles (quinn,
|
|
//! TCP+TLS, iroh), dials a remote `AlknetEndpoint` on a chosen ALPN, and
|
|
//! produces a `Connection`. The protocol take-overs
|
|
//! (`CallClient::spawn_dispatch`, `ChannelClient::from_connection`)
|
|
//! consume the `Connection` — the dial is below the protocol.
|
|
//!
|
|
//! An optional SOCKS5 proxy (ADR-090) routes the dials through a proxy
|
|
//! to hide the client's real IP from the hub.
|
|
|
|
pub mod client;
|
|
pub mod dial;
|
|
pub mod error;
|
|
#[cfg(feature = "socks5")]
|
|
pub mod socks5;
|
|
|
|
// Re-exports (filled in by subsequent tasks)
|
|
```
|
|
|
|
Each module file gets a doc comment and `// TODO: implement` marker.
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [ ] `crates/alknet-client/Cargo.toml` exists with all dependencies and feature flags
|
|
- [ ] `crates/alknet-client/src/lib.rs` exists with module declarations
|
|
- [ ] Module skeleton files exist: `error.rs`, `client.rs`, `dial/mod.rs`, `dial/quinn.rs`, `dial/tcp_tls.rs`, `dial/iroh.rs`, `socks5.rs`
|
|
- [ ] Root `Cargo.toml` `members` list includes `crates/alknet-client`
|
|
- [ ] `cargo check -p alknet-client` succeeds
|
|
- [ ] `cargo clippy -p alknet-client` succeeds with no warnings
|
|
- [ ] Dual licensing: `MIT OR Apache-2.0` (workspace-inherited)
|
|
- [ ] `alknet-core` dependency uses workspace path (`path = "../alknet-core"`)
|
|
- [ ] `alknet-tls` dependency uses workspace path (`path = "../alknet-tls"`)
|
|
- [ ] No dependency on `alknet-call` (dial is below the protocol)
|
|
- [ ] Feature flags: `quinn`, `tcp`, `iroh`, `socks5` (all optional, default off)
|
|
- [ ] `quinn` feature pulls `alknet-tls/quinn` + `alknet-core/quinn`
|
|
- [ ] `tcp` feature pulls `alknet-tls/tcp`
|
|
- [ ] `iroh` feature pulls `alknet-core/iroh`
|
|
- [ ] `socks5` feature pulls `dep:fast-socks5` only (no alknet feature deps)
|
|
- [ ] `cargo build --workspace` still succeeds (old code untouched)
|
|
|
|
## References
|
|
|
|
- docs/research/alknet-crate-extraction/findings.md — Phase 3
|
|
- docs/architecture/crates/client/README.md — full architecture spec
|
|
- docs/architecture/decisions/089-alknetclient-native-dial-seam.md — ADR-089
|
|
- docs/architecture/decisions/090-client-dial-socks5-proxy-seam.md — ADR-090
|
|
- docs/architecture/decisions/091-connectioncredentials-decouple-dial-from-call.md — ADR-091
|
|
- crates/alknet-tls/Cargo.toml — reference for dep versions and feature flag pattern
|
|
- crates/alknet-endpoint/Cargo.toml — reference for builder-method pattern
|
|
- crates/alknet-call/src/client/call_client.rs — reference `connect()` implementation (lines 142-168)
|
|
|
|
## Notes
|
|
|
|
> This is the foundational setup task for alknet-client. All subsequent client/*
|
|
> tasks depend on this one. The crate depends on `alknet-tls` (for `TlsClientConfig`)
|
|
> and `alknet-core` (for `Connection`, `ConnectionCredentials`, `RemoteIdentity`).
|
|
> It does NOT depend on `alknet-call` — the dial is below the protocol. The old
|
|
> `CallClient::connect` in `alknet-call` is intentionally still present (duplicated)
|
|
> — the prune happens in Phase 5. The `socks5` feature and `fast-socks5` dep are
|
|
> opt-in; deployments that don't use a proxy pay nothing.
|
|
|
|
## Summary
|
|
|
|
> To be filled on completion
|