// ## End-to-End Encrypted Messages (Supplementary)
//
// This is a supplementary section to the corresponding structbuf section
// with newer messages that use protobuf instead of structbuf. All defined
// messages here follow the same logic.

syntax = "proto3";

package csp_e2e;

option java_package = "ch.threema.protobuf.csp.e2e";
option java_multiple_files = true;
option php_namespace = "Threema\\Protocols\\Csp\\E2e";

import "common.proto";
import "md-d2d-sync.proto";

// Metadata sent within a CSP payload `message-with-metadata-box` struct.
message MessageMetadata {
  // Padding that is ignored by the receiver.
  // Recommended to be chosen such that the total length of padding + nickname
  // is at least 16 bytes. May be empty if the nickname is long enough.
  bytes padding = 1;

  // Unique message ID. Must match the message ID of the outer struct
  // (i.e. `message-with-metadata-box.message-id`).
  fixed64 message_id = 3;

  // Unix-ish timestamp in milliseconds for when the message has been created.
  //
  // Messages sent in a group must have the same timestamp for each group
  // member.
  uint64 created_at = 4;

  // Nickname
  //
  // Should be sent when the associate message requires _user profile
  // distribution_.
  //
  // When the user cleared its nickname, send an empty string. Do not send the
  // user's Threema ID (i.e. process data).
  //
  // Recommended to not exceed 32 grapheme clusters. Should not contain
  // whitespace characters at the beginning or the end of string.
  optional string nickname = 2;
}

