Skip to content

Instantly share code, notes, and snippets.

@eaglgenes101
Last active October 20, 2018 21:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eaglgenes101/a99438772511ecfc0542f3377796bb9e to your computer and use it in GitHub Desktop.
Save eaglgenes101/a99438772511ecfc0542f3377796bb9e to your computer and use it in GitHub Desktop.
The protocol is implemented on top of QUIC.
First, the server and client recognise protocol compatibility through ALPN. The protocol is actually a potentially limitless family of mutually incompatible protocols, and has ALPNs of the form "laminar/<game name>/<game ID>", where game name is a string that is 32 bytes or less for the name of the game and perhaps if room permits, a name for the version, and game ID is a 32-byte bytestring that is randomly generated each version that make a change to the network format or game mechanics (even if backwards compatible).
Once compatibility is confirmed and the server and client establish a connection, they can handshake and negotiate extensions. Two QUIC extensions are recommended in particular:
Setting QUIC transport parameter 0x000B (accepts_datagrams) to 1 enables DATAGRAM frame types, which correspond to frame type numbers 0x1C and 0x1D. They are described in detail at https://datatracker.ietf.org/doc/draft-pauly-quic-datagram/ .
Setting QUIC transport parameter 0x04C0 (accepts_implicit_rst) to 1 enables STREAM_IMPLICIT_RST, which correspond to frame type numbers 0x04C0 to 0x04C7. They are described in detail below (this is an extension designed for this protocol):
The least significant 3 bits of the frame type number for STREAM_IMPLICIT_RST indicate the OFF, LEN, and FIN bit flags for STREAM_IMPLICIT_RST frames in the same manner as for STREAM frames.
0.......1.......2.......3.......4.......5.......6.......7.......
[ Stream ID ]
[ Instance ID ]
[ (Offset) ]
[ (Length) ]
[ Stream data... ]
Stream ID: A integer encoded through QUIC's variable-length integer encoding, indicating the stream ID of the stream.
Instance ID: An instance ID for a different stream instance sharing the same stream ID, encoded through QUIC's implicit integer encoding.
Offset: If the offset bit is set, the byte offset of the data in the frame, encoded through QUIC's implicit integer encoding. If the offset bit is not set, the field is absent, and the offset of the data is zero.
Length: If the length bit is set, the length of the stream data field, encoded through QUIC's variable-length integer encoding. If the length bit is not set, the field is absent, and the stream data field extends to the rest of the packet.
Stream data: The data associated with that slice of the stream.
STREAM_IMPLICIT_RST frames are interpreted much like STREAM frames, but if a STREAM_IMPLICIT_RST frame is received after a STREAM frame, a STREAM_IMPLICIT_RST frame is received after one with a different instance ID, or a STREAM frame is received after a STREAM_IMPLICIT_RST frame for a given stream ID, the frame is interpreted as a stream reset and then subsequent data reception. The intended interpretation of STREAM_IMPLICIT_RST is that each independent message sharing the ID is associated with an instance ID, and its addition, change, or removal is the border between messages. This makes divisions between messages more robust and message transmission faster, as it lets messages on the same channel be sent one after another without having to wait too long for acknowledgements of resets to come back. Because packet delay is a thing, short delays should be put between transmissions of messages on the same channel. Explicit RST_STREAM frames are still available to reset the stream explicitly if needed.
... and that's pretty much it to the format. Most of the details are in how the constructs are assembled together for the purposes of transmission.
The protocol is designed as a base system just above QUIC which converts back between the byte buffers of well-defined length seen by the application and the QUIC frames sent along the network. To get messages across efficiently, a mix of frame encodings are used for messages:
- Messages which are small enough to fit in a single frame are encoded as a datagram frame, if available.
- Messages which are not small enough to fit in a single frame but are less than about 10 frames in length are encoded through STREAM_IMPLICIT_RST frames, if available.
- Messages which do not fit into the above categories are encoded by sending a message, and then explicitly expiring it through a RST_STREAM frame with application code 0x0022 to indicate that the data is expired.
Stream IDs do not carry significant meaning beyond distinguishing frames of concurrent messages. Rather, they are kept in a pool, and the library schedues messages to be sent over streams in FIFO order, maximizing throughput while attempting to make sure that different messages are clearly delineated and do not interfere with each other. Outdated messages may, and probably should, be removed from the message queue and/or expired as they become outdated, but should be kept around while they may still be useful to the recipient.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment