Files
alknet/docs/architecture/decisions/001-pluggable-transport.md
glm-5.1 d3633b7839 docs: complete Phase 0 architecture — spec updates, review fixes, and link portability
Update four existing specs (overview, server, napi-and-pubsub, call-protocol) to
reflect Phase 0 decisions: three-layer model, IdentityProvider, ForwardingPolicy,
OperationEnv, static/dynamic config split. Review all 9 Phase 0a ADRs (026-034)
for consistency. Fix 4 critical issues from architecture review: missing OQ-SVC-05
in open-questions.md, deprecated hub terminology, undefined AuthService and noq
terms. Replace inline OQ text with cross-references per format rules. Add
ConfigServiceImpl definition to configuration.md. Port absolute workspace paths
to project-relative links by copying referenced docs (feasibility, certbot,
fail2ban, event_source_types) into docs/research/.
2026-06-07 11:27:52 +00:00

2.1 KiB

ADR-001: Pluggable Transport via AsyncRead+AsyncWrite Trait

Status

Accepted

Context

Alknet needs to support multiple transport modes (TCP, TLS, iroh) for SSH sessions. Each mode has different connection establishment logic but produces the same result: a bidirectional byte stream. Without an abstraction, each transport would need its own SSH connection code path.

russh's client::connect_stream() and server::run_stream() both accept AsyncRead + AsyncWrite + Unpin + Send, meaning SSH is already transport-agnostic at the API level. The design question is whether to enshrine this in alknet's own type system or handle each transport case-by-case.

Decision

Define a Transport trait that produces AsyncRead + AsyncWrite + Unpin + Send streams. Each transport (TCP, TLS, iroh) implements this trait. The SSH layer calls transport.connect() and passes the result to russh::client::connect_stream().

On the server side, define a TransportAcceptor trait that produces incoming streams. Each acceptor (TCP listener, TLS listener, iroh endpoint) implements this trait. The server calls acceptor.accept() and passes the result to russh::server::run_stream().

This makes adding a new transport (e.g., WebSocket, QUIC directly) a matter of implementing the trait, not modifying SSH code.

Consequences

  • Positive: Clean separation between transport and protocol. Adding transports is additive. SSH code is transport-agnostic.
  • Positive: Testing is simplified — mock transports can produce in-memory streams.
  • Negative: Slight indirection for the single-transport case (just TCP). The trait boilerplate is minimal though.
  • Negative: The trait must be object-safe if we want dynamic dispatch. Using impl Trait in function signatures avoids this but limits runtime transport selection. CLI-selected transport needs dynamic dispatch: Box<dyn Transport<Stream = Box<dyn AsyncRead+AsyncWrite+Unpin+Send>>>.

References