// Edit an existing message (e.g. a text message or a media message caption).
//
// **Properties (1:1)**:
// - Kind: 1:1
// - Flags:
//   - `0x01`: Send push notification.
// - User profile distribution: No
// - Exempt from blocking: No
// - Implicit _direct_ contact creation: No
// - Protect against replay: Yes
// - Unarchive: No
// - Bump _last update_: No
// - Reflect:
//   - Incoming: Yes
//   - Outgoing: Yes
//   - _Sent_ update: No
// - Delivery receipts: No
// - Reactions: No
// - Edit applies to: N/A (obviously)
// - Deletable by: N/A
// - When rejected: N/A (ignored)
// - Send to Threema Gateway ID group creator: N/A
//
// **Properties (Group)**:
// - Kind: Group
// - Flags:
//   - `0x01`: Send push notification.
// - User profile distribution: No
// - Exempt from blocking: No
// - Implicit _direct_ contact creation: No
// - Protect against replay: Yes
// - Unarchive: No
// - Bump _last update_: No
// - Reflect:
//   - Incoming: Yes
//   - Outgoing: Yes
//   - _Sent_ update: No
// - Delivery receipts: N/A
// - Reactions: No
// - Edit applies to: N/A (obviously)
// - Deletable by: N/A
// - When rejected: N/A (ignored)
// - Send to Threema Gateway ID group creator: If capture is enabled
//
// The following steps must be invoked when the user wants to edit a 1:1
// message:
//
// 1. If the sender or the receiver do not have `EDIT_MESSAGE_SUPPORT`, disallow
//    editing and abort these steps.
// 2. Let `message` be the referred message.
// 3. If the user is not the original sender of `message`, disallow editing and
//    abort these steps.
// 4. If `message` has been sent (`sent-at`) more than 6 hours ago, disallow
//    editing and abort these steps.¹
// 5. Allow the user to edit the referred message.
//
// The following steps must be invoked when the user wants to edit a group
// message:
//
// 1. If the group is marked as _left_, disallow editing and abort these steps.
// 2. If the sender or all of the group members do not have
//    `EDIT_MESSAGE_SUPPORT`, disallow editing and abort these steps.
// 3. Let `message` be the referred message.
// 4. If the user is not the original sender of `message`, disallow editing and
//    abort these steps.
// 5. If the group is not a _notes_ group:
//    1. If `message` has been sent (`sent-at`) more than 6 hours ago, disallow
//       editing and abort these steps.¹
// 6. If any of the group members do not have `EDIT_MESSAGE_SUPPORT`, notify the
//    user that the affected contacts will not receive the edited content.
// 7. Allow the user to edit the referred message.
//
// The following steps must be invoked when the user wants to submit an edited
// 1:1 message.
//
// 1. If the sender or the receiver do not have `EDIT_MESSAGE_SUPPORT`, discard
//    the edited message and abort these steps.
// 2. Run the _Common Edit Message Submit Preflight Steps_.
// 3. Let `edited-at` be the current timestamp.
// 4. Run the _1:1 Messages Submit Steps_ with `messages` set from the following
//    properties:
//    - `created-at` set to `edited-at`,
//    - to construct an `EditMessage` message.
// 5. Edit the referred message as defined by the associated _Edit applies to_
//    property and add an indicator to the referred message, informing the user
//    that the referred message has been edited by the user at `edited-at`.
//
// The following steps must be invoked when the user wants to submit an edited
// group message.
//
// 1. If the group is marked as _left_, discard the edited message and abort
//    these steps.
// 2. If the sender or all of the group members do not have
//    `EDIT_MESSAGE_SUPPORT`, discard the edited message and abort these steps.
// 3. Run the _Common Edit Message Submit Preflight Steps_.
// 4. Let `edited-at` be the current timestamp.
// 5. Run the _Group Messages Submit Steps_ with `messages` set from the
//    following properties:
//    - `created-at` set to `edited-at`,
//    - to construct an `EditMessage` message (wrapped by
//      [`group-member-container`](ref:e2e.group-member-container)).
// 6. Edit the referred message as defined by the associated _Edit applies to_
//    property and add an indicator to the referred message, informing the user
//    that the referred message has been edited by the user at `edited-at`.
//
// The following steps are defined as the _Common Edit Message Submit Preflight
// Steps_:
//
// 1. Lookup the message with `message_id` originally sent by the sender within
//    the associated conversation and let `message` be the result.
// 2. If `message` is no longer defined, discard the edited message and abort
//    these steps.
// 3. If the content of `message` is identical to the edited message, discard
//    the edited message and abort these steps.
//
// When reflected from another device as an incoming or outgoing 1:1 message:
//
// 1. Run the _Common Edit Message Receive Steps_.
//
// When receiving this message as a 1:1 message:
//
// 1. Run the _Common Edit Message Receive Steps_.
//
// When reflected from another device as an incoming or outgoing group message:
//
// 1. Run the _Common Edit Message Receive Steps_.
//
// When receiving this message as a group message (wrapped by
// [`group-member-container`](ref:e2e.group-member-container)):
//
// 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the message
//    has been discarded, abort these steps.
// 2. Run the _Common Edit Message Receive Steps_.
//
// The following steps are defined as the _Common Edit Message Receive Steps_:
//
// 1. Lookup the message with `message_id` originally sent by the sender within
//    the associated conversation and let `referred-message` be the result.
// 2. If `referred-message` is not defined or the sender is not the original
//    sender of `referred-message`, discard the message and abort these steps.²
// 3. If `referred-message` is not editable (see the associated _Edit applies
//    to_ property), discard the message and abort these steps.
// 4. Edit `referred-message` as defined by the associated _Edit applies to_
//    property and add an indicator to `referred-message`, informing the user
//    that the message has been edited by the sender at the message's (the
//    `EditMessage`'s) `created-at`.
//
// ¹: For simplicity, the time constraint is applied on the sender side only.
// The receiver will always accept a request to edit a message. This is deemed
// acceptable considering this is not a security feature.
//
// ²: Implementations do not track the group member setup at the time a message
// was received. Therefore, an edited message is always sent to the **current**
// group member setup, including any group members that weren't part of the
// group when the message was sent. However, any ordinary client will discard
// `EditMessage` for unknown messages. This leak is not great but considered
// acceptable for now.
message EditMessage {
  // Unique ID of the referred message to be edited.
  fixed64 message_id = 1;

  // Text (or caption) to update the referred message with. Should be ≤ 6000
  // bytes.
  string text = 2;
}

