# Protocol Specification The ttrpc protocol is client/server protocol to support multiple request streams over a single connection with lightweight framing. The client represents the process which initiated the underlying connection and the server is the process which accepted the connection. The protocol is currently defined as asymmetrical, with clients sending requests and servers sending responses. Both clients and servers are able to send stream data. The roles are also used in determining the stream identifiers, with client initiated streams using odd number identifiers and server initiated using even number. The protocol may be extended in the future to support server initiated streams, that is not supported in the latest version. ## Purpose The ttrpc protocol is designed to be lightweight and optimized for low latency and reliable connections between processes on the same host. The protocol does not include features for handling unreliable connections such as handshakes, resets, pings, or flow control. The protocol is designed to make low-overhead implementations as simple as possible. It is not intended as a suitable replacement for HTTP2/3 over the network. ## Message Frame Each Message Frame consists of a 10-byte message header followed by message data. The data length and stream ID are both big-endian 4-byte unsigned integers. The message type is an unsigned 1-byte integer. The flags are also an unsigned 1-byte integer and use is defined by the message type. +---------------------------------------------------------------+ | Data Length (32) | +---------------------------------------------------------------+ | Stream ID (32) | +---------------+-----------------------------------------------+ | Msg Type (8) | +---------------+ | Flags (8) | +---------------+-----------------------------------------------+ | Data (*) | +---------------------------------------------------------------+ The Data Length field represents the number of bytes in the Data field. The total frame size will always be Data Length + 10 bytes. The maximum data length is 4MB and any larger size should be rejected. Due to the maximum data size being less than 16MB, the first frame byte should always be zero. This first byte should be considered reserved for future use. The Stream ID must be odd for client initiated streams and even for server initiated streams. Server initiated streams are not currently supported. ## Mesage Types | Message Type | Name | Description | |--------------|----------|----------------------------------| | 0x01 | Request | Initiates stream | | 0x02 | Response | Final stream data and terminates | | 0x03 | Data | Stream data | ### Request The request message is used to initiate stream and send along request data for properly routing and handling the stream. The stream may indicate unary without any inbound or outbound stream data with only a response is expected on the stream. The request may also indicate the stream is still open for more data and no response is expected until data is finished. If the remote indicates the stream is closed, the request may be considered non-unary but without anymore stream data sent. In the case of `remote closed`, the remote still expects to receive a response or stream data. For compatibility with non streaming clients, a request with empty flags indicates a unary request. #### Request Flags | Flag | Name | Description | |------|-----------------|--------------------------------------------------| | 0x01 | `remote closed` | Non-unary, but no more data expected from remote | | 0x02 | `remote open` | Non-unary, remote is still sending data | ### Response The response message is used to end a stream with data, an empty response, or an error. A response message is the only expected message after a unary request. A non-unary request does not require a response message if the server is sending back stream data. A non-unary stream may return a single response message but no other stream data may follow. #### Response Flags No response flags are defined at this time, flags should be empty. ### Data The data message is used to send data on an already initialized stream. Either client or server may send data. A data message is not allowed on a unary stream. A data message should not be sent after indicating `remote closed` to the peer. The last data message on a stream must set the `remote closed` flag. The `no data` flag is used to indicate that the data message does not include any data. This is normally used with the `remote closed` flag to indicate the stream is now closed without transmitting any data. Since ttrpc normally transmits a single object per message, a zero length data message may be interpreted as an empty object. For example, transmitting the number zero as a protobuf message ends up with a data length of zero, but the message is still considered data and should be processed. #### Data Flags | Flag | Name | Description | |------|-----------------|-----------------------------------| | 0x01 | `remote closed` | No more data expected from remote | | 0x04 | `no data` | This message does not have data | ## Streaming All ttrpc requests use streams to transfer data. Unary streams will only have two messages sent per stream, a request from a client and a response from the server. Non-unary streams, however, may send any numbers of messages from the client and the server. This makes stream management more complicated than unary streams since both client and server need to track additional state. To keep this management as simple as possible, ttrpc minimizes the number of states and uses two flags instead of control frames. Each stream has two states while a stream is still alive: `local closed` and `remote closed`. Each peer considers local and remote from their own perspective and sets flags from the other peer's perspective. For example, if a client sends a data frame with the `remote closed` flag, that is indicating that the client is now `local closed` and the server will be `remote closed`. A unary operation does not need to send these flags since each received message always indicates `remote closed`. Once a peer is both `local closed` and `remote closed`, the stream is considered finished and may be cleaned up. Due to the asymmetric nature of the current protocol, a client should always be in the `local closed` state before `remote closed` and a server should always be in the `remote closed` state before `local closed`. This happens because the client is always initiating requests and a client always expects a final response back from a server to indicate the initiated request has been fulfilled. This may mean server sends a final empty response to finish a stream even after it has already completed sending data before the client. ### Unary State Diagram +--------+ +--------+ | Client | | Server | +---+----+ +----+---+ | +---------+ | local >---------------+ Request +--------------------> remote closed | +---------+ | closed | | | +----------+ | finished <--------------+ Response +--------------------< finished | +----------+ | | | ### Non-Unary State Diagrams RC: `remote closed` flag RO: `remote open` flag +--------+ +--------+ | Client | | Server | +---+----+ +----+---+ | +--------------+ | >-------------+ Request [RO] +-----------------> | +--------------+ | | | | +------+ | >-----------------+ Data +---------------------> | +------+ | | | | +-----------+ | local >---------------+ Data [RC] +------------------> remote closed | +-----------+ | closed | | | +----------+ | finished <--------------+ Response +--------------------< finished | +----------+ | | | +--------+ +--------+ | Client | | Server | +---+----+ +----+---+ | +--------------+ | local >-------------+ Request [RC] +-----------------> remote closed | +--------------+ | closed | | | +------+ | <-----------------+ Data +---------------------< | +------+ | | | | +-----------+ | finished <---------------+ Data [RC] +------------------< finished | +-----------+ | | | +--------+ +--------+ | Client | | Server | +---+----+ +----+---+ | +--------------+ | >-------------+ Request [RO] +-----------------> | +--------------+ | | | | +------+ | >-----------------+ Data +---------------------> | +------+ | | | | +------+ | <-----------------+ Data +---------------------< | +------+ | | | | +------+ | >-----------------+ Data +---------------------> | +------+ | | | | +-----------+ | local >---------------+ Data [RC] +------------------> remote closed | +-----------+ | closed | | | +------+ | <-----------------+ Data +---------------------< | +------+ | | | | +-----------+ | finished <---------------+ Data [RC] +------------------< finished | +-----------+ | | | ## RPC While this protocol is defined primarily to support Remote Procedure Calls, the protocol does not define the request and response types beyond the messages defined in the protocol. The implementation provides a default protobuf definition of request and response which may be used for cross language rpc. All implementations should at least define a request type which support routing by procedure name and a response type which supports call status. ## Version History | Version | Features | |---------|---------------------| | 1.0 | Unary requests only | | 1.2 | Streaming support |