Architecture
How Traversio is layered, which boundaries are stable, and where the implementation still needs refinement.
What This Page Is For
This page is about how Traversio is built.
If you are trying to use the public API, start with Mental Model first. That page already covers:
- the public object graph
- the difference between connection, channel, session, and SFTP client
- which top-level API to reach for first
This page starts one layer lower:
- how the package is split conceptually
- where protocol state lives
- which implementation constraints are deliberate
- which seams still need hardening
Layer Diagram
Your code
|
v
Public API wrappers
SSHClient / SSHConnection / SSHSession / SFTPClient / forwarding wrappers
|
v
Connection and forwarding orchestration
|
v
SSHTransportProtocolClient actor
- version exchange
- key exchange
- encrypted transport
- authentication sequencing
- channel lifecycle and flow control
|
+--> Authentication helpers
+--> Connection message coders
+--> SFTP protocol helpers
|
v
Wire and algorithm layers
- SSH binary framing
- typed message codecs
- key derivation
- cipher/MAC handling
|
v
Transport abstraction
SSHByteStreamTransport
|
v
Apple 26+ Network.framework backendThese are logical layers first. The public library still ships mainly as one Swift target while those boundaries settle, with small helper targets where platform or validation work requires them.
Working Source Layout
The source tree currently reflects those boundaries under Sources/Traversio/:
Transport/Wire/Algorithms/Authentication/Connection/Forwarding/SFTP/TransportProtocol/Client/
Main Constraints
- Apple platforms first
- first public transport path targets platform release 26 and later
- Swift Concurrency is the default model
- the SSH core must stay testable without a live network
- transport details stay isolated
- the first meaningful milestone still needs exec, shell, SFTP, and forwarding
Transport Layer
Purpose
SSH is defined over a reliable byte stream. Traversio therefore keeps a byte-stream abstraction so the protocol layers stay portable while the first concrete backend is Apple-specific.
Direction
The first transport backend uses the new Network framework API family available on Apple platform release 26 and later. The transport layer provides:
- async send
- async receive
- orderly connection lifetime around a single byte stream
This keeps the SSH core separate from Apple transport types while still using the preferred modern APIs first.
Wire Layer
The wire layer owns deterministic byte-level SSH behavior:
- primitive encoding and decoding
- packet framing
- identification parsing
- binary readers and writers
- typed transport and connection messages
Design rules:
- the wire layer should be testable without a live connection
- the wire layer stays focused on byte-level SSH behavior
Algorithms Layer
Implemented interoperability path:
curve25519-sha256,[email protected], andecdh-sha2-nistp256key exchange- Ed25519, RSA SHA-2, and ECDSA host-key verification
- password auth
- keyboard-interactive auth
- Ed25519, RSA/SHA-2, and ECDSA public-key auth
aes128-ctr/aes256-ctrpacket protection with HMAC-SHA-2 and OpenSSH UMAC MACs, including classic and ETM forms- OpenSSH
[email protected]/[email protected] - OpenSSH
[email protected] - OpenSSH
[email protected]strict-kex marker handling on the initial exchange
The project prefers Apple system frameworks where practical, and algorithm coverage remains intentionally focused while the protocol core hardens.
Transport Protocol Engine
This layer owns the SSH transport state machine:
- version exchange
SSH_MSG_KEXINIT- key exchange
- transition into encrypted packet flow
- service requests
- inbound and outbound sequencing
- selected post-auth transport messages such as
SSH_MSG_EXT_INFOandSSH_MSG_DEBUG
Mutable protocol state is actor-owned so sequencing stays explicit.
Authentication Layer
Public auth methods:
- password
- keyboard-interactive callbacks
- Ed25519 and ECDSA public key from raw private-key bytes
- RSA public key from PKCS#1 DER private-key bytes
- Ed25519, RSA, and ECDSA public key from OpenSSH private keys, including the current bcrypt + AES encrypted-key path
- OpenSSH key-pair generation for Ed25519, ECDSA P-256/P-384/P-521, and RSA, including matching authorized-key output
Trust posture:
- the caller must choose a host-key policy
- exact pinning plus
known_hostsimport for exact, wildcard, negated, hashed, CIDR,@revoked, and@cert-authorityentries are available - trust-on-first-use, changed-key helper, and async trust callbacks are public
- trust-any is explicit and not implicit fallback
Follow-up work:
- broader host-key trust behavior
- broader credential-loading options such as agent-backed auth
Connection And Channels
Above the transport actor, the connection layer is responsible for:
- connect lifecycle orchestration
- authentication sequencing
- channel allocation
- session-channel exec and shell flows
- transcript collection
- channel window tracking for send and receive paths
The public connection wrapper exists in two forms:
- explicit long-lived ownership through
SSHClient.connect(configuration:) - closure-scoped convenience through
SSHClient.withConnection(configuration:_:)
The planned direction for the first stable release is:
- one SSH connection must support multiple concurrent shell, exec, SFTP, and forwarding channels
- reconnect remains an upper-layer concern
- shell, streamed exec sessions, and raw forwarding channels already expose event-based stream surfaces, and richer shell control plus final forwarding lifecycle semantics remain release work
SFTP
The current SFTP design is actor-based and routes replies by SFTP request ID inside one subsystem session:
- requests go through one actor-owned channel/session pair
- replies are matched back to the correct waiter by request ID
- path operations and handle operations on one
SFTPClientcan overlap safely - high-level bulk helpers keep a simple whole-file model
That keeps protocol coordination localized without forcing callers to open a second SFTP subsystem just to overlap simple metadata or handle work.
The release-scope question has shifted. Traversio already supports overlapping in-session requests, and fuller bulk-transfer engines remain future work.
The first concrete trigger for another round of SFTP work should be a real requirement such as:
- read-ahead
- pipelined writes with bounded in-flight windows
- throughput isolation between independent long-running transfers
Forwarding
Forwarding is part of the implemented surface. Traversio includes a raw direct-tcpip public wrapper, Apple 26+ local-forwarding and dynamic-SOCKS helpers built on NetworkListener, a raw remote-forward listener, and a remote-forwarding helper that bridges remote listeners back to one local TCP endpoint. The main remaining work is:
- stronger lifecycle contracts for the local, dynamic, and remote forwarding helpers
- broader interoperability evidence outside the current OpenSSH-heavy and local proxy-matrix baseline
- clarity on whether the tightened actor-plus-pending-queue coordination should remain the release design or later become a dedicated mailbox/dispatcher
The release scope keeps two points explicit:
- local, remote, and dynamic forwarding are all required and already exist as public APIs
- forwarding failures should default to the forwarding scope unless the caller chooses a stronger policy
Main Structural Risk Today
The largest structural risk is the size and responsibility load of SSHTransportProtocolClient.
That actor carries too much of:
- post-auth sequencing
- channel routing
- forwarding coordination
- SFTP/session plumbing
It remains workable, and future protocol work should keep reducing that concentration of responsibilities.
Read Next
- Current Status for the current validation and readiness picture
- Release Scope for the first-release boundary and deferred work