// Remove an existing message.
//
// Note: This is a soft-security feature at best and it applies a best effort
// approach, meaning that it relies on some level of good will on the receiving
// end. A malicious receiver can easily persist a message prior to removal by
// e.g. making a screenshot, forwarding it, changing the date, explicitly saving
// it (if it contains media), etc.
//
// **Properties (1:1)**:
// - Kind: 1:1
// - Flags:
//   - `0x01`: Send push notification.
// - User profile distribution: No
// - Exempt from blocking: No
// - Implicit _direct_ contact creation: No
// - Protect against replay: Yes
// - Unarchive: No
// - Bump _last update_: No
// - Reflect:
//   - Incoming: Yes
//   - Outgoing: Yes
//   - _Sent_ update: No
// - Delivery receipts: No
// - Reactions: No
// - Edit applies to: N/A
// - Deletable by: N/A (obviously)
// - When rejected: N/A (ignored)
// - Send to Threema Gateway ID group creator: N/A
//
// **Properties (Group)**:
// - Kind: Group
// - Flags:
//   - `0x01`: Send push notification.
// - User profile distribution: No
// - Exempt from blocking: No
// - Implicit _direct_ contact creation: No
// - Protect against replay: Yes
// - Unarchive: No
// - Bump _last update_: No
// - Reflect:
//   - Incoming: Yes
//   - Outgoing: Yes
//   - _Sent_ update: No
// - Delivery receipts: N/A
// - Reactions: No
// - Edit applies to: N/A
// - Deletable by: N/A (obviously)
// - When rejected: N/A (ignored)
// - Send to Threema Gateway ID group creator: If capture is enabled
//
// The following steps must be invoked when the user wants to delete a 1:1
// message:
//
// 1. If the sender or the receiver do not have `DELETE_MESSAGE_SUPPORT`,
//    disallow removal and abort these steps.
// 2. Let `message` be the referred message.
// 3. If the user is not the original sender of `message`, disallow removal and
//    abort these steps.
// 4. If `message` has been sent (`sent-at`) more than 6 hours ago, disallow
//    removal and abort these steps.¹
// 5. Let `deleted-at` be the current timestamp.
// 6. Run the _1:1 Messages Submit Steps_ with `messages` set from the following
//    properties:
//    - `created-at` set to `deleted-at`,
//    - to construct a `DeleteMessage` message.
// 7. Replace the referred message with a message informing the user that the
//    referred message of the user has been removed at `deleted-at`.²
//
// The following steps must be invoked when the user wants to delete a group
// message.
//
// 1. If the group is marked as _left_, disallow removal and abort these steps.
// 2. If the sender or all of the group members do not have
//    `DELETE_MESSAGE_SUPPORT`, disallow removal and abort these steps.
// 3. Let `message` be the referred message.
// 4. If the user is not the original sender of `message`, disallow removal and
//    abort these steps.
// 5. If the group is not a _notes_ group:
//    1. If `message` has been sent (`sent-at`) more than 6 hours ago, disallow
//       removal and abort these steps.¹
// 6. Let `deleted-at` be the current timestamp.
// 7. If any of the group members do not have `DELETE_MESSAGE_SUPPORT`, notify
//    the user that the affected contacts will continue to see the message.
// 8. Run the _Group Messages Submit Steps_ with `messages` set from the
//    following properties:
//    - `created-at` set to `deleted-at`,
//    - to construct a `DeleteMessage` message (wrapped by
//      [`group-member-container`](ref:e2e.group-member-container)).
// 9. Replace the referred message with a message informing the user that the
//    referred message of the user has been removed at `deleted-at`.²
//
// When reflected from another device as an incoming or outgoing 1:1 message:
//
// 1. Run the _Common Delete Message Receive Steps_.
//
// When receiving this message as a 1:1 message:
//
// 1. Run the _Common Delete Message Receive Steps_.
//
// When reflected from another device as an incoming or outgoing group message:
//
// 1. Run the _Common Delete Message Receive Steps_.
//
// When receiving this message as a group message (wrapped by
// [`group-member-container`](ref:e2e.group-member-container)):
//
// 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the message
//    has been discarded, abort these steps.
// 2. Run the _Common Delete Message Receive Steps_.
//
// The following steps are defined as the _Common Delete Message Receive Steps_:
//
// 1. Lookup the message with `message_id` originally sent by the sender within
//    the associated conversation and let `referred-message` be the result.
// 2. If `referred-message` is not defined or the sender is not the original
//    sender of `referred-message`, discard the message and abort these steps.
// 3. If `referred-message` is not deletable (see the associated _Deletable by_
//    property), discard the message and abort these steps.
// 4. Replace `referred-message` with a message informing the user that the
//    message of the sender has been removed at the message's (the
//    `DeleteMessage`'s) `created-at`.²
//
// ¹: For simplicity, the time constraint is applied on the sender side only.
// The receiver will always accept a request to delete a message. This is deemed
// acceptable considering this is just barely a soft-security feature.
//
// ²: All references to a removed message (e.g. quotes) must be updated as well,
// so that the message content is no longer visible. An implementation should
// also try to withdraw or update any notification created for a removed
// message.
message DeleteMessage {
  // Unique ID of the referred message to be removed.
  fixed64 message_id = 1;
}

