# Dilation Internals

Wormhole dilation involves several moving parts. Both sides exchange messages
through the Mailbox server to coordinate the establishment of a more direct
connection. This connection might flow in either direction, so they trade
"connection hints" to point at potential listening ports. This process might
succeed in making multiple connections at about the same time, so one side
must select the best one to use, and cleanly shut down the others. To make
the dilated connection *durable*, this side must also decide when the
connection has been lost, and then coordinate the construction of a
replacement. Within this connection, a series of queued-and-acked subchannel
messages are used to open/use/close the application-visible subchannels.

## Versions and can-dilate

The Wormhole protocol includes a `versions` message sent immediately after
the shared PAKE key is established. This also serves as a key-confirmation
message, allowing each side to confirm that the other side knows the right
key. The body of the `versions` message is a JSON-formatted string with keys
that are available for learning the abilities of the peer. Dilation is
signaled by a key named `can-dilate`, whose value is a list of strings. Any
version present in both side's lists is eligible for use.

## Leaders and Followers

Each side of a Wormhole has a randomly-generated dilation `side` string (this
is included in the `please-dilate` message, and is independent of the
Wormhole's mailbox "side"). When the wormhole is dilated, the side with the
lexicographically-higher "side" value is named the "Leader", and the other
side is named the "Follower". The general wormhole protocol treats both sides
identically, but the distinction matters for the dilation protocol.

Both sides send a `please-dilate` as soon as dilation is triggered. Each side
discovers whether it is the Leader or the Follower when the peer's
"please-dilate" arrives. The Leader has exclusive control over whether a
given connection is considered established or not: if there are multiple
potential connections to use, the Leader decides which one to use, and the
Leader gets to decide when the connection is no longer viable (and triggers
the establishment of a new one).

The `please-dilate` includes a `use-version` key, computed as the "best"
version of the intersection of the two sides' abilities as reported in the
`versions` message. Both sides will use whichever `use-version` was specified
by the Leader (they learn which side is the Leader at the same moment they
learn the peer's `use-version` value). If the Follower cannot handle the
`use-version` value, dilation fails (this shouldn't happen, as the Leader
knew what the Follower was and was not capable of before sending that
message).

## Connection Layers

We describe the protocol as a series of layers. Messages sent on one layer
may be encoded or transformed before being delivered on some other layer.

L1 is the mailbox channel (queued store-and-forward messages that always go
to the mailbox server, and then are forwarded to other clients subscribed to
the same mailbox). Both clients remain connected to the mailbox server until
the Wormhole is closed. They send DILATE-n messages to each other to manage
the dilation process, including records like `please`, `connection-hints`,
`reconnect`, and `reconnecting`.

L2 is the set of competing connection attempts for a given generation of
connection. Each time the Leader decides to establish a new connection, a new
generation number is used. Hopefully these are direct TCP connections between
the two peers, but they may also include connections through the transit
relay. Each connection must go through an encrypted handshake process before
it is considered viable. Viable connections are then submitted to a selection
process (on the Leader side), which chooses exactly one to use, and drops the
others. It may wait an extra few seconds in the hopes of getting a "better"
connection (faster, cheaper, etc), but eventually it will select one.

L3 is the current selected connection. There is one L3 for each generation.
At all times, the wormhole will have exactly zero or one L3 connection. L3 is
responsible for the selection process, connection monitoring/keepalives, and
serialization/deserialization of the plaintext frames. L3 delivers decoded
frames and connection-establishment events up to L4.

L4 is the persistent higher-level channel. It is created as soon as the first
L3 connection is selected, and lasts until wormhole is closed entirely. L4
contains OPEN/DATA/CLOSE/ACK messages: OPEN/DATA/CLOSE have a sequence number
(scoped to the L4 connection and the direction of travel), and the ACK
messages reference those sequence numbers. When a message is given to the L4
channel for delivery to the remote side, it is always queued, then
transmitted if there is an L3 connection available. This message remains in
the queue until an ACK is received to retire it. If a new L3 connection is
made, all queued messages will be re-sent (in seqnum order).

L5 are subchannels. There is one pre-established subchannel 0 known as the
"control channel", which does not require an OPEN message. All other
subchannels are created by the receipt of an OPEN message with the subchannel
number. DATA frames are delivered to a specific subchannel. When the
subchannel is no longer needed, one side will invoke the ``close()`` API
(``loseConnection()`` in Twisted), which will cause a CLOSE message to be
sent, and the local L5 object will be put into the "closing "state. When the
other side receives the CLOSE, it will send its own CLOSE for the same
subchannel, and fully close its local object (``connectionLost()``). When the
first side receives CLOSE in the "closing" state, it will fully close its
local object too.

All L5 subchannels will be paused (``pauseProducing()``) when the L3
connection is paused or lost. They are resumed when the L3 connection is
resumed or reestablished.

## Initiating Dilation

Dilation is triggered by calling the `w.dilate()` API. This returns a
Deferred that will fire once the first L3 connection is established. It fires
with a 3-tuple of endpoints that can be used to establish subchannels, or an
error if dilation is not possible. If the other side's `versions` message
indicates that it does not support dilation, the Deferred will errback with
an `OldPeerCannotDilateError`.

For dilation to succeed, both sides must call `w.dilate()`, since the
resulting endpoints are the only way to access the subchannels. If the other
side is capable of dilation, but never calls `w.dilate()`, the Deferred will
never fire.

The L1 (mailbox) path is used to deliver dilation requests and connection
hints. The current mailbox protocol uses named "phases" to distinguish
messages (rather than behaving like a regular ordered channel of arbitrary
frames or bytes), and all-number phase names are reserved for application
data (sent via `w.send_message()`). Therefore the dilation control messages
use phases named `DILATE-0`, `DILATE-1`, etc. Each side maintains its own
counter, so one side might be up to e.g. `DILATE-5` while the other has only
gotten as far as `DILATE-2`. This effectively creates a pair of
unidirectional streams of `DILATE-n` messages, each containing one or more
dilation record, of various types described below. Note that all phases
beyond the initial VERSION and PAKE phases are encrypted by the shared
session key.

A future mailbox protocol might provide a simple ordered stream of typed
messages, with application records and dilation records mixed together.

Each `DILATE-n` message is a JSON-encoded dictionary with a `type` field that
has a string value. The dictionary will have other keys that depend upon the
type.

`w.dilate()` triggers transmission of a `please` (i.e. "please dilate")
record with a set of versions that can be accepted. Versions use strings,
rather than integers, to support experimental protocols, however there is
still a total ordering of preferability.

```
{ "type": "please",
  "side": "abcdef",
  "accepted-versions": ["1"]
}
```

If one side receives a `please` before `w.dilate()` has been called locally,
the contents are stored in case `w.dilate()` is called in the future. Once
both `w.dilate()` has been called and the peer's `please` has been received,
the side determines whether it is the Leader or the Follower. Both sides also
compare `accepted-versions` fields to choose the best mutually-compatible
version to use: they should always pick the same one.

Then both sides begin the connection process for generation 1 by opening
listening sockets and sending `connection-hint` records for each one. After a
slight delay they will also open connections to the Transit Relay of their
choice and produce hints for it too. The receipt of inbound hints (on both
sides) will trigger outbound connection attempts.

Some number of these connections may succeed, and the Leader decides which to
use (via an in-band signal on the established connection). The others are
dropped.

If something goes wrong with the established connection and the Leader
decides a new one is necessary, the Leader will send a `reconnect` message.
This might happen while connections are still being established, or while the
Follower thinks it still has a viable connection (the Leader might observe
problems that the Follower does not), or after the Follower thinks the
connection has been lost. In all cases, the Leader is the only side which
should send `reconnect`. The state machine code looks the same on both sides,
for simplicity, but one path on each side is never used.

Upon receiving a `reconnect`, the Follower should stop any pending connection
attempts and terminate any existing connections (even if they appear viable).
Listening sockets may be retained, but any previous connection made through
them must be dropped.

Once all connections have stopped, the Follower should send a `reconnecting`
message, then start the connection process for the next generation, which
will send new `connection-hint` messages for all listening sockets.

Generations are non-overlapping. The Leader will drop all connections from
generation 1 before sending the `reconnect` for generation 2, and will not
initiate any gen-2 connections until it receives the matching `reconnecting`
from the Follower. The Follower must drop all gen-1 connections before it
sends the `reconnecting` response (even if it thinks they are still
functioning: if the Leader thought the gen-1 connection still worked, it
wouldn't have started gen-2).

(TODO: what about a follower->leader connection that was started before
start-dilation is received, and gets established on the Leader side after
start-dilation is sent? the follower will drop it after it receives
start-dilation, but meanwhile the leader may accept it as gen2)

(probably need to include the generation number in the handshake, or in the
derived key)

(TODO: reduce the number of round-trip stalls here, I've added too many)

Each side is in the "connecting" state (which encompasses both making
connection attempts and having an established connection) starting with the
receipt of a `please-dilate` message and a local `w.dilate()` call. The
Leader remains in that state until it abandons the connection and sends a
`reconnect` message, at which point it remains in the "flushing" state until
the Follower's `reconnecting` message is received. The Follower remains in
"connecting" until it receives `reconnect`, then it stays in "dropping" until
it finishes halting all outstanding connections, after which it sends
`reconnecting` and switches back to "connecting".

"Connection hints" are type/address/port records that tell the other side of
likely targets for L2 connections. Both sides will try to determine their
external IP addresses, listen on a TCP port, and advertise `(tcp,
external-IP, port)` as a connection hint. The Transit Relay is also used as a
(lower-priority) hint. These are sent in `connection-hint` records, which can
be sent any time after both sending and receiving a `please` record. Each
side will initiate connections upon receipt of the hints.

```
{ "type": "connection-hints",
  "hints": [ ... ]
}
```

Hints can arrive at any time. One side might immediately send hints that can
be computed quickly, then send additional hints later as they become
available. For example, it might enumerate the local network interfaces and
send hints for all of the LAN addresses first, then send port-forwarding
(UPnP) requests to the local router. When the forwarding is established
(providing an externally-visible IP address and port), it can send additional
hints for that new endpoint. If the other peer happens to be on the same LAN,
the local connection can be established without waiting for the router's
response.


### Connection Hint Format

Each member of the `hints` field describes a potential L2 connection target
endpoint, with an associated priority and a set of hints.

The priority is a number (positive or negative float), where larger numbers
indicate that the client supplying that hint would prefer to use this
connection over others of lower number. This indicates a sense of cost or
performance. For example, the Transit Relay is lower priority than a direct
TCP connection, because it incurs a bandwidth cost (on the relay operator),
as well as adding latency.

Each endpoint has a set of hints, because the same target might be reachable
by multiple hints. Once one hint succeeds, there is no point in using the
other hints.

TODO: think this through some more. What's the example of a single endpoint
reachable by multiple hints? Should each hint have its own priority, or just
each endpoint?

## L2 protocol

Upon ``connectionMade()``, both sides send their handshake message. The
Leader sends "Magic-Wormhole Dilation Handshake v1 Leader\n\n". The Follower
sends "Magic-Wormhole Dilation Handshake v1 Follower\n\n". This should
trigger an immediate error for most non-magic-wormhole listeners (e.g. HTTP
servers that were contacted by accident). If the wrong handshake is received,
the connection will be dropped. For debugging purposes, the node might want
to keep looking at data beyond the first incorrect character and log
a few hundred characters until the first newline.

Everything beyond that point is a Noise protocol message, which consists of a
4-byte big-endian length field, followed by the indicated number of bytes.
This uses the `NNpsk0` pattern with the Leader as the first party ("-> psk,
e" in the Noise spec), and the Follower as the second ("<- e, ee"). The
pre-shared-key is the "dilation key", which is statically derived from the
master PAKE key using HKDF. Each L2 connection uses the same dilation key,
but different ephemeral keys, so each gets a different session key.

The Leader sends the first message, which is a psk-encrypted ephemeral key.
The Follower sends the next message, its own psk-encrypted ephemeral key.
These two messages are known as "handshake messages" in the Noise protocol,
and must be processed in a specific order (the Leader must not accept the
Follower's message until it has generated its own). Noise allows handshake
messages to include a payload, but we do not use this feature.

All subsequent messages as known as "Noise transport messages", and use
independent channels for each direction, so they no longer have ordering
dependencies. Transport messages are encrypted by the shared key, in a form
that evolves as more messages are sent.

The Follower's first transport message is an empty packet, which we use as a
"key confirmation message" (KCM).

The Leader doesn't send a transport message right away: it waits to see the
Follower's KCM, which indicates this connection is viable (i.e. the Follower
used the same dilation key as the Leader, which means they both used the same
wormhole code).

The Leader delivers the now-viable protocol object to the L3 manager, which
will decide which connection to select. When some L2 connection is selected
to be the new L3, the Leader finally sends an empty KCM of its own over that
L2, to let the Follower know which connection has been selected. All other L2
connections (either viable or still in handshake) are dropped, and all other
connection attempts are cancelled. All listening sockets may or may not be
shut down (TODO: think about it).

After sending their KCM, the Follower will wait for either an empty KCM (at
which point the L2 connection is delivered to the Dilation manager as the new
L3), a disconnection, or an invalid message (which causes the connection to
be dropped). Other connections and/or listening sockets are stopped.

Internally, the L2Protocol object manages the Noise session itself. It knows
(via a constructor argument) whether it is on the Leader or Follower side,
which affects both the role is plays in the Noise pattern, and the reaction
to receiving the handshake message / ephemeral key (for which only the
Follower sends an empty KCM message). After that, the L2Protocol notifies the
L3 object in three situations:

* the Noise session produces a valid decrypted frame (for Leader, this
  includes the Follower's KCM, and thus indicates a viable candidate for
  connection selection)
* the Noise session reports a failed decryption
* the TCP session is lost

All notifications include a reference to the L2Protocol object (`self`). The
L3 object uses this reference to either close the connection (for errors or
when the selection process chooses someone else), to send the KCM message
(after selection, only for the Leader), or to send other L4 messages. The L3
object will retain a reference to the winning L2 object.

## L3 protocol

The L3 layer is responsible for connection selection, monitoring/keepalives,
and message (de)serialization. Framing is handled by L2, so the inbound L3
codepath receives single-message bytestrings, and delivers the same down to
L2 for encryption, framing, and transmission.

Connection selection takes place exclusively on the Leader side, and includes
the following:

* receipt of viable L2 connections from below (indicated by the first valid
  decrypted frame received for any given connection)
* expiration of a timer
* comparison of TBD quality/desirability/cost metrics of viable connections
* selection of winner
* instructions to losing connections to disconnect
* delivery of KCM message through winning connection
* retain reference to winning connection

On the Follower side, the L3 manager just waits for the first connection to
receive the Leader's KCM, at which point it is retained and all others are
dropped.

The L3 manager knows which "generation" of connection is being established.
Each generation uses a different dilation key (?), and is triggered by a new
set of L1 messages. Connections from one generation should not be confused
with those of a different generation.

Each time a new L3 connection is established, the L4 protocol is notified. It
will will immediately send all the L4 messages waiting in its outbound queue.
The L3 protocol simply wraps these in Noise frames and sends them to the
other side.

The L3 manager monitors the viability of the current connection, and declares
it as lost when bidirectional traffic cannot be maintained. It uses PING and
PONG messages to detect this. These also serve to keep NAT entries alive,
since many firewalls will stop forwarding packets if they don't observe any
traffic for e.g. 5 minutes.

Our goals are:

* don't allow more than 30? seconds to pass without at least *some* data
  being sent along each side of the connection
* allow the Leader to detect silent connection loss within 60? seconds
* minimize overhead

We need both sides to:

* maintain a 30-second repeating timer
* set a flag each time we write to the connection
* each time the timer fires, if the flag was clear then send a PONG,
  otherwise clear the flag

In addition, the Leader must:

* run a 60-second repeating timer (ideally somewhat offset from the other)
* set a flag each time we receive data from the connection
* each time the timer fires, if the flag was clear then drop the connection,
  otherwise clear the flag

In the future, we might have L2 links that are less connection-oriented,
which might have a unidirectional failure mode, at which point we'll need to
monitor full roundtrips. To accomplish this, the Leader will send periodic
unconditional PINGs, and the Follower will respond with PONGs. If the
Leader->Follower connection is down, the PINGs won't arrive and no PONGs will
be produced. If the Follower->Leader direction has failed, the PONGs won't
arrive. The delivery of both will be delayed by actual data, so the timeouts
should be adjusted if we see regular data arriving.

If the connection is dropped before the wormhole is closed (either the other
end explicitly dropped it, we noticed a problem and told TCP to drop it, or
TCP noticed a problem itself), the Leader-side L3 manager will initiate a
reconnection attempt. This uses L1 to send a new DILATE message through the
mailbox server, along with new connection hints. Eventually this will result
in a new L3 connection being established.

Finally, L3 is responsible for message serialization and deserialization. L2
performs decryption and delivers plaintext frames to L3. Each frame starts
with a one-byte type indicator. The rest of the message depends upon the
type:

* 0x00 PING, 4-byte ping-id
* 0x01 PONG, 4-byte ping-id
* 0x02 OPEN, 4-byte subchannel-id, 4-byte seqnum
* 0x03 DATA, 4-byte subchannel-id, 4-byte seqnum, variable-length payload
* 0x04 CLOSE, 4-byte subchannel-id, 4-byte seqnum
* 0x05 ACK, 4-byte response-seqnum

All seqnums are big-endian, and are provided by the L4 protocol. The other
fields are arbitrary and not interpreted as integers. The subchannel-ids must
be allocated by both sides without collision, but otherwise they are only
used to look up L5 objects for dispatch. The response-seqnum is always copied
from the OPEN/DATA/CLOSE packet being acknowledged.

L3 consumes the PING and PONG messages. Receiving any PING will provoke a
PONG in response, with a copy of the ping-id field. The 30-second timer will
produce unprovoked PONGs with a ping-id of all zeros. A future viability
protocol will use PINGs to test for roundtrip functionality.

All other messages (OPEN/DATA/CLOSE/ACK) are deserialized and delivered
"upstairs" to the L4 protocol handler.

The current L3 connection's `IProducer`/`IConsumer` interface is made
available to the L4 flow-control manager.

## L4 protocol

The L4 protocol manages a durable stream of OPEN/DATA/CLOSE/ACK messages.
Since each will be enclosed in a Noise frame before they pass to L3, they do
not need length fields or other framing.

Each OPEN/DATA/CLOSE has a sequence number, starting at 0, and monotonically
increasing by 1 for each message. Each direction has a separate number space.

The L4 manager maintains a double-ended queue of unacknowledged outbound
messages. Subchannel activity (opening, closing, sending data) cause messages
to be added to this queue. If an L3 connection is available, these messages
are also sent over that connection, but they remain in the queue in case the
connection is lost and they must be retransmitted on some future replacement
connection. Messages stay in the queue until they can be retired by the
receipt of an ACK with a matching response-sequence-number. This provides
reliable message delivery that survives the L3 connection being replaced.

ACKs are not acked, nor do they have seqnums of their own. Each inbound side
remembers the highest ACK it has sent, and ignores incoming OPEN/DATA/CLOSE
messages with that sequence number or higher. This ensures in-order
at-most-once processing of OPEN/DATA/CLOSE messages.

Each inbound OPEN message causes a new L5 subchannel object to be created.
Subsequent DATA/CLOSE messages for the same subchannel-id are delivered to
that object.

Each time an L3 connection is established, the side will immediately send all
L4 messages waiting in the outbound queue. A future protocol might reduce
this duplication by including the highest received sequence number in the L1
PLEASE-DILATE message, which would effectively retire queued messages before
initiating the L2 connection process. On any given L3 connection, all
messages are sent in-order. The receipt of an ACK for seqnum `N` allows all
messages with `seqnum <= N` to be retired.

The L4 layer is also responsible for managing flow control among the L3
connection and the various L5 subchannels.

## L5 subchannels

The L5 layer consists of a collection of "subchannel" objects, a dispatcher,
and the endpoints that provide the Twisted-flavored API.

Other than the "control channel", all subchannels are created by a client
endpoint connection API. The side that calls this API is named the Initiator,
and the other side is named the Acceptor. Subchannels can be initiated in
either direction, independent of the Leader/Follower distinction. For a
typical file-transfer application, the subchannel would be initiated by the
side seeking to send a file.

Each subchannel uses a distinct subchannel-id, which is a four-byte
identifier. Both directions share a number space (unlike L4 seqnums), so the
rule is that the Leader side sets the last bit of the last byte to a 1, while
the Follower sets it to a 0. These are not generally treated as integers,
however for the sake of debugging, the implementation generates them with a
simple big-endian-encoded counter (`counter*2+1` for the Leader,
`counter*2+2` for the Follower, with id `0` reserved for the control
channel).

When the `client_ep.connect()` API is called, the Initiator allocates a
subchannel-id and sends an OPEN. It can then immediately send DATA messages
with the outbound data (there is no special response to an OPEN, so there is
no need to wait). The Acceptor will trigger their `.connectionMade` handler
upon receipt of the OPEN.

Subchannels are durable: they do not close until one side calls
`.loseConnection` on the subchannel object (or the enclosing Wormhole is
closed). Either the Initiator or the Acceptor can call `.loseConnection`.
This causes a CLOSE message to be sent (with the subchannel-id). The other
side will send its own CLOSE message in response. Each side will signal the
`.connectionLost()` event upon receipt of a CLOSE.

There is no equivalent to TCP's "half-closed" state, however if only one side
calls `close()`, then all data written before that call will be delivered
before the other side observes `.connectionLost()`. Any inbound data that was
queued for delivery before the other side sees the CLOSE will still be
delivered to the side that called `close()` before it sees
`.connectionLost()`. Internally, the side which called `.loseConnection` will
remain in a special "closing" state until the CLOSE response arrives, during
which time DATA payloads are still delivered. After calling `close()` (or
receiving CLOSE), any outbound `.write()` calls will trigger an error.

DATA payloads that arrive for a non-open subchannel are logged and discarded.

This protocol calls for one OPEN and two CLOSE messages for each subchannel,
with some arbitrary number of DATA messages in between. Subchannel-ids should
not be reused (it would probably work, the protocol hasn't been analyzed
enough to be sure).

The "control channel" is special. It uses a subchannel-id of all zeros, and
is opened implicitly by both sides as soon as the first L3 connection is
selected. It is routed to a special client-on-both-sides endpoint, rather
than causing the listening endpoint to accept a new connection. This avoids
the need for application-level code to negotiate who should be the one to
open it (the Leader/Follower distinction is private to the Wormhole
internals: applications are not obligated to pick a side).

OPEN and CLOSE messages for the control channel are logged and discarded. The
control-channel client endpoints can only be used once, and does not close
until the Wormhole itself is closed.

Each OPEN/DATA/CLOSE message is delivered to the L4 object for queueing,
delivery, and eventual retirement. The L5 layer does not keep track of old
messages.

### Flow Control

Subchannels are flow-controlled by pausing their writes when the L3
connection is paused, and pausing the L3 connection when the subchannel
signals a pause. When the outbound L3 connection is full, *all* subchannels
are paused. Likewise the inbound connection is paused if *any* of the
subchannels asks for a pause. This is much easier to implement and improves
our utilization factor (we can use TCP's window-filling algorithm, instead of
rolling our own), but will block all subchannels even if only one of them
gets full. This shouldn't matter for many applications, but might be
noticeable when combining very different kinds of traffic (e.g. a chat
conversation sharing a wormhole with file-transfer might prefer the IM text
to take priority).

Each subchannel implements Twisted's `ITransport`, `IProducer`, and
`IConsumer` interfaces. The Endpoint API causes a new `IProtocol` object to
be created (by the caller's factory) and glued to the subchannel object in
the `.transport` property, as is standard in Twisted-based applications.

All subchannels are also paused when the L3 connection is lost, and are
unpaused when a new replacement connection is selected.
