07 / Reference

Protocol

The app and the daemon (and the command line) talk over one byte stream: the daemon’s Unix socket (~/.enowx-app/enowxd.sock) on the same machine, or enowxd bridge over SSH for a server. App and daemon update independently, so the protocol keeps working between versions; this is what it promises (proto/).

Frames

Every message is a frame:

+--------+--------+-----------+----------+
| length | type   | channel   | payload  |
| u32 BE | u8     | u32 BE    | length-5 |
+--------+--------+-----------+----------+

length counts the type, the channel and the payload; a payload is at most 16 MiB.

TypeValueChannelPayload
hello10JSON Hello
request20JSON Request (id, method, params)
response30JSON Response (id, result or error)
event40JSON Event (name, data), pushed by the daemon
output5a streama terminal’s output, from the daemon
resize6a streamthe daemon’s screen size before the output drawn at it: u16 cols, u16 rows, big endian
input7a streamtyped bytes, to the daemon
tunnel-data8a tunnelbytes of a tunnel (tunnel.open), both ways
tunnel-close9a tunnelthe tunnel ended, from either side
tunnel-ack10a tunnelcredit for bytes taken: u32, big endian

Streams come from session.attach, tunnels from tunnel.open.

Handshake and versions

Each side sends a Hello first: {"protocol": N, "agent": "enowx/0.1.0"}. The version N is that of the framing and the handshake, which change only incompatibly. A connection speaks the lower of the two sides’ versions (proto.Negotiate), which must be proto.MinVersion or later; the daemon answers an older app with an error, and the app tells the user to reconnect, which updates the daemon. Today both are 1.

Within a version

Methods, events and fields are added without a new version, under these rules, which go test ./proto enforces against a record of the API (proto/testdata/api.txt: every method and event name, frame type, constant value, and JSON field with its type):

  • Nothing is removed or renamed, and no field changes type. A method kept only for older apps stays answered.
  • New request and result fields are optional (omitempty): an older side leaves them out, a newer one reads them as zero. Unknown fields are ignored.
  • A daemon answers a method it doesn’t know with an error containing unknown method; the app then does without (client.IsUnknownMethod), or tells the user the host’s daemon is older (client.Explain), which reconnecting updates.
  • Events a side doesn’t know are ignored.
  • Behaviors gated on a new daemon are found by asking (a new method, or a field of daemon.info), not by comparing version strings.

Adding to the API means recording it: go test ./proto -run API -update, and checking the diff of testdata/api.txt has only additions.

Methods

The methods, by domain, are in proto/*.go (each file’s constants say what they take and return) and are handled in daemon/rpc_*.go; TestEveryMethodHasHandler checks every one has a handler. testdata/api.txt lists them all.

Edit this page on GitHub