// Announces and immediately starts a group call.
//
// **Properties**:
// - Kind: Group
// - Flags:
//   - `0x01`: Send push notification.
// - User profile distribution: Yes
// - Exempt from blocking: Yes
// - Implicit _direct_ contact creation: No
// - Protect against replay: Yes
// - Unarchive: TODO(SE-508)
// - Bump _last update_: TODO(SE-508)
// - Reflect:
//   - Incoming: Yes
//   - Outgoing: Yes
//   - _Sent_ update: No
// - Delivery receipts: N/A
// - Reactions: No
// - When rejected: N/A¹
// - Edit applies to: N/A
// - Deletable by: N/A
// - Send to Threema Gateway ID group creator: If capture is enabled
//
// ¹: For the group creator it will be handled as if `group-sync-request` was
// received, re-sending a `GroupCallStart` if still ongoing, implicitly
// triggered by FS `Reject` receive steps.
//
// When the user wants to create a new group call or join an existing group
// call, run the steps outlined in the _Create or Join_ section of the Group
// Call Protocol.
//
// When reflected from another device as an incoming or outgoing message:
//
// 1. Run the _Common Group Call Start Receive Steps_.
//
// When receiving this message:
//
// 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the message
//    has been discarded, abort these steps.
// 2. Run the _Common Group Call Start Receive Steps_.
//
// The following steps are defined as the _Common Group Call Start Receive
// Steps_:
//
// 1. If the hostname of `sfu_base_url` does not use the scheme `https` or does
//    not end with one of the set of [_Allowed SFU Hostname
//    Suffixes_](ref:group-calls#obtain-sfu-information), log a warning, discard
//    the message and abort these steps.
// 2. Let `running` be the list of group calls that are currently considered
//    running within the group.
// 3. If another call with the same GCK exists in `running`, log a warning,
//    discard the message and abort these steps.
// 4. Add the received call to the list of group calls that are currently
//    considered running (even if `protocol_version` is unsupported¹).
// 5. Start a task to run the _Group Call Refresh Steps_.²
//
// ¹: Adding unsupported `protocol_version`s allows the user to join an ongoing
//  call after an app update where support for `protocol_version` has been
//  added.
//
// ²: This ensures that the user automatically switches to the chosen call if it
// is currently participating in a group call of this group.
message GroupCallStart {
  // Protocol version used for group calls of this group. The current version
  // number is `1`.
  //
  // Note: This is a _major_ version and may only be increased in case of
  // breaking changes due to the significant UX impact this has when running the
  // _Common Group Receive Steps_ (i.e. only calls with supported protocol
  // versions can be _chosen_).
  uint32 protocol_version = 1;

  // The secret Group Call Key (`GCK`) used for this call.
  bytes gck = 2;

  // The base URL of the SFU, used to join or peek the call.
  string sfu_base_url = 3;
}

