wire

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 10, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Index

Constants

View Source
const (
	FrameOutput byte = 0x00 // daemon -> client
	FrameInput  byte = 0x01 // client -> daemon
)

Binary frame types. Layout is [1 byte type][4 bytes ref BE][payload].

Variables

View Source
var ErrShortFrame = errors.New("wire: frame shorter than header")

Functions

func DecodeBinary

func DecodeBinary(b []byte) (typ byte, ref uint32, payload []byte, err error)

DecodeBinary parses a binary data frame. The returned payload aliases b.

func DecodeControl

func DecodeControl(b []byte) (any, error)

DecodeControl parses a control message into its concrete type.

func EncodeBinary

func EncodeBinary(typ byte, ref uint32, payload []byte) []byte

EncodeBinary builds a binary data frame.

func EncodeControl

func EncodeControl(msg any) ([]byte, error)

EncodeControl marshals msg and injects its "type" discriminator.

Types

type Attach

type Attach struct {
	ID      string `json:"id"`
	LastSeq uint64 `json:"lastSeq"`
	// ReqID correlates this request with the attached or error answering it.
	ReqID uint64 `json:"reqId,omitempty"`
}

type Attached

type Attached struct {
	Ref       uint32 `json:"ref"`
	ID        string `json:"id"`
	Cols      uint16 `json:"cols"`
	Rows      uint16 `json:"rows"`
	Title     string `json:"title"`
	Seq       uint64 `json:"seq"`
	Truncated bool   `json:"truncated"`
	// Head is the offset one past the replayed backlog: bytes below Head are
	// history, bytes at or after it are live. Head == Seq means no backlog.
	Head    uint64 `json:"head"`
	Primary bool   `json:"primary"`
	// ReqID echoes the reqId of the attach or spawn this answers.
	ReqID uint64 `json:"reqId,omitempty"`
}

type CloseSession

type CloseSession struct {
	Ref uint32 `json:"ref,omitempty"`
	ID  string `json:"id,omitempty"`
}

CloseSession ends a session, addressed one of two ways.

Ref is the original spelling: an attachment handle, for a view that is looking at the session it is closing. ID arrived with the all-machines sessions list, which closes rows it never attached to — attaching first just to earn a ref would cost a subscribe, a backlog replay and a detach, all to deliver one verb. A message carries one address or the other; when both appear, the ref wins and the id is ignored, so the ref semantics are exactly what they always were.

Both fields are omitempty because each is absent in the other's message. Zero is no loss to either: refs are numbered from 1, so ref 0 never names an attachment, and an empty id never names a session.

No reply on success, by either address. The session's end announces itself: attached views get exit, and the list sees state "exited" on its next poll. An id the daemon does not hold is answered with error{not_found}, exactly as update answers it, and a ref this connection does not hold with error{bad_ref}.

type Detach

type Detach struct {
	Ref uint32 `json:"ref"`
}

type DeviceInfo

type DeviceInfo struct {
	ID       string `json:"id"`
	Label    string `json:"label"`
	PairedAt int64  `json:"pairedAt"`
	LastSeen int64  `json:"lastSeen"`
}

DeviceInfo is one paired device as the wire reports it. Timestamps are unix seconds rather than the registry's time.Time, so a client reads them without parsing RFC 3339.

type DeviceList

type DeviceList struct {
	Devices []DeviceInfo `json:"devices"`
}

DeviceList answers devices, and follows a revoke that succeeded.

func (DeviceList) MarshalJSON

func (d DeviceList) MarshalJSON() ([]byte, error)

MarshalJSON writes an empty list as [] rather than null.

A nil slice marshals to null by default, and "no devices are paired" is the one state a caller reaches by building the zero value — precisely the path that would ship null. The field is not optional and the client declares it `DeviceInfo[]`, so null would throw in every consumer that ranges over it. Normalising here rather than at each call site means no producer can get it wrong.

type Devices

type Devices struct{}

Devices asks for the paired-device list.

type Error

type Error struct {
	Code string `json:"code"`
	Msg  string `json:"msg"`
	// ReqID echoes the reqId of the request this error answers, when it
	// answers one — not_found and spawn_failed do; a lagged stream does not.
	ReqID uint64 `json:"reqId,omitempty"`
}

type Exit

type Exit struct {
	Ref  uint32 `json:"ref"`
	Code int    `json:"code"`
}

type Hello

type Hello struct {
	Ver  string   `json:"ver"`
	Caps []string `json:"caps,omitempty"`
}

type List

type List struct{}

