# Meta information meta: # Document name and ID id: md-d2m name: Device to Mediator Protocol # References used by the structs references: # A reflect message identifier reflect-id: &reflect-id u32-le # A Unix-ish timestamp in milliseconds timestamp: ×tamp u64-le # Virtual namespace, just containing the below docstring index: &index _doc: |- # Device to Mediator Protocol This protocol describes the communication between Threema client and mediator server. As transport protocol, WebSocket is used. Over this WebSocket connection, messages both between the device and the mediator server (D2M) and between the device and the chat server (CSP) are multiplexed. ## General Information **Encryption cipher:** XSalsa20-Poly1305, unless otherwise specified. All strings are UTF-8 encoded. ## Payload Format All messages are wrapped in the [`container`](ref:payload.container). Most payload messages contained within the container are encoded with protobuf, with a few exceptions for messages that are sent very frequently (proxying and reflection) and which use a more compact representation. ## Chat Server Proxying The chat server uses a TCP stream based protocol (chat server protocol / CSP). To be able to proxy this protocol over the message based WebSocket protocol, we need framing. Framed messages from/to the chat server are sent using [`proxy`](ref:payload.proxy) messages wrapped in [`container`](ref:payload.container). ## Size Limitations The device to mediator protocol currently allows for up to 65536 bytes within a single message. To elaborate this down to encrypted device to device messages, the limitations are: - 65536 bytes for a payload container struct, - 65532 bytes for a payload struct, before wrapping it with a container, - 65516 bytes for an encrypted `d2d.Envelope` in a [`reflected`](ref:payload.reflected) struct. - 65492 bytes for the plain `d2d.Envelope` to be sent within a [`reflected`](ref:payload.reflected) struct. Note that the header lengths of [`reflect`](ref:payload.reflect) and [`reflected`](ref:payload.reflected) are dynamic, so the maximum size of a `d2d.Envelope` may be reduced further in the future. ## Version Negotiation The server sends along the highest supported protocol version in the `ServerHello` message. The client then chooses a `ClientHello.version` <= `ServerHello.version`. If the server version sent in `ServerHello` is unsupported by the client, the client disconnects with a [close code](ref:index#close-codes) of 4110 (Unsupported Protocol Version). If the client version sent in `ClientHello` is unsupported by the server, the server disconnects with a [close code](ref:index#close-codes) of 4110 (Unsupported Protocol Version). Otherwise, the version from `ClientHello.version` is used for further communication. ## Close Codes WebSocket Internal Close Codes (1xxx): - `1000`: Normal closure, connection successfully completed - `1001`: Server is shutting down - `1011`: Server terminated the connection due to an internal error Chat Server Close Codes (400x and 410x): - `4000`: Chat server connection closed - `4001`: Chat server connection could not be established - `4009`: Internal error related to chat server connection Mediator Server Close Codes (40[1-9]x and 41[1-9]x): - `4010`: Protocol error - `4011`: Transaction TTL reached - `4012`: Unknown message acked - `4013`: Client idle timeout exceeded - `4110`: Unsupported protocol version - `4111`: Device limit reached - `4112`: Duplicate connection (i.e. the same device reconnected, terminating the previous connection) - `4113`: Dropped by other device - `4114`: Dropped by server because the reflection queue length limit was reached - `4115`: Device slot state mismatch Reconnect policy: - `1xxx` and `40xx` do allow for automatic reconnect. - `41xx` does not allow for automatic reconnect and require user interaction before a reconnect attempt may be made. - Any other close code should result in a warning in the log, but automatic reconnects are allowed. Important: Whenever a `close-error` message is being received from the Chat Server, the reconnect policy solely depends on the `close-error.can-reconnect` field and the (following) Close Code must be ignored. When automatically reconnecting, linear backoff should be applied. In case the connection fails repeatedly, user interaction should be required to continue reconnecting. ## Security The client must pin the TLS certificate of the server, so that the server can be authenticated. The client authenticates itself during the handshake with the server that it is part of the device group by responding to a challenge. A malicious server can connect arbitrary devices with one another but this would be detected eventually because decrypting reflected messages would fail. # Payload structs payload: &payload container: _doc: |- Contains a mediator message payload. Direction: Client <-> Server fields: - _doc: |- Identifies the payload type contained in the `payload` field. Chat server proxying: - `0x00`: [`proxy`](ref:payload.proxy) Handshake: - `0x10`: `d2m.ServerHello` - `0x11`: `d2m.ClientHello` - `0x12`: `d2m.ServerInfo` States: - `0x20`: `d2m.ReflectionQueueDry` - `0x21`: `d2m.RolePromotedToLeader` Device management: - `0x30`: `d2m.GetDevicesInfo` - `0x31`: `d2m.DevicesInfo` - `0x32`: `d2m.DropDevice` - `0x33`: `d2m.DropDeviceAck` - `0x34`: `d2m.SetSharedDeviceData` Transactions: - `0x40`: `d2m.BeginTransaction` - `0x41`: `d2m.BeginTransactionAck` - `0x42`: `d2m.CommitTransaction` - `0x43`: `d2m.CommitTransactionAck` - `0x44`: `d2m.TransactionRejected` - `0x45`: `d2m.TransactionEnded` Reflection: - `0x80`: [`reflect`](ref:payload.reflect) - `0x81`: [`reflect-ack`](ref:payload.reflect-ack) - `0x82`: [`reflected`](ref:payload.reflected) - `0x83`: [`reflected-ack`](ref:payload.reflected-ack) name: type type: u8 - _doc: |- Should be set to all `0`s and ignored by the receiver. name: reserved type: b3 - _doc: |- Message payload. Needs to be parsed according to the `type` field. name: payload type: b* proxy: _doc: |- Proxy a message to/from the chat server. fields: - _doc: |- The data to be proxied to/from the chat server, encrypted by following the Chat Server Protocol. name: data type: b* reflect: _doc: |- Reflect a message into the reflection queue of all other devices. Direction: Client --> Server fields: - _doc: |- Contains the byte length of all fields prior to the `envelope` field (`8` at the moment). name: header-length type: u8 - _doc: |- Should be set to `0` and ignored by the receiver. name: reserved type: u8 - _doc: |- Flags: - `0x00_01`: Ephemeral marker. The server will forward the message only to devices that are currently connected while still maintaining the order of the reflection queue. If the receiving device disconnects before the ephemeral message was forwarded to it, that message should be discarded. An acknowledgement must not be sent. name: flags type: u16-le - _doc: |- Unique number (per connection) used for acknowledgement. name: reflect-id type: *reflect-id - _doc: |- The protobuf-encoded and encrypted data to be reflected, encrypted by `DGRK.secret` and prefixed with a random nonce. See `d2d.proto` for details on the `Envelope` contents. name: envelope type: b* reflect-ack: _doc: |- Acknowledges that a message to be reflected to all other devices has been stored in their respective reflection queues. Direction: Client <-- Server fields: - _doc: |- Should be set to all `0`s and ignored by the receiver. name: reserved type: b4 - _doc: |- Refers to the `Reflect ID` as sent in the `Reflect` message. name: reflect-id type: *reflect-id - _doc: |- Unix-ish timestamp in milliseconds when the message has been stored in the reflection queue of the mediator server. name: timestamp type: *timestamp reflected: _doc: |- Deliver a message from the device's reflection queue. Direction: Client <-- Server fields: - _doc: |- Contains the byte length of all fields prior to the `envelope` field (`16` at the moment). name: header-length type: u8 - _doc: |- Should be set to `0` and ignored by the receiver. name: reserved type: u8 - _doc: |- Flags: - `0x00_01`: Ephemeral marker. The sending device requested this message to only be reflected to devices that are currently online. An acknowledgement must not be sent. name: flags type: u16-le - _doc: |- Monotonically increasing unique number (per device slot) used for acknowledgement. May wrap. name: reflected-id type: *reflect-id - _doc: |- Unix-ish timestamp in milliseconds when the message has been stored in the reflection queue of the mediator server. name: timestamp type: *timestamp - _doc: |- The protobuf-encoded and encrypted data to be reflected, encrypted by `DGRK.secret` and prefixed with a random nonce. See `d2d.proto` for details on the `Envelope` contents. name: envelope type: b* reflected-ack: _doc: |- Acknowledges that a reflected message has been processed by the device. Direction: Client --> Server fields: - _doc: |- Should be set to all `0`s and ignored by the receiver. name: reserved type: b4 - _doc: |- Refers to the `Reflected ID` as sent in the `Reflected` message. name: reflect-id type: *reflect-id # Parsed struct namespaces (mapped into separate files) namespaces: index: *index payload: *payload