// React to a message.
//
// **Properties (1:1)**:
// - Kind: 1:1
// - Flags:
//   - `0x01`: Send push notification.
// - User profile distribution: Yes
// - Exempt from blocking: No
// - Implicit _direct_ contact creation: No
// - Protect against replay: Yes
// - Reflect:
//   - Incoming: Yes
//   - Outgoing: Yes
//   - _Sent_ update: No
// - Delivery receipts: No
// - Reactions: No, that would be silly!
// - When rejected: N/A (ignored)
// - Edit applies to: N/A (can withdraw and apply with another `Reaction`)
// - Deletable by: N/A (can withdraw with another `Reaction`)
// - Send to Threema Gateway ID group creator: N/A
//
// **Properties (Group)**:
// - Kind: 1:1
// - Flags:
//   - `0x01`: Send push notification.
// - User profile distribution: Yes
// - Exempt from blocking: No
// - Implicit _direct_ contact creation: No
// - Protect against replay: Yes
// - Reflect:
//   - Incoming: Yes
//   - Outgoing: Yes
//   - _Sent_ update: No
// - Delivery receipts: N/A
// - Reactions: No, that would be silly!
// - When rejected: N/A (ignored)
// - Edit applies to: N/A (can withdraw and apply with another `Reaction`)
// - Deletable by: N/A (can withdraw with another `Reaction`)
// - Send to Threema Gateway ID group creator: If capture is enabled
//
// When the user submits a reaction in a 1:1 conversation:
//
// 1. Let `reaction` be the reaction to be applied to or withdrawn from a
//    referred message which must contain a single fully-qualified [emoji
//    codepoint sequence that is part of the currently supported Unicode
//    standard][emoji-test.txt].
// 2. Run the _Legacy Reaction Mapping Steps_ with `reaction` and let
//    `legacy-reaction` be the result.
// 3. If `legacy-reaction` is not defined and the sender or the receiver does
//    not have `REACTION_SUPPORT`, log a warning and abort these steps.¹
// 4. Let `reacted-at` be the current timestamp.
// 5. If both sender and receiver have `REACTION_SUPPORT`, run the _1:1 Messages
//    Submit Steps_ with `messages` set from the following properties:
//    - `created-at` set to `reacted-at`,
//    - to construct a `Reaction` message from `reaction`.
// 6. If the sender or the receiver does not have `REACTION_SUPPORT`, run the
//    _1:1 Messages Submit Steps_ with `messages` set from the following
//    properties:
//    - `created-at` set to `reacted-at`,
//    - to construct the `legacy-reaction`.
// 7. Apply `reaction` (i.e. apply or withdraw) to the referred message with the
//    `reacted-at` timestamp.
//
// When the user submits a reaction in a group conversation:
//
// 1. Let `reaction` be the reaction to be applied to or withdrawn from a
//    referred message which must contain a single fully-qualified [emoji
//    codepoint sequence that is part of the currently supported Unicode
//    standard][emoji-test.txt].
// 2. Run the _Legacy Reaction Mapping Steps_ with `reaction` and let
//    `legacy-reaction` be the result.
// 3. If `legacy-reaction` is not defined:
//    1. If the sender does not have `REACTION_SUPPORT`, log a warning and abort
//       these steps.²
//    2. If all of the group members do not have `REACTION_SUPPORT`, log a
//       warning and and abort these steps.²
//    3. If any of the group members do not have `REACTION_SUPPORT`, notify the
//       user that the affected contacts will not receive the reaction.
// 4. Let `reacted-at` be the current timestamp.
// 5. Run the _Group Messages Submit Steps_ with `messages` set from the
//    following properties:
//    - `created-at` set to `reacted-at`,
//    - to construct a _canonical_ `Reaction` message from `reaction`,
//    - to construct a _specific_ message in the following way:
//      1. Let `receiver` be the specific receiver.
//      2. If the `receiver` does not have `REACTION_SUPPORT` and
//         `legacy-reaction` is defined, return the `legacy-reaction` (wrapped
//         by [`group-member-container`](ref:e2e.group-member-container)).
//      3. Construct and return the _canonical_ `Reaction` message from
//         `reaction` (wrapped by
//         [`group-member-container`](ref:e2e.group-member-container)).³
// 6. Apply `reaction` (i.e. apply or withdraw) to the referred message with the
//    `reacted-at` timestamp.
//
// The following steps are defined as the _Legacy Reaction Mapping Steps_:
//
// 1. If `action` is of variant `apply`:
//    1. If `action.apply` equals one of the following codepoint sequences,
//       return a `e2e.delivery-receipt` of type _acknowledge_ (0x03):
//       - `1F44D` (👍)
//       - `1F44D 1F3FB` (👍🏻)
//       - `1F44D 1F3FC` (👍🏼)
//       - `1F44D 1F3FD` (👍🏽)
//       - `1F44D 1F3FE` (👍🏾)
//       - `1F44D 1F3FF` (👍🏿)
//    2. If `action.apply` equals one of the following codepoint sequences,
//       return a `e2e.delivery-receipt` of type _decline_ (0x04):
//       - `1F44E` (👎)
//       - `1F44E 1F3FB` (👎🏻)
//       - `1F44E 1F3FC` (👎🏼)
//       - `1F44E 1F3FD` (👎🏽)
//       - `1F44E 1F3FE` (👎🏾)
//       - `1F44E 1F3FF` (👎🏿)
// 2. Return no message.
//
// When reflected from another device as an incoming or outgoing 1:1 message:
//
// 1. Run the _Common Reaction Receive Steps_.
//
// When receiving this message:
//
// 1. Run the _Common Reaction Receive Steps_.
//
// When receiving this message (wrapped by
// [`group-member-container`](ref:e2e.group-member-container)):
//
// 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the reaction
//    message has been discarded, abort these steps.
// 1. Run the _Common Reaction Receive Steps_.
//
// The following steps are defined as the _Common Reaction Receive Steps_:
//
// 1. Lookup the referred message with `message_id` within the associated
//    conversation and let `referred-message` be the result.
// 2. If `referred-message` is not defined, discard the message and abort these
//    steps.
// 3. If `referred-message` is not reactable (see the associated _Reactions_
//    property), discard the message and abort these steps.
// 4. If `action` is of variant `apply`:
//    1. If `apply` contains more than 64 bytes, discard the message and abort
//       these steps.
//    2. Decode `apply` to a UTF-8 string. If the string is empty, discard the
//       message and abort these steps.
//    3. Apply (or re-apply) the resulting emoji from the sender to
//       `referred-message` with the message's (the `Reaction`'s) `created-at`
//       timestamp used for the time of reaction.⁴⁵
// 5. If `action` is of variant `withdraw`:
//    1. If `withdraw` contains more than 64 bytes, discard the message and
//       abort these steps.
//    2. Decode `withdraw` to a UTF-8 string. If the string is empty, discard
//       the message and abort these steps.
//    3. Remove the resulting emoji reaction from the sender for `message`.⁴⁵
//
// ¹: The UI should not allow to create non-legacy reactions in 1:1
// conversations with a sender or receiver that does not have
// `REACTION_SUPPORT`.
//
// ²: The UI should not allow to create non-legacy reactions in group
// conversations with a sender that does not have `REACTION_SUPPORT` or when all
// other group members don't have `REACTION_SUPPORT`.
//
// ³: In case the reaction could not be mapped to a legacy reaction, this
// results in a `Reaction` message being sent to group members that currently do
// not support reactions. This is intentional.
//
// ⁴: Note that the _apply_ mechanism is additive, meaning multiple reactions
// from the same sender are allowed on a single message. This is why the
// _withdraw_ mechanism is needed which removes a specific reaction. On the
// other hand, a deprecated `e2e.delivery-receipt` will replace all existing
// reactions of the sender at once (including these new-style reactions).
//
// ⁵: The UI should display a placeholder (�) for unknown emojis, meaining those
// which are not a single fully-qualified [emoji codepoint sequence that is part
// of the currently supported Unicode standard][emoji-test.txt]. But the
// individual code sequences still have individual display buckets.
//
// [emoji-test.txt]: https://www.unicode.org/Public/emoji/latest/emoji-test.txt
message Reaction {
  // Unique ID of the referred message.
  fixed64 message_id = 1;

  // A single emoji reaction to be applied or withdrawn.
  oneof action {
    // Apply a new emoji reaction.
    bytes apply = 2;

    // Withdraw a specific emoji reaction.
    bytes withdraw = 3;
  }
}

