Public API
The public types, methods, and lifecycle rules for Traversio.
Entry Points
The public surface starts here:
@available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *)
public static func connect(
configuration: SSHClientConfiguration
) async throws -> SSHConnection
@available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *)
public static func connect(
configuration: SSHClientConfiguration,
logHandler: SSHClientLogHandler
) async throws -> SSHConnection
@available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *)
public static func withConnection<Result>(
configuration: SSHClientConfiguration,
_ body: @escaping (SSHConnection) async throws -> Result
) async throws -> Result
@available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *)
public static func withConnection<Result>(
configuration: SSHClientConfiguration,
logHandler: SSHClientLogHandler,
_ body: @escaping (SSHConnection) async throws -> Result
) async throws -> Resultconnect(configuration:) is the core long-lived entry point.
withConnection(configuration:_:) is a convenience wrapper over that same connection setup path. It closes the returned SSHConnection automatically when the body returns or throws.
The logHandler: overloads receive structured connection lifecycle and wrapped operation-failure events. Logging stays disabled until you pass a handler.
Long-running connection, session, and SFTP operations observe task cancellation and typically surface CancellationError. Session transcript collectors and raw channel event iterators also attempt a best-effort channel-close on cancellation. Broader graceful-shutdown semantics remain under active definition.
SSHClient.measurePortLatency(...)
Traversio also exposes a small public latency utility for direct SSH-port timing:
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, visionOS 1.0, *)
public static func measurePortLatency(
host: String,
port: UInt16 = 22,
options: SSHPortLatencyOptions = .init()
) async throws -> SSHPortLatencyReportThis utility opens a direct TCP socket to the target host and port, records TCP connect timing, then waits for the first server byte after connect.
Scope:
- direct TCP socket to the target endpoint
- independent from
SSHClientConfiguration - independent from
connectionProxyandproxyJumpHosts - no authentication or host-key verification
- no guarantee about full SSH handshake success
The main public supporting types are:
SSHPortLatencyOptionsSSHPortLatencyReportSSHPortLatencySampleSSHPortLatencyFailureSSHPortLatencyStatistics
Practical example:
import Traversio
@available(macOS 10.15, iOS 13.0, *)
func inspectSSHPort() async throws {
let report = try await SSHClient.measurePortLatency(
host: "example.com",
port: 22,
options: SSHPortLatencyOptions(
sampleCount: 5,
connectTimeout: 2,
firstServerByteTimeout: 2,
delayBetweenSamples: 0.1
)
)
print(report.connectRTTStatistics.averageMilliseconds)
print(report.estimatedPathOneWayFromFirstServerByteStatistics.averageMilliseconds)
}Keep both summary metrics when possible:
connectRTTStatistics.averageMillisecondstells you how quickly the TCP connection became readyestimatedPathOneWayFromFirstServerByteStatistics.averageMillisecondsis usually the better approximation of "how long until the server answered" on VPN or proxied paths
For a longer guide on when to choose one metric versus the other, see SSH Port Latency.
SSHClientConfiguration
Use SSHClientConfiguration to describe the remote endpoint and auth/trust policy.
| Field | Type | Meaning |
|---|---|---|
host | String | Remote host name or address |
port | UInt16 | Remote port, default 22 |
username | String | SSH username |
authentication | SSHAuthenticationMethod | Auth method |
hostKeyPolicy | SSHHostKeyPolicy | Required host trust policy |
compressionPreference | SSHCompressionPreference | Optional transport compression preference: .disabled, RFC 4253 .zlib, or delayed OpenSSH .delayedZlib |
legacyAlgorithmOptions | SSHLegacyAlgorithmOptions | Optional explicit legacy ssh-rsa host-key and RSA auth compatibility |
automaticRekeyPolicy | SSHAutomaticRekeyPolicy | Optional automatic client-initiated rekey thresholds and idle timer |
keepalivePolicy | SSHKeepalivePolicy | Optional post-auth idle keepalive policy, disabled by default |
timeoutPolicy | SSHTimeoutPolicy | Optional setup/reply timeout policy, disabled by default |
connectionProxy | SSHConnectionProxy? | Optional outermost-hop SOCKS5 or HTTP CONNECT proxy used before SSH starts |
proxyJumpHosts | [SSHProxyJumpHost] | Optional explicit jump-host chain used before the final target |
Example:
let configuration = SSHClientConfiguration(
host: "example.com",
port: 22,
username: "deploy",
authentication: .password("secret"),
hostKeyPolicy: .knownHostsFile("/Users/me/.ssh/known_hosts"),
legacyAlgorithmOptions: .disabled,
compressionPreference: .delayedZlib,
automaticRekeyPolicy: .currentProfileDefault,
keepalivePolicy: .init(interval: 30),
timeoutPolicy: .init(
connectionSetupTimeInterval: 15,
responseTimeInterval: 5
)
)By default, compressionPreference is .disabled, legacyAlgorithmOptions is .disabled, automaticRekeyPolicy is .currentProfileDefault, keepalivePolicy is .disabled, and timeoutPolicy is .disabled.
SSHProxyJumpHost uses the same explicit per-hop inputs:
hostportusernameauthenticationhostKeyPolicycompressionPreferencelegacyAlgorithmOptionsautomaticRekeyPolicykeepalivePolicytimeoutPolicy
SSHCompressionPreference keeps a focused surface:
.disabled.zlib.delayedZlib
.zlib means RFC 4253 zlib: compression starts once encrypted transport is active after each key exchange and stays on for later protected packets until the next rekey resets the context.
.delayedZlib means OpenSSH [email protected]: compression stays off during key exchange and user authentication, then turns on for later protected packets after authentication succeeds.
proxyJumpHosts defines SSH hop chaining.
connectionProxy defines the transport proxy used for the first TCP hop.
Current connectionProxy scope:
.socks5(SSHSOCKS5ConnectionProxy(...)).httpConnect(SSHHTTPConnectConnectionProxy(...))
If you set both connectionProxy and proxyJumpHosts, the external proxy is used only for the first TCP connection.
Later SSH hops still travel inside SSH direct-tcpip channels.
SSHLegacyAlgorithmOptions
Use SSHLegacyAlgorithmOptions when one connection or one jump-host hop must talk to an older SSH server.
Current members:
allowsSSHRSA.disabled.sshRSA
.sshRSA enables two behaviors for that configuration:
- Traversio appends
ssh-rsato the preferred server-host-key list for key exchange - RSA public-key authentication can retry with
ssh-rsaafter the server declines the RSA-SHA2 attempt and still offerspublickey
The RSA path still starts with rsa-sha2-512 and rsa-sha2-256.
Use .sshRSA only for older endpoints which still require the SHA-1 RSA signature path.
SSHConnectionProxy
Use SSHConnectionProxy when Traversio itself must reach the SSH server through an external proxy before the SSH handshake begins.
Current cases:
.socks5(SSHSOCKS5ConnectionProxy).httpConnect(SSHHTTPConnectConnectionProxy)
SSHSOCKS5ConnectionProxy exposes:
hostportauthentication
SSHSOCKS5ProxyAuthentication exposes:
.none.usernamePassword(username:password:)
SSHHTTPConnectConnectionProxy exposes:
hostportauthentication
SSHHTTPConnectProxyAuthentication exposes:
.none.basic(username:password:)
These types describe the outermost TCP proxy hop. Later proxyJumpHosts continue through SSH direct-tcpip channels after the first SSH session is established.
SSHAutomaticRekeyPolicy
Use SSHAutomaticRekeyPolicy when you want to keep the default packet thresholds,
disable automatic client-initiated rekey,
or tighten those thresholds for testing.
Current members:
outboundPacketThresholdinboundPacketThresholdidleTimeInterval.currentProfileDefault.disabled
currentProfileDefault currently uses 1_048_576 encrypted packets in each direction.
The outbound threshold is checked before the next protected send. The inbound threshold is checked after authentication, before the next protected receive wait on the connection/session path. The optional idle timer is checked after authentication and can start a local rekey even when the connection is otherwise idle.
disabled turns off the automatic client-initiated rekey path entirely, including the idle timer.
Example:
let configuration = SSHClientConfiguration(
host: "example.com",
username: "deploy",
authentication: .password("secret"),
hostKeyPolicy: .knownHostsFile("/Users/me/.ssh/known_hosts"),
automaticRekeyPolicy: .init(
outboundPacketThreshold: 50_000,
inboundPacketThreshold: 50_000,
idleTimeInterval: 600
)
)SSHKeepalivePolicy
Use SSHKeepalivePolicy when you want Traversio to send a keepalive on an otherwise idle authenticated connection.
Current members:
interval.disabled
interval is a post-auth idle threshold in seconds.
Current behavior:
- keepalive is checked only after authentication succeeds
- it is meant for long-lived idle connections, shells, and forwarding sessions
- it is not a blanket timeout for long-running output collection or already-open streams
If SSHTimeoutPolicy.responseTimeInterval is also set, keepalive reply waits use the tighter of that reply timeout and the keepalive interval itself.
Example:
let configuration = SSHClientConfiguration(
host: "example.com",
username: "deploy",
authentication: .password("secret"),
hostKeyPolicy: .knownHostsFile("/Users/me/.ssh/known_hosts"),
keepalivePolicy: .init(interval: 30)
)SSHTimeoutPolicy
Use SSHTimeoutPolicy when the caller wants Traversio to stop waiting indefinitely on connection setup or reply-style protocol waits.
Current members:
connectionSetupTimeIntervalresponseTimeInterval.disabled
connectionSetupTimeInterval covers one connect attempt, including identification exchange, key exchange, host-key trust, and user authentication.
responseTimeInterval covers waits where the server is expected to answer promptly:
- channel-open replies
- channel-request replies such as
exec,pty-req,shell, andsubsystem - keepalive replies
- global-request replies such as
tcpip-forward - SFTP responses
It does not act as a blanket timeout for long-running shell/exec output collection, event streams, or remote-forward accept loops.
Example:
let configuration = SSHClientConfiguration(
host: "example.com",
username: "deploy",
authentication: .password("secret"),
hostKeyPolicy: .knownHostsFile("/Users/me/.ssh/known_hosts"),
timeoutPolicy: .init(
connectionSetupTimeInterval: 15,
responseTimeInterval: 5
)
)SSHClientLogHandler
Use SSHClientLogHandler with the logHandler: overloads on SSHClient.connect(...) and SSHClient.withConnection(...) when the application wants structured library log events.
Current members:
.disabledinit(minimumLevel:emitter:).sink(minimumLevel:_:).recorder(_:minimumLevel:).osLog(_ logger: Logger, minimumLevel:).osLog(subsystem:category:minimumLevel:)
SSHClientLogLevel exposes:
.debug.info.notice.warning.error
SSHClientLogCategory exposes:
.connection.authentication.session.sftp.forwarding.transport
SSHClientLogEvent exposes:
timestamplevelcategorymessagemetadata
Traversio currently emits structured events for connection start/success/failure, authentication success/rejection, and wrapped operation failures on the stable public error paths.
SSHClientLogRecorder
Use SSHClientLogRecorder when the application wants a bounded in-memory buffer of recent SSHClientLogEvent values for support export or an in-app diagnostics screen.
Current members:
init(maximumEventCount:)record(_:)clear()snapshot()diagnosticReport(for:)logHandler(minimumLevel:)
SSHClientLogRecorderSnapshot exposes:
eventsmaximumEventCountdroppedEventCountformattedLinesformattedTextdiagnosticReport(for:)
When the recorder reaches its capacity it keeps the newest events, drops older ones, and records how many were discarded in droppedEventCount.
SSHAuthenticationMethod
Current cases:
.password(String).keyboardInteractive(submethods:responseProvider:).ed25519PrivateKey(rawRepresentation: [UInt8]).rsaPrivateKey(pkcs1DERRepresentation: [UInt8]).ecdsaP256PrivateKey(rawRepresentation: [UInt8]).ecdsaP384PrivateKey(rawRepresentation: [UInt8]).ecdsaP521PrivateKey(rawRepresentation: [UInt8])
Current factory helpers:
try SSHAuthenticationMethod.ed25519PrivateKey(openSSHPrivateKey: String, passphrase: String? = nil)try SSHAuthenticationMethod.ed25519PrivateKey(contentsOfOpenSSHPrivateKeyFile: String, passphrase: String? = nil)try SSHAuthenticationMethod.rsaPrivateKey(openSSHPrivateKey: String, passphrase: String? = nil)try SSHAuthenticationMethod.rsaPrivateKey(contentsOfOpenSSHPrivateKeyFile: String, passphrase: String? = nil)try SSHAuthenticationMethod.ecdsaPrivateKey(openSSHPrivateKey: String, passphrase: String? = nil)try SSHAuthenticationMethod.ecdsaPrivateKey(contentsOfOpenSSHPrivateKeyFile: String, passphrase: String? = nil)try SSHOpenSSHKeyPair.generate(algorithm: SSHOpenSSHKeyPair.Algorithm, comment: String = "traversio", encryption: SSHOpenSSHPrivateKeyEncryption? = nil)
The Ed25519 raw case expects a 32-byte private-key seed. The RSA raw case expects PKCS#1 DER private-key bytes. The ECDSA raw cases expect the corresponding curve-specific raw private-key representation. The throwing helpers load OpenSSH openssh-key-v1 Ed25519, RSA, or ECDSA private keys and return the matching enum case. SSHOpenSSHKeyPair.generate(...) returns the generated authenticationMethod, the OpenSSH private-key PEM text, and the matching authorized-key line. Pass passphrase: or SSHOpenSSHPrivateKeyEncryption(...) when the private key should use the OpenSSH bcrypt KDF and a supported AES cipher such as aes256-ctr or aes256-cbc.
SSHOpenSSHKeyPair.Algorithm exposes:
.ed25519.ecdsaP256.ecdsaP384.ecdsaP521.rsa(bitCount: Int)
SSHOpenSSHPrivateKeyEncryption.Cipher exposes:
.aes128CTR.aes192CTR.aes256CTR.aes128CBC.aes192CBC.aes256CBC
SSHOpenSSHKeyPair exposes:
algorithmcommentauthenticationMethodprivateKeyPEMauthorizedKeyLine
For the full generation workflow, including saving the generated PEM and installing the matching public line, see Key Generation.
keyboardInteractive gives you a generic async challenge-response hook. Traversio passes an SSHKeyboardInteractiveChallenge containing the server's name, instruction, languageTag, and ordered prompts. Your closure must return one UTF-8 response per prompt.
SSHKeyboardInteractiveChallenge exposes:
usernameserviceNamenameinstructionlanguageTagprompts
Each SSHKeyboardInteractivePrompt exposes:
promptshouldEcho
If your callback returns the wrong number of responses, Traversio throws SSHAuthenticationMethodError.invalidKeyboardInteractiveResponseCount(expected:received:).
SSHHostKeyPolicy
Current factory members:
.acceptAnyVerifiedHostKey.requireMatch(SSHTrustedHostKey).requireMatchAny([SSHTrustedHostKey]).trustOnFirstUse(lookup:store:).trustOnFirstUse(lookup:store:onStoredHostKeyMismatch:).trustOnFirstUse(using: SSHHostKeyTrustStore).knownHostsFile(String).knownHostsFile(String, additionalLookupNames: [String]).callback((SSHHostKeyValidationRequest) async throws -> SSHHostKeyTrustMethod)
.trustOnFirstUse(lookup:store:) is the simplest first-seen trust path. Traversio gives your app a lookup closure plus an SSHHostKeyStoreRequest on writes. That store request includes expectedStoredHostKey, so a shared trust store can do compare-and-set instead of blindly overwriting concurrent updates.
The overload with onStoredHostKeyMismatch keeps the same store boundary but lets the app decide whether a changed stored key should be rejected or replaced. That callback receives an SSHHostKeyChangeRequest with both the stored and newly received host key, and it returns an SSHHostKeyChangeDecision.
.trustOnFirstUse(using:) wraps that same first-seen flow behind an app-owned SSHHostKeyTrustStore. The default protocol implementation rejects changed stored keys, and a store can opt into planned rotation by implementing decisionForChangedHostKey(_:). The write hook still receives the same compare-and-set SSHHostKeyStoreRequest.
.knownHostsFile(...) currently resolves exact, wildcard, negated, hashed, CIDR, @revoked, and @cert-authority OpenSSH known_hosts entries, including non-default-port forms like [host]:port.
The overload with additionalLookupNames lets you include extra host or IP strings in the lookup, so a connection opened as example.com can still match entries stored under a resolved address like 192.0.2.10.
.callback(...) runs after the server proves possession of its host key and before the SSH session is activated. That gives the upper layer one explicit place to consult or update its own trust store without requiring Traversio to own persistence.
SSHTrustedHostKey exposes:
algorithmNamerawRepresentationfingerprintSHA256
SSHHostKeyTrustMethod tells you which trust path was used:
acceptAnyVerifiedHostKeyexactMatchtrustedSetMatchcertificateAuthorityMatchcallback
SSHHostKeyValidationRequest exposes:
endpointHostendpointPortremoteIdentificationtrustedHostKeymatches(_:)
SSHHostKeyChangeRequest exposes:
endpointHostendpointPortremoteIdentificationstoredHostKeyreceivedHostKey
SSHHostKeyStoreRequest exposes:
endpointHostendpointPortremoteIdentificationexpectedStoredHostKeytrustedHostKeymatchesExpectedStoredHostKey(_:)
SSHHostKeyChangeDecision exposes:
rejectreplaceStoredHostKey
SSHHostKeyTrustStore exposes:
lookupHostKey(endpointHost:endpointPort:)storeHostKey(_:)decisionForChangedHostKey(_:)
SSHConnection
SSHConnection is the public connection wrapper returned by connect(configuration:) and passed into withConnection(...).
Public members:
| Member | Type | Meaning |
|---|---|---|
metadata | SSHConnectionMetadata | Handshake and trust details for the active connection |
close() | async | Close the SSH connection explicitly and invalidate every wrapper created from it |
execute(_:environment:) | async throws -> SSHExecResult | Execute a one-shot remote command, optionally with SSH env requests sent before exec |
openExec(_:environment:) | async throws -> SSHSession | Open a streamed exec session on a session channel, optionally with SSH env requests |
openShell(pseudoTerminalRequest:environment:) | async throws -> SSHSession | Open a PTY-backed shell session, optionally with SSH env requests sent before shell startup |
openSFTP(clientVersion:) | async throws -> SFTPClient | Start an SFTP client on a subsystem channel |
openDirectTCPIPChannel(targetHost:targetPort:originatorAddress:originatorPort:) | async throws -> SSHDirectTCPIPChannel | Open a raw direct-tcpip forwarding channel |
withLocalPortForwarding(targetHost:targetPort:localHost:localPort:_:) | async throws -> Result | Start a closure-scoped local listener on Apple 26+ |
withDynamicPortForwarding(localHost:localPort:socks5Authentication:_:) | async throws -> Result | Start a closure-scoped local SOCKS proxy on Apple 26+ |
withRemotePortForwardListener(remoteHost:remotePort:_:) | async throws -> Result | Start a closure-scoped remote listener and accept raw incoming forwarded-tcpip channels |
withRemotePortForwarding(localPort:remoteHost:remotePort:localHost:_:) | async throws -> Result | Start a closure-scoped remote listener on Apple 26+ and bridge it back to one local TCP endpoint |
SSHConnectionMetadata
Metadata fields:
endpointHostendpointPortusernameclientIdentificationremoteIdentificationpreIdentificationLineshostKeyAlgorithmhostKeyFingerprintSHA256hostKeyTrustMethod
This is useful when you want to log what server answered, which key was accepted, and which trust path allowed the handshake to continue.
SSHExecResult
SSHExecResult contains:
standardOutputstandardErrorexitStatusexitSignaldidReceiveEOF
It is a value type designed for one-shot command execution.
execute(_:) remains the collected-result convenience API, while openExec(_:) exposes the same underlying session channel as a streamed SSHSession.
SSHSessionEnvironmentVariable
Use SSHSessionEnvironmentVariable when you want execute(...), openExec(...), or openShell(...) to send one or more RFC 4254 env requests before the remote process or shell starts.
Fields:
namevalue
Each value becomes one SSH env request. Traversio currently waits for a reply to every environment request, so a server-side rejection fails the session startup instead of being ignored silently.
SSHSession
SSHSession is the public wrapper for an open session channel used by streamed exec and PTY-backed shell flows.
Public methods:
| Method | Meaning |
|---|---|
write(_ bytes: [UInt8]) | Send raw bytes to the session |
write(_ string: String) | Send UTF-8 text |
sendEOF() | Close the sending side |
close() | Send channel close explicitly |
resizePseudoTerminal(characterWidth:characterHeight:pixelWidth:pixelHeight:) | Send an SSH window-change request on the current session channel |
sendSignal(_:) | Send an SSH signal request on the current session channel without waiting for a reply |
nextEvent() | Read the next shell event, or nil after channel close |
events | Consume shell events as an AsyncSequence |
readStandardOutputChunk() | Read the next stdout chunk, or nil on close |
collectOutputUntilClose() | Gather stdout, stderr, exit status, exit signal, and EOF state until the session closes |
Supporting value type:
SSHSessionOutputmirrors the transcript fields ofSSHExecResultSSHSessionEventexposes.standardOutput,.standardError,.exitStatus,.exitSignal, and.endOfFileresizePseudoTerminal(...)is mainly useful for PTY-backed shells opened throughopenShell(...); plain exec sessions currently do not allocate a PTY automaticallysendSignal(_:)sends RFC 4254signalrequests such asTERMorINT; the server may ignore them, and there is no reply message for this request- remote
exit-signalrequests now surface throughSSHSessionEvent.exitSignal(...)and theexitSignalfield on collected outputs - cancelling
collectOutputUntilClose()now triggers a best-effortchannel-closebefore the task surfacesCancellationError - cancelling
for try await event in session.eventsnow also triggers that same best-effortchannel-close
SSHSessionSignal
Use SSHSessionSignal when you need to send a POSIX-style signal request on a shell or streamed exec session.
Common members include:
.interrupt.terminate.kill.quit.hangup.abort.alarm.floatingPointException.illegalInstruction.brokenPipe.segmentationViolation.user1.user2
For custom names, use SSHSessionSignal(rawValue: ...).
Traversio sends the raw value directly as the RFC 4254 signal name, so standard signals should use the protocol form without the SIG prefix.
SSHSessionExitSignal
SSHSessionExitSignal describes a remote exit-signal notification reported by the peer.
Fields:
signaldidCoreDumperrorMessagelanguageTag
The errorMessage and languageTag fields are optional because many peers send them as empty strings.
SSHDirectTCPIPChannel
SSHDirectTCPIPChannel is the current expert forwarding wrapper for a raw direct-tcpip channel.
Public methods:
| Method | Meaning |
|---|---|
write(_ bytes: [UInt8]) | Send raw bytes to the forwarded target |
write(_ string: String) | Send UTF-8 text |
sendEOF() | Close the sending side |
close() | Send channel close explicitly |
nextEvent() | Read the next forwarding event, or nil after channel close |
events | Consume forwarding events as an AsyncSequence |
readChunk() | Read the next data chunk, or nil on close |
collectDataUntilClose() | Gather all bytes plus EOF state until the channel closes |
Supporting value type:
SSHDirectTCPIPChannelOutputcontainsdataanddidReceiveEOFSSHTCPIPChannelEventexposes.dataand.endOfFile- if you want one consistent incremental view, prefer
nextEvent()/eventsinstead of mixing them withreadChunk()on the same channel - cancelling
for try await event in channel.eventsnow attempts a best-effortchannel-closebefore surfacingCancellationError
SSHLocalPortForward
SSHLocalPortForward describes the currently bound local forwarding listener inside withLocalPortForwarding(...).
Fields:
localHostlocalPorttargetHosttargetPort
Current usage note:
withLocalPortForwarding(...)is only available on Apple 26+ because it currently usesNetworkListener- the helper is intentionally closure-scoped, just like
withConnection - the helper currently uses a best-effort shutdown contract: after scope exit Traversio stops bridging data and late accepted local connections are closed as soon as possible
- Traversio does not currently promise that the local port becomes immediately unconnectable at the exact moment the scope returns
SSHDynamicPortForward
SSHDynamicPortForward describes the currently bound local SOCKS listener inside withDynamicPortForwarding(...).
Fields:
localHostlocalPort
Supporting current auth type:
SSHSOCKS5ProxyAuthentication.noneSSHSOCKS5ProxyAuthentication.usernamePassword(username:password:)
Current usage note:
withDynamicPortForwarding(...)is only available on Apple 26+ because it currently usesNetworkListener- the helper is intentionally closure-scoped, just like
withConnection - the local listener speaks SOCKS5 in no-auth or username/password mode, and it still accepts SOCKS4 / SOCKS4a only when SOCKS5 auth is not configured
- shutdown is currently best-effort rather than an instant listener invalidation guarantee
SSHRemotePortForward
SSHRemotePortForward describes the currently active remote listener inside withRemotePortForwarding(...).
Fields:
localHostlocalPortremoteHostremotePort
Current usage note:
withRemotePortForwarding(...)is only available on Apple 26+ because the current helper bridges back to a local TCP endpoint with the same Apple-first transport path- the helper is intentionally closure-scoped, just like
withConnectionandwithLocalPortForwarding(...) - if you request
remotePort: 0, Traversio reports the allocated remote port throughSSHRemotePortForward.remotePort - the helper now builds on the lower-level remote listener API, but it still bridges to one fixed local TCP endpoint
- accepted remote bridge failures stay scoped to the offending connection instead of poisoning later remote clients in the same forwarding scope
- multiple accepted remote connections can stay bridged at the same time while the forwarding scope remains open
SSHRemotePortForwardListener
SSHRemotePortForwardListener describes the currently active remote listener inside withRemotePortForwardListener(...).
Fields:
remoteHostremotePort
Public methods:
| Method | Meaning |
|---|---|
accept() | Wait for the next incoming forwarded-tcpip channel |
Current usage note:
withRemotePortForwardListener(...)is closure-scoped, just like the other public connection helpers- if you request
remotePort: 0, Traversio reports the allocated remote port throughSSHRemotePortForwardListener.remotePort - pending accepts are canceled before Traversio sends
cancel-tcpip-forwardon scope exit - the listener lifecycle and concurrency contract should still be treated as evolving even though the current OpenSSH data path is now live-validated
SSHForwardedTCPIPChannel
SSHForwardedTCPIPChannel is the current expert wrapper for one incoming remote-forward connection accepted through SSHRemotePortForwardListener.accept().
Fields:
listeningHostlisteningPortoriginatorHostoriginatorPort
Public methods:
| Method | Meaning |
|---|---|
write(_ bytes: [UInt8]) | Send raw bytes back through the accepted forwarded channel |
write(_ string: String) | Send UTF-8 text |
sendEOF() | Close the sending side |
close() | Send channel close explicitly |
nextEvent() | Read the next forwarding event, or nil after channel close |
events | Consume forwarding events as an AsyncSequence |
readChunk() | Read the next data chunk, or nil on close |
collectDataUntilClose() | Gather all bytes plus EOF state until the channel closes |
Supporting value type:
SSHForwardedTCPIPChannelOutputcontainsdataanddidReceiveEOFSSHTCPIPChannelEventexposes.dataand.endOfFile- if you want one consistent incremental view, prefer
nextEvent()/eventsinstead of mixing them withreadChunk()on the same channel - cancelling
for try await event in channel.eventsnow attempts a best-effortchannel-closebefore surfacingCancellationError
SSHPseudoTerminalRequest
Use this type when opening a shell with custom PTY settings.
Fields:
terminalTypecharacterWidthcharacterHeightpixelWidthpixelHeightencodedTerminalModes
Convenience:
SSHPseudoTerminalRequest.default
SFTPClient
SFTPClient is the public file-transfer wrapper.
Public methods:
| Method | Meaning |
|---|---|
close() | Close the SFTP subsystem channel explicitly |
currentVersionExchange() | Return SSHSFTPVersionExchange |
realPath(_:) | Resolve a remote path |
lstat(_:) | Query symlink-aware metadata |
stat(_:) | Query metadata |
setAttributes(_:,attributes:) | Update path metadata with SSH_FXP_SETSTAT |
fileSystemAttributes(_:) | Query filesystem-level capacity and flags through OpenSSH [email protected] |
openFile(_:,flags:,attributes:) | Open a public SFTPFileHandle for handle-scoped reads, writes, metadata, and explicit close |
listDirectory(_:) | Read directory entries |
readFile(_:,chunkSize:maxConcurrentReads:progress:) | Read a whole file, optionally using bounded concurrent read requests on one handle and reporting cumulative progress |
resumeDownloadFile(_:,existingData:,chunkSize:maxConcurrentReads:progress:) | Resume a whole-file download from a caller-provided local prefix when the remote file is larger, then return SSHSFTPResumeDownloadResult |
writeFile(_:,data:,chunkSize:maxConcurrentWrites:syncAfterWrite:progress:) | Write a whole file, optionally keeping a bounded number of SSH_FXP_WRITE requests in flight before close and reporting cumulative progress |
resumeUploadFile(_:,data:,chunkSize:maxConcurrentWrites:syncAfterWrite:progress:) | Resume a whole-file upload from the current remote file size when the server reports one, then return SSHSFTPResumeUploadResult |
makeDirectory(_:,attributes:) | Create a directory |
removeFile(_:) | Remove a file |
removeDirectory(_:) | Remove a directory |
rename(_:,to:) | Rename a path |
readLink(_:) | Read a symlink target |
createSymbolicLink(targetPath:linkPath:) | Create a symlink |
SFTPFileHandle
SFTPFileHandle is the public low-level file-handle wrapper returned by SFTPClient.openFile(...).
Public methods:
| Method | Meaning |
|---|---|
read(at:length:) | Read up to length bytes from a specific offset, returning nil on EOF |
readAll(chunkSize:maxConcurrentReads:progress:) | Read a whole file from the current handle using bounded concurrent read requests when desired, with cumulative progress reporting |
readChunks(startingAt:chunkSize:) | Return an AsyncSequence of SSHSFTPFileChunk values for caller-controlled streamed reads |
write(_:at:) | Write bytes at a specific offset |
write(contentsOf:startingAt:progress:) | Consume an AsyncSequence<[UInt8]> and write the yielded chunks sequentially from one starting offset |
stat() | Query handle-scoped metadata through SSH_FXP_FSTAT |
setAttributes(_:) | Update handle-scoped metadata through SSH_FXP_FSETSTAT |
fileSystemAttributes() | Query handle-scoped filesystem capacity and flags through OpenSSH [email protected] |
synchronize() | Request OpenSSH [email protected] for the open handle |
close() | Close the remote file handle explicitly |
Supporting public SFTP value types:
SFTPFileHandleSSHSFTPFileChunkSSHSFTPFileChunkSequenceSSHSFTPVersionExchangeSSHSFTPExtensionSSHSFTPNameEntrySSHSFTPOpenFileFlagsSSHSFTPResumeDownloadResultSSHSFTPResumeUploadResultSSHSFTPResumeErrorSSHSFTPTransferProgressSSHSFTPFileAttributesSSHSFTPFileSystemAttributesSSHSFTPFileSystemFlags
SSHSFTPResumeDownloadResult, SSHSFTPResumeUploadResult, and SSHSFTPResumeError
resumeDownloadFile(...) returns SSHSFTPResumeDownloadResult.
Current members:
pathstartingOffsetbytesDownloadedtotalBytesdatadidResumefinalOffset
resumeUploadFile(...) returns SSHSFTPResumeUploadResult.
Current members:
pathstartingOffsetbytesUploadedtotalBytesdidResumefinalOffset
SSHSFTPResumeError currently exposes:
.remoteFileSizeUnavailable(path:).remoteFileIsSmallerThanLocalData(path:remoteSize:localSize:).remoteFileIsLargerThanLocalData(path:remoteSize:localSize:)
SSHSFTPTransferProgress
SSHSFTPTransferProgress is the public progress payload used by the whole-file SFTP convenience APIs.
Current members:
operationbytesTransferredtotalBytesfractionCompleted
Current behavior:
- reads report cumulative
bytesTransferred resumeDownloadFile(...)reports cumulative read progress against the full remote length- writes report cumulative
bytesTransferredplustotalBytes fractionCompletedis available whentotalBytesis known
Error Surface
The stable public error type today is SSHClientError.
Current cases:
authenticationRejected(methodName:availableMethods:partialSuccess:)connectionFailed(SSHConnectionFailure)operationFailed(SSHOperationFailure)passwordChangeRequired(prompt:)connectionScopeEnded
Supporting public connection-diagnostics types:
SSHConnectionFailureSSHConnectionFailureStageSSHConnectionFailureCodeSSHConnectionFailureCallbackSourceSSHConnectionFailureCallbackDetailsSSHConnectionFailureDiagnosticsSSHNegotiatedTransportAlgorithmsSSHRemoteDisconnectSSHRemoteDebugMessageSSHOperationFailureSSHOperationFailureScopeSSHOperationFailureCodeSSHOperationFailureDiagnosticsSSHSFTPStatusDetailsSSHClientLogRecorderSSHClientLogRecorderSnapshot
connectionFailed(...) is used for connection-setup failures and keeps stage plus diagnostic context instead of exposing lower-level error enums directly.
SSHConnectionFailure now also has diagnosticReport, a copy-ready multi-line summary for support/debug flows.
operationFailed(...) is the first stable wrapper for post-auth library operations such as session reads/writes, direct TCP/IP channel setup, forwarded channel I/O, remote-forward listener accept, and SFTP requests. It keeps:
- the operation scope
- a stable failure code
- connection/channel diagnostics such as negotiated algorithms, whether the server sent
SSH_MSG_EXT_INFO, the server extension names seen so far, remote disconnect/debug context, local/remote channel IDs when known, and SFTP status details when the server returned them
SSHOperationFailure now also has diagnosticReport for the same copy/paste workflow.
SSHNegotiatedTransportAlgorithms now also reports effective integrity per direction. For CTR + HMAC or UMAC transports that matches the negotiated MAC name; for OpenSSH AES-GCM and [email protected] it reports implicit while still keeping the raw negotiated MAC fields available.
SSHClientLogEvent also now has formattedLine. Applications can still build their own sink-based pipeline around that, and SSHClientLogRecorder now provides the built-in bounded recent-event buffer when they want a ready-to-use copy/export path.
For a concrete developer-facing integration pattern, including a recent-event recorder and copyable support payload export, see Diagnostics.
When the host-key policy callback or keyboard-interactive response callback throws, SSHConnectionFailureDiagnostics.callbackFailure describes the callback source and error type without exposing lower-level callback plumbing.
Some errors can still escape unchanged when they come from your own callbacks or trust-policy code. The library does not hide those behind SSHClientError.
Practical Rule
When you evaluate Traversio, think of this public API as:
- real enough to build against
- intentionally narrow
- explicit about connection ownership and wrapper lifetime
- still evolving in the areas of exec/cancellation semantics, host trust breadth, and forwarding lifecycle details