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)
- Your program talks to the socket API (socket APIs) as if it were plain TCP.
- Spectranext detects destination port 22, opens an SSH session to the server, and bridges bytes between the Z80 and that session.
- 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.
- After the shell is ready, the co-processor sends
CONNECTEDand 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
| Phase | When | What you read/write |
|---|---|---|
| Control | From first data after connect until CONNECTED | Line-oriented text. Each logical message is one line ending \r\n. |
| Raw | After CONNECTED\r\n | Unframed 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:
KEY-GENERATION— only the first time on this device, while a new RSA identity keypair is being created and stored.- (SSH handshake with the server — not visible on the Z80 socket.)
IDENTITY— device public key line (informational; no reply required).TRUST?— if the server host key is new or changed.USER?/PASSWORD?— if username or password is still needed.CONNECTED— PTY shell is open; switch to raw terminal I/O.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.
| Line | Details | Reply expected? |
|---|---|---|
KEY-GENERATION\r\n | A new device SSH identity is being generated. Safe to show a short “please wait” message. | No |
IDENTITY <public-key-line>\r\n | OpenSSH-format public key for this device (for example ssh-rsa AAAA… spectranext-esp32c3). Informational only. | No |
TRUST? <fingerprint>\r\n | Server host key is unknown or changed. <fingerprint> is SHA256: followed by base64 (padding = stripped), e.g. SHA256:abc123…. | Yes — see Trust replies |
USER?\r\n | Username required. | Yes — one line: username |
PASSWORD?\r\n | Password required (password auth offered and no configured credential worked). | Yes — one line: password |
CONNECTED\r\n | Shell session ready. Stop parsing control lines; treat all further socket data as terminal I/O. | No |
ERROR <reason>\r\n | Setup 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, ortrust(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_rejectedorERROR hostkey_changed
ERROR reasons
<reason> | Meaning |
|---|---|
ssh_busy | Another SSH session is already in progress on the co-processor |
identity_store_unavailable | Could not open device identity storage |
identity_generate_failed | Failed to generate a new device keypair |
identity_store_failed | Generated key could not be saved |
zx_stream_failed | Could not read or write the Z80 socket stream |
prompt_timeout | No reply to a prompt in time |
disconnected | Socket closed while waiting for a prompt |
hostkey_unavailable | Server did not provide a host key |
fingerprint_failed | Could not compute host-key fingerprint |
hostkey_rejected | User declined an unknown host key |
hostkey_changed | User declined a changed host key |
trust_store_failed | Could not save accepted host key |
auth_method_unavailable | Server offers no usable auth (e.g. public-key only with no configured key) |
auth_failed | Authentication failed (wrong password, etc.) |
channel_open_failed | Could not open SSH channel |
pty_failed | Could not allocate PTY |
shell_failed | Could not start remote shell |
session_alloc_failed | Could not allocate SSH session |
handshake_failed | SSH handshake with server failed |
write_failed | Error writing to server during session |
read_failed | Error reading from server during session |
failure | Generic 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 APIs —
socket,connect,send,recv,poll_fd - I/O ports — low-level cartridge ports (separate from SSH offload)