Why Traversio
Why Traversio exists, how it is positioned, and how it relates to libssh2 wrappers and SwiftNIO SSH.
Traversio exists because SwiftServer needs an SSH library whose transport model, concurrency model, and public API fit modern Swift and modern Apple platforms.
A native Swift implementation allows the library architecture to follow those goals directly.
Positioning
libssh2wrappers focus on reusing an existing engine behind a Swift-facing API.- Traversio focuses on a Swift-native SSH client designed around
Network.frameworkand structured concurrency.
That design choice affects four layers:
| Topic | libssh2 and thin wrappers | swift-nio-ssh | Traversio |
|---|---|---|---|
| Core foundation | C library, usually wrapped from Swift or Objective-C | Native Swift, built on SwiftNIO | Native Swift, built for Apple-first transport plus Swift Concurrency |
| Transport model | Socket-oriented core | NIO channels, event loops, handlers, delegates | Byte-stream abstraction with a Network.framework backend first |
| Async model | Blocking, polling, callbacks, or wrapper-specific state APIs | Event-driven pipeline model | async/await, actors, explicit cancellation |
| Product boundary | Low-level engine | Low-level programmatic SSH building blocks | Client-oriented library surface for commands, shell, SFTP, and forwarding |
Why Architecture Matters
The official libssh2 examples still look like a traditional socket-driven SSH engine. Even when adapted into cleaner Swift or Objective-C call sites, the same underlying ideas remain:
- create or inherit a socket
- manage a session handle
- handle blocking vs non-blocking behavior
- retry on
EAGAIN - manage channel handles explicitly
This is the shape of a typical libssh2 exec flow:
// Adapted from the shape of libssh2's ssh2_exec example.
int fd = socket(AF_INET, SOCK_STREAM, 0);
connect(fd, ...);
LIBSSH2_SESSION *session = libssh2_session_init();
libssh2_session_set_blocking(session, 0);
for (;;) {
int rc = libssh2_session_handshake(session, fd);
if (rc == 0) break;
if (rc != LIBSSH2_ERROR_EAGAIN) fail();
waitForSocket(fd, session);
}
LIBSSH2_CHANNEL *channel = libssh2_channel_open_session(session);
int rc = libssh2_channel_exec(channel, "uname -a");A Swift wrapper can remove much of the C syntax and still preserve the same lifecycle:
// Representative wrapper-style usage around a libssh2 core.
let client = SomeSSHWrapper(host: "example.com", port: 22)
try client.connect()
try client.authenticate(username: "deploy", password: password)
let output = try client.execute("uname -a")
client.disconnect()That usually keeps the same architectural shape:
- session-handle oriented
- wrapper-specific state checking
- coupled to the behavior and assumptions of the underlying C engine
Traversio tries to make the boundary look different from the start:
import Traversio
@available(macOS 26.0, iOS 26.0, *)
func uname() async throws -> String {
let configuration = SSHClientConfiguration(
host: "example.com",
username: "deploy",
authentication: .password("secret"),
hostKeyPolicy: .knownHostsFile("/Users/me/.ssh/known_hosts")
)
let result = try await SSHClient.withConnection(configuration: configuration) { connection in
try await connection.execute("uname -a")
}
return String(decoding: result.standardOutput, as: UTF8.self)
}The main difference is the public model: connection ownership, trust policy, async failure, and wrapper lifetime are all part of the API design.
Modern Transport Matters
SSH needs a reliable byte stream. Traversio keeps that requirement while letting applications work with a more modern transport surface.
The IETF Transport Services work, published as RFC 9622, points toward path-aware, asynchronous transport interfaces. Apple has taken a similar direction with Network.framework.
For Traversio, this matters for practical reasons:
- Apple platforms already have a modern networking stack designed around endpoint selection, path changes, and modern protocol behavior.
- Mobile and laptop environments routinely switch between Wi-Fi, Ethernet, hotspot, and cellular paths.
- IPv6 belongs in the baseline behavior.
- The SSH core can depend on reliable byte-stream semantics while staying independent from any single socket API.
That is why Traversio keeps the SSH core behind a byte-stream protocol layer while the first concrete transport lives in NetworkTCPByteStreamTransport.
That keeps two boundaries clear:
- the SSH core stays independent from
socket,poll, andselect - the Apple-specific transport code stays isolated from the wire, auth, channel, and SFTP layers
Native Concurrency
Traditional SSH libraries often expose one of these shapes:
- blocking calls
- non-blocking calls plus retry loops
- callbacks layered over a blocking or polling core
- lifetime rules expressed outside the type system
Traversio is built around structured concurrency:
async/awaitfor operations that suspend- actors for mutable protocol and session state
- explicit cancellation checks in long waits
- closure-scoped public wrappers so invalid lifetimes fail clearly
That makes common code simpler to read:
try await SSHClient.withConnection(configuration: configuration) { connection in
let session = try await connection.openShell()
try await session.write("uname -a\n")
while let chunk = try await session.readStandardOutputChunk() {
print(String(decoding: chunk, as: UTF8.self), terminator: "")
}
}The same design also gives the implementation clearer rules:
- who owns mutable state
- where cancellation is checked
- when wrappers become invalid
- which layer is responsible for packet parsing, transport, or session orchestration
Clear Boundaries
Wrapping a mature C library can be useful. It also tends to preserve more of that library's structure.
Traversio instead keeps clear boundaries between:
- transport
- wire encoding and decoding
- transport protocol
- authentication
- connection and channel orchestration
- SFTP
- forwarding
- high-level client API
Those boundaries have direct engineering value.
It makes it easier to:
- test packet parsing without a live network
- validate state transitions deterministically
- grow public APIs without exposing raw packet choreography
- keep transport-specific behavior out of SSH core logic
For a security-sensitive protocol, this improves reviewability. Parsing, sequencing, trust policy, and public lifecycle rules stay visible as separate layers.
Relationship To SwiftNIO SSH
swift-nio-ssh deserves a separate answer because it is a serious native Swift implementation with a different product boundary.
Its official README describes it as a programmatic SSH implementation and explicitly says it is closer to libssh2 than to OpenSSH. That is an honest and useful description. If you are already building around NIO channels, handlers, event loops, and protocol pipelines, swift-nio-ssh can be a very strong fit.
Traversio targets a different boundary.
This is roughly the shape of the client setup shown in the swift-nio-ssh example client:
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let bootstrap = ClientBootstrap(group: group)
.channelInitializer { channel in
let ssh = NIOSSHHandler(
role: .client(.init(
userAuthDelegate: authDelegate,
serverAuthDelegate: hostKeyDelegate
)),
allocator: channel.allocator,
inboundChildChannelInitializer: nil
)
return channel.pipeline.addHandler(ssh)
}That model is powerful and well suited to a different kind of library:
- NIO-first protocol composition
- child channels and handlers as the primary abstraction
- delegate and pipeline coordination as part of normal usage
Traversio instead optimizes for:
- Apple-first transport through
Network.framework async/awaitas the primary public style- a client-oriented API that starts at
SSHClient.withConnection(...) - explicit trust policy, connection scope, and wrapper lifetime
Use swift-nio-ssh when you want SSH as a low-level NIO building block.
Use Traversio when you want an Apple-first SSH client library with concurrency-native public APIs.
SwiftNIO also has an Apple transport story through swift-nio-transport-services. The larger difference is the programming model and the product boundary exposed to library consumers.
Design Priorities
Traversio is deliberately optimizing for these outcomes:
- Swift-native public APIs.
- Apple-first modern transport.
- Structured concurrency.
- Testable protocol layering.
- A library surface that grows into SwiftServer's exec, shell, SFTP, and forwarding needs without exposing low-level SSH choreography everywhere.
That is why Traversio exists.