Skip to main content

SSH

Spectranext treats TCP port 22 as SSH. Your Z80 program opens a normal socket, connects to port 22, and uses send / recv (or stream I/O) as usual. The co-processor runs the real SSH client (handshake, host-key check, authentication, PTY shell) on your behalf.

No special syscall is required — connecting to port 22 is enough to trigger SSH offload.

How it works (simple view)

  1. Your program talks to the socket API (socket APIs) as if it were plain TCP.
  2. Spectranext detects destination port 22, opens an SSH session to the server, and bridges bytes between the Z80 and that session.
  3. Before the shell is ready, the co-processor sends short text control lines on the socket so your program can handle trust prompts, username, and password.
  4. After the shell is ready, the co-processor sends CONNECTED and the socket switches to raw terminal mode — every byte is shell input or output, like a normal SSH terminal.

The SSH wire protocol (binary packets, encryption, key exchange) never appears on the Z80 side. You only see the control lines below, then terminal data.

Control mode vs raw mode

PhaseWhenWhat you read/write
ControlFrom first data after connect until CONNECTEDLine-oriented text. Each logical message is one line ending \r\n.
RawAfter CONNECTED\r\nUnframed bytes — keystrokes to the remote shell, output from the server.

While in control mode, read the socket in a loop (for example with poll_fd) and split incoming data on \r\n. Several control lines can arrive in a single recv; handle them one line at a time.

When the co-processor needs input (trust, username, password), it waits for your reply as a single line ending \r\n.

Username and hostname

The connect request can carry a hostname and an optional username. If the username is still unknown, the co-processor may parse user@host from the hostname string. If there is still no username, it sends USER? and waits for your answer.

Typical order of messages

Not every connection sends every line. A rough sequence:

  1. KEY-GENERATION — only the first time on this device, while a new RSA identity keypair is being created and stored.
  2. (SSH handshake with the server — not visible on the Z80 socket.)
  3. IDENTITY — device public key line (informational; no reply required).
  4. TRUST? — if the server host key is new or changed.
  5. USER? / PASSWORD? — if username or password is still needed.
  6. CONNECTED — PTY shell is open; switch to raw terminal I/O.
  7. ERROR — on failure at any stage (connection closes).

Control lines from the cartridge

These are the text lines the co-processor sends to your program on the socket during setup. The payload after the keyword is described in the Details column.

LineDetailsReply expected?
KEY-GENERATION\r\nA new device SSH identity is being generated. Safe to show a short “please wait” message.No
IDENTITY <public-key-line>\r\nOpenSSH-format public key for this device (for example ssh-rsa AAAA… spectranext-esp32c3). Informational only.No
TRUST? <fingerprint>\r\nServer host key is unknown or changed. <fingerprint> is SHA256: followed by base64 (padding = stripped), e.g. SHA256:abc123….Yes — see Trust replies
USER?\r\nUsername required.Yes — one line: username
PASSWORD?\r\nPassword required (password auth offered and no configured credential worked).Yes — one line: password
CONNECTED\r\nShell session ready. Stop parsing control lines; treat all further socket data as terminal I/O.No
ERROR <reason>\r\nSetup or auth failed. <reason> is a short token; see ERROR reasons. Socket is not usable for a shell after this.No

Trust replies

For TRUST?, send one line (ending \r\n) with any of:

  • yes, y, accept, or trust (case-insensitive) — accept and store the host key
  • The exact fingerprint string from the prompt (e.g. SHA256:abc123…) — same effect as accepting
  • Any other answer — treated as rejection; you receive ERROR hostkey_rejected or ERROR hostkey_changed

ERROR reasons

<reason>Meaning
ssh_busyAnother SSH session is already in progress on the co-processor
identity_store_unavailableCould not open device identity storage
identity_generate_failedFailed to generate a new device keypair
identity_store_failedGenerated key could not be saved
zx_stream_failedCould not read or write the Z80 socket stream
prompt_timeoutNo reply to a prompt in time
disconnectedSocket closed while waiting for a prompt
hostkey_unavailableServer did not provide a host key
fingerprint_failedCould not compute host-key fingerprint
hostkey_rejectedUser declined an unknown host key
hostkey_changedUser declined a changed host key
trust_store_failedCould not save accepted host key
auth_method_unavailableServer offers no usable auth (e.g. public-key only with no configured key)
auth_failedAuthentication failed (wrong password, etc.)
channel_open_failedCould not open SSH channel
pty_failedCould not allocate PTY
shell_failedCould not start remote shell
session_alloc_failedCould not allocate SSH session
handshake_failedSSH handshake with server failed
write_failedError writing to server during session
read_failedError reading from server during session
failureGeneric failure

Minimal handling sketch

/* After connect() to port 22, read lines until CONNECTED or ERROR. */
/* Pseudocode — split buffer on "\r\n", dispatch on prefix. */

if (line starts with "TRUST? ")
send(sock, "yes\r\n", 5);
else if (line equals "USER?")
send(sock, username_line); /* must end with \r\n */
else if (line equals "PASSWORD?")
send(sock, password_line);
else if (line equals "CONNECTED")
raw_mode = 1;
else if (line starts with "ERROR ")
/* show reason and close */

A full interactive client with keyboard polling and reconnect is in the repository example examples/ssh-client.

See also

  • HTTPS / TLS — automatic offload on port 443 (fully transparent, no control lines)
  • TLS/HTTPS sockets — development guide with BASIC/assembly examples and CA details
  • Socket APIssocket, connect, send, recv, poll_fd
  • I/O ports — low-level cartridge ports (separate from SSH offload)