type PairCancel

type PairCancel struct{}

PairCancel leaves pairing mode, invalidating any outstanding token.

type PairStart

type PairStart struct{}

PairStart enters pairing mode and is answered by pairing.

type Pairing

type Pairing struct {
	Token string `json:"token"`
	// URL is absolute: the /pair page on this origin, carrying the token.
	URL string `json:"url"`
	// DaemonPub is the daemon's static public key, base64.
	DaemonPub string `json:"daemonPub"`
	// ExpiresAt is unix seconds. The token is single-use and short-lived.
	ExpiresAt int64 `json:"expiresAt"`
}

Pairing answers pairStart with the credentials the second device needs.

type Peek

type Peek struct {
	ID    string `json:"id"`
	Bytes int    `json:"bytes,omitempty"`
	// ReqID correlates this request with the preview or error answering it.
	ReqID uint64 `json:"reqId,omitempty"`
}

Peek asks for the tail of a session's scrollback without attaching to it.

It exists for the sessions list, which wants to show what a row is doing without becoming a subscriber to it. An attach would work and is the wrong shape: it costs a ref, a backlog replay, a delivery channel and a detach per row, and a list hovering over twenty sessions would leave twenty subscriptions behind it. This is a read and nothing else — no ref is minted, no stream starts, and the session's LastActive is not touched, because looking at a preview is not activity inside the session.

Bytes caps how much of the tail is returned. Zero means the daemon's own default; anything above PeekMaxBytes is clamped to it rather than refused, since a client asking for too much wants as much as it can have.

Answered by preview, or by error{not_found} for an id the daemon does not hold. ReqID correlates the two, because a list peeks at many rows at once and the answers arrive in whatever order the daemon reaches them.

type Preview

type Preview struct {
	ID string `json:"id"`
	// Data is the raw tail of the session's scrollback.
	Data []byte `json:"data"`
	// Cols and Rows are the dimensions the bytes were drawn at, so a consumer
	// that replays them into an emulator can size it the way the session is.
	Cols uint16 `json:"cols"`
	Rows uint16 `json:"rows"`
	// ReqID echoes the reqId of the peek this answers.
	ReqID uint64 `json:"reqId,omitempty"`
}

Preview answers peek with raw terminal output — escape sequences and all, exactly as the ring holds them.

Raw rather than rendered, because rendering is the client's job and it already owns a terminal emulator. A daemon that flattened this to text would have to make every decision an emulator makes — wrapping at which width, what a cursor move means, which of two overwrites won — and would make them differently from the emulator the same client uses to draw the session for real.

Data is base64 on the wire, as encoding/json carries every []byte. It is the *tail*, so it will usually begin mid-escape-sequence; a consumer must expect to discard a partial sequence at the front rather than treat it as content.

func (Preview) MarshalJSON

func (p Preview) MarshalJSON() ([]byte, error)

MarshalJSON writes an empty tail as "" rather than null.

encoding/json carries a nil []byte as null, and a session that has produced no output at all is reached by the ordinary path rather than an exotic one. The client declares the field a string it base64-decodes, so null would throw on exactly the sessions that have least to show.

type RelayInfo

type RelayInfo struct {
	// Status is "connecting" while the daemon is dialling and "connected" once
	// the socket is up. "off" is what a daemon with no relay would say and is
	// never sent: it is expressed by omitting Relay entirely.
	Status string `json:"status"`
	// Origin is the https origin the relay serves browsers on — the address a
	// pairing URL names while the relay is up. Empty unless Status is
	// "connected", because a socket that is not up carries nothing.
	Origin string `json:"origin,omitempty"`
	// MachineID is the slot this daemon holds on the relay — the <id> in the
	// /client/<id> URL a browser opens to reach this machine. It comes from
	// relay.json rather than from the socket, so it is present whenever the
	// relay is configured, connecting and connected alike.
	MachineID string `json:"machineId,omitempty"`
	// MachineName is the machine's human label, free text from the same file.
	// For lists and titles, never for URLs — that is what MachineID is for.
	MachineName string `json:"machineName,omitempty"`
}

RelayInfo is the state of the daemon's relay leg, as of the moment the connection carrying it was accepted.

It rides the welcome rather than a message of its own because it is not a stream: a client needs it to decide what to render — a QR that names an address a phone can reach, an honest "remote access is down" — and the connection's own opening frame is when it needs it. A daemon whose relay changes state does not push an update; the next connection carries the truth.