// A control message from the Threema Work Directory, providing a set of Work
// Sync Deltas for immediate visibility (or requesting a full Work Sync).
//
// **Properties (1:1)**:
// - Kind: 1:1
// - Flags: None
// - User profile distribution: N/A (not sent by apps)
// - Exempt from blocking: Yes
// - Implicit _direct_ contact creation: N/A (blocking is circumvented)
// - Protect against replay: Yes
// - Unarchive: No
// - Bump _last update_: No
// - Reflect:
//   - Incoming: No
//   - Outgoing: No
//   - _Sent_ update: No
// - Delivery receipts: N/A
// - Reactions: N/A
// - When rejected: N/A (not sent by clients)
// - Edit applies to: N/A
// - Deletable by: N/A
// - Send to Threema Gateway ID group creator: N/A
//
// When receiving this message:
//
// 1. If not Work flavour, log a warning, discard the message and abort these
//    steps.
// 2. If the sender is not `*3MAW0RK`, discard the message and abort these
//    steps.
// 3. If `action` is of variant `require_work_sync`, schedule a persistent task
//    to make a full Work Sync.
// 4. If `action` is of variant `apply`:
//    1. Run the _Work Sync Delta Change Determination Steps_ with
//       `apply.deltas` and let `changes` be the result.
//    2. If `changes` is a marker that a _full Work Sync is required_, schedule
//       a persistent task to make a full Work Sync and abort these steps.
//    3. If `changes` is empty, discard the message and abort these steps.
//    4. (MD) Run the following sub-steps:
//       1. Begin a transaction with scope `WORK_SYNC_DELTA` and no
//          precondition.
//       2. Run the _Work Sync Delta Change Determination Steps_ another time
//          and update `changes` with the result.¹
//       3. If `changes` is a marker that a _full Work Sync is required_,
//          schedule a persistent task to make a full Work Sync. Otherwise, reflect
//          all `changes`.
//       4. Commit the transaction and await acknowledgement.
//    5. Apply all `changes` persistently.
// 5. If `action` is an unknown variant, log a warning that an unknown Work Sync
//    variant has been encountered.
//
// ¹: The two-tiered approach is necessary since we want to prevent unnecessary
// transactions from Work Sync Deltas that don't affect any contact in the
// user's contact list. But since changes may affect information that has been
// altered since the transaction was started, it needs to be run twice when
// multi-device is enabled.
//
// The following steps are defined as the _Work Sync Delta Change Determination
// Steps_:
//
// 1. Let `deltas` be a list of `WorkSyncDelta.Delta`.
// 2. Let `changes` be an empty list of _change instructions_ that would be
//    required to apply all `deltas`.
// 3. For each `delta` of `deltas`:
//    1. If `delta.action` is of variant `contact_sync`:
//       1. If `contact_sync.action` is of variant `update`:
//           1. Lookup the contact associated to `update.identity` and let
//              `contact` be the result.
//           2. If `contact` is not defined, discard `update` and continue with
//              the next delta.¹
//           3. If `contact` is not currently considered a work contact, return
//              that a _full Work Sync is required_.
//           4. If `contact`'s last full Work Sync timestamp is defined and ≥
//              `delta.applied_at`, discard `update` and continue with the next
//              delta.
//           5. If `update` does not diverge from the properties of `contact`,
//              discard `update` and continue with the next delta.
//           6. Add a change to `changes` for the necessary changes defined by
//              `update` to update the `contact` in form of a
//              `d2d_sync.Contact`.
//       2. If `contact_sync.action` is an unknown variant, log a warning that
//          an unknown Work Sync Delta contact action has been encountered.
//    2. If `delta.action` is an unknown variant, log a warning that an unknown
//       Work Sync Delta action has been encountered.
// 4. Return `changes`.
//
// ¹: We intentionally do not filter contacts marked as _invalid_ since it may
// be a relevant use case to revoke a contact and then updating other properties
// afterwards, e.g. removing its availability status, etc.
//
// Note: The _Work Sync Delta Change Determination Steps_ may result in
// inconsistencies until the next full Work Sync in some rare edge cases. For
// example, if a change to a contact is currently in progress by another
// device's transaction while this device determines that no change is required.
// This is considered acceptable.
message WorkSyncDelta {
  // Work contact synchronisation message.
  message ContactSync {
    // Update a Work contact.
    message Update {
      // Threema ID of the contact.
      //
      // Always required.
      string identity = 1;

      // Availability status of the contact.
      //
      // Always optional.
      d2d_sync.WorkAvailabilityStatus availability_status = 2;
    }

    // Synchronisation type.
    oneof action {
      // Update a Work contact.
      Update update = 1;
    }
  }

  message Delta {
    // Unix-ish timestamp in milliseconds for when the delta has been applied on
    // the server side (i.e. since when the change is in effect and would be
    // picked up by querying the _Work Sync_, a _Work Contacts_, or the _Work
    // Directory_ endpoint).
    uint64 applied_at = 1;

    oneof action {
      ContactSync contact_sync = 2;
    }
  }

  message Apply {
    repeated Delta deltas = 1;
  }

  oneof action {
    common.Unit require_work_sync = 1;
    Apply apply = 2;
  }
}
