// ## Device to Mediator Protocol (Supplementary) // // This is a supplementary section to the corresponding structbuf section // with complementary messages that use protobuf instead of structbuf. All // defined messages here follow the same logic. // // Note that all messages defined here, with the exception of `ClientUrlInfo`, // are wrapped by `payload.container`. syntax = "proto3"; package d2m; option java_package = "ch.threema.protobuf.d2m"; // D2M protocol versions. enum ProtocolVersion { // Initial D2M protocol version (alpha, may break). V0 = 0; } // Send along client information when connecting to the mediator server. // // This message is serialized, hex-encoded (lowercase) and then used as the // WebSocket path. // // Type: n/a // Direction: Client -> Server message ClientUrlInfo { reserved 2; // Deprecated numeric server group // 32 byte device group id (`DGPK.public`) bytes device_group_id = 1; // Server group, as assigned by the server when the Threema identity has been // created. Must consist of only digits or ASCII letters (`^[0-9a-zA-Z]+$`). string server_group = 3; } // Initial message from the server, containing an authentication challenge. // // Type: 0x10 // Direction: Client <-- Server message ServerHello { // Highest protocol version (`ProtocolVersion`) the server supports. uint32 version = 1; // 32 byte ephemeral server key (`ESK.public`) bytes esk = 2; // 32 byte random challenge bytes challenge = 3; } // Policy determining the device slot's lifetime. enum DeviceSlotExpirationPolicy { // The device slot should be removed shortly after the device // disconnected. However, there should be a delay of several minutes to // ensure that the device can reconnect if it disconnected unintentionally. VOLATILE = 0; // The device slot should be kept as long as possible PERSISTENT = 1; } // Device registration state on the mediator server. enum DeviceSlotState { // A new device slot has been allocated for the device (i.e. the device's // id was not registered on the server). NEW = 0; // An existing device slot has been reused for the device (i.e. the // device's id is already registered on the server). EXISTING = 1; } // Initial message from the client, containing the authentication challenge // response and additional login information. // // Type: 0x11 // Direction: Client --> Server message ClientHello { // Protocol version (`ProtocolVersion`) which the client has selected. uint32 version = 1; // Challenge response (72 bytes) for authentication. // // The response is created by encrypting the server's challenge in the // following way: // // ```text // XSalsa20-Poly1305( // key=X25519HSalsa20(DGPK.secret, ESK.public), // nonce=, // ) // ``` // // The nonce is then prefixed to the encrypted challenge. bytes response = 2; // Unique device id fixed64 device_id = 3; // Policy to be applied in case the device id is not registered on the server // and all device slots have been exhausted. enum DeviceSlotsExhaustedPolicy { // Terminate the connection REJECT = 0; // Drop the least recently used device DROP_LEAST_RECENT = 1; } DeviceSlotsExhaustedPolicy device_slots_exhausted_policy = 4; // Policy determining the device slot's lifetime DeviceSlotExpirationPolicy device_slot_expiration_policy = 5; // The expected device slot state on the server. // // If the expected device slot state does not match the actual device slot // state, the device will be dropped by the mediator server with the close // code `4115` before being registered. DeviceSlotState expected_device_slot_state = 7; // Device info (`d2d.DeviceInfo`), encrypted by `DGDIK.secret` and prefixed // with a random nonce. bytes encrypted_device_info = 6; } // Parts of the server's configuration and the device slot state. // // Type: 0x12 // Direction: Client <-- Server message ServerInfo { // Current Unix-ish timestamp in milliseconds of the server. // // If the client's current timestamp deviates by more than 20 minutes, the // client should disconnect and prompt the user to synchronise its clock. // The user should also have an option to _connect anyway_ which should be // cached for a reasonable amount of time. uint64 current_time = 4; // Maximum number of device slots uint32 max_device_slots = 1; // Informs the device about its device slot state on the server DeviceSlotState device_slot_state = 2; // Device data shared among devices (`SharedDeviceData`), encrypted by // `DGSDDK.secret` and prefixed with a random nonce. bytes encrypted_shared_device_data = 3; // Amount of messages in the reflection queue that will now be sent to the // device. If the client is up-to-date, the value will be 0. // // Note: The amount of messages in the reflection queue may increase at any // time, so there is no guarantee that `ReflectionQueueDry` will be received // after having received `reflection_queue_length` reflected messages. uint32 reflection_queue_length = 5; } // The device's reflection queue on the server has been fully transmitted to // the device. // // Note: This does not mean that reflected messages have already been // acknowledged by the device! // // Type: 0x20 // Direction: Client <-- Server message ReflectionQueueDry {} // The device's role has been promoted to leader, indicating that the device // should now request to receive and reflect messages from the chat server. // // Type: 0x21 // Direction: Client <-- Server message RolePromotedToLeader {} // Request device information of all devices. // // Type: 0x30 // Direction: Client --> Server message GetDevicesInfo {} // Device information of all devices. // // Type: 0x31 // Direction: Client <-- Server message DevicesInfo { // Device id to (augmented) device info map of all devices. message AugmentedDeviceInfo { // Device info (`d2d.DeviceInfo`), encrypted by `DGDIK.secret` and prefixed // with a random nonce. bytes encrypted_device_info = 1; // Connection state oneof connection_state { // Unix-ish timestamp in milliseconds containing the most recent login // time of the device. Only set if device is currently connected. uint64 connected_since = 2; // Unix-ish timestamp in milliseconds containing the most recent // disconnect time of the device. Only set if device is not connected. uint64 last_disconnect_at = 4; } // Expiration policy of the device. DeviceSlotExpirationPolicy device_slot_expiration_policy = 3; } map augmented_device_info = 1; } // Request to drop a device and free its device slot. // // Type: 0x32 // Direction: Client --> Server message DropDevice { // Unique device id fixed64 device_id = 1; } // Acknowledges that a device has been dropped and the device slot has been // free'd. // // Type: 0x33 // Direction: Client <-- Server message DropDeviceAck { // Unique device id fixed64 device_id = 1; } // Set the shared device data which is being sent to each device during login. // // Type: 0x34 // Direction: Client --> Server message SetSharedDeviceData { // Device data shared among devices (`d2d.SharedDeviceData`), encrypted by // `DGSDDK.secret` and prefixed with a random nonce. bytes encrypted_shared_device_data = 1; } // Acquires a device group lock for an atomic operation shared across the // device group. // // Reflection messages from the device to the mediator server will only be // reflected once the transaction is committed. // // Type: 0x40 // Direction: Client --> Server message BeginTransaction { // The transaction scope (`d2d.TransactionScope`), encrypted by // `DGTSK.secret` and prefixed with a random nonce. bytes encrypted_scope = 1; // Time-to-live in seconds for this transaction. Once the TTL is reached, the // mediator server will abort the transaction and disconnect the client. When // set to `0`, the server's maximum transaction TTL will be used. uint32 ttl = 2; } // Acknowledges that the device group lock has been acquired and that the // transaction has been started. // // Type: 0x41 // Direction: Client <-- Server message BeginTransactionAck {} // Commits a transaction, releases a device group lock. // // Type: 0x42 // Direction: Client --> Server message CommitTransaction {} // Acknowledges that the transaction has been committed and that the device // group lock has been released. // // Type: 0x43 // Direction: Client <-- Server message CommitTransactionAck {} // A `BeginTransaction` request is rejected because another transaction is // already in process. // // Type: 0x44 // Direction: Client <-- Server message TransactionRejected { // The device that currently holds the lock fixed64 device_id = 1; // The encrypted transaction scope (`d2d.TransactionScope`) associated with // the currently locked transaction, encrypted by `DGTSK.secret` and prefixed // with a random nonce. bytes encrypted_scope = 2; } // When a transaction ends (either because it was committed or because the // device disconnected), this message is sent to all connected devices except // for the device that committed the transaction. // // This can be used by the other devices as a "retry signal" if a previous // "BeginTransaction" attempt was unsuccessful. // // Type: 0x45 // Direction: Client <-- Server message TransactionEnded { // The device that held the lock up until now fixed64 device_id = 1; // The encrypted transaction scope (`d2d.TransactionScope`) associated with // the transaction that just ended, encrypted by `DGTSK.secret` and prefixed // with a random nonce. bytes encrypted_scope = 2; }