A pointer on Welcome, so "no relay configured" is an absent field rather than an object saying "off". The client's type is a union of the three statuses, and a fourth state — present but empty — is one neither side has a meaning for.

type Resize

type Resize struct {
	Ref     uint32 `json:"ref"`
	Cols    uint16 `json:"cols"`
	Rows    uint16 `json:"rows"`
	Primary bool   `json:"primary"`
}

type Revoke

type Revoke struct {
	DeviceID string `json:"deviceId"`
}

Revoke removes a paired device. The daemon answers the requester with a fresh deviceList, and the revoked device's own connections with revoked.

type Revoked

type Revoked struct {
	Reason string `json:"reason"`
}

Revoked goes to the revoked device's own connections just before the daemon closes them, so the tab can say why rather than showing a bare disconnect.

type Sessions

type Sessions struct {
	Sessions []session.Info `json:"sessions"`
}

func (Sessions) MarshalJSON

func (s Sessions) MarshalJSON() ([]byte, error)

MarshalJSON writes an empty list as [] rather than null, for the reason DeviceList does below.

"The daemon is running nothing" is reached by building the zero value — the one path a nil slice takes to the wire. The field is not optional and the client declares it `SessionInfo[]`, so null would throw in every consumer that ranges over the list, and would do it on a fresh machine, which is the first list anyone sees.

type Signal

type Signal struct {
	Ref uint32 `json:"ref"`
	Sig string `json:"sig"`
}

type SizeChanged

type SizeChanged struct {
	Ref     uint32 `json:"ref"`
	Cols    uint16 `json:"cols"`
	Rows    uint16 `json:"rows"`
	Primary bool   `json:"primary"`
}

type Spawn

type Spawn struct {
	Cwd  string   `json:"cwd,omitempty"`
	Cmd  []string `json:"cmd,omitempty"`
	Cols uint16   `json:"cols"`
	Rows uint16   `json:"rows"`
	// ReqID correlates this request with the attached or error answering it.
	// Client-chosen; zero means the client asked for no correlation.
	ReqID uint64 `json:"reqId,omitempty"`
}

type Update

type Update struct {
	ID     string    `json:"id"`
	Name   *string   `json:"name,omitempty"`
	Tags   *[]string `json:"tags,omitempty"`
	Pinned *bool     `json:"pinned,omitempty"`
}

Update edits the metadata a human owns on a session — its name, its tags, whether it is pinned. Nothing the program inside the session says can reach these fields, and nothing here touches what that program says.

Partial, and partial by construction: a field this message does not carry is a field the edit leaves alone. Two views on one session is the ordinary case, so a message that had to restate every field would undo whatever the other view changed since this one last read.

Tags is a pointer to a slice for the one distinction a plain []string cannot keep: "the user removed the last tag" arrives as `[]` and "this edit is not about tags" arrives as nothing, and both decode to a nil slice. The shape mirrors session.MetaPatch field for field, so the daemon can hand the patch straight to the registry rather than rebuild it — a translation being exactly where that distinction would go missing.

Answered by a fresh sessions to the connection that asked, or by error{not_found}. Only that connection: nothing broadcasts the edit, so a second browser sees it on its next list rather than the instant it lands.

type Welcome

type Welcome struct {
	DaemonID string   `json:"daemonId"`
	Host     string   `json:"host"`
	Ver      string   `json:"ver"`
	Caps     []string `json:"caps,omitempty"`
	// Relay is how this daemon is reachable from outside the machine, or nil
	// when it is not configured for a relay at all. See RelayInfo.
	Relay *RelayInfo `json:"relay,omitempty"`

	// FleetCert is this device's own fleet certificate — the signed blob it
	// presents to every *other* machine in the fleet to be admitted without a
	// second ceremony (spec/fleet-trust.md, rule 2) — or empty when there is
	// none to give.
	//
	// It rides the welcome because this connection is the one place the cert
	// can be handed over privately and to the right device at once: the socket
	// is inside Noise, so the relay carries ciphertext, and the far end has
	// already proved it holds the key the cert names. The alternative this
	// replaced was publishing every device cert to the credential-less
	// `GET /directory` and letting the browser find its own — which worked, and
	// put every device's public key and human label in a document anybody on
	// the internet could read, and spent one of the directory's 512 permanent
	// entries on every pairing ceremony ever performed.
	//
	// Empty for a loopback connection (no device identity to hand one to), for
	// a daemon with no fleet key, and for a device paired before the fleet key
	// existed. A client that gets none keeps whatever it already had.
	FleetCert []byte `json:"fleetCert,omitempty"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL