Traversio

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

  • libssh2 wrappers focus on reusing an existing engine behind a Swift-facing API.
  • Traversio focuses on a Swift-native SSH client designed around Network.framework and structured concurrency.

That design choice affects four layers:

Topiclibssh2 and thin wrappersswift-nio-sshTraversio
Core foundationC library, usually wrapped from Swift or Objective-CNative Swift, built on SwiftNIONative Swift, built for Apple-first transport plus Swift Concurrency
Transport modelSocket-oriented coreNIO channels, event loops, handlers, delegatesByte-stream abstraction with a Network.framework backend first
Async modelBlocking, polling, callbacks, or wrapper-specific state APIsEvent-driven pipeline modelasync/await, actors, explicit cancellation
Product boundaryLow-level engineLow-level programmatic SSH building blocksClient-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, and select
  • 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/await for 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/await as 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:

  1. Swift-native public APIs.
  2. Apple-first modern transport.
  3. Structured concurrency.
  4. Testable protocol layering.
  5. 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.

Further Reading

On this page