// This file has been generated by structbuf. Do not modify it!
import * as base from '~/common/network/structbuf/base.js';
import type * as types from '~/common/types.js';
import * as utils from '../utils.js';

/**
 * ## End-to-End Encrypted Messages
 *
 * An end-to-end encrypted message can be sent or received once the handshake
 * was successful. Every end-to-end encrypted message is wrapped inside of a
 * [`container`](ref:payload.container) struct that is then encrypted and
 * wrapped by a payload [`legacy-message`](ref:payload.legacy-message) or
 * [`message-with-metadata-box`](ref:payload.message-with-metadata-box)
 * struct.
 *
 * ### Predefined Contacts
 *
 * A pedefined contacts can be added to the contact list and is automatically
 * initialised with its identity, nickname, hard-coded public key and the verification
 * level _fully verified_. Once a predefined contact is in the contact list, it
 * is treated like any other normal contact (with editable properties like
 * first and last name, etc.).
 *
 * A predefined contact may be marked _special_ meaning it follows special
 * logic. These are also known as _Special Contact_s. Even though special
 * contacts should not normally appear in the contact list, there's nothing
 * stopping a user from adding a special contact to its contact list. While
 * they are treated like normal contacts in the contact list, depending on the
 * special handling logic, it may not be possible to send or receive normal
 * messages from them.
 *
 * The following list contains all predefined contacts:
 *
 * - `*3MAPUSH`:
 *   - Nickname: Threema Push
 *   - Public Key:
 *     - Production: fd711e1a0db0e2f03fcaab6c43da2575b9513664a62a12bd0728d87f7125cc24
 *     - Sandbox: fd711e1a0db0e2f03fcaab6c43da2575b9513664a62a12bd0728d87f7125cc24
 *   - Special: Yes
 *
 * - `*3MATOKN`:
 *   - Nickname: Threema Token
 *   - Public Key:
 *     - Production: 04884d12d668f855d00d71fb1d9d413c95f271312f7e077846af671875c4101b
 *   - Special: No
 *
 * - `*3MAWORK`:
 *   - Nickname: Threema Work Channel
 *   - Public Key:
 *     - Production: 9aa0a72a8fb6f0cc53727fea6096f1b7b0ebefcc2650ad39a1e54837bba0bc4b
 *     - Sandbox: 9aa0a72a8fb6f0cc53727fea6096f1b7b0ebefcc2650ad39a1e54837bba0bc4b
 *   - Special: No
 *
 * - `*BETAFBK`:
 *   - Nickname: Threema Beta Feedback
 *   - Public Key:
 *     - Production: 5684d6dcd32a16488df8371095fc9a1fc25baeb6b97366d99fdf2aba00e2bc5c
 *   - Special: No
 *
 * - `*MY3DATA`:
 *   - Nickname: My Threema Data
 *   - Public Key:
 *     - Production: 3b01854f24736e2d0d2dc387eaf2c0273c5049052147132369bf3960d0a0bf02
 *     - Sandbox: 83adfee6558b68ae3cd6bbe2a33f4e4409d5624a7cea23a18975aea6272a0070
 *   - Special: No
 *
 * - `*SUPPORT`:
 *   - Nickname: Threema Support
 *   - Public Key:
 *     - Production: 0f944d18324b2132c61d8e40afce60a0ebd701bb11e89be94972d4229e94722a
 *     - Sandbox: 0f944d18324b2132c61d8e40afce60a0ebd701bb11e89be94972d4229e94722a
 *   - Special: No
 *
 * - `*THREEMA`:
 *   - Nickname: Threema Channel
 *   - Public Key:
 *     - Production: 3a38650c681435bd1fb8498e213a2919b09388f5803aa44640e0f706326a865c
 *     - Sandbox: 3a38650c681435bd1fb8498e213a2919b09388f5803aa44640e0f706326a865c
 *   - Special: No
 *
 * Note: OnPrem provisions predefined contacts in the associated OPPF file.
 *
 * ### Mitigating Replay
 *
 * To prevent replay attacks, a client must permanently store used nonces for
 * incoming and outgoing end-to-end encrypted messages. Messages reusing
 * previously used nonces must not be processed and discarded. One nonce
 * store for all end-to-end encrypted messages across different contacts is
 * sufficient.
 *
 * Note that it is still possible for the chat server to replay old messages to
 * a device whose database has been erased (e.g. when restoring a backup).
 * However, this is not applicable to forward security encrypted messages.
 *
 * ### Message ID
 *
 * Each message has an associated message ID. It is crucial to understand
 * that this is not a unique identifier across multiple conversations.
 * Unique identification of a message is determined by:
 *
 * - 1:1 Chats: The message ID in combination with the contact's Threema ID.
 * - Group Chats: The message ID in combination with the group creator's
 *   Threema ID and the group ID.
 * - Distribution Lists: The message ID with an artificial distribution list
 *   ID.
 *
 * When a message is being quoted, it may only be looked up within the
 * associated conversation.
 *
 * ### Flags
 *
 * For each message, we will define _mandatory_ and _optional_ flags
 * referring to the `flags` field of the payload
 * [`legacy-message`](ref:payload.legacy-message) or
 * [`message-with-metadata-box`](ref:payload.message-with-metadata-box)
 * struct. A flag must be considered _mandatory_ unless it has been explicitly
 * marked _optional_.
 *
 * ### Delivery Receipts
 *
 * There are two types of delivery receipts (sent using the
 * [`delivery-receipt`](ref:e2e.delivery-receipt) message):
 *
 * - Automatic: "received" and "read"
 * - Manual: "acknowledged" and "declined"
 *
 * For each message, we will define whether automatic delivery receipts should
 * be sent and whether it is eligible for sending manual delivery receipts
 * (e.g. acknowledge/decline). However, two general exceptions apply:
 *
 * 1. Automatic delivery receipts are not sent to group members (i.e. when
 *    any message struct is wrapped in a `group` message struct).
 * 2. Messages whose flags include `0x80` must not trigger any automatic
 *    delivery receipts.
 *
 * ### Blocking
 *
 * The sender Threema ID may be blocked explicitly (i.e. blocking a specific
 * Threema ID) or implicitly (blocking all unknown Threema IDs). This does not
 * require special handling on the server but instead is done entirely by the
 * clients.
 *
 * Note that the protocol does not distinguish between implicitly and
 * explicitly blocked Threema IDs. An implicitly blocked Threema ID (i.e.
 * blocking unknown contacts) must be treated the same as an explicitly blocked
 * Threema ID (i.e. blocking specific contacts).
 *
 * The UI must prevent users from composing or submitting messages towards a
 * blocked contact. In practise, this is only relevant for explicitly blocked
 * contacts.
 *
 * The following steps are defined as the _Identity Blocked Steps_:
 *
 * 1.  Let `identity` be the Threema ID to be checked.
 * 2.  If `identity` is a _Special Contact_, return that it is not blocked.
 * 3.  If `identity` is explicitly blocked, return that it is blocked.
 * 4.  If the settings indicate that unknown contacts should not be blocked,
 *     return that it is not blocked.
 * 5.  If `identity` is a _Predefined Contact_, return that it is not blocked.
 * 6.  Let `contact` be the associated contact to `identity`.
 * 7.  If `contact` is not defined, return that it is blocked.
 * 8.  If `contact` has acquaintance level _direct_, return that it is not
 *     blocked.
 * 9.  If `contact` is part of a group that is not marked as _left_, return
 *     that it is not blocked.
 * 10. Return that it is blocked.
 *
 * ### Contact Flows
 *
 * The following steps are defined as the _Valid Contacts Lookup Steps_:
 *
 * 1.  Let `identities` be the identities to look up.
 * 2.  Let `contact-or-inits` be an empty map of Threema IDs to a contact,
 *     properties to create a contact from, or _contact is the user_ or
 *     _contact is invalid_ marker.
 * 3.  Let `unknown-identities` be an empty list.
 * 4.  For each `identity` of `identities`:
 *     1. If `identity` equals the user's Threema ID, add the information
 *        that the _contact is the user_ to the `contact-or-inits` map and
 *        abort these sub-steps.
 *     2. If `identity` is a _Special Contact_, add that special contact to the
 *        `contact-or-inits` map and abort these sub-steps.
 *     3. Lookup the contact associated to `identity` and let `contact` be the
 *        result.
 *     4. If `contact` is defined, add `contact` to the `contact-or-inits` map
 *        and abort these sub-steps.
 *     5. Lookup the properties to create a contact from associated to
 *        `identity` from the _contact lookup cache_ and let `init` be the
 *        result.
 *     6. If `init` is defined, add `init` to the `contact-or-inits` map and
 *        abort these sub-steps.
 *     7. Add `identity` to `unknown-identities`.
 * 5.  Let `directory-response` be the response of asynchronously looking up
 *     `unknown-identities` on the Directory Server.
 * 6.  If work build, let `work-directory-response` be the response of
 *     asynchronously looking up `unknown-identities` on the Work Contacts API
 *     endpoint.
 * 7.  Await `directory-response` and `work-directory-response`.
 * 8.  Process the result of `directory-response`:
 *     1. If the server could not be reached, exceptionally abort these steps.
 *     2. If the status code is `429`, exceptionally abort these steps and add
 *        a minimum delay of 10s before retrying a connection.
 *     3. If the status code is not `200`, exceptionally abort these steps.
 *     4. For each contact entry of the result:
 *        1. Remove the contact from `unknown-identities`. If it was not
 *           present in `unknown-identities`, log a warning and abort these
 *           sub-steps.
 *        2. If the contact is marked as _invalid_ (never existed or has been
 *           revoked), add the information that the _contact is invalid_ to the
 *           `contact-or-inits` map and abort these sub-steps.
 *        3. If the contact is a _Predefined Contact_:
 *           1. If the contact's public key does not equal the _Predefined
 *              Contact_s public key, log a warning and exceptionally abort these
 *              steps.
 *           2. Update the contact information with the following information:
 *              - set `verification_level` to `FULLY_VERIFIED`,
 *              - set `nickname` to the nickname of the _Predefined Contact_,
 *              - if _Predefined Contact_ defines a first name, set it
 *                accordingly,
 *              - if _Predefined Contact_ defines a last name, set it
 *                accordingly,
 *        3. Add the resulting contact information to the `contact-or-inits` map
 *           from which a new contact can be created.
 *     5. For each `identity` of `unknown-identities`:
 *        1. Add the information that the _contact is invalid_ to the
 *           `contact-or-inits` map for `identity`.
 *     6. Clear `unknown-identities`.
 * 9.  If `work-directory-response` is defined, process its result:
 *     1. If the server could not be reached, exceptionally abort these steps.
 *     2. If the status code is `401`, exceptionally abort these steps,
 *        notify the user that the Work credentials are invalid and request new
 *        ones. The connection should not be retried until new Work credentials
 *        have been entered and checked for validity.
 *     3. If the status code is `429`, exceptionally abort these steps and add a
 *        minimum delay of 10s before retrying a connection.
 *     4. If the status code is not `200`, exceptionally abort these steps.
 *     5. For each `work-contact` of the result:
 *        1. If an entry for the `work-contact`'s identity does not exist in
 *           `contact-or-inits`, log a warning and abort these sub-steps.
 *        2. If `work-contact`'s public key does not equal `contact-or-init`'s
 *           public key, log a warning and exceptionally abort these steps.
 *        3. Update the contact entry for `work-contact`'s identity in
 *           `contact-or-inits` with the following information:
 *           - if `verification_level` is not defined or `UNVERIFIED`, set it
 *             to `SERVER_VERIFIED`,
 *           - set `work_verification_level` to `WORK_SUBSCRIPTION_VERIFIED`,
 *           - if `work-contact.first-name` is defined, set the first name
 *             accordingly,
 *           - if `work-contact.last-name` is defined, set the last name
 *             accordingly,
 * 10. TODO(SE-173): Run the contact import flow for `contact-or-inits` and
 *     update the `verification_level` for all whose associated phone number /
 *     email could be matched. Import `first_name` and `last_name` (if not
 *     already defined) and set `sync_state` to `IMPORTED`. Clarify precedence
 *     regarding Work API.
 * 11. For each `init` of `contact-or-inits`:
 *     1. If `init` does not contain properties to create a contact from (i.e.
 *        it is a contact or any of the special markers), abort these
 *        sub-steps.
 *     2. If `init.sync_state` is not defined, set it to `INITIAL`.
 *     3. If `init.verification_level` is not defined, set it to `UNVERIFIED`.
 *     4. If `init.work_verification_level` is not defined, set it to `NONE`.
 * 12.  Update the _contact lookup cache_ with the contents of
 *     `contact-or-inits`. Each newly added or updated entry has an expiration
 *     time of 10m after which the entry is to be removed from the cache.
 * 13. Return `contact-or-inits`.
 *
 * ### Groups
 *
 * Groups are handled in a decentralised manner. Messages are sent to each
 * group member individually. On a technical level, a group is identified by
 * **both** the Threema ID of the creator and the random group ID the creator
 * chose. A group **must never** be identified by the group ID alone.
 *
 * Group messages are special containers wrapped around normal messages (it is
 * actually just a common header):
 *
 * - [`group-member-container`](ref:e2e.group-member-container): For group
 *   message communication between members, including the creator.
 * - [`group-creator-container`](ref:e2e.group-creator-container): For special
 *   messages that may only be sent from the creator to normal group members
 *   and vice versa.
 *
 * Group messages have special types in order to separate them from other
 * messages. These types also define which container must be used.
 *
 * The group members are determined by the
 * [`group-setup`](ref:e2e.group-setup) message and continuously updated by
 * any following [`group-leave`](ref:e2e.group-leave) messages. Any following
 * [`group-setup`](ref:e2e.group-setup) overrides the previous member state.
 *
 * ### Implicit Contact Creation
 *
 * When the user is added to a group, every unknown member of the group must be
 * added to the contact list with acquaintance level _group_. Messages from a
 * contact with any acquaintance level will not be implicitly blocked by a
 * _block unknown_ setting.
 *
 * The contact remains at the acquaintance level _group_ until a 1:1
 * conversation with that contact is being started by either side in which
 * case the acquaintance level should be changed to _direct_.
 *
 * A contact with acquaintance level _group_ will remain indefinitely even if
 * the contact is being removed from all groups of the user or if all remaining
 * common groups are marked as _left_. In that case, the contact is implicitly
 * marked as _deleted_ so that it is covered by _block unknown_.
 *
 * ### Notes Group
 *
 * A group is identified as a _notes_ group if all of the following criteria
 * are met:
 *
 * 1. The user is the creator of the group.
 * 2. The group currently has no members (besides the creator).
 * 3. The group is not marked as _left_.
 *
 * Messages in a _notes_ group are synchronised across devices but are not
 * sent to the chat server (since there are no other members). Therefore,
 * it is ideal for "notes to self", hence the name.
 *
 * A group seamlessly transforms into a _notes_ group and out of it given the
 * above criteria. Right now this can happen in three scenarios:
 *
 * - A _notes_ group is created explicitly (i.e. a group with only the user
 *   is being created).
 * - The user is the creator of a group and one or more members are being
 *   added in which case the _notes_ group transforms into a regular group.
 * - The user is the creator of a group whose members have just been removed
 *   (but the group has not been disbanded) in which case the group
 *   transforms into a _notes_ group.
 *
 * The UI should signal the _notes_ status of a group to the user.
 *
 * ### Group Flows
 *
 * The following steps are defined as the _Active Group Update Steps_:
 *
 * 1.  If the user is not the creator of the group or the group is marked as
 *     _left_, log an error and abort these steps.
 * 2.  Let `message-ids` be a list of four pre-generated message IDs.
 * 3.  Let `changes` be the set of expected changes to the group which may
 *     contain the following properties:
 *     - `profile-picture` is defined if the group's profile picture is
 *       expected to be changed and contains either the new profile picture or
 *       a _remove_ mark to remove it
 *     - `profile-picture.blob` may contain the associated blob information
 *       data in case of a changed profile picture.
 *     - `add-members` is a set of new members to be added to the group
 *     - `remove-members` is a set of existing members to be removed from the
 *       group
 * 4.  Let `group` be a snapshot of the current group state.
 * 5.  Remove all members from `changes.add-members` that are not in
 *     `group.members`.
 * 6.  Remove all members from `changes.remove-members` that are in
 *     `group.members`.
 * 7.  Let `messages` be an empty list.
 * 8.  If `changes.remove-members` is not empty, add a message entry to
 *     `messages` to remove members to be removed with the following
 *     properties:
 *     - `id` set to the first message ID of `message-ids`,
 *     - `created-at` set to the current timestamp,
 *     - `receivers` set to `changes.remove-members`,
 *     - to construct a [`group-setup`](ref:e2e.group-setup) (wrapped by
 *       [`group-creator-container`](ref:e2e.group-creator-container))
 *       with an empty members set.
 * 9.  If `group.members` is not empty:
 *     1. Add a message entry to `messages` to update the group for the
 *         members with the following properties:
 *         - `id` set to the first message ID of `message-ids`,
 *         - `created-at` set to the current timestamp,
 *         - `receivers` set to `group.members`,
 *         - to construct a [`group-setup`](ref:e2e.group-setup) (wrapped by
 *           [`group-creator-container`](ref:e2e.group-creator-container))
 *           from `group.members`.
 *     2. Add a message entry to `messages` to announce the group's name to
 *         the members with the following properties:
 *         - `id` set to the second message ID of `message-ids`,
 *         - `created-at` set to the current timestamp,
 *         - `receivers` set to `group.members`,
 *         - to construct a [`group-name`](ref:e2e.group-name) (wrapped by
 *           [`group-creator-container`](ref:e2e.group-creator-container))
 *           from `group.name`.¹
 *     3. If `group.profile-picture` is defined:
 *         1. Let `profile-picture-blob` be `changes.profile-picture.blob`.
 *         2. If `group.profile-picture` does not equal
 *            `changes.profile-picture`, upload `group.profile-picture` to the
 *            blob server with the _persist_ flag and set
 *            `profile-picture-blob` to the result.
 *     4. Add a message entry to `messages` to announce the group's profile
 *         picture to the members with the following properties:
 *         - `id` set to the third message ID of `message-ids`,
 *         - `created-at` set to the current timestamp,
 *         - `receivers` set to `group.members`,
 *         - to construct a
 *           [`set-profile-picture`](ref:e2e.set-profile-picture) (wrapped by
 *           [`group-creator-container`](ref:e2e.group-creator-container)) from
 *           `profile-picture-blob` if `profile-picture-blob` is defined or
 *           [`delete-profile-picture`](ref:e2e.delete-profile-picture)
 *           (wrapped by
 *           [`group-creator-container`](ref:e2e.group-creator-container))
 *           otherwise.¹
 *     5. Let `chosen-call` be the result of the most recent invocation of
 *         the _Group Call Refresh Steps_ for the group.
 *     6. If `chosen-call` is defined, add a message entry to `messages` to
 *         announce the ongoing group call to newly added members with the
 *         following properties:
 *         - `id` set to the fourth message ID of `message-ids`,
 *         - `created-at` set to the `started_at` value associated to
 *           `chosen-call`,
 *         - `receivers` set to `changes.add-members`,
 *         - to construct a repeat of `csp-e2e.GroupCallStart` (wrapped by
 *           [`group-member-container`](ref:e2e.group-member-container))
 *           that is associated to `chosen-call`.
 * 10. Run the _Bundled Messages Send Steps_ with `messages`.
 *
 * ¹: This results in the group name and the group profile picture being
 * distributed to all members regardless of whether it was changed or not. This
 * is deemed acceptable for the sake of implementation simplicity and
 * reusability.
 *
 * The following steps are defined as the _Active Group State Resync Steps_:
 *
 * 1.  If the user is not the creator of the group or the group is marked as
 *     _left_, log an error and abort these steps.
 * 2.  Let `message-ids` be a list of four pre-generated message IDs.
 * 3.  Let `target-members` be a set of members to receive the resync.
 * 4.  Remove all members from `target-members` that are not in
 *     `group.members`.
 * 5.  If `target-members` is empty, abort these steps.
 * 6.  Let `messages` be an empty list.
 * 7.  Add a message entry to `messages` to announce the group composition with
 *     the following properties:
 *     - `id` set to the first message ID of `message-ids`,
 *     - `created-at` set to the current timestamp,
 *     - `receivers` set to `target-members`,
 *     - to construct a [`group-setup`](ref:e2e.group-setup) (wrapped by
 *       [`group-creator-container`](ref:e2e.group-creator-container)) from
 *       `group.members`.
 * 8.  Add a message entry to `messages` to announce the group's name with the
 *     following properties:
 *     - `id` set to the second message ID of `message-ids`,
 *     - `created-at` set to the current timestamp,
 *     - `receivers` set to `target-members`,
 *     - to construct a [`group-name`](ref:e2e.group-name) (wrapped by
 *       [`group-creator-container`](ref:e2e.group-creator-container)) from
 *       `group.name`.
 * 9.  If `group.profile-picture` is defined, upload `group.profile-picture` to
 *     the blob server with the _persist_ flag and let `profile-picture-blob`
 *     be the result.
 * 10. Add a message entry to `messages` to announce the group's profile
 *     picture with the following properties:
 *     - `id` set to the third message ID of `message-ids`,
 *     - `created-at` set to the current timestamp,
 *     - `receivers` set to `target-members`,
 *     - to construct a
 *       [`set-profile-picture`](ref:e2e.set-profile-picture) (wrapped by
 *       [`group-creator-container`](ref:e2e.group-creator-container)) from
 *       `profile-picture-blob` if `profile-picture-blob` is defined or
 *       [`delete-profile-picture`](ref:e2e.delete-profile-picture) (wrapped by
 *       [`group-creator-container`](ref:e2e.group-creator-container))
 *       otherwise.
 * 11. Let `chosen-call` be the result of the most recent invocation of
 *     the _Group Call Refresh Steps_ for the group.
 * 12. If `chosen-call` is defined, add a message entry to `messages` to
 *     announce the ongoing group call with the following properties:
 *     - `id` set to the fourth message ID of `message-ids`,
 *     - `created-at` set to the `started_at` value associated to
 *           `chosen-call`,
 *     - `receivers` set to `target-members`,
 *     - to construct a repeat of `csp-e2e.GroupCallStart` (wrapped by
 *       [`group-member-container`](ref:e2e.group-member-container)) that is
 *       associated to `chosen-call`.
 * 13. Run the _Bundled Messages Send Steps_ with `messages`.
 * 14. For each member of `target-members`, mark the group as _recently
 *     resynced_ for 1h.
 *
 * #### Create/Clone Group
 *
 * The following steps must be invoked when the user wants to create or clone a
 * group:
 *
 * 1.  Let `init` contain the following properties to create a group:
 *     - `name` of the new group or an empty string
 *     - `profile-picture` of the new group or undefined
 *     - `members` is a set of initial members to be added to the group¹
 * 2.  Let `parameters` be the MDM parameters. If
 *     `parameters.th_disable_create_group` is `true`, abort these steps.
 * 3.  Let `group-id` be a random group ID.
 * 4.  (MD) Begin a transaction with scope `GROUP_SYNC` and the following
 *     precondition:
 *     1. If a group with `group-id` and the user as creator exists, log an
 *        error and abort these steps.
 *     2. If `init.members` contains a member that is not an existing
 *        contact, log an error and abort these steps.
 * 5.  If `init.profile-picture` is defined, upload `init.profile-picture` to
 *     the blob server with the _persist_ flag and set
 *     `init.profile-picture.blob` to the result.
 * 6.  (MD) Reflect a `GroupSync.Create` with `group` set to contain:
 *     - `group_identity` being `group-id` and the user as the creator,
 *     - `created_at` set to now,
 *     - `name` set to `init.name`
 *     - `user_state` set to `MEMBER`,
 *     - `profile_picture` set from `init.profile-picture.blob`,
 *     - `member_identities` set from `init.members`,
 *     - all policies and categories set to their defaults.
 * 7.  (MD) Commit the transaction and await acknowledgement.
 * 8.  Persist the newly created group from `init` and `group-id` to storage.
 * 9.  If `init.members` is empty, abort these steps.
 * 10. Let `message-ids` be a list of four random message IDs.
 * 11. Schedule a persistent task to run the following steps:
 *     1. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
 *        precondition:
 *        1. If the group does not exist or the group is marked as _left_ or the
 *           group has no members, log a warning that a group sync race occurred
 *           and abort these steps.
 *     2. Let `group` be a snapshot of the current group state.
 *     3. If any of the following cases is true, log a warning that a group
 *        sync race occurred:
 *        - `init.name` is defined and does not equal `group.name`,
 *        - `init.profile-picture` does not equal `group.profile-picture`,
 *        - `init.members` does not equal `group.members`.
 *     4. Run the _Active Group Update Steps_ with `message-ids` and the
 *        following expected set of `changes`:
 *        - `profile-picture` set to `init.profile-picture`,
 *        - `add-members` set to `init.members`,
 *        - `remove-members` set to an empty list.
 *     5. (MD) Commit the transaction and await acknowledgement.
 *
 * ¹: Note that all contacts must be added before they can be added as initial
 * members of the group.
 *
 * #### Update Group
 *
 * The following steps must be invoked when the user is the creator of a group
 * and intends to apply a change to the group's name, profile picture or
 * add/remove members to/from the group:
 *
 * 1.  If the user is not the creator of the group or the group is marked as
 *     _left_, log an error and abort these steps.
 * 2.  Let `changes` be the set of changes to the group which may contain the
 *     following properties:
 *     - `name` is defined if the group's name is to be changed and contains the
 *       new name or an empty string
 *     - `profile-picture` is defined if the group's profile picture is to be
 *       changed and contains either the new profile picture or a _remove_ mark
 *       to remove it
 *     - `add-members` is a set of new members to be added to the group¹
 *     - `remove-members` is a set of existing members to be removed from the
 *       group¹
 * 3.  (MD) Begin a transaction with scope `GROUP_SYNC` and the following
 *     precondition:
 *     1. If `changes.add-members` or `changes.remove-members` contains a
 *        member that is not an existing contact, log an error and abort these
 *        steps.
 *     2. If the group does not exist or the group is marked as _left_, log a
 *        warning and abort these steps.
 * 4.  Let `updated-members` be a copy of the current member set of the group.
 *     Add all `changes.add-members` to this set that are to be added to the
 *     group. Remove all `changes.remove-members` from this set that are to be
 *     removed from the group.
 * 5.  If `changes.profile-picture` is defined and contains a profile picture,
 *     upload `changes.profile-picture` to the blob server with the _persist_
 *     flag and let `changes.profile-picture.blob` be the result.
 * 6.  (MD) Reflect a `GroupSync.Update` with `member_state_changes`
 *     constructed from `changes.add-members` and `changes.remove-members` and
 *     `group` set to contain:
 *     - `name` set to `changes.name`,
 *     - `profile_picture` set according to `changes.profile-picture` (and
 *       `changes.profile-picture.blob`),
 *     - `member_identities` set from `updated-members`.
 * 7.  (MD) Commit the transaction and await acknowledgement.
 * 8.  If the user is currently participating in a group call of this group,
 *     remove all `change.remove-members` participants from the group call
 *     (handle them as if they left the call).
 * 9.  Persist the `updated-members` and other `changes` to the group.
 * 10. If `changes.add-members` or `changes.remove-members` is not empty, run
 *     the _Rejected Messages Refresh Steps_ for the group.
 * 11. Let `message-ids` be a list of four random message IDs.
 * 12. Schedule a persistent task to run the following steps:
 *     1. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
 *        precondition:
 *        1. If the group does not exist or the group is marked as _left_, log
 *           a warning that a group sync race occurred and abort these steps.
 *     2. Let `group` be a snapshot of the current group state.
 *     3. If any of the following cases is true, log a warning that a group
 *        sync race occurred:
 *        - `changes.name` is defined and does not equal `group.name`,
 *        - `changes.profile-picture` contains a profile picture and does not
 *           equal `group.profile-picture`,
 *        - `changes.profile-picture` contains the _remove_ mark and
 *           `group.profile-picture` is defined,
 *        - `updated-members` does not equal `group.members`.
 *     4. Run the _Active Group Update Steps_ with `message-ids` and `changes`.
 *     5. (MD) Commit the transaction and await acknowledgement.
 *
 * ¹: Note that all contacts must be added before they can be added as members
 * to the group. The same applies to members that are being removed, obviously.
 *
 * #### Disband/Remove Group
 *
 * The following steps must be invoked when the user is the creator of a group
 * and intends to _disband_ or _disband and remove_ the group:
 *
 * 1.  Let `intent` be the user's intent which can be either to _disband_ or to
 *     _disband and remove_ the group.
 * 2.  If the user is not the creator of the group or the group is marked as
 *     _left_, log an error and abort these steps.
 * 3.  (MD) Begin a transaction with scope `GROUP_SYNC` and the following
 *     precondition:
 *     1. If the group does not exist or the group is marked as _left_, log a
 *        warning and abort these steps.
 * 4.  (MD) If `intent` is to _disband_, reflect a `GroupSync.Update` with
 *     `group` set to contain `user_state` set to `LEFT`.
 * 5.  (MD) If `intent` is to _disband and remove_, reflect a
 *     `GroupSync.Delete` for this group.
 * 6.  (MD) Commit the transaction and await acknowledgement.
 * 7.  If the user is participating in a group call of this group, trigger
 *     leaving the call.
 * 8.  If the `intent` is to _disband_:
 *     1. Mark the group as _left_.
 *     2. Persist the previous member setup so that the group can be cloned.
 *     3. Run the _Rejected Messages Refresh Steps_ for the group.
 * 9.  If the `intent` is to _disband and remove_, remove the group and all
 *     associated messages from storage.
 * 10. Let `message-id` be a random message ID.
 * 11. Schedule a persistent task to run the following steps:
 *     1. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
 *        precondition:
 *        1. If the group exists and is not marked as _left_, log an error that
 *           a major group state inconsistency has been detected¹ and abort
 *           these steps.
 *     2. Let `group` be a snapshot of the current group state.
 *     3. Run the _Bundled Messages Send Steps_ with the following properties:
 *        - `id` set to `message-id`,
 *        - `created-at` set to the current timestamp,
 *        - `receivers` set to `group.members`,
 *        - to construct a [`group-setup`](ref:e2e.group-setup) (wrapped by
 *          [`group-creator-container`](ref:e2e.group-creator-container)) with
 *          an empty members set.
 *     4. (MD) Commit the transaction and await acknowledgement.
 *
 * ¹: Disbanding a group as the creator makes the group strictly non-reusable.
 *
 * #### Leave/Remove Group
 *
 * The following steps must be invoked when the user is not the creator of a
 * group and intends to _leave_ or _leave and remove_ the group:
 *
 * 1.  Let `intent` be the user's intent which can be either to _leave_ or to
 *     _leave and remove_ the group.
 * 2.  If the user is the creator of the group or the group is marked as
 *     _left_, log an error and abort these steps.
 * 3.  (MD) Begin a transaction with scope `GROUP_SYNC` and the following
 *     precondition:
 *     1. If the group does not exist or the group is marked as _left_, log a
 *        warning and abort these steps.
 * 4.  (MD) If `intent` is to _leave_, reflect a `GroupSync.Update` with
 *     `group` set to contain `user_state` set to `LEFT`.
 * 5.  (MD) If `intent` is to _leave and remove_, reflect a `GroupSync.Delete`
 *     for this group.
 * 6.  (MD) Commit the transaction and await acknowledgement.
 * 7.  If the user is participating in a group call of this group, trigger
 *     leaving the call.
 * 8.  If the `intent` is to _leave_:
 *     1. Mark the group as _left_.
 *     2. Persist the previous member setup so that the group can be cloned.
 *     3. Run the _Rejected Messages Refresh Steps_ for the group.
 * 9.  If the `intent` is to _leave and remove_, remove the group and all
 *     associated messages from storage.
 * 10. Let `message-id` be a random message ID.
 * 11. Schedule a persistent task to run the following steps:
 *     1. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
 *        precondition:
 *        1. If the group exists and is not marked as _left_, log a warning
 *           that a group sync race occurred and abort these steps.
 *     2. Let `group` be a snapshot of the current group state.
 *     3. Run the _Bundled Messages Send Steps_ with the following properties:
 *        - `id` set to `message-id`,
 *        - `created-at` set to the current timestamp,
 *        - `receivers` set to `group.members`,
 *        - to construct a [`group-leave`](ref:e2e.group-leave) (wrapped by
 *          [`group-member-container`](ref:e2e.group-member-container))
 *     4. (MD) Commit the transaction and await acknowledgement.
 *
 * #### Group Resync
 *
 * The following steps must be invoked when the user is the creator of a group
 * and intends to resync the group manually:
 *
 * 1. If the user is not the creator of the group or the group is marked as
 *     _left_, log an error and abort these steps.
 * 2. Let `message-ids` be a list of four random message IDs.
 * 3. Schedule a volatile task to run the following steps:
 *    1. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
 *       precondition:
 *       1. If the group does not exist or the group is marked as _left_, log a
 *          warning and abort these steps.
 *    2. Run the _Active Group State Resync Steps_ with `message-ids` and
 *       `target-members` being all current group members.¹
 *    3. (MD) Commit the transaction and await acknowledgement.
 *
 * ¹: This mechanic intentionally bypasses the 1h _recently resynced_ mark due
 * to an explicit manual request by the user to resync the group.
 *
 * #### Update Conversation
 *
 * The following steps must be invoked when the user intends to change any
 * other synchronised property of the group:
 *
 * 1. Let `change` be one of the following changes to the group as defined by
 *    `sync.Group`:
 *    - `notification_trigger_policy_override`
 *    - `notification_sound_policy_override`
 *    - `conversation_category`
 *    - `conversation_visibility`
 * 2. Persist the `change` to the group.
 * 3. (MD) Schedule a persistent task to run the following steps:
 *    1. Begin a transaction with scope `GROUP_SYNC` and the following precondition:
 *       1. If the group does not exist, log a warning and abort these steps.
 *    2. Reflect a `GroupSync.Update` with `group` set to contain the `change`.
 *    3. Commit the transaction and await acknowledgement.
 *    4. Persist the `change` to the group (again).
 *
 * ### Sending
 *
 * The following steps are defined as the _Bundled Messages Send Steps_ and
 * must always be invoked as part of a task in order to send a message:
 *
 * 1.  Let `messages` be a list of messages with each having the following
 *     properties:
 *     - `id` being the associated message ID¹,
 *     - `created-at` timestamp,
 *     - `receivers` being the set of receivers for the message²,
 *     - the associated conversation,
 *     - all necessary information to construct a _canonical_ message from it,
 *     - all necessary information to construct a _specific_ message from it,
 *       given the specific receiver.
 *
 *     Note: If only one set of informations to construct a message is
 *     provided, this is considered both the _canonical_ and the _specific_
 *     message construction variant.
 * 2.  For each `message` of `messages`:
 *     1. For each `receiver` of `message.receivers`:
 *        1. If `receiver` is the user, log a warning, remove `receiver` from
 *           `receivers` and abort these sub-steps.
 *        2. If `receiver` is marked as _invalid_, remove `receiver` from
 *           `receivers and abort these sub-steps.
 *        3. If the properties associated to `message` to be constructed for
 *           `receiver` given its feature mask indicates that the message is
 *           not exempted from blocking, run the _Identity Blocked Steps_ for
 *           `receiver`'s identity. If the result indicates that `receiver` is
 *           blocked, remove `receiver` from `receivers` and abort these
 *           sub-steps.
 *        4. Construct the _specific_ `message` for `receiver` and attach the
 *           constructed message to `receiver`.
 *        5. Run the _Profile Picture Distribution Steps_ with the constructed
 *           _specific_ message for `receiver`'s type and `receiver` and extend
 *           `messages` with the result.
 * 3.  For each `message` of `messages` attach a random nonce for `message` to
 *     each receiver of `message.receivers`.
 * 4.  (MD) Let `pending-reflect-acks` be an empty list.
 * 5.  (MD) For each `message` of `messages`:
 *     1. If the properties associated to the _canonical_ `message` do not
 *        require reflecting outgoing messages, abort these sub-steps.
 *     2. Construct a `d2d.OutgoingMessage` from the _canonical_ `message` for
 *        the associated conversation and reflect it.
 *     3. Add the pending acknowledgement to `pending-reflect-acks`.
 * 6.  (MD) Await all `pending-reflect-acks`.
 * 7.  Let `pending-csp-acks` and `fs-commit-fns` be empty lists.
 * 8.  For each `message` of `messages`:
 *     1. For each `receiver` of `message.receivers`:
 *        1. If the constructed _specific_ message for `receiver` is of type
 *           `0xa0`, let `outer-messages` be a list including only the
 *           constructed message for `receiver` and `fs-commit-fns` be an empty
 *           list.
 *        2. If the constructed _specific_ message for `receiver` is not of
 *           type `0xa0`, run the _FS Encapsulation Steps_ with the constructed
 *           _specific_ message for `receiver` and let `outer-messages` and
 *           `fs-commit-fns` be the result.³
 *        3. For each `outer-message` of `outer-messages`:
 *           1. Create a `payload.message-with-metadata-box` for `outer-message`
 *              with `receiver` and let `payload` be the result.
 *           2. If `payload.flags` does not contain the _no server
 *              acknowledgement_ (`0x04`) flag, add `payload.message-id` to
 *              `pending-csp-acks`.
 *           3. If the properties associated to `outer-message` requires
 *              protection against replay, mark the nonce of `outer-message` as
 *              used.
 *           4. Send `payload`.
 * 9.  Await all `pending-csp-acks`.
 * 10. Run each function of `fs-commit-fns`.
 * 11. (MD) Let `pending-reflect-acks` be an empty list.
 * 12. (MD) For each `message` of `messages`:
 *     1. If the properties associated to the _canonical_ `message` is eligible
 *        for reflecting `OutgoingMessageUpdate.Sent`:
 *        1. Create an `OutgoingMessageUpdate.Sent` for `message.id` and the
 *           associated conversation and reflect it.
 *        2. Add the pending acknowledgement to `pending-reflect-acks`.
 * 13. (MD) Await all `pending-reflect-acks`.
 * 14. For each `message` of `messages`:
 *     1. (MD) If the properties associated to the _canonical_ `message` was
 *        eligible for reflecting `OutgoingMessageUpdate.Sent`, mark it as
 *        _sent_ with the timestamp from the corresponding `reflect-ack`
 *        message.
 *     2. (non-MD) Mark `message` as _sent_ with the current timestamp.
 *
 * ¹: Note that, in groups, this implicitly assigns the same message ID towards
 * each group member which in fact is a requirement of the protocol.
 *
 * ²: Reflecting with `receivers` empty is a legitimate case that occurs when
 * sending a message in a notes group.
 *
 * ³: Always invoking the _FS Encapsulation Steps_ ensures that an FS session
 * is being initiated as soon as possible, so that messages can be protected by
 * FS. Moreover, it ensures that the announced FS session version is up to date
 * (a newer version potentially increasing security or making more messages
 * eligible for FS protection).
 *
 * The following steps are defined as the _Messages Submit Steps_ which must be
 * invoked to submit a user-created message in a conversation:
 *
 * 1. Let `messages` be a list of messages of the user to be added to
 *    the conversation with each having the following properties:
 *    - `id` set to a random message ID,
 *    - `created-at` set to the current timestamp,
 *    - `blobs` be an optional set of blobs that must be uploaded prior to
 *      being able to construct the message,
 *    - all necessary information to construct a _canonical_ message from it,
 *    - all necessary information to construct a _specific_ message from it,
 *      given the specific receiver.
 *
 *    Note: If only one set of informations to construct a message is
 *    provided, this is considered both the _canonical_ and the _specific_
 *    message construction variant.
 * 2. If the associated conversation is a 1:1 conversation, run the _1:1
 *    Messages Submit Steps_ with `messages`.
 * 3. If the associated conversation is a group conversation, run the _Group
 *    Messages Submit Steps_ with `messages`.
 * 4. If the associated conversation is a distribution list conversation, run
 *    the _Distribution List Messages Submit Steps_ with `messages`.
 * 5. (Unreachable)
 *
 * The following steps are defined as the _1:1 Messages Submit Steps_ which
 * must be invoked to submit a user-created message in a 1:1 conversation:
 *
 * 1.  Let `messages` be a list of messages of the user to be added to
 *     the conversation with each having the following properties:
 *     - `id` defaulting to a random message ID,
 *     - `created-at` defaulting to the current timestamp,
 *     - `blobs` be an optional set of blobs that must be uploaded prior to
 *       being able to construct the message,
 *     - all necessary information to construct a _canonical_ message from it,
 *     - all necessary information to construct a _specific_ message from it,
 *       given the specific receiver.
 *
 *     Note: If only one set of informations to construct a message is
 *     provided, this is considered both the _canonical_ and the _specific_
 *     message construction variant.
 * 2.  Let `receiver` be the conversation's associated contact.
 * 3.  If `receiver` has acquaintance level _deleted_, discard `messages`,
 *     log a warning and abort these steps.¹
 * 4.  Run the _Identity Blocked Steps_ for `receiver`'s identity'. If the
 *     result indicates that `receiver` is blocked, discard `messages`, log
 *     a warning and abort these steps.¹
 * 5.  For each `message` of `messages`, assign a random message ID to
 *     `message.id`.
 * 6.  Schedule a persistent task to run the following steps:²
 *     1. If the contact for `receiver` no longer exists, log an error,
 *        discard `messages` and abort these steps.
 *     2. For each `message` of `messages`:
 *        1. If `message.blob` is not defined, abort these sub-steps.
 *        2. Upload all `message.blobs` to the blob server and update
 *           `message` with the result (so that the message can be
 *           constructed).
 *     3. (MD) If `receiver`'s acquaintance level is not _direct_ or if at
 *        least one of the `messages` associated properties requires to
 *        unarchive the conversation and the associated conversation visibility
 *        is set to _archived_:
 *        1. Begin a transaction with scope `CONTACT_SYNC` and the following
 *           precondition:
 *           1. If the contact for `receiver` no longer exists, log an error,
 *              discard `messages` and abort these steps.
 *        2. Let `change` be the following changes as defined by
 *           `sync.Contact`:
 *           - `acquaintance_level` set to `DIRECT`,
 *           - `conversation_visibility` set to `NORMAL` if the associated
 *              conversation visibility is currently _archived_,
 *        3. Reflect a `ContactSync.Update` with `contact` set from `change`.
 *        4. Commit the transaction and await acknowledgement.
 *        5. Persist the `change` to the `receiver`.³
 *     4. Run the _Bundled Messages Send Steps_ for `messages` with the
 *        following additional properties added to each message:
 *        - `receivers` set to `receiver`.
 * 7.  If the the `receiver`'s acquaintance level is not _direct_, update it to
 *     _direct_.
 * 8.  If at least one of the `messages` associated properties requires to
 *     unarchive the conversation and the associated conversation visibility is
 *     set to _archived_, set it to _normal_.
 * 9.  For each `message` of `messages`, add `message` to the associated
 *     conversation or update a stateful message referred to by `message`
 *     respectively.
 * 10. If at least one of the `messages` associated properties requires to
 *     bump the conversation's _last update_ timestamp, update it to the
 *     current timestamp.
 *
 * ¹: While the UI should not allow to submit a message in these states,
 * another device may alter the state just prior to submission, allowing to
 * hit these steps in rare circumstances.
 *
 * ²: Rationale to not check for acquaintance level _deleted_ or whether the
 * receiver is blocked again is the legitimate use case of the user sending a
 * final message prior to blocking or removing a contact.
 *
 * ³: This is intentionally done only for MD since the user may e.g.
 * immediately archive a conversation after submitting a message. This results
 * in both the message and the contact sync being queued as tasks whereas in
 * the non-MD case only the message task would be queued. In the MD case, the
 * conversation briefly flicks back to _unarchived_ once the message has been
 * sent but it immediately flicks back to _archived_ once the contact sync task
 * has been executed. But because no contact sync task is created in the non-MD
 * case, the message task would leave the conversation _unarchived_ which is
 * not intended by the user.
 *
 * The following steps are defined as the _Group Messages Submit Steps_ which
 * must be invoked to submit a user-created message in a group conversation:
 *
 * 1. Let `messages` be a list of messages of the user to be added to
 *    the conversation with each having the following properties:
 *    - `id` set to a random message ID,
 *    - `created-at` set to the current timestamp,
 *    - `blobs` be an optional set of blobs that must be uploaded prior to
 *      being able to construct the message,
 *    - all necessary information to construct a _canonical_ message from it,
 *    - all necessary information to construct a _specific_ message from it,
 *      given the specific receiver.
 *
 *    Note: If only one set of informations to construct a message is provided,
 *    this is considered both the _canonical_ and the _specific_ message
 *    construction variant.
 * 2. If the group is marked as _left_, discard `messages`, log a warning and
 *    abort these steps.¹
 * 3. For each `message` of `messages`, assign a random message ID to
 *    `message.id`.
 * 4. Schedule a persistent task to run the following steps:²
 *    1. If the group does not exist or is marked as _left_, log a warning,
 *       discard `messages` and abort these steps.
 *    2. For each `message` of `messages`:
 *       1. If `message.blob` is not defined, abort these sub-steps.
 *       2. Upload all `message.blobs` to the blob server with the _persist_
 *          flag and update `message` with the result (so that the message
 *          can be constructed).
 *    3. (MD) If at least one of the `messages` associated properties requires
 *       to unarchive the conversation and the associated conversation
 *       visibility is set to _archived_:
 *       1. Begin a transaction with scope `GROUP_SYNC` and the following
 *          precondition:
 *          1. If the group does not exist or is marked as left, log a warning
 *             that a group sync race occurred, discard `messages` and abort
 *             these steps.
 *       2. Let `change` be the following changes as defined by `sync.Group`:
 *          - `conversation_visibility` set to `NORMAL` if the associated
 *            conversation visibility is currently _archived_,
 *       3. Reflect a `GroupSync.Update` with `group` set from `change`.
 *       4. Commit the transaction and await acknowledgement.
 *       5. Persist the `change` to the group.³
 *    4. Run the _Bundled Messages Send Steps_ for `messages` with the
 *       following additional properties added to each message:
 *       - `receivers` set to the group's members.
 * 5. If at least one of the `messages` associated properties requires to
 *    unarchive the conversation and the associated conversation visibility is
 *    set to _archived_, set it to _normal_.
 * 6. For each `message` of `messages`, add `message` to the associated
 *    conversation or update a stateful message referred to by `message`
 *    respectively.
 * 7. If at least one of the `messages` associated properties requires to
 *    bump the conversation's _last update_ timestamp, update it to the current
 *    timestamp.
 *
 * ¹: While the UI should not allow to submit a message in these states,
 * another device may alter the state just prior to submission, allowing to
 * hit these steps in rare circumstances.
 *
 * ²: Rationale to not check for acquaintance level _deleted_ or whether the
 * receiver is blocked again is the legitimate use case of the user sending a
 * final message prior to removing a group.
 *
 * ³: This is intentionally done only for MD since the user may e.g.
 * immediately archive a conversation after submitting a message. This results
 * in both the message and the group sync being queued as tasks whereas in the
 * non-MD case only the message task would be queued. In the MD case, the
 * conversation briefly flicks back to _unarchived_ once the message has been
 * sent but it immediately flicks back to _archived_ once the group sync task
 * has been executed. But because no group sync task is created in the non-MD
 * case, the message task would leave the conversation _unarchived_ which is
 * not intended by the user.
 *
 * The following steps are defined as the _Distribution List Messages Submit
 * Steps_ which must be invoked to submit a user-composed message in a
 * distribution list conversation:
 *
 * 1. Let `messages` be a list of messages of the user to be added to
 *    the conversation with each having the following properties:
 *    - `id` set to a random message ID,
 *    - `created-at` set to the current timestamp,
 *    - `blobs` be an optional set of blobs that must be uploaded prior to
 *      being able to construct the message,
 *    - all necessary information to construct a _canonical_ message from it,
 *    - all necessary information to construct a _specific_ message from it,
 *      given the specific receiver.
 *
 *    Note: If only one set of informations to construct a message is
 *    provided, this is considered both the _canonical_ and the _specific_
 *    message construction variant.
 * 2. For each `message` of `messages`, assign a random message ID to
 *    `message.id`.
 * 3. Schedule a persistent task to run the following steps:
 *    1. If the distribution list does not exist, log a warning, discard
 *       `messages` and abort these steps.
 *    2. For each `message` of `messages`:
 *       1. If `message.blob` is not defined, abort these sub-steps.
 *       2. Upload all `message.blobs` to the blob server with the _persist_
 *          flag and update `message` with the result (so that the message
 *          can be constructed).
 *    3. (non-MD) Let `members` be all current members of the distribution
 *       list. Remove any members with acquaintance level _deleted_ from
 *       `members`.¹
 *    4. (MD) If at least one of the `messages` associated properties requires
 *       to unarchive the conversation and the associated conversation
 *       visibility is set to _archived_:
 *       1. Begin a transaction with scope `DISTRIBUTION_LIST_SYNC` and the
 *          following precondition:
 *          1. If the distribution list does not exist, log a warning that a
 *             distribution list sync race occurred, discard `messages` and
 *             abort these steps.
 *       2. Let `members` be all current members of the distribution list.
 *          Remove any members with acquaintance level _deleted_ from `members`.¹
 *       3. Let `change` be the following changes as defined by
 *          `sync.DistributionList`:
 *          - `member_identities` from `members`,
 *          - `conversation_visibility` set to `NORMAL` if the associated
 *            conversation visibility is currently _archived_,
 *       4. Reflect a `DistributionList.Update` with `distribution_list` set from `change`.
 *       5. Commit the transaction and await acknowledgement.
 *       6. Persist the `change` to the distribution list.²
 *    5. Run the _Bundled Messages Send Steps_ for `messages` with the
 *       following additional properties added to each message:
 *       - `receivers` from `members`.
 * 4. If at least one of the `messages` associated properties requires to
 *    unarchive the conversation and the associated conversation visibility is
 *    set to _archived_, set it to _normal_.
 * 5. For each `message` of `messages`, add `message` to the associated
 *    conversation or update a stateful message referred to by `message`
 *    respectively.
 * 6. If at least one of the `messages` associated properties requires to
 *    bump the conversation's _last update_ timestamp, let `last-update-at` be
 *    the current timestamp.
 * 7. If `last-update-at` is defined, update the associated _last update_
 *    timestamp of the conversation to `last-update-at`.
 * 8. For each `member` of the distribution list:
 *    1. Add each message of `messages` to the 1:1 conversation associated to
 *       `member`.
 *    2. If `last-update-at` is defined, update the associated _last update_
 *       timestamp of the 1:1 conversation of `member` to `last-update-at`.
 *
 * ¹: There should be no receivers in a distribution list that have
 * acquaintance level _deleted_, so the filtering is only done for safety.
 *
 * 2: This is intentionally done only for MD since the user may e.g.
 * immediately archive a conversation after submitting a message. This results
 * in both the message and the distribution list sync being queued as tasks
 * whereas in the non-MD case only the message task would be queued. In the MD
 * case, the conversation briefly flicks back to _unarchived_ once the message
 * has been sent but it immediately flicks back to _archived_ once the
 * distribution list sync task has been executed. But because no distribution
 * list sync task is created in the non-MD case, the message task would leave
 * the conversation _unarchived_ which is not intended by the user.
 *
 * ### Receiving
 *
 * The following steps are defined as _Common Group Receive Steps_ and will
 * be applied in most cases for group messages:
 *
 * 1. Look up the group.
 * 2. If the group could not be found:
 *    1. If the user is not the creator of the group, run the _Group Sync
 *       Request Steps_.
 *    2. Discard the message and abort these steps.
 * 3. If the group is marked as _left_:
 *    1. If the user is the creator of the group, run the _Bundled Messages
 *       Send Steps_ with the following properties:
 *       - `id` set to a random message ID,
 *       - `created-at` set to the current timestamp,
 *       - `receivers` set to the sender,
 *       - to construct a
 *         [`group-setup`](ref:e2e.group-setup) (wrapped by
 *         [`group-creator-container`](ref:e2e.group-creator-container)) with
 *         an empty members set
 *    2. If the user is not the creator of the group, run the _Bundled
 *       Messages Send Steps_ with the following properties:
 *       - `id` set to a random message ID,
 *       - `created-at` set to the current timestamp,
 *       - `receivers` set to the sender,
 *       - to construct a
 *         [`group-leave`](ref:e2e.group-leave) (wrapped by
 *         [`group-member-container`](ref:e2e.group-member-container))
 *    3. Discard the message and abort these steps.
 * 4. If the sender is not a member of the group:
 *    1. If the user is the creator of the group, run the _Bundled Messages
 *       Send Steps_ with the following properties:
 *       - `id` set to a random message ID,
 *       - `created-at` set to the current timestamp,
 *       - `receivers` set to the sender,
 *       - to construct a
 *         [`group-setup`](ref:e2e.group-setup) (wrapped by
 *         [`group-creator-container`](ref:e2e.group-creator-container)) with
 *         an empty members set
 *    2. If the user is not the creator of the group, run the _Group Sync
 *       Request Steps_, then discard the message and abort these steps.
 *    3. Discard the message and abort these steps.
 *
 * This rule and any exceptions will be referenced/defined explicitly for each
 * message.
 *
 * Note that steps are not allowed to discard messages from blocked contacts
 * prior to running these steps if the message alters group state (group
 * control messages), or is stateful (i.e. introduces a poll, poll vote, or a
 * group call).
 *
 * ### Periodic Group Sync
 *
 * When the creator of a group...
 *
 * - is about to send a group conversation message, or
 * - did just receive a group conversation message,
 *
 * it must trigger a _group sync_ for this group if the last time the
 * _group sync_ was triggered is more than seven days ago.
 *
 * When a _group sync_ is triggered, the creator assumes it has received a
 * [`group-sync-request`](ref:e2e.group-sync-request) from every group member
 * and must now respond accordingly to each member of the group.
 *
 * A newly created group counts as an initial _group sync_ trigger. In other
 * words, the first group sync of a newly created group triggers seven days
 * in the future when one of the above described conditions is met.
 *
 * This provides a form of self-healing in case a device lost its group state
 * (e.g. due to a backup restore) and was unable to correct this mischief.
 *
 * [//]: # "TODO(SE-40): Group states"
 *
 * ### Blobs
 *
 * Since messages have a strict maximum size limitation, large binary blobs
 * are uploaded to the blob server. Blobs currently have a maximum size of
 * 50 MiB.
 *
 * When Multi-Device is activated, all Blobs must be downloaded via the
 * respective Blob Mirror unless explicitly stated otherwise.
 *
 * #### Upload
 *
 * The following steps are defined as the _Blob Upload Steps_:
 *
 * 1. Let `blob` be the following properties:
 *    - `data` being the encrypted binary data to be uploaded,
 *    - `scope` being either _public_ (for public facing blobs) or _local_ (for
 *        device group facing blobs).
 *    - `persist` being a mark (primarily for usage within groups).
 * 2. (non-MD) Upload to the blob server from `blob`.
 * 3. (MD) Upload to the blob mirror server from `blob` and the device group
 *    information.
 * 4. If the upload stream stalls for more than 10s, exceptionally abort these
 *    steps.
 * 5. Return the resulting blob ID.
 *
 * #### Download
 *
 * The following steps are defined as the _Blob Download Steps_:
 *
 * 1. Let `blob` be the following properties:
 *    - `id` being the blob ID,
 *    - `scope` being either _public_ (for public facing blobs) or _local_ (for
 *        device group facing blobs).
 * 2. (non-MD) Download the blob data from the blob server using `blob`.
 * 3. (MD) Download the blob data from the blob mirror server using `blob` and
 *    the device group information.
 * 4. If the download stream stalls for more than 10s, exceptionally abort
 *    these steps.
 * 5. Schedule a volatile background task to run the following steps:
 *    1. (non-MD) Request to mark the blob download from the blob server of
 *       `blob.id` as _done_.
 *    2. (MD) Request to mark the blob download from the blob mirror server of
 *       `blob.id` as _done_ with the device group information.
 *    3. If no response could be obtained within 10s or the request was
 *       unsuccessful, log a warning.
 * 6. Return the resulting blob data.
 *
 * ### Image, Audio, Video vs. File
 *
 * Images, as well as audio and video sources can be either send as special
 * media messages or as files. When sending as a file, i.e. a
 * [`file`](ref:e2e.file) message struct with rendering type `0x00` (file), no
 * transcoding is necessary and no media type restrictions apply.
 *
 * Clients should intelligently choose between a media message and a file
 * message but always leave the final choice to the user.
 *
 * The following sections describe what restrictions apply and modifications
 * need to be made in case the source is sent as a media message, i.e. one
 * of the specialised (deprecated) media structs or a [`file`](ref:e2e.file)
 * message struct with rendering type `0x01` (media) or `0x02` (sticker).
 *
 * ### Images
 *
 * Images must be in JPEG format for the legacy
 * [`deprecated-image`](ref:e2e.deprecated-image) message and for profile
 * pictures.
 *
 * When using the [`file`](ref:e2e.file) message struct, the following media
 * types are explicitly supported:
 *
 * - image/gif
 * - image/jpeg
 * - image/png
 * - image/webp
 *
 * The following media types are explicitly not supported:
 *
 * - image/svg+xml
 *
 * Other media types _may_ be supported.
 *
 * Keep the format when resizing images or creating thumbnails, if possible
 * (e.g. if the source is a JPEG, make the thumbnail a JPEG). When the format
 * cannot be kept, use PNG for source images with transparency or
 * lossless encoding (e.g. screenshots) and JPEG for images without
 * transparency or lossy encoding (e.g. photos).
 *
 * Recommended maximum dimensions:
 *
 * - Small: 640x640
 * - Medium: 1024x1024
 * - Large: 1600x1600
 * - Extra Large: 2592x2592
 * - Original: As is
 *
 * ### Thumbnails
 *
 * Apply the logic described for images to all thumbnails with recommended
 * maximum dimensions of 512x512.
 *
 * ### User Profile Distribution
 *
 * The shareable part of the user profile consists of the user's public
 * nickname and the profile picture:
 *
 * - The nickname is sent along with outgoing messages as `sender-nickname`
 *   inside the [`legacy-message`](ref:payload.legacy-message) or as part of
 *   the metadata in
 *   [`message-with-metadata-box`](ref:payload.message-with-metadata-box).
 * - The profile picture is distributed as described in
 *   [Profile Picture Distribution](ref:e2e#profile-picture-distribution).
 *
 * Whether user profile distribution should be triggered by an outgoing message
 * is specified in the description of every message type below.
 *
 * ### Profile Pictures
 *
 * Apply the logic described for images to all profile pictures with
 * recommended maximum dimensions of 512x512 with a square aspect ratio.
 *
 * #### Profile Picture Distribution
 *
 * Every time a message is being sent to a specific contact or a group of
 * contacts, the sender needs to evaluate whether the profile picture needs to
 * be sent. If the receiver of the message is a group, the evaluation needs to
 * be done for each contact of that group.
 *
 * The following steps are defined as the _Profile Picture Distribution Steps_:
 *
 * 1.  Let `type` be a message type associated to a message that is about to be
 *     sent.
 * 2.  Let `receiver` be the receiver of the message.
 * 3.  If the properties associated to `type` do not require _User Profile
 *     Distribution_, abort these steps without a message.
 * 4.  If `receiver`'s Threema ID is `ECHOECHO` or a Threema Gateway ID (starts
 *     with a `*`), abort these steps without a message.
 * 5.  Let `cache` be the most recently distributed profile picture message
 *     variant towards `receiver`, being either
 *     - a blob ID if the user's profile picture was distributed via a
 *       [`set-profile-picture`](ref:e2e.set-profile-picture) message, or
 *     - a _remove_ mark and a timestamp if the user's profile picture was
 *       removed via a
 *       [`delete-profile-picture`](ref:e2e.delete-profile-picture) message.
 * 6.  If the user has no profile picture or the settings indicate that the
 *     user's profile picture should be shared with nobody or the settings
 *     associated to `receiver` indicate that the profile picture should not be
 *     distributed to it:
 *     1. If `cache` is a _remove_ mark and indicates that the most recent
 *        [`delete-profile-picture`](ref:e2e.delete-profile-picture) message
 *        towards `receiver` was sent less than seven days ago, abort these
 *        steps without a message.
 *     2. Update the `cache` for `receiver` to a _remove_ mark with the current
 *        timestamp.
 *     3. Return the following properties:
 *        - `id` being a random message ID,
 *        - `created-at` set to the current timestamp,
 *        - `receivers` set to `receiver`,
 *        - to construct a
 *        [`delete-profile-picture`](ref:e2e.delete-profile-picture).
 * 7.  If there is a cached profile picture of the user and the associated blob
 *     was uploaded more than seven days ago, remove the cached profile
 *     picture.
 * 8.  If `cache` contains a blob ID of the most recent
 *     [`set-profile-picture`](ref:e2e.set-profile-picture) message sent
 *     towards `receiver` that equals the blob ID of the cached profile
 *     picture, abort these steps without a message.
 * 9.  If there is no cached profile picture, encrypt the user's profile
 *     picture with a random symmetric key and upload it to the blob server.
 *     Store the key and the resulting blob ID as the cached profile picture of
 *     the user.
 * 10. Update the `cache` for `receiver` to the blob ID of the cached profile
 *     picture.
 * 11. Return the following properties:
 *     - `id` being a random message ID,
 *     - `created-at` set to the current timestamp,
 *     - `receivers` set to `receiver`,
 *     - to construct a [`set-profile-picture`](ref:e2e.set-profile-picture)
 *       message using the cached profile picture's blob ID and key.
 *
 * When the user changes the profile picture, run the steps associated to
 * update the user's profile with the new profile picture or the newly removed
 * profile picture.
 *
 * #### Profile Picture Sharing Settings
 *
 * In the client settings, there are three profile picture sharing options that
 * the user can choose from:
 *
 * - Share with nobody
 * - Share with everybody you write to
 * - Share with selected contacts only
 *
 * The default is to share the profile picture with everyone.
 *
 * #### Contact Profile Picture Precedence
 *
 * There are three different sources of profile pictures, ordered by
 * precedence:
 *
 * 1. _contact-defined_: Set by the contact, distributed through a
 *    [`set-profile-picture`](ref:e2e.set-profile-picture) message.
 * 2. _gateway-defined_: Set by the creator in the Threema Gateway (or Threema
 *    Broadcast) control panel and distributed through `avatar.threema.ch`.
 *    Only applicable to Threema Gateway IDs (starting with a `*`).
 * 3. _user-defined_: Set by the app user for this contact or imported from
 *    the address book. Applicable to all Threema IDs which are not Threema
 *    Gateway IDs.
 *
 * The following steps are defined as _Contact Profile Picture Selection Steps_
 * and will be applied to determine the contact's profile picture that should
 * be displayed:
 *
 * 1. Let `id` be the Threema ID of the contact.
 * 2. If the _contact-defined_ picture is set for the contact, apply it and
 *    abort these steps.
 * 3. If `id` starts with a `*` (is a Threema Gateway ID) and the
 *    _gateway-defined_ picture is set for the contact, apply it and abort
 *    these steps.
 * 4. If `id` does not start with `*` and the _user-defined_ picture is set
 *    for the contact, apply it and abort these steps.
 * 5. Apply a fallback picture.
 *
 * #### Recurring Gateway Contact Profile Picture Refresh
 *
 * For contacts with a Threema Gateway ID (starting with a `*`), the profile
 * picture needs to be fetched recurringly:
 *
 * 1. Fetch the profile picture for the ID from `avatar.threema.ch`.
 * 2. If no profile picture could be found, schedule the next refresh in 24h
 *    and abort these steps.
 * 3. Store the profile picture as the _gateway-defined_ picture.
 * 4. Schedule the next refresh according to the `expires` header of the HTTP
 *    response.
 * 5. Run the _Contact Profile Picture Selection Steps_ for this contact.
 *
 * ### Audio
 *
 * Audio must be in AAC format.
 *
 * If the source is already in AAC, no transcoding is necessary. Otherwise,
 * the recommended transcoding settings are: Bitrate 128 kbit/s, 2 channels.
 *
 * When recording audio (i.e. a voice message), the recommended recording
 * settings are: Sample rate 44.1 kHz, bitrate 32 kbit/s, 1 channel.
 *
 * ### Video
 *
 * Videos must be encoded in H.264 and the MP4 container format.
 *
 * Recommended encoding settings for all videos:
 *
 * - Low: 480x480, scale by maintaining aspect ratio to nearest multiple
 *   of 16px. Video bitrate 384 kbit/s, audio bitrate 32 kbit/s (2
 *   channels). Baseline Profile, Level 3.1.
 * - High: 848x848, scale by maintaining aspect ratio to nearest multiple
 *   of 16px. Video bitrate 1500 kbit/s, audio bitrate 64 kbit/s (2
 *   channels). Baseline Profile, Level 3.1.
 * - Original: As is. Still needs transcoding in case a different codec has
 *   been used.
 *
 * When recording a video, the following recording settings are recommended
 * to avoid post-reencoding: 1280x720 / 720x1280. Video bitrate 2000 kbit/s
 * at 30 fps, audio bitrate 128 kbit/s (2 channels).
 *
 * ### Call Features
 *
 * Call features are transmitted within either a
 * [`call-offer`](ref:e2e.call-offer) or a [`call-answer`](ref:e2e.call-answer)
 * message. It is an optional object containing the below defined fields. If
 * the object is not provided, assume an empty features object.
 *
 * - Video Support (`'video'`): Set this field to `null` or an empty object if
 *   video calls are enabled. If either side omits this field, video support
 *   is disabled for the upcoming call.
 *
 * ### Application Setup
 *
 * The following steps are defined as _Application Setup Steps_ and must be run
 * when a new Threema ID has been created or when application state has been
 * restored from a backup:
 *
 * 1. [...]
 * 2. If application state has not been set up by the _Device Join Protocol_
 *    (meaning that multi-device is deactivated):
 *    1.  [...]
 *    2.  Update the user's feature mask on the directory server.
 *    3.  Let `contacts` be the list of all contacts, including those with an
 *        acquaintance level different than _direct_.
 *    4.  Refresh the state, type and feature mask of all `contacts` from the
 *        directory server and make any changes persistent.
 *    5.  Let `solicited-contacts` be a copy of `contacts` filtered in the
 *        following way. For each `contact`:
 *        1. If the `contact`'s activity state is _invalid_ (i.e. it does not
 *           exist or has been revoked), remove `contact` from the list and
 *           abort these sub-steps.
 *        2. If `contact` is part of a group that is not marked as _left_, add
 *           `contact` to the list and abort these sub-steps.
 *        3. Lookup the 1:1 conversation with `contact` and let `last-update`
 *           be the associated _last update_ timestamp.
 *        4. If `last-update` is defined, add `contact` to the list and abort
 *           these sub-steps.
 *        5. Remove `contact` from the list.
 *    6.  If FS is supported by the client, run the _FS Refresh Steps_ with
 *        `solicited-contacts`.
 *    7.  For each `contact` of `solicited-contacts` run the _Bundled Messages
 *        Send Steps_ with the following properties:
 *        - `id` being a random message ID,
 *        - `created-at` set to the current timestamp,
 *        - `receivers` set to `contact`,
 *        - to construct a
 *          [`contact-request-profile-picture`](ref:e2e.contact-request-profile-picture)
 *    8.  For each group not marked as _left_:
 *        1. If the user is the creator of the group, trigger a _group sync_
 *           for that group.
 *        2. If the user is not the creator of the group, run the _Group Sync
 *           Request Steps_ for the group.
 *    9. [...]
 * 3. Commit the application state and exit the setup phase.
 *
 * ### Application Update
 *
 * The following steps are defined as _Application Update Steps_ and must be
 * run as a persistent task when the application has just been updated to a new
 * version or downgraded to a previous version:
 *
 * 1. [...]
 * 2. Update the user's feature mask on the directory server.
 * 3. Let `contacts` be the list of all contacts (regardless of the
 *    acquaintance level).
 * 4. Refresh the state, type and feature mask of all `contacts` from the
 *    directory server and make any changes persistent.
 * 5. For each `contact` of `contacts`:
 *    1. If an associated FS session with `contact` exists and any of the FS
 *       states is unknown or any of the stored FS versions (local or remote)
 *       is unknown, terminate the FS session by running the _Bundled Messages
 *       Send Steps_ with the following properties:
 *       - `id` being a random message ID,
 *       - `created-at` set to the current timestamp,
 *       - `receivers` set to `contact`,
 *       - to construct a `csp-e2e-fs.Terminate` message with cause `RESET`.
 * 6. [...]
 *
 * Note: Reactivation of FS due to disabling multi-device should run the
 * _Application Setup Steps_ step 2.2. through 2.6. TODO(SE-199): This note
 * will be removed once multi-device supports FS.
 */

/**
 * Contains an end-to-end encrypted message.
 */
export interface ContainerLike {
    /**
     * Type of the message (`common.CspE2eMessageType`).
     */
    readonly type: types.u8;

    /**
     * Inner message. Needs to be parsed according to the `type` field.
     * Padded with a random amount from 1 to 255 bytes in [PKCS#7
     * format](https://datatracker.ietf.org/doc/html/rfc5652#section-6.3).
     *
     * Additionally, for security reasons, the total size of `padded-data`
     * should be at least 32 bytes, to avoid leaking information about the
     * contents.
     *
     * Example padding (hex representation):
     *
     * - 1 byte: `01`
     * - 3 bytes: `030303`
     * - 10 bytes: `0A0A0A0A0A0A0A0A0A0A`
     *
     * To add padding without information leaks, run the following steps:
     *
     * 1. Let `data` be the data to be padded.
     * 2. Let `pad-length` be a random number between (inclusive) 1 and 255.
     * 3. If the sum of the byte length of `data` and `pad-length` is less
     *    than 32, update `pad-length` so the sum is precisely 32.
     * 4. Let `pad-byte` be the encoded unsigned 8-bit integer
     *    representation of `pad-length`.
     * 5. Let `padded-data` be the padded data by adding `pad-length`
     *    trailing `pad-byte` bytes to `data`.
     *
     * To remove padding:
     *
     * 1. Let `pad-length` be the decoded unsigned 8-bit integer
     *    representation of the last byte of `padded-data`.
     * 2. Let `data` be the unpadded data by ignoring the trailing
     *    `pad-length` bytes of `padded-data`.
     */
    readonly paddedData: Uint8Array;
}

/**
 * Encodable of {@link ContainerLike}.
 */
interface ContainerEncodable_ {
    /**
     * 'type' field value or encoder. See {@link ContainerLike#type} for
     * the field's description.
     */
    readonly type: types.u8;

    /**
     * 'padded-data' field value or encoder. See {@link ContainerLike#paddedData} for
     * the field's description.
     */
    readonly paddedData: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for ContainerEncodable.
 */
export type ContainerEncodable = types.WeakOpaque<
    ContainerEncodable_,
    {readonly ContainerEncodable: unique symbol}
>;

/** @inheritdoc */
export class Container extends base.Struct implements ContainerLike {
    private readonly _array: Uint8Array;

    /**
     * Create a Container from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a container struct from an array.
     *
     * @param array Array to decode from.
     * @returns Container instance.
     */
    public static decode(array: Uint8Array): Container {
        return new Container(array);
    }

    /**
     * Encode a container struct into an array.
     *
     * @param struct ContainerEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<ContainerEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        let offset = 1;

        // Encode `type`
        array[0] = struct.type;

        // Encode `padded-data`
        offset += utils.encodeBytes(struct.paddedData, array, offset);

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a container struct into an
     * array.
     *
     * @param struct ContainerEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<ContainerEncodable, 'byteLength'>,
    ): types.u53 {
        let offset = 1;
        offset += utils.getByteLength(struct.paddedData);
        return offset;
    }

    /**
     * 'type' field accessor. See {@link ContainerLike#type} for the
     * field's description.
     */
    public get type(): types.u8 {
        return utils.unwrap(this._array[0]);
    }

    /**
     * 'padded-data' field accessor. See {@link ContainerLike#paddedData} for the
     * field's description.
     */
    public get paddedData(): Uint8Array {
        return this._array.subarray(1);
    }

    /**
     * Create a snapshot of ContainerLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns ContainerLike snapshot.
     */
    public snapshot(): ContainerLike {
        return {
            type: this.type,
            paddedData: this.paddedData,
        };
    }

    /**
     * Create a clone of ContainerLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns ContainerLike clone.
     */
    public clone(): Container {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new Container(array);
    }
}

/**
 * Container that is wrapped around some special group messages sent by the
 * creator to normal group members and vice versa.
 */
export interface GroupCreatorContainerLike {
    /**
     * 8 byte random group ID. Uniquely identifies the group when combined
     * with the creator's Threema ID.
     */
    readonly groupId: types.u64;

    /**
     * Inner message struct.
     */
    readonly innerData: Uint8Array;
}

/**
 * Encodable of {@link GroupCreatorContainerLike}.
 */
interface GroupCreatorContainerEncodable_ {
    /**
     * 'group-id' field value or encoder. See {@link GroupCreatorContainerLike#groupId} for
     * the field's description.
     */
    readonly groupId: types.u64;

    /**
     * 'inner-data' field value or encoder. See {@link GroupCreatorContainerLike#innerData} for
     * the field's description.
     */
    readonly innerData: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for GroupCreatorContainerEncodable.
 */
export type GroupCreatorContainerEncodable = types.WeakOpaque<
    GroupCreatorContainerEncodable_,
    {readonly GroupCreatorContainerEncodable: unique symbol}
>;

/** @inheritdoc */
export class GroupCreatorContainer extends base.Struct implements GroupCreatorContainerLike {
    private readonly _array: Uint8Array;
    private readonly _view: DataView;

    /**
     * Create a GroupCreatorContainer from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
        this._view = new DataView(array.buffer, array.byteOffset, array.byteLength);
    }

    /**
     * Decode a group-creator-container struct from an array.
     *
     * @param array Array to decode from.
     * @returns GroupCreatorContainer instance.
     */
    public static decode(array: Uint8Array): GroupCreatorContainer {
        return new GroupCreatorContainer(array);
    }

    /**
     * Encode a group-creator-container struct into an array.
     *
     * @param struct GroupCreatorContainerEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<GroupCreatorContainerEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        const view = new DataView(array.buffer, array.byteOffset, array.byteLength);
        let offset = 8;

        // Encode `group-id`
        view.setBigUint64(0, struct.groupId, true);

        // Encode `inner-data`
        offset += utils.encodeBytes(struct.innerData, array, offset);

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a group-creator-container struct into an
     * array.
     *
     * @param struct GroupCreatorContainerEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<GroupCreatorContainerEncodable, 'byteLength'>,
    ): types.u53 {
        let offset = 8;
        offset += utils.getByteLength(struct.innerData);
        return offset;
    }

    /**
     * 'group-id' field accessor. See {@link GroupCreatorContainerLike#groupId} for the
     * field's description.
     */
    public get groupId(): types.u64 {
        return this._view.getBigUint64(0, true);
    }

    /**
     * 'inner-data' field accessor. See {@link GroupCreatorContainerLike#innerData} for the
     * field's description.
     */
    public get innerData(): Uint8Array {
        return this._array.subarray(8);
    }

    /**
     * Create a snapshot of GroupCreatorContainerLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns GroupCreatorContainerLike snapshot.
     */
    public snapshot(): GroupCreatorContainerLike {
        return {
            groupId: this.groupId,
            innerData: this.innerData,
        };
    }

    /**
     * Create a clone of GroupCreatorContainerLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns GroupCreatorContainerLike clone.
     */
    public clone(): GroupCreatorContainer {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new GroupCreatorContainer(array);
    }
}

/**
 * Container that is wrapped around most messages sent by group members to
 * other group members.
 */
export interface GroupMemberContainerLike {
    /**
     * The group creator's Threema ID.
     */
    readonly creatorIdentity: Uint8Array;

    /**
     * 8 byte random group ID assigned to the group by the creator.
     */
    readonly groupId: types.u64;

    /**
     * Inner message struct.
     */
    readonly innerData: Uint8Array;
}

/**
 * Encodable of {@link GroupMemberContainerLike}.
 */
interface GroupMemberContainerEncodable_ {
    /**
     * 'creator-identity' field value or encoder. See {@link GroupMemberContainerLike#creatorIdentity} for
     * the field's description.
     */
    readonly creatorIdentity: Uint8Array | types.ByteLengthEncoder;

    /**
     * 'group-id' field value or encoder. See {@link GroupMemberContainerLike#groupId} for
     * the field's description.
     */
    readonly groupId: types.u64;

    /**
     * 'inner-data' field value or encoder. See {@link GroupMemberContainerLike#innerData} for
     * the field's description.
     */
    readonly innerData: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for GroupMemberContainerEncodable.
 */
export type GroupMemberContainerEncodable = types.WeakOpaque<
    GroupMemberContainerEncodable_,
    {readonly GroupMemberContainerEncodable: unique symbol}
>;

/** @inheritdoc */
export class GroupMemberContainer extends base.Struct implements GroupMemberContainerLike {
    private readonly _array: Uint8Array;
    private readonly _view: DataView;

    /**
     * Create a GroupMemberContainer from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
        this._view = new DataView(array.buffer, array.byteOffset, array.byteLength);
    }

    /**
     * Decode a group-member-container struct from an array.
     *
     * @param array Array to decode from.
     * @returns GroupMemberContainer instance.
     */
    public static decode(array: Uint8Array): GroupMemberContainer {
        return new GroupMemberContainer(array);
    }

    /**
     * Encode a group-member-container struct into an array.
     *
     * @param struct GroupMemberContainerEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<GroupMemberContainerEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        const view = new DataView(array.buffer, array.byteOffset, array.byteLength);
        let offset = 16;

        // Encode `creator-identity`
        utils.encodeBytes(struct.creatorIdentity, array, 0);

        // Encode `group-id`
        view.setBigUint64(8, struct.groupId, true);

        // Encode `inner-data`
        offset += utils.encodeBytes(struct.innerData, array, offset);

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a group-member-container struct into an
     * array.
     *
     * @param struct GroupMemberContainerEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<GroupMemberContainerEncodable, 'byteLength'>,
    ): types.u53 {
        let offset = 16;
        offset += utils.getByteLength(struct.innerData);
        return offset;
    }

    /**
     * 'creator-identity' field accessor. See {@link GroupMemberContainerLike#creatorIdentity} for the
     * field's description.
     */
    public get creatorIdentity(): Uint8Array {
        const offset = 0;
        return this._array.subarray(offset, offset + 8);
    }

    /**
     * 'group-id' field accessor. See {@link GroupMemberContainerLike#groupId} for the
     * field's description.
     */
    public get groupId(): types.u64 {
        return this._view.getBigUint64(8, true);
    }

    /**
     * 'inner-data' field accessor. See {@link GroupMemberContainerLike#innerData} for the
     * field's description.
     */
    public get innerData(): Uint8Array {
        return this._array.subarray(16);
    }

    /**
     * Create a snapshot of GroupMemberContainerLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns GroupMemberContainerLike snapshot.
     */
    public snapshot(): GroupMemberContainerLike {
        return {
            creatorIdentity: this.creatorIdentity,
            groupId: this.groupId,
            innerData: this.innerData,
        };
    }

    /**
     * Create a clone of GroupMemberContainerLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns GroupMemberContainerLike clone.
     */
    public clone(): GroupMemberContainer {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new GroupMemberContainer(array);
    }
}

/**
 * An empty message (duh).
 *
 * Only used when encapsulated by an `csp_e2e_fs.Envelope` to announce a new
 * FS version without explicit renegotiation.
 *
 * **Properties**:
 * - Kind: 1:1
 * - Flags: None
 * - User profile distribution: No
 * - Exempt from blocking: Yes
 * - Implicit _direct_ contact creation: No
 * - Protect against replay: Yes
 * - Unarchive: No
 * - Bump _last update_: No
 * - Reflect:
 *   - Incoming: No
 *   - Outgoing: No
 *   - _Sent_ update: No
 * - Delivery receipts: No
 * - Reactions: No
 * - When rejected: N/A (ignored)
 * - Send to Threema Gateway ID group creator: N/A
 *
 * When the user submits this message in a 1:1, group or distribution list,
 * _Rick Astley - Never Gonna Give You Up_ must be played and looped
 * indefinitely on the user's device.
 *
 * When receiving this message, a mechanical finger must be unlatched from
 * the device and boop the receiving user on the nose.
 */
export interface EmptyLike {}

/**
 * Encodable of {@link EmptyLike}.
 */
interface EmptyEncodable_ {}

/**
 * New-type for EmptyEncodable.
 */
export type EmptyEncodable = types.WeakOpaque<
    EmptyEncodable_,
    {readonly EmptyEncodable: unique symbol}
>;

/** @inheritdoc */
export class Empty extends base.Struct implements EmptyLike {
    private readonly _array: Uint8Array;

    /**
     * Create a Empty from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a empty struct from an array.
     *
     * @param array Array to decode from.
     * @returns Empty instance.
     */
    public static decode(array: Uint8Array): Empty {
        return new Empty(array);
    }

    /**
     * Encode a empty struct into an array.
     *
     * @param struct EmptyEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<EmptyEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        return array.subarray(0, 0);
    }

    /**
     * Get the amount of bytes that would be written when encoding a empty struct into an
     * array.
     *
     * @param struct EmptyEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(struct: types.EncoderPick<EmptyEncodable, 'byteLength'>): types.u53 {
        return 0;
    }

    /**
     * Create a snapshot of EmptyLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns EmptyLike snapshot.
     */
    public snapshot(): EmptyLike {
        return {};
    }

    /**
     * Create a clone of EmptyLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns EmptyLike clone.
     */
    public clone(): Empty {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new Empty(array);
    }
}

/**
 * A text message.
 *
 * **Properties (1:1)**:
 * - Kind: 1:1
 * - Flags:
 *   - `0x01`: Send push notification.
 * - User profile distribution: Yes
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: Yes
 * - Protect against replay: Yes
 * - Unarchive: Yes
 * - Bump _last update_: Yes
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: Yes
 *   - _Sent_ update: Yes
 * - Delivery receipts: Yes
 * - Reactions: Yes
 * - When rejected: Re-send after confirmation
 * - Edit applies to: Text
 * - Deletable by: User and sender
 * - Send to Threema Gateway ID group creator: N/A
 *
 * **Properties (Group)**:
 * - Kind: Group
 * - Flags:
 *   - `0x01`: Send push notification.
 * - User profile distribution: Yes
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: No
 * - Protect against replay: Yes
 * - Unarchive: Yes
 * - Bump _last update_: Yes
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: Yes
 *   - _Sent_ update: Yes
 * - Delivery receipts: N/A
 * - Reactions: Yes
 * - When rejected: Re-send after confirmation
 * - Edit applies to: Text
 * - Deletable by: User and sender
 * - Send to Threema Gateway ID group creator: If capture is enabled
 *
 * When the user submits this message in a 1:1, group or distribution list
 * conversation:
 *
 * 1. Split the provided text into multiple segments with at most 6000 UTF-8
 *    encoded bytes while preserving grapheme clusters at boundaries and let
 *    `text-segments` be the result.¹
 * 2. Run the _Messages Submit Steps_ with `messages` set from
 *    `text-segments` (for a group, wrapped by
 *    [`group-member-container`](ref:e2e.group-member-container)).
 *
 * ¹: The UI may warn the user that the message will be split if the UTF-8
 * encoded text exceeds 6000 bytes and request confirmation before
 * submission.
 *
 * When reflected from another device as an incoming or outgoing 1:1
 * message:
 *
 * 1. Add the message to the associated 1:1 conversation.
 *
 * When receiving this message as a 1:1 message:
 *
 * 1. Add the message to the associated 1:1 conversation.
 *
 * When reflected from another device as an incoming or outgoing group
 * message (wrapped by
 * [`group-member-container`](ref:e2e.group-member-container)):
 *
 * 1. Add the message to the associated group conversation.
 *
 * 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. Add the message to the associated group conversation.
 */
export interface TextLike {
    /**
     * UTF-8 encoded text.
     */
    readonly text: Uint8Array;
}

/**
 * Encodable of {@link TextLike}.
 */
interface TextEncodable_ {
    /**
     * 'text' field value or encoder. See {@link TextLike#text} for
     * the field's description.
     */
    readonly text: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for TextEncodable.
 */
export type TextEncodable = types.WeakOpaque<
    TextEncodable_,
    {readonly TextEncodable: unique symbol}
>;

/** @inheritdoc */
export class Text extends base.Struct implements TextLike {
    private readonly _array: Uint8Array;

    /**
     * Create a Text from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a text struct from an array.
     *
     * @param array Array to decode from.
     * @returns Text instance.
     */
    public static decode(array: Uint8Array): Text {
        return new Text(array);
    }

    /**
     * Encode a text struct into an array.
     *
     * @param struct TextEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<TextEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        let offset = 0;

        // Encode `text`
        offset += utils.encodeBytes(struct.text, array, offset);

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a text struct into an
     * array.
     *
     * @param struct TextEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(struct: types.EncoderPick<TextEncodable, 'byteLength'>): types.u53 {
        let offset = 0;
        offset += utils.getByteLength(struct.text);
        return offset;
    }

    /**
     * 'text' field accessor. See {@link TextLike#text} for the
     * field's description.
     */
    public get text(): Uint8Array {
        return this._array.subarray(0);
    }

    /**
     * Create a snapshot of TextLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns TextLike snapshot.
     */
    public snapshot(): TextLike {
        return {
            text: this.text,
        };
    }

    /**
     * Create a clone of TextLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns TextLike clone.
     */
    public clone(): Text {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new Text(array);
    }
}

/**
 * An image message.
 *
 * Note: This message is deprecated and may be phased out eventually. When
 * sending images, use the [`file`](ref:e2e.file) message with the rendering
 * type `0x01` (media).
 *
 * **Properties**:
 * - Kind: 1:1
 * - Flags:
 *   - `0x01`: Send push notification.
 * - User profile distribution: Yes
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: Yes
 * - Protect against replay: Yes
 * - Unarchive: Yes
 * - Bump _last update_: Yes
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: N/A
 *   - _Sent_ update: Yes
 * - Delivery receipts: Yes
 * - Reactions: Yes
 * - When rejected: N/A (deprecated message is not being sent)
 * - Edit applies to: N/A
 * - Deletable by: User and sender
 * - Send to Threema Gateway ID group creator: N/A
 *
 * The image must be in JPEG format, is uploaded to the blob server and
 * encrypted by:
 *
 *     XSalsa20-Poly1305(
 *       key=X25519HSalsa20(<sender.CK>.secret, <receiver.CK>.public),
 *       nonce=<deprecated-image.nonce>,
 *     )
 *
 * This message is deprecated and may no longer be submitted.
 *
 * When reflected from another device as an incoming message:
 *
 * 1. Run the _Common Deprecated Image Receive Steps_.
 *
 * When receiving this message:
 *
 * 1. Run the _Common Deprecated Image Receive Steps_.
 *
 * The following steps are defined as the _Common Deprecated Image Receive
 * Steps_:
 *
 * 1. Add the message to the associated 1:1 conversation.
 * 2. If this message is eligible for auto-download, schedule downloading the
 *    image data from the blob server and request the blob to be removed.
 */
export interface DeprecatedImageLike {
    /**
     * Blob ID to obtain the image data.
     */
    readonly imageBlobId: Uint8Array;

    /**
     * Image size in bytes.
     */
    readonly imageSize: types.u32;

    /**
     * Random nonce used to encrypt the image data.
     */
    readonly nonce: Uint8Array;
}

/**
 * Encodable of {@link DeprecatedImageLike}.
 */
interface DeprecatedImageEncodable_ {
    /**
     * 'image-blob-id' field value or encoder. See {@link DeprecatedImageLike#imageBlobId} for
     * the field's description.
     */
    readonly imageBlobId: Uint8Array | types.ByteLengthEncoder;

    /**
     * 'image-size' field value or encoder. See {@link DeprecatedImageLike#imageSize} for
     * the field's description.
     */
    readonly imageSize: types.u32;

    /**
     * 'nonce' field value or encoder. See {@link DeprecatedImageLike#nonce} for
     * the field's description.
     */
    readonly nonce: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for DeprecatedImageEncodable.
 */
export type DeprecatedImageEncodable = types.WeakOpaque<
    DeprecatedImageEncodable_,
    {readonly DeprecatedImageEncodable: unique symbol}
>;

/** @inheritdoc */
export class DeprecatedImage extends base.Struct implements DeprecatedImageLike {
    private readonly _array: Uint8Array;
    private readonly _view: DataView;

    /**
     * Create a DeprecatedImage from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
        this._view = new DataView(array.buffer, array.byteOffset, array.byteLength);
    }

    /**
     * Decode a deprecated-image struct from an array.
     *
     * @param array Array to decode from.
     * @returns DeprecatedImage instance.
     */
    public static decode(array: Uint8Array): DeprecatedImage {
        return new DeprecatedImage(array);
    }

    /**
     * Encode a deprecated-image struct into an array.
     *
     * @param struct DeprecatedImageEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<DeprecatedImageEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        const view = new DataView(array.buffer, array.byteOffset, array.byteLength);

        // Encode `image-blob-id`
        utils.encodeBytes(struct.imageBlobId, array, 0);

        // Encode `image-size`
        view.setUint32(16, struct.imageSize, true);

        // Encode `nonce`
        utils.encodeBytes(struct.nonce, array, 20);

        return array.subarray(0, 44);
    }

    /**
     * Get the amount of bytes that would be written when encoding a deprecated-image struct into an
     * array.
     *
     * @param struct DeprecatedImageEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<DeprecatedImageEncodable, 'byteLength'>,
    ): types.u53 {
        return 44;
    }

    /**
     * 'image-blob-id' field accessor. See {@link DeprecatedImageLike#imageBlobId} for the
     * field's description.
     */
    public get imageBlobId(): Uint8Array {
        const offset = 0;
        return this._array.subarray(offset, offset + 16);
    }

    /**
     * 'image-size' field accessor. See {@link DeprecatedImageLike#imageSize} for the
     * field's description.
     */
    public get imageSize(): types.u32 {
        return this._view.getUint32(16, true);
    }

    /**
     * 'nonce' field accessor. See {@link DeprecatedImageLike#nonce} for the
     * field's description.
     */
    public get nonce(): Uint8Array {
        const offset = 20;
        return this._array.subarray(offset, offset + 24);
    }

    /**
     * Create a snapshot of DeprecatedImageLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns DeprecatedImageLike snapshot.
     */
    public snapshot(): DeprecatedImageLike {
        return {
            imageBlobId: this.imageBlobId,
            imageSize: this.imageSize,
            nonce: this.nonce,
        };
    }

    /**
     * Create a clone of DeprecatedImageLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns DeprecatedImageLike clone.
     */
    public clone(): DeprecatedImage {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new DeprecatedImage(array);
    }
}

/**
 * An image message (only used by groups).
 *
 * Note: This message is deprecated and may be phased out eventually. When
 * sending images, use the [`file`](ref:e2e.file) message with the rendering
 * type `0x01` (media).
 *
 * **Properties**:
 * - Kind: Group
 * - Flags:
 *   - `0x01`: Send push notification.
 * - User profile distribution: Yes
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: No
 * - Protect against replay: Yes
 * - Unarchive: Yes
 * - Bump _last update_: Yes
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: N/A
 *   - _Sent_ update: Yes
 * - Delivery receipts: N/A
 * - Reactions: Yes
 * - When rejected: N/A (deprecated message is not being sent)
 * - Edit applies to: N/A
 * - Deletable by: User and sender
 * - Send to Threema Gateway ID group creator: If capture is enabled
 *
 * The image must be in JPEG format, is uploaded to the blob server and
 * encrypted by:
 *
 *     XSalsa20-Poly1305(key=<deprecated-group-image.key>, nonce=00..01)
 *
 * This message is deprecated and may no longer be submitted.
 *
 * When reflected from another device as an incoming message (wrapped by
 * [`group-member-container`](ref:e2e.group-member-container)):
 *
 * 1. Run the _Common Deprecated Group Image 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
 *    message has been discarded, abort these steps.
 * 2. Run the _Common Deprecated Group Image Receive Steps_.
 *
 * The following steps are defined as the _Common Deprecated Group Image
 * Receive Steps_:
 *
 * 1. Add the message to the associated group conversation.
 * 2. If this message is eligible for auto-download, schedule downloading the
 *    image data from the blob server but do not request the blob to be
 *    removed.
 */
export interface DeprecatedGroupImageLike {
    /**
     * Blob ID to obtain the image data.
     */
    readonly imageBlobId: Uint8Array;

    /**
     * Image size in bytes.
     */
    readonly imageSize: types.u32;

    /**
     * Random symmetric key used to encrypt the image data.
     */
    readonly key: Uint8Array;
}

/**
 * Encodable of {@link DeprecatedGroupImageLike}.
 */
interface DeprecatedGroupImageEncodable_ {
    /**
     * 'image-blob-id' field value or encoder. See {@link DeprecatedGroupImageLike#imageBlobId} for
     * the field's description.
     */
    readonly imageBlobId: Uint8Array | types.ByteLengthEncoder;

    /**
     * 'image-size' field value or encoder. See {@link DeprecatedGroupImageLike#imageSize} for
     * the field's description.
     */
    readonly imageSize: types.u32;

    /**
     * 'key' field value or encoder. See {@link DeprecatedGroupImageLike#key} for
     * the field's description.
     */
    readonly key: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for DeprecatedGroupImageEncodable.
 */
export type DeprecatedGroupImageEncodable = types.WeakOpaque<
    DeprecatedGroupImageEncodable_,
    {readonly DeprecatedGroupImageEncodable: unique symbol}
>;

/** @inheritdoc */
export class DeprecatedGroupImage extends base.Struct implements DeprecatedGroupImageLike {
    private readonly _array: Uint8Array;
    private readonly _view: DataView;

    /**
     * Create a DeprecatedGroupImage from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
        this._view = new DataView(array.buffer, array.byteOffset, array.byteLength);
    }

    /**
     * Decode a deprecated-group-image struct from an array.
     *
     * @param array Array to decode from.
     * @returns DeprecatedGroupImage instance.
     */
    public static decode(array: Uint8Array): DeprecatedGroupImage {
        return new DeprecatedGroupImage(array);
    }

    /**
     * Encode a deprecated-group-image struct into an array.
     *
     * @param struct DeprecatedGroupImageEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<DeprecatedGroupImageEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        const view = new DataView(array.buffer, array.byteOffset, array.byteLength);

        // Encode `image-blob-id`
        utils.encodeBytes(struct.imageBlobId, array, 0);

        // Encode `image-size`
        view.setUint32(16, struct.imageSize, true);

        // Encode `key`
        utils.encodeBytes(struct.key, array, 20);

        return array.subarray(0, 52);
    }

    /**
     * Get the amount of bytes that would be written when encoding a deprecated-group-image struct into an
     * array.
     *
     * @param struct DeprecatedGroupImageEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<DeprecatedGroupImageEncodable, 'byteLength'>,
    ): types.u53 {
        return 52;
    }

    /**
     * 'image-blob-id' field accessor. See {@link DeprecatedGroupImageLike#imageBlobId} for the
     * field's description.
     */
    public get imageBlobId(): Uint8Array {
        const offset = 0;
        return this._array.subarray(offset, offset + 16);
    }

    /**
     * 'image-size' field accessor. See {@link DeprecatedGroupImageLike#imageSize} for the
     * field's description.
     */
    public get imageSize(): types.u32 {
        return this._view.getUint32(16, true);
    }

    /**
     * 'key' field accessor. See {@link DeprecatedGroupImageLike#key} for the
     * field's description.
     */
    public get key(): Uint8Array {
        const offset = 20;
        return this._array.subarray(offset, offset + 32);
    }

    /**
     * Create a snapshot of DeprecatedGroupImageLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns DeprecatedGroupImageLike snapshot.
     */
    public snapshot(): DeprecatedGroupImageLike {
        return {
            imageBlobId: this.imageBlobId,
            imageSize: this.imageSize,
            key: this.key,
        };
    }

    /**
     * Create a clone of DeprecatedGroupImageLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns DeprecatedGroupImageLike clone.
     */
    public clone(): DeprecatedGroupImage {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new DeprecatedGroupImage(array);
    }
}

/**
 * A location message.
 *
 * **Properties (1:1)**:
 * - Kind: 1:1
 * - Flags:
 *   - `0x01`: Send push notification.
 * - User profile distribution: Yes
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: Yes
 * - Protect against replay: Yes
 * - Unarchive: Yes
 * - Bump _last update_: Yes
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: Yes
 *   - _Sent_ update: Yes
 * - Delivery receipts: Yes
 * - Reactions: Yes
 * - When rejected: Re-send after confirmation
 * - Edit applies to: N/A
 * - Deletable by: User and sender
 * - Send to Threema Gateway ID group creator: N/A
 *
 * **Properties (Group)**:
 * - Kind: Group
 * - Flags:
 *   - `0x01`: Send push notification.
 * - User profile distribution: Yes
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: No
 * - Protect against replay: Yes
 * - Unarchive: Yes
 * - Bump _last update_: Yes
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: Yes
 *   - _Sent_ update: Yes
 * - Delivery receipts: N/A
 * - Reactions: Yes
 * - When rejected: Re-send after confirmation
 * - Edit applies to: N/A
 * - Deletable by: User and sender
 * - Send to Threema Gateway ID group creator: If capture is enabled
 *
 * When the user submits this message in a 1:1, group or distribution list
 * conversation:
 *
 * 1. Let `location` be the provided location with the following properties:
 *    - `latitude` being a WGS-84 string,
 *    - `longitude` being a WGS-84 string,
 *    - `accuracy` being a floating point number or undefined,
 *    - `address` being an address of a point of interest or undefined,
 *    - `name` being a name for the point of interest or undefined,
 * 2. If `location.name` is defined but `location.address` is not defined,
 *    discard `location`, log an error and abort these steps.¹
 * 3. If the UTF-8 encoded bytes of `location.address` or `location.name`
 *    exceed 512 bytes, discard `location`, log an error and abort these
 *    steps.¹
 * 4. Run the _Messages Submit Steps_ with `messages` set from `location`
 *    (for a group, wrapped by
 *    [`group-member-container`](ref:e2e.group-member-container)).
 *
 * ¹: The UI should prevent submission in these cases.
 *
 * When reflected from another device as an incoming or outgoing 1:1
 * message:
 *
 * 1. Add the message to the associated 1:1 conversation.
 *
 * When receiving this message as a 1:1 message:
 *
 * 1. Add the message to the associated 1:1 conversation.
 *
 * When reflected from another device as an incoming or outgoing group
 * message (wrapped by
 * [`group-member-container`](ref:e2e.group-member-container)):
 *
 * 1. Add the message to the associated group conversation.
 *
 * 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. Add the message to the associated group conversation.
 */
export interface LocationLike {
    /**
     * Location coordinates and meta information encoded in comma- and
     * line-separated UTF-8:
     *
     *     <latitude>,<longitude>[,<accuracy>]
     *
     * or
     *
     *     <latitude>,<longitude>[,<accuracy>]
     *     <address>
     *
     * or
     *
     *     <latitude>,<longitude>[,<accuracy>]
     *     <name>
     *     <address>
     *
     * Values:
     *
     * - `latitude` and `longitude` are the geographic coordinates
     *   represented in a WGS-84 string.
     * - `accuracy` is the accuracy in meters represented by a floating
     *   point number formatted as a string.
     * - `address` is a full address. If it contains multiple lines, each
     *   line feed must be escaped (literal `\n`).
     * - `name` is the name of a point of interest.
     *
     * _Latitude_ and _longitude_ must always be present while _accuracy_
     * is optional and should only be provided when the current location of
     * the device is being sent. These values are comma-separated.
     *
     * Following values are optional and separated by line feeds (`\n`).
     * This may be either:
     *
     * - a single line containing the _address_ representing the closest
     *   address matching the coordinates, or
     * - two lines containing a point of interest _name_ and _address_
     *   (which means that the coordinates refer to the point of interest).
     */
    readonly location: Uint8Array;
}

/**
 * Encodable of {@link LocationLike}.
 */
interface LocationEncodable_ {
    /**
     * 'location' field value or encoder. See {@link LocationLike#location} for
     * the field's description.
     */
    readonly location: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for LocationEncodable.
 */
export type LocationEncodable = types.WeakOpaque<
    LocationEncodable_,
    {readonly LocationEncodable: unique symbol}
>;

/** @inheritdoc */
export class Location extends base.Struct implements LocationLike {
    private readonly _array: Uint8Array;

    /**
     * Create a Location from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a location struct from an array.
     *
     * @param array Array to decode from.
     * @returns Location instance.
     */
    public static decode(array: Uint8Array): Location {
        return new Location(array);
    }

    /**
     * Encode a location struct into an array.
     *
     * @param struct LocationEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<LocationEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        let offset = 0;

        // Encode `location`
        offset += utils.encodeBytes(struct.location, array, offset);

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a location struct into an
     * array.
     *
     * @param struct LocationEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<LocationEncodable, 'byteLength'>,
    ): types.u53 {
        let offset = 0;
        offset += utils.getByteLength(struct.location);
        return offset;
    }

    /**
     * 'location' field accessor. See {@link LocationLike#location} for the
     * field's description.
     */
    public get location(): Uint8Array {
        return this._array.subarray(0);
    }

    /**
     * Create a snapshot of LocationLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns LocationLike snapshot.
     */
    public snapshot(): LocationLike {
        return {
            location: this.location,
        };
    }

    /**
     * Create a clone of LocationLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns LocationLike clone.
     */
    public clone(): Location {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new Location(array);
    }
}

/**
 * An audio message.
 *
 * Note: This message is deprecated and may be phased out eventually. When
 *       sending audio, use the [`file`](ref:e2e.file) message with the
 *       rendering type `0x01` (media).
 *
 * **Properties (1:1)**:
 * - Kind: 1:1
 * - Flags:
 *   - `0x01`: Send push notification.
 * - User profile distribution: Yes
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: Yes
 * - Protect against replay: Yes
 * - Unarchive: Yes
 * - Bump _last update_: Yes
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: N/A
 *   - _Sent_ update: Yes
 * - Delivery receipts: Yes
 * - Reactions: Yes
 * - When rejected: N/A (deprecated message is not being sent)
 * - Edit applies to: N/A
 * - Deletable by: User and sender
 * - Send to Threema Gateway ID group creator: N/A
 *
 * **Properties (Group)**:
 * - Kind: Group
 * - Flags:
 *   - `0x01`: Send push notification.
 * - User profile distribution: Yes
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: No
 * - Protect against replay: Yes
 * - Unarchive: Yes
 * - Bump _last update_: Yes
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: N/A
 *   - _Sent_ update: Yes
 * - Delivery receipts: N/A
 * - Reactions: Yes
 * - When rejected: N/A (deprecated message is not being sent)
 * - Edit applies to: N/A
 * - Deletable by: User and sender
 * - Send to Threema Gateway ID group creator: If capture is enabled
 *
 * The audio is uploaded to the blob server and encrypted by:
 *
 *     XSalsa20-Poly1305(key=<deprecated-audio.key>, nonce=00..01)
 *
 * This message is deprecated and may no longer be submitted.
 *
 * When reflected from another device as an incoming 1:1 message:
 *
 * 1. Run the _Common Deprecated Audio Receive Steps_.
 *
 * When receiving this message as a 1:1 message:
 *
 * 1. Run the _Common Deprecated Audio Receive Steps_.
 *
 * When reflected from another device as an incoming group message (wrapped
 * by [`group-member-container`](ref:e2e.group-member-container)):
 *
 * 1. Run the _Common Deprecated Audio 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 Deprecated Audio Receive Steps_.
 *
 * The following steps are defined as the _Common Deprecated Audio Receive
 * Steps_:
 *
 * 1. Add the message to the associated conversation.
 * 2. If this message is eligible for auto-download, schedule downloading the
 *    audio data from the blob server. Only request the blob to be removed if
 *    the associated conversation is a 1:1 conversation.
 */
export interface DeprecatedAudioLike {
    /**
     * Audio duration in seconds.
     */
    readonly duration: types.u16;

    /**
     * Blob ID to obtain the audio data.
     */
    readonly audioBlobId: Uint8Array;

    /**
     * Audio size in bytes.
     */
    readonly audioSize: types.u32;

    /**
     * Random symmetric key used to encrypt the audio data.
     */
    readonly key: Uint8Array;
}

/**
 * Encodable of {@link DeprecatedAudioLike}.
 */
interface DeprecatedAudioEncodable_ {
    /**
     * 'duration' field value or encoder. See {@link DeprecatedAudioLike#duration} for
     * the field's description.
     */
    readonly duration: types.u16;

    /**
     * 'audio-blob-id' field value or encoder. See {@link DeprecatedAudioLike#audioBlobId} for
     * the field's description.
     */
    readonly audioBlobId: Uint8Array | types.ByteLengthEncoder;

    /**
     * 'audio-size' field value or encoder. See {@link DeprecatedAudioLike#audioSize} for
     * the field's description.
     */
    readonly audioSize: types.u32;

    /**
     * 'key' field value or encoder. See {@link DeprecatedAudioLike#key} for
     * the field's description.
     */
    readonly key: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for DeprecatedAudioEncodable.
 */
export type DeprecatedAudioEncodable = types.WeakOpaque<
    DeprecatedAudioEncodable_,
    {readonly DeprecatedAudioEncodable: unique symbol}
>;

/** @inheritdoc */
export class DeprecatedAudio extends base.Struct implements DeprecatedAudioLike {
    private readonly _array: Uint8Array;
    private readonly _view: DataView;

    /**
     * Create a DeprecatedAudio from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
        this._view = new DataView(array.buffer, array.byteOffset, array.byteLength);
    }

    /**
     * Decode a deprecated-audio struct from an array.
     *
     * @param array Array to decode from.
     * @returns DeprecatedAudio instance.
     */
    public static decode(array: Uint8Array): DeprecatedAudio {
        return new DeprecatedAudio(array);
    }

    /**
     * Encode a deprecated-audio struct into an array.
     *
     * @param struct DeprecatedAudioEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<DeprecatedAudioEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        const view = new DataView(array.buffer, array.byteOffset, array.byteLength);

        // Encode `duration`
        view.setUint16(0, struct.duration, true);

        // Encode `audio-blob-id`
        utils.encodeBytes(struct.audioBlobId, array, 2);

        // Encode `audio-size`
        view.setUint32(18, struct.audioSize, true);

        // Encode `key`
        utils.encodeBytes(struct.key, array, 22);

        return array.subarray(0, 54);
    }

    /**
     * Get the amount of bytes that would be written when encoding a deprecated-audio struct into an
     * array.
     *
     * @param struct DeprecatedAudioEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<DeprecatedAudioEncodable, 'byteLength'>,
    ): types.u53 {
        return 54;
    }

    /**
     * 'duration' field accessor. See {@link DeprecatedAudioLike#duration} for the
     * field's description.
     */
    public get duration(): types.u16 {
        return this._view.getUint16(0, true);
    }

    /**
     * 'audio-blob-id' field accessor. See {@link DeprecatedAudioLike#audioBlobId} for the
     * field's description.
     */
    public get audioBlobId(): Uint8Array {
        const offset = 2;
        return this._array.subarray(offset, offset + 16);
    }

    /**
     * 'audio-size' field accessor. See {@link DeprecatedAudioLike#audioSize} for the
     * field's description.
     */
    public get audioSize(): types.u32 {
        return this._view.getUint32(18, true);
    }

    /**
     * 'key' field accessor. See {@link DeprecatedAudioLike#key} for the
     * field's description.
     */
    public get key(): Uint8Array {
        const offset = 22;
        return this._array.subarray(offset, offset + 32);
    }

    /**
     * Create a snapshot of DeprecatedAudioLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns DeprecatedAudioLike snapshot.
     */
    public snapshot(): DeprecatedAudioLike {
        return {
            duration: this.duration,
            audioBlobId: this.audioBlobId,
            audioSize: this.audioSize,
            key: this.key,
        };
    }

    /**
     * Create a clone of DeprecatedAudioLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns DeprecatedAudioLike clone.
     */
    public clone(): DeprecatedAudio {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new DeprecatedAudio(array);
    }
}

/**
 * A video message.
 *
 * Note: This message is deprecated and may be phased out eventually. When
 *       sending video, use the [`file`](ref:e2e.file) message with the
 *       rendering type `0x01` (media).
 *
 * **Properties (1:1)**:
 * - Kind: 1:1
 * - Flags:
 *   - `0x01`: Send push notification.
 * - User profile distribution: Yes
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: Yes
 * - Protect against replay: Yes
 * - Unarchive: Yes
 * - Bump _last update_: Yes
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: N/A
 *   - _Sent_ update: Yes
 * - Delivery receipts: Yes
 * - Reactions: Yes
 * - When rejected: N/A (deprecated message is not being sent)
 * - Edit applies to: N/A
 * - Deletable by: User and sender
 * - Send to Threema Gateway ID group creator: N/A
 *
 * **Properties (Group)**:
 * - Kind: Group
 * - Flags:
 *   - `0x01`: Send push notification.
 * - User profile distribution: Yes
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: No
 * - Protect against replay: Yes
 * - Unarchive: Yes
 * - Bump _last update_: Yes
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: N/A
 *   - _Sent_ update: Yes
 * - Delivery receipts: N/A
 * - Reactions: Yes
 * - When rejected: N/A (deprecated message is not being sent)
 * - Edit applies to: N/A
 * - Deletable by: User and sender
 * - Send to Threema Gateway ID group creator: If capture is enabled
 *
 * The video is uploaded to the blob server and encrypted by:
 *
 *     XSalsa20-Poly1305(key=<deprecated-video.key>, nonce=00..01)
 *
 * The thumbnail must be in JPEG format, is uploaded to the blob server
 * and encrypted by:
 *
 *     XSalsa20-Poly1305(key=<deprecated-video.key>, nonce=00..02)
 *
 * This message is deprecated and may no longer be submitted.
 *
 * When reflected from another device as an incoming 1:1 message:
 *
 * 1. Run the _Common Deprecated Audio Receive Steps_.
 *
 * When receiving this message as a 1:1 message:
 *
 * 1. Run the _Common Deprecated Audio Receive Steps_.
 *
 * When reflected from another device as an incoming group message (wrapped
 * by [`group-member-container`](ref:e2e.group-member-container)):
 *
 * 1. Run the _Common Deprecated Audio 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 Deprecated Audio Receive Steps_.
 *
 * The following steps are defined as the _Common Deprecated Video Receive
 * Steps_:
 *
 * 1. Add the message to the associated conversation.
 * 2. If this message is eligible for auto-download, schedule downloading the
 *    video data from the blob server. Only request the blob to be removed if
 *    the associated conversation is a 1:1 conversation.
 */
export interface DeprecatedVideoLike {
    /**
     * Video duration in seconds.
     */
    readonly duration: types.u16;

    /**
     * Blob ID to obtain the video data.
     */
    readonly videoBlobId: Uint8Array;

    /**
     * Video size in bytes.
     */
    readonly videoSize: types.u32;

    /**
     * Blob ID to obtain the thumbnail in JPEG format.
     */
    readonly thumbnailBlobId: Uint8Array;

    /**
     * Thumbnail size in bytes.
     */
    readonly thumbnailSize: types.u32;

    /**
     * Random symmetric key used to encrypt the video and thumbnail data.
     */
    readonly key: Uint8Array;
}

/**
 * Encodable of {@link DeprecatedVideoLike}.
 */
interface DeprecatedVideoEncodable_ {
    /**
     * 'duration' field value or encoder. See {@link DeprecatedVideoLike#duration} for
     * the field's description.
     */
    readonly duration: types.u16;

    /**
     * 'video-blob-id' field value or encoder. See {@link DeprecatedVideoLike#videoBlobId} for
     * the field's description.
     */
    readonly videoBlobId: Uint8Array | types.ByteLengthEncoder;

    /**
     * 'video-size' field value or encoder. See {@link DeprecatedVideoLike#videoSize} for
     * the field's description.
     */
    readonly videoSize: types.u32;

    /**
     * 'thumbnail-blob-id' field value or encoder. See {@link DeprecatedVideoLike#thumbnailBlobId} for
     * the field's description.
     */
    readonly thumbnailBlobId: Uint8Array | types.ByteLengthEncoder;

    /**
     * 'thumbnail-size' field value or encoder. See {@link DeprecatedVideoLike#thumbnailSize} for
     * the field's description.
     */
    readonly thumbnailSize: types.u32;

    /**
     * 'key' field value or encoder. See {@link DeprecatedVideoLike#key} for
     * the field's description.
     */
    readonly key: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for DeprecatedVideoEncodable.
 */
export type DeprecatedVideoEncodable = types.WeakOpaque<
    DeprecatedVideoEncodable_,
    {readonly DeprecatedVideoEncodable: unique symbol}
>;

/** @inheritdoc */
export class DeprecatedVideo extends base.Struct implements DeprecatedVideoLike {
    private readonly _array: Uint8Array;
    private readonly _view: DataView;

    /**
     * Create a DeprecatedVideo from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
        this._view = new DataView(array.buffer, array.byteOffset, array.byteLength);
    }

    /**
     * Decode a deprecated-video struct from an array.
     *
     * @param array Array to decode from.
     * @returns DeprecatedVideo instance.
     */
    public static decode(array: Uint8Array): DeprecatedVideo {
        return new DeprecatedVideo(array);
    }

    /**
     * Encode a deprecated-video struct into an array.
     *
     * @param struct DeprecatedVideoEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<DeprecatedVideoEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        const view = new DataView(array.buffer, array.byteOffset, array.byteLength);

        // Encode `duration`
        view.setUint16(0, struct.duration, true);

        // Encode `video-blob-id`
        utils.encodeBytes(struct.videoBlobId, array, 2);

        // Encode `video-size`
        view.setUint32(18, struct.videoSize, true);

        // Encode `thumbnail-blob-id`
        utils.encodeBytes(struct.thumbnailBlobId, array, 22);

        // Encode `thumbnail-size`
        view.setUint32(38, struct.thumbnailSize, true);

        // Encode `key`
        utils.encodeBytes(struct.key, array, 42);

        return array.subarray(0, 74);
    }

    /**
     * Get the amount of bytes that would be written when encoding a deprecated-video struct into an
     * array.
     *
     * @param struct DeprecatedVideoEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<DeprecatedVideoEncodable, 'byteLength'>,
    ): types.u53 {
        return 74;
    }

    /**
     * 'duration' field accessor. See {@link DeprecatedVideoLike#duration} for the
     * field's description.
     */
    public get duration(): types.u16 {
        return this._view.getUint16(0, true);
    }

    /**
     * 'video-blob-id' field accessor. See {@link DeprecatedVideoLike#videoBlobId} for the
     * field's description.
     */
    public get videoBlobId(): Uint8Array {
        const offset = 2;
        return this._array.subarray(offset, offset + 16);
    }

    /**
     * 'video-size' field accessor. See {@link DeprecatedVideoLike#videoSize} for the
     * field's description.
     */
    public get videoSize(): types.u32 {
        return this._view.getUint32(18, true);
    }

    /**
     * 'thumbnail-blob-id' field accessor. See {@link DeprecatedVideoLike#thumbnailBlobId} for the
     * field's description.
     */
    public get thumbnailBlobId(): Uint8Array {
        const offset = 22;
        return this._array.subarray(offset, offset + 16);
    }

    /**
     * 'thumbnail-size' field accessor. See {@link DeprecatedVideoLike#thumbnailSize} for the
     * field's description.
     */
    public get thumbnailSize(): types.u32 {
        return this._view.getUint32(38, true);
    }

    /**
     * 'key' field accessor. See {@link DeprecatedVideoLike#key} for the
     * field's description.
     */
    public get key(): Uint8Array {
        const offset = 42;
        return this._array.subarray(offset, offset + 32);
    }

    /**
     * Create a snapshot of DeprecatedVideoLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns DeprecatedVideoLike snapshot.
     */
    public snapshot(): DeprecatedVideoLike {
        return {
            duration: this.duration,
            videoBlobId: this.videoBlobId,
            videoSize: this.videoSize,
            thumbnailBlobId: this.thumbnailBlobId,
            thumbnailSize: this.thumbnailSize,
            key: this.key,
        };
    }

    /**
     * Create a clone of DeprecatedVideoLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns DeprecatedVideoLike clone.
     */
    public clone(): DeprecatedVideo {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new DeprecatedVideo(array);
    }
}

/**
 * A file or media message.
 *
 * **Properties (1:1)**:
 * - Kind: 1:1
 * - Flags:
 *   - `0x01`: Send push notification.
 * - User profile distribution: Yes
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: Yes
 * - Protect against replay: Yes
 * - Unarchive: Yes
 * - Bump _last update_: Yes
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: Yes
 *   - _Sent_ update: Yes
 * - Delivery receipts: Yes
 * - Reactions: Yes
 * - When rejected: Re-send after confirmation
 * - Edit applies to: Caption
 * - Deletable by: User and sender
 * - Send to Threema Gateway ID group creator: N/A
 *
 * **Properties (Group)**:
 * - Kind: Group
 * - Flags:
 *   - `0x01`: Send push notification.
 * - User profile distribution: Yes
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: No
 * - Protect against replay: Yes
 * - Unarchive: Yes
 * - Bump _last update_: Yes
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: Yes
 *   - _Sent_ update: Yes
 * - Delivery receipts: N/A
 * - Reactions: Yes
 * - When rejected: Re-send after confirmation
 * - Edit applies to: Caption
 * - Deletable by: User and sender
 * - Send to Threema Gateway ID group creator: If capture is enabled
 *
 * The file is uploaded to the blob server and encrypted by:
 *
 *     XSalsa20-Poly1305(key=<file.key>, nonce=00..01)
 *
 * The thumbnail is uploaded to the blob server and encrypted by:
 *
 *     XSalsa20-Poly1305(key=<file.key>, nonce=00..02)
 *
 * When the user submits this message in a 1:1, group or distribution list
 * conversation:
 *
 * 1. Let `file` be all necessary properties to construct this message.
 * 2. If the UTF-8 encoded bytes of `file.name` exceed 256 bytes, discard
 *    `file`, log an error and abort these steps.¹
 * 3. If the UTF-8 encoded bytes of `file.caption` exceed 6000 bytes, discard
 *    `file`, log an error and abort these steps.¹
 * 4. Run the _Messages Submit Steps_ with `messages` set from `file` (for a
 *    group, wrapped by
 *    [`group-member-container`](ref:e2e.group-member-container)).
 *
 * ¹: The UI should prevent submission in these cases.
 *
 * When reflected from another device as an incoming or outgoing 1:1
 * message:
 *
 * 1. Run the _Common File Receive Steps_.
 *
 * When receiving this message as a 1:1 message:
 *
 * 1. Run the _Common File Receive Steps_.
 *
 * When reflected from another device as an incoming or outgoing group
 * message (wrapped by
 * [`group-member-container`](ref:e2e.group-member-container)):
 *
 * 1. Run the _Common File 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 File Receive Steps_.
 *
 * The following steps are defined as the _Common File Receive Steps_:
 *
 * 1. Add the message to the associated conversation.
 * 2. Schedule auto-downloading the thumbnail data from the blob server. Only
 *    request the blob to be removed if the associated conversation is a 1:1
 *    conversation.
 * 3. If this message is eligible for auto-download, schedule downloading the
 *    file data from the blob server. Only request the blob to be removed if
 *    the associated conversation is a 1:1 conversation.
 */
export interface FileLike {
    /**
     * UTF-8, JSON-encoded object with the following fields:
     *
     * - Rendering type (`'j'`):
     *   - `0`: Render as a file.
     *   - `1`: Render as media (e.g. an image, audio or video).
     *   - `2`: Render as a sticker (for transparent images).
     *
     *   If this field is not set, fall back to the value of `'i'`. If no
     *   value could be determined or the rendering type is unassigned,
     *   assume `0`.
     * - Deprecated (`'i'`): Set this to the integer `1` if the rendering
     *   type is `1` or `2`, otherwise set this to the integer `0`.
     * - Encryption key (`'k'`): Random symmetric key used to encrypt the
     *   blobs (file and thumbnail data) in lowercase hex string.
     * - File blob ID (`'b'`): Blob ID in lowercase hex string
     *   representation to obtain the file data.
     * - File media type (`'m'`): The media type of the file.
     * - File name (`'n'`): Optional filename of the file.
     * - File size (`'s'`): File size in bytes.
     * - Thumbnail Blob ID (`'t'`): Optional blob containing the thumbnail
     *   file data.
     * - Thumbnail media type (`'p'`): Media type of the thumbnail.
     *   If not set, assume `image/jpeg`.
     * - Caption (`'d'`): Optional caption text.
     * - Correlation ID (`'c'`): Optional random 32 byte ASCII string to
     *   collocate multiple media files.
     * - Metadata (`'x'`): An optional metadata object as defined below.
     *
     * Metadata object fields depend on the media type of the file. All
     * fields are optional but recommended to set in order to determine the
     * layout logic while the file is being downloaded. Once the file has
     * been parsed, the parsed data supersedes this object.
     *
     * For images:
     *
     * - Width (`'w'`): The width as an integer in px.
     * - Height (`'h'`): The height as an integer in px.
     * - Animated (`'a'`): Set this to the boolean `true` if the image is
     *   animated (e.g. an animated GIF).
     *
     * For audio:
     *
     * - Duration (`'d'`): The duration as a float in seconds.
     *
     * For video:
     *
     * - Width (`'w'`): The width as an integer in px.
     * - Height (`'h'`): The height as an integer in px.
     * - Duration (`'d'`): The duration as a float in seconds.
     *
     * Note that the rendering logic depends on three key fields which
     * should be set accordingly:
     *
     * - Media type,
     * - Rendering type,
     * - Animated flag in the metadata object.
     */
    readonly file: Uint8Array;
}

/**
 * Encodable of {@link FileLike}.
 */
interface FileEncodable_ {
    /**
     * 'file' field value or encoder. See {@link FileLike#file} for
     * the field's description.
     */
    readonly file: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for FileEncodable.
 */
export type FileEncodable = types.WeakOpaque<
    FileEncodable_,
    {readonly FileEncodable: unique symbol}
>;

/** @inheritdoc */
export class File extends base.Struct implements FileLike {
    private readonly _array: Uint8Array;

    /**
     * Create a File from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a file struct from an array.
     *
     * @param array Array to decode from.
     * @returns File instance.
     */
    public static decode(array: Uint8Array): File {
        return new File(array);
    }

    /**
     * Encode a file struct into an array.
     *
     * @param struct FileEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<FileEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        let offset = 0;

        // Encode `file`
        offset += utils.encodeBytes(struct.file, array, offset);

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a file struct into an
     * array.
     *
     * @param struct FileEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(struct: types.EncoderPick<FileEncodable, 'byteLength'>): types.u53 {
        let offset = 0;
        offset += utils.getByteLength(struct.file);
        return offset;
    }

    /**
     * 'file' field accessor. See {@link FileLike#file} for the
     * field's description.
     */
    public get file(): Uint8Array {
        return this._array.subarray(0);
    }

    /**
     * Create a snapshot of FileLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns FileLike snapshot.
     */
    public snapshot(): FileLike {
        return {
            file: this.file,
        };
    }

    /**
     * Create a clone of FileLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns FileLike clone.
     */
    public clone(): File {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new File(array);
    }
}

/**
 * Creates a new poll or finalises an existing poll.
 *
 * During the lifecycle of a poll, this message will be used exactly twice:
 * Once to create the poll, and once to close it.
 *
 * **Properties (1:1)**:
 * - Kind: 1:1
 * - Flags:
 *   - `0x01`: Send push notification.
 * - User profile distribution: Yes
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: Yes
 * - Protect against replay: Yes
 * - Unarchive: Yes
 * - Bump _last update_: Yes
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: Yes
 *   - _Sent_ update: Yes
 * - Delivery receipts: Yes
 * - Reactions: Yes
 * - When rejected: Re-send after confirmation
 * - Edit applies to: N/A
 * - Deletable by: User only (TODO(SE-383))
 * - Send to Threema Gateway ID group creator: N/A
 *
 * **Properties (Group)**:
 * - 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: Yes
 * - Bump _last update_: Yes
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: Yes
 *   - _Sent_ update: Yes
 * - Delivery receipts: N/A
 * - Reactions: Yes
 * - When rejected: Re-send after confirmation
 * - Edit applies to: N/A
 * - Deletable by: User only (TODO(SE-383))
 * - Send to Threema Gateway ID group creator: If capture is enabled
 *
 * When the user submits this message in a 1:1 or group conversation:
 *
 * 1. Let `poll` be all necessary properties to construct this message.
 * 2. If the UTF-8 encoded bytes of `poll.description` exceed 256 bytes,
 *    discard `poll`, log an error and abort these steps.¹
 * 3. If the UTF-8 encoded bytes of any of the `poll.choices` exceeds 256
 *    bytes, discard `poll`, log an error and abort these steps.¹
 * 4. JSON encode `poll` and let `encoded-poll` be the UTF-8 encoded result.
 * 5. If `encoded-poll` exceeds 6000 bytes, discard `poll` (and
 *    `encoded-poll`), log an error and abort these steps.¹
 * 6. Run the _Messages Submit Steps_ with `messages` set from
 *    `encoded-poll` (for a group, wrapped by
 *    [`group-member-container`](ref:e2e.group-member-container)).
 *
 * ¹: The UI should prevent submission in these cases.
 *
 * When reflected from another device as an incoming or outgoing 1:1
 * message:
 *
 * 1. Run the _Common Poll Setup Receive Steps_.
 *
 * When receiving this message as a 1:1 message:
 *
 * 1. Run the _Common Poll Setup Receive Steps_.
 *
 * When reflected from another device as an incoming or outgoing group
 * message (wrapped by
 * [`group-member-container`](ref:e2e.group-member-container)):
 *
 * 1. Run the _Common Poll Setup 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 Poll Setup Receive Steps_.
 *
 * The following steps are defined as the _Common Poll Setup Receive Steps_:
 *
 * 1. Let `state` be the _State_ field of the message. Let `participants` be
 *    the _Participants_ field of the message.
 * 2. Look up the poll with the given ID within the conversation.
 * 3. If no associated poll could be found:
 *     1. If `state` is `1` (closed), discard the message and abort these steps.
 *     2. Add the poll to the associated conversation with the provided
 *        fields of the message and abort these steps.
 * 4. If the associated poll is closed, discard the message and abort these steps.
 * 5. If `state` is `0` (open), discard the message and abort these steps.
 * 6. Close the poll with the given `participants`, ignore any other fields
 *    of the message.
 */
export interface PollSetupLike {
    /**
     * Random unique (per creator within this conversation) ID of the poll.
     */
    readonly id: types.u64;

    /**
     * UTF-8, JSON-encoded object with the following fields:
     *
     * - Description (`'d'`): A short description/topic string for the poll.
     * - State (`'s'`):
     *   - `0`: Poll is _open_ for votes.
     *   - `1`: Poll has been _closed_.
     *
     *   A state transition from _closed_ to _open_ is illegal and must be
     *   ignored by the receiving client.
     * - Answer type (`'a'`):
     *   - `0`: Single choice poll.
     *   - `1`: Multiple choice poll.
     *
     *   Any transition from one of the types to another is illegal and
     *   must be ignored by the receiving client.
     * - Announce type (`'t'`):
     *   - `0`: Announce votes in form of the `poll-vote` message
     *     only to the creator of the ballot. Results are invisible until
     *     the creator closes the vote and reports the final results.
     *   - `1`: Announce votes in form of the `poll-vote` message to
     *     everyone in the conversation. Interim results are therefore
     *     visible to everyone.
     *
     *   Any transition from one of the types to another is illegal and
     *   must be ignored by the receiving client.
     * - Display mode (`'u'`):
     *   - `0`: List mode. List choices of all participants as presented
     *     by this message.
     *   - `1`: Summary mode. Only display the total amount of votes per
     *     choice and the user's vote (if any).
     *
     *   If the field is not present, assume _list_ mode (`0`). Any
     *   transition from one of the modes to another is illegal and must be
     *   ignored by the receiving client.
     * - Choices type (`'o'`, DEPRECATED): Always set this to the integer `0`.
     * - Participants (`'p'`): A list of Threema IDs that participated in
     *   the poll (i.e. they cast a vote). This field must only be
     *   present if the poll is being _closed_. In display mode _summary_,
     *   this field should be an empty list and must be ignored by the
     *   receiver.
     * - Choices (`'c'`): A list of choice objects as defined below.
     *
     * Choice object fields:
     *
     * - Choice ID (`'i'`): A per-poll unique ID of the choice in form of
     *   an integer. Used when casting a vote.
     * - Description (`'n'`): Choice description in form of a string.
     * - Sort key (`'o'`, DEPRECATED): Set this to the index of the choice
     *   object within the _choices_ list.
     * - Participant votes (`'r'`): A list of indices referring to the
     *   index of the participant (as defined in the _participants_ list)
     *   that cast a vote for this choice. This field must only be present
     *   if the poll is being _closed_. In display mode _summary_ this
     *   should be an empty list and must be ignored by the receiver.
     * - Total amount of votes (`'t'`): The total amount of votes for this
     *   choice. This field must only be present if the poll is being
     *   _closed_. In display mode _normal_ this field should not be
     *   present and must be ignored by the receiver.
     */
    readonly poll: Uint8Array;
}

/**
 * Encodable of {@link PollSetupLike}.
 */
interface PollSetupEncodable_ {
    /**
     * 'id' field value or encoder. See {@link PollSetupLike#id} for
     * the field's description.
     */
    readonly id: types.u64;

    /**
     * 'poll' field value or encoder. See {@link PollSetupLike#poll} for
     * the field's description.
     */
    readonly poll: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for PollSetupEncodable.
 */
export type PollSetupEncodable = types.WeakOpaque<
    PollSetupEncodable_,
    {readonly PollSetupEncodable: unique symbol}
>;

/** @inheritdoc */
export class PollSetup extends base.Struct implements PollSetupLike {
    private readonly _array: Uint8Array;
    private readonly _view: DataView;

    /**
     * Create a PollSetup from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
        this._view = new DataView(array.buffer, array.byteOffset, array.byteLength);
    }

    /**
     * Decode a poll-setup struct from an array.
     *
     * @param array Array to decode from.
     * @returns PollSetup instance.
     */
    public static decode(array: Uint8Array): PollSetup {
        return new PollSetup(array);
    }

    /**
     * Encode a poll-setup struct into an array.
     *
     * @param struct PollSetupEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<PollSetupEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        const view = new DataView(array.buffer, array.byteOffset, array.byteLength);
        let offset = 8;

        // Encode `id`
        view.setBigUint64(0, struct.id, true);

        // Encode `poll`
        offset += utils.encodeBytes(struct.poll, array, offset);

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a poll-setup struct into an
     * array.
     *
     * @param struct PollSetupEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<PollSetupEncodable, 'byteLength'>,
    ): types.u53 {
        let offset = 8;
        offset += utils.getByteLength(struct.poll);
        return offset;
    }

    /**
     * 'id' field accessor. See {@link PollSetupLike#id} for the
     * field's description.
     */
    public get id(): types.u64 {
        return this._view.getBigUint64(0, true);
    }

    /**
     * 'poll' field accessor. See {@link PollSetupLike#poll} for the
     * field's description.
     */
    public get poll(): Uint8Array {
        return this._array.subarray(8);
    }

    /**
     * Create a snapshot of PollSetupLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns PollSetupLike snapshot.
     */
    public snapshot(): PollSetupLike {
        return {
            id: this.id,
            poll: this.poll,
        };
    }

    /**
     * Create a clone of PollSetupLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns PollSetupLike clone.
     */
    public clone(): PollSetup {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new PollSetup(array);
    }
}

/**
 * Cast a vote on a poll.
 *
 * **Properties (1:1)**:
 * - Kind: 1:1
 * - Flags: None
 * - User profile distribution: Yes
 * - 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
 * - When rejected: N/A (ignored)
 * - Edit applies to: N/A (can just send another `poll-vote`)
 * - Deletable by: User only (TODO(SE-383))
 * - Send to Threema Gateway ID group creator: N/A
 *
 * **Properties (Group)**:
 * - Kind: Group
 * - Flags: None
 * - User profile distribution: Yes
 * - Exempt from blocking: Yes
 * - 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
 * - When rejected: N/A (ignored)
 * - Edit applies to: N/A (can just send another `poll-vote`)
 * - Deletable by: User only (TODO(SE-383))
 * - Send to Threema Gateway ID group creator: If capture is enabled
 *
 * ¹: A [`poll-vote`](ref:e2e.poll-vote) updates the poll state initiated by
 * a corresponding [`poll-setup`](ref:e2e.poll-setup), meaning it does not
 * produce a new visible message.
 *
 * When the user submits this message in a 1:1 or group conversation by
 * casting a vote:
 *
 * 1. Let `vote` be all necessary properties to construct this message.
 * 2. JSON encode `vote` and let `encoded-vote` be the UTF-8 encoded result.
 * 3. If `encoded-vote` exceeds 6000 bytes, discard `vote` (and
 *    `encoded-vote`), log an error and abort these steps.¹
 * 4. Run the _Messages Submit Steps_ with `messages` set from
 *    `encoded-vote` (for a group, wrapped by
 *    [`group-member-container`](ref:e2e.group-member-container)).
 *
 * ¹: The UI should prevent submission in these cases.
 *
 * When reflected from another device as an incoming or outgoing 1:1
 * message:
 *
 * 1. Run the _Common Poll Vote Receive Steps_.
 *
 * When receiving this message as a 1:1 message:
 *
 * 1. Run the _Common Poll Vote Receive Steps_.
 *
 * When reflected from another device as an incoming or outgoing group
 * message (wrapped by
 * [`group-member-container`](ref:e2e.group-member-container)):
 *
 * 1. Run the _Common Poll Vote 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 Poll Vote Receive Steps_.
 *
 * The following steps are defined as the _Common Poll Vote Receive Steps_.
 *
 * 1. Look up the poll with the given ID within the conversation.
 * 2. If no associated poll could be found or if the associated poll is
 *    closed, discard the message and abort these steps.
 * 3. Update the poll with the provided choices of the sender.
 */
export interface PollVoteLike {
    /**
     * ID of the associated poll.
     */
    readonly pollId: types.u64;

    /**
     * The Threema ID of the creator of the poll.
     */
    readonly creatorIdentity: Uint8Array;

    /**
     * UTF-8, JSON-encoded list containing one or more choice tuples. Each
     * choice tuple contains the following two integer values:
     *
     * - Choice ID, referring to the Choice ID defined in the
     *   `poll-setup` message.
     * - Selected:
     *   - `0`: The choice has not been selected.
     *   - `1`: The choice has been selected.
     *
     * Note: For protocol simplicity, a vote must always include all possible
     * choices, whether or not they have been selected.
     */
    readonly choices: Uint8Array;
}

/**
 * Encodable of {@link PollVoteLike}.
 */
interface PollVoteEncodable_ {
    /**
     * 'poll-id' field value or encoder. See {@link PollVoteLike#pollId} for
     * the field's description.
     */
    readonly pollId: types.u64;

    /**
     * 'creator-identity' field value or encoder. See {@link PollVoteLike#creatorIdentity} for
     * the field's description.
     */
    readonly creatorIdentity: Uint8Array | types.ByteLengthEncoder;

    /**
     * 'choices' field value or encoder. See {@link PollVoteLike#choices} for
     * the field's description.
     */
    readonly choices: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for PollVoteEncodable.
 */
export type PollVoteEncodable = types.WeakOpaque<
    PollVoteEncodable_,
    {readonly PollVoteEncodable: unique symbol}
>;

/** @inheritdoc */
export class PollVote extends base.Struct implements PollVoteLike {
    private readonly _array: Uint8Array;
    private readonly _view: DataView;

    /**
     * Create a PollVote from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
        this._view = new DataView(array.buffer, array.byteOffset, array.byteLength);
    }

    /**
     * Decode a poll-vote struct from an array.
     *
     * @param array Array to decode from.
     * @returns PollVote instance.
     */
    public static decode(array: Uint8Array): PollVote {
        return new PollVote(array);
    }

    /**
     * Encode a poll-vote struct into an array.
     *
     * @param struct PollVoteEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<PollVoteEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        const view = new DataView(array.buffer, array.byteOffset, array.byteLength);
        let offset = 16;

        // Encode `poll-id`
        view.setBigUint64(0, struct.pollId, true);

        // Encode `creator-identity`
        utils.encodeBytes(struct.creatorIdentity, array, 8);

        // Encode `choices`
        offset += utils.encodeBytes(struct.choices, array, offset);

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a poll-vote struct into an
     * array.
     *
     * @param struct PollVoteEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<PollVoteEncodable, 'byteLength'>,
    ): types.u53 {
        let offset = 16;
        offset += utils.getByteLength(struct.choices);
        return offset;
    }

    /**
     * 'poll-id' field accessor. See {@link PollVoteLike#pollId} for the
     * field's description.
     */
    public get pollId(): types.u64 {
        return this._view.getBigUint64(0, true);
    }

    /**
     * 'creator-identity' field accessor. See {@link PollVoteLike#creatorIdentity} for the
     * field's description.
     */
    public get creatorIdentity(): Uint8Array {
        const offset = 8;
        return this._array.subarray(offset, offset + 8);
    }

    /**
     * 'choices' field accessor. See {@link PollVoteLike#choices} for the
     * field's description.
     */
    public get choices(): Uint8Array {
        return this._array.subarray(16);
    }

    /**
     * Create a snapshot of PollVoteLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns PollVoteLike snapshot.
     */
    public snapshot(): PollVoteLike {
        return {
            pollId: this.pollId,
            creatorIdentity: this.creatorIdentity,
            choices: this.choices,
        };
    }

    /**
     * Create a clone of PollVoteLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns PollVoteLike clone.
     */
    public clone(): PollVote {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new PollVote(array);
    }
}

/**
 * Initiates a call.
 *
 * **Properties**:
 * - Kind: 1:1
 * - Flags:
 *   - `0x01`: Send push notification.
 *   - `0x20`: Short-lived server queuing.
 * - User profile distribution: Yes
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: Yes
 * - Protect against replay: Yes
 * - Unarchive: Yes
 * - Bump _last update_: Yes
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: Yes
 *   - _Sent_ update: No
 * - Delivery receipts: No
 * - Reactions: No
 * - When rejected: Abort call
 * - Edit applies to: N/A
 * - Deletable by: User only (TODO(SE-384))
 * - Send to Threema Gateway ID group creator: N/A
 *
 * [//]: # "When submit / receiving / reflected: TODO(SE-102)"
 */
export interface CallOfferLike {
    /**
     * UTF-8, JSON-encoded object with the following fields:
     *
     * - Call ID (`'callId'`): Random 32 bit unsigned integer greater than 0
     *   that uniquely identifies a call throughout its lifetime. Assume
     *   `0` if not set.
     * - WebRTC Offer (`'offer'`): An offer object.
     * - Feature negotiation (`'features'`): Optional Call Features object.
     *
     * Offer object fields:
     *
     * - WebRTC Offer SDP type (`'sdpType'`): Set this to `'offer'` and ignore
     *   offers with other types.
     * - WebRTC Offer SDP (`'sdp'`): Opaque string containing the SDP.
     */
    readonly offer: Uint8Array;
}

/**
 * Encodable of {@link CallOfferLike}.
 */
interface CallOfferEncodable_ {
    /**
     * 'offer' field value or encoder. See {@link CallOfferLike#offer} for
     * the field's description.
     */
    readonly offer: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for CallOfferEncodable.
 */
export type CallOfferEncodable = types.WeakOpaque<
    CallOfferEncodable_,
    {readonly CallOfferEncodable: unique symbol}
>;

/** @inheritdoc */
export class CallOffer extends base.Struct implements CallOfferLike {
    private readonly _array: Uint8Array;

    /**
     * Create a CallOffer from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a call-offer struct from an array.
     *
     * @param array Array to decode from.
     * @returns CallOffer instance.
     */
    public static decode(array: Uint8Array): CallOffer {
        return new CallOffer(array);
    }

    /**
     * Encode a call-offer struct into an array.
     *
     * @param struct CallOfferEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<CallOfferEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        let offset = 0;

        // Encode `offer`
        offset += utils.encodeBytes(struct.offer, array, offset);

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a call-offer struct into an
     * array.
     *
     * @param struct CallOfferEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<CallOfferEncodable, 'byteLength'>,
    ): types.u53 {
        let offset = 0;
        offset += utils.getByteLength(struct.offer);
        return offset;
    }

    /**
     * 'offer' field accessor. See {@link CallOfferLike#offer} for the
     * field's description.
     */
    public get offer(): Uint8Array {
        return this._array.subarray(0);
    }

    /**
     * Create a snapshot of CallOfferLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns CallOfferLike snapshot.
     */
    public snapshot(): CallOfferLike {
        return {
            offer: this.offer,
        };
    }

    /**
     * Create a clone of CallOfferLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns CallOfferLike clone.
     */
    public clone(): CallOffer {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new CallOffer(array);
    }
}

/**
 * Answer or reject a call.
 *
 * **Properties**:
 * - Kind: 1:1
 * - Flags:
 *   - `0x01`: Send push notification.
 *   - `0x20`: Short-lived server queuing.
 * - User profile distribution: Only if accepted (`action`: `1`)
 * - 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
 * - When rejected: Abort call
 * - Edit applies to: N/A
 * - Deletable by: User only (TODO(SE-384))
 * - Send to Threema Gateway ID group creator: N/A
 *
 * ¹: A [`call-answer`](ref:e2e.call-answer) updates the call state initiated
 * by a corresponding [`call-offer`](ref:e2e.call-offer), meaning it does not
 * produce a new visible message.
 *
 * [//]: # "When submit / receiving / reflected: TODO(SE-102)"
 */
export interface CallAnswerLike {
    /**
     * UTF-8, JSON-encoded object with the following fields:
     *
     * - Call ID (`'callId'`): Random 32 bit unsigned integer greater than 0
     *   that uniquely identifies a call throughout its lifetime. Assume
     *   `0` if not set.
     * - Required action (`'action'`):
     *   - `0`: The call has been rejected and needs to be aborted.
     *   - `1`: The call has been accepted and a connection needs to be
     *     established.
     * - Rejection reason (`'rejectReason'`): If the call has been rejected,
     *   this field contains a reject reason:
     *   - `0`: Generic or unspecified rejection.
     *   - `1`: The callee is busy (another call is active).
     *   - `2`: The callee did not accept the call in time.
     *   - `3`: The callee explicitly rejected the call.
     *   - `4`: The callee disabled calls.
     *   - `5`: The callee was called during an off-hour period.
     * - WebRTC Answer (`'answer'`): An answer object.
     * - Feature negotiation (`'features'`): Optional Call Features object.
     *
     * Answer object fields:
     *
     * - WebRTC Answer SDP type (`'sdpType'`): Set this to `'answer'` and ignore
     *   answers with other types.
     * - WebRTC Answer SDP (`'sdp'`): Opaque string containing the SDP.
     */
    readonly answer: Uint8Array;
}

/**
 * Encodable of {@link CallAnswerLike}.
 */
interface CallAnswerEncodable_ {
    /**
     * 'answer' field value or encoder. See {@link CallAnswerLike#answer} for
     * the field's description.
     */
    readonly answer: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for CallAnswerEncodable.
 */
export type CallAnswerEncodable = types.WeakOpaque<
    CallAnswerEncodable_,
    {readonly CallAnswerEncodable: unique symbol}
>;

/** @inheritdoc */
export class CallAnswer extends base.Struct implements CallAnswerLike {
    private readonly _array: Uint8Array;

    /**
     * Create a CallAnswer from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a call-answer struct from an array.
     *
     * @param array Array to decode from.
     * @returns CallAnswer instance.
     */
    public static decode(array: Uint8Array): CallAnswer {
        return new CallAnswer(array);
    }

    /**
     * Encode a call-answer struct into an array.
     *
     * @param struct CallAnswerEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<CallAnswerEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        let offset = 0;

        // Encode `answer`
        offset += utils.encodeBytes(struct.answer, array, offset);

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a call-answer struct into an
     * array.
     *
     * @param struct CallAnswerEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<CallAnswerEncodable, 'byteLength'>,
    ): types.u53 {
        let offset = 0;
        offset += utils.getByteLength(struct.answer);
        return offset;
    }

    /**
     * 'answer' field accessor. See {@link CallAnswerLike#answer} for the
     * field's description.
     */
    public get answer(): Uint8Array {
        return this._array.subarray(0);
    }

    /**
     * Create a snapshot of CallAnswerLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns CallAnswerLike snapshot.
     */
    public snapshot(): CallAnswerLike {
        return {
            answer: this.answer,
        };
    }

    /**
     * Create a clone of CallAnswerLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns CallAnswerLike clone.
     */
    public clone(): CallAnswer {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new CallAnswer(array);
    }
}

/**
 * An ICE candidate for an ongoing call.
 *
 * **Properties**:
 * - Kind: 1:1
 * - Flags:
 *   - `0x01`: Send push notification.
 *   - `0x20`: Short-lived server queuing.
 * - User profile distribution: No
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: No
 * - Protect against replay: No¹
 * - Unarchive: No
 * - Bump _last update_: No
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: No
 *   - _Sent_ update: No
 * - Delivery receipts: No
 * - Reactions: No
 * - When rejected: N/A (ignored)
 * - Edit applies to: N/A
 * - Deletable by: N/A
 * - Send to Threema Gateway ID group creator: N/A
 *
 * ¹: This message does not trigger any kind of reaction and adding ICE
 * candidates again has no ill-effect.
 *
 * [//]: # "When submit / receiving / reflected: TODO(SE-102)"
 */
export interface CallIceCandidateLike {
    /**
     * UTF-8, JSON-encoded object with the following fields:
     *
     * - Call ID (`'callId'`): Random 32 bit unsigned integer greater than 0
     *   that uniquely identifies a call throughout its lifetime. Assume
     *   `0` if not set.
     * - Deprecated (`'removed'`): Always set this to `false` and ignore
     *   messages with this field set to `true`.
     * - WebRTC Candidates (`'candidates'`): An array of candidate objects.
     *
     * Candidate object fields:
     * - WebRTC Candidate SDP (`'candidate'`): Opaque string containing the
     *   ICE candidate SDP.
     * - WebRTC MID (`'sdpMid'`): Media stream identification string or
     *   `null`.
     * - WebRTC Media Line Index (`'sdpMLineIndex'`): Media description
     *   line index integer or `null`.
     * - WebRTC Username Fragment (`'ufrag'`): ICE username fragment or
     *   `null`.
     */
    readonly candidates: Uint8Array;
}

/**
 * Encodable of {@link CallIceCandidateLike}.
 */
interface CallIceCandidateEncodable_ {
    /**
     * 'candidates' field value or encoder. See {@link CallIceCandidateLike#candidates} for
     * the field's description.
     */
    readonly candidates: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for CallIceCandidateEncodable.
 */
export type CallIceCandidateEncodable = types.WeakOpaque<
    CallIceCandidateEncodable_,
    {readonly CallIceCandidateEncodable: unique symbol}
>;

/** @inheritdoc */
export class CallIceCandidate extends base.Struct implements CallIceCandidateLike {
    private readonly _array: Uint8Array;

    /**
     * Create a CallIceCandidate from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a call-ice-candidate struct from an array.
     *
     * @param array Array to decode from.
     * @returns CallIceCandidate instance.
     */
    public static decode(array: Uint8Array): CallIceCandidate {
        return new CallIceCandidate(array);
    }

    /**
     * Encode a call-ice-candidate struct into an array.
     *
     * @param struct CallIceCandidateEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<CallIceCandidateEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        let offset = 0;

        // Encode `candidates`
        offset += utils.encodeBytes(struct.candidates, array, offset);

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a call-ice-candidate struct into an
     * array.
     *
     * @param struct CallIceCandidateEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<CallIceCandidateEncodable, 'byteLength'>,
    ): types.u53 {
        let offset = 0;
        offset += utils.getByteLength(struct.candidates);
        return offset;
    }

    /**
     * 'candidates' field accessor. See {@link CallIceCandidateLike#candidates} for the
     * field's description.
     */
    public get candidates(): Uint8Array {
        return this._array.subarray(0);
    }

    /**
     * Create a snapshot of CallIceCandidateLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns CallIceCandidateLike snapshot.
     */
    public snapshot(): CallIceCandidateLike {
        return {
            candidates: this.candidates,
        };
    }

    /**
     * Create a clone of CallIceCandidateLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns CallIceCandidateLike clone.
     */
    public clone(): CallIceCandidate {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new CallIceCandidate(array);
    }
}

/**
 * Hang up a call.
 *
 * **Properties**:
 * - 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: If no corresponding `call-offer` can be found¹
 * - Bump _last update_: If no corresponding `call-offer` can be found¹
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: Yes
 *   - _Sent_ update: No
 * - Delivery receipts: No
 * - Reactions: No
 * - When rejected: N/A (ignored)
 * - Edit applies to: N/A
 * - Deletable by: User only (TODO(SE-384))
 * - Send to Threema Gateway ID group creator: N/A
 *
 * ¹: A [`call-hangup`](ref:e2e.call-hangup) usually updates the call state
 * initiated by a corresponding [`call-offer`](ref:e2e.call-offer), meaning
 * it does not produce a new visible message. However, the
 * [`call-offer`](ref:e2e.call-offer) uses the _Short-lived server queuing_
 * flag and therefore may be lost. In that case, the
 * [`call-hangup`](ref:e2e.call-hangup) does create a visible _call missed_
 * message.
 *
 * [//]: # "When submit / receiving / reflected: TODO(SE-102)"
 */
export interface CallHangupLike {
    /**
     * UTF-8, JSON-encoded object. If this field contains zero bytes, assume
     * an empty object. Contains the following fields:
     *
     * - Call ID (`'callId'`): Random 32 bit unsigned integer greater than 0
     *   that uniquely identifies a call throughout its lifetime. Assume
     *   `0` if not set.
     */
    readonly hangup: Uint8Array;
}

/**
 * Encodable of {@link CallHangupLike}.
 */
interface CallHangupEncodable_ {
    /**
     * 'hangup' field value or encoder. See {@link CallHangupLike#hangup} for
     * the field's description.
     */
    readonly hangup: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for CallHangupEncodable.
 */
export type CallHangupEncodable = types.WeakOpaque<
    CallHangupEncodable_,
    {readonly CallHangupEncodable: unique symbol}
>;

/** @inheritdoc */
export class CallHangup extends base.Struct implements CallHangupLike {
    private readonly _array: Uint8Array;

    /**
     * Create a CallHangup from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a call-hangup struct from an array.
     *
     * @param array Array to decode from.
     * @returns CallHangup instance.
     */
    public static decode(array: Uint8Array): CallHangup {
        return new CallHangup(array);
    }

    /**
     * Encode a call-hangup struct into an array.
     *
     * @param struct CallHangupEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<CallHangupEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        let offset = 0;

        // Encode `hangup`
        offset += utils.encodeBytes(struct.hangup, array, offset);

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a call-hangup struct into an
     * array.
     *
     * @param struct CallHangupEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<CallHangupEncodable, 'byteLength'>,
    ): types.u53 {
        let offset = 0;
        offset += utils.getByteLength(struct.hangup);
        return offset;
    }

    /**
     * 'hangup' field accessor. See {@link CallHangupLike#hangup} for the
     * field's description.
     */
    public get hangup(): Uint8Array {
        return this._array.subarray(0);
    }

    /**
     * Create a snapshot of CallHangupLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns CallHangupLike snapshot.
     */
    public snapshot(): CallHangupLike {
        return {
            hangup: this.hangup,
        };
    }

    /**
     * Create a clone of CallHangupLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns CallHangupLike clone.
     */
    public clone(): CallHangup {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new CallHangup(array);
    }
}

/**
 * Sent by the callee to indicate that the call is ringing.
 *
 * **Properties**:
 * - Kind: 1:1
 * - Flags:
 *   - `0x01`: Send push notification.
 *   - `0x20`: Short-lived server queuing.
 * - 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
 * - When rejected: Abort call
 * - Edit applies to: N/A
 * - Deletable by: N/A
 * - Send to Threema Gateway ID group creator: N/A
 *
 * ¹: A [`call-ringing`](ref:e2e.call-ringing) updates the call state
 * initiated by a corresponding [`call-offer`](ref:e2e.call-offer), meaning
 * it does not produce a new visible message.
 *
 * [//]: # "When submit / receiving / reflected: TODO(SE-102)"
 */
export interface CallRingingLike {
    /**
     * UTF-8, JSON-encoded object. If this field contains zero bytes, assume
     * an empty object. Contains the following fields:
     *
     * - Call ID (`'callId'`): Random 32 bit unsigned integer greater than 0
     *   that uniquely identifies a call throughout its lifetime. Assume
     *   `0` if not set.
     */
    readonly hangup: Uint8Array;
}

/**
 * Encodable of {@link CallRingingLike}.
 */
interface CallRingingEncodable_ {
    /**
     * 'hangup' field value or encoder. See {@link CallRingingLike#hangup} for
     * the field's description.
     */
    readonly hangup: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for CallRingingEncodable.
 */
export type CallRingingEncodable = types.WeakOpaque<
    CallRingingEncodable_,
    {readonly CallRingingEncodable: unique symbol}
>;

/** @inheritdoc */
export class CallRinging extends base.Struct implements CallRingingLike {
    private readonly _array: Uint8Array;

    /**
     * Create a CallRinging from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a call-ringing struct from an array.
     *
     * @param array Array to decode from.
     * @returns CallRinging instance.
     */
    public static decode(array: Uint8Array): CallRinging {
        return new CallRinging(array);
    }

    /**
     * Encode a call-ringing struct into an array.
     *
     * @param struct CallRingingEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<CallRingingEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        let offset = 0;

        // Encode `hangup`
        offset += utils.encodeBytes(struct.hangup, array, offset);

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a call-ringing struct into an
     * array.
     *
     * @param struct CallRingingEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<CallRingingEncodable, 'byteLength'>,
    ): types.u53 {
        let offset = 0;
        offset += utils.getByteLength(struct.hangup);
        return offset;
    }

    /**
     * 'hangup' field accessor. See {@link CallRingingLike#hangup} for the
     * field's description.
     */
    public get hangup(): Uint8Array {
        return this._array.subarray(0);
    }

    /**
     * Create a snapshot of CallRingingLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns CallRingingLike snapshot.
     */
    public snapshot(): CallRingingLike {
        return {
            hangup: this.hangup,
        };
    }

    /**
     * Create a clone of CallRingingLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns CallRingingLike clone.
     */
    public clone(): CallRinging {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new CallRinging(array);
    }
}

/**
 * Confirms reception or delivers detailed status updates of a message.
 *
 * **Properties (1:1)**:
 * - Kind: 1:1
 * - Flags: None
 * - User profile distribution: Only for reactions
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: No
 * - Protect against replay: Only for reactions¹
 * - Unarchive: No
 * - Bump _last update_: No
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: Yes²
 *   - _Sent_ update: No
 * - Delivery receipts: No, that would be silly!
 * - Reactions: No (also silly)
 * - When rejected: N/A (ignored)
 * - Edit applies to: N/A (can just send another `delivery-receipt`)
 * - Deletable by: N/A
 * - Send to Threema Gateway ID group creator: N/A
 *
 * ¹: Repeating a status of type _received_ or _read_ has no ill-effects.
 *
 * ²: When the message is being _read_ and _read_ receipts are disabled, an
 * `d2d.IncomingMessageUpdate` will be reflected instead.
 *
 * **Properties (Group)**:
 * - Kind: Group
 * - Flags: None
 * - User profile distribution: Only for reactions
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: No
 * - Protect against replay: Only for reactions¹
 * - Unarchive: No
 * - Bump _last update_: No
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: Yes. When the message is being _read_ and _read_ receipts
 *     are disabled, reflect an `d2d.IncomingMessageUpdate` (since no
 *     `delivery-receipt` is sent in this case).
 *   - _Sent_ update: No
 * - Delivery receipts: No, that would be silly!
 * - Reactions: No (also silly)
 * - When rejected: N/A (ignored)
 * - Edit applies to: N/A (can just send another `delivery-receipt`)
 * - Deletable by: N/A
 * - Send to Threema Gateway ID group creator: If capture is enabled
 *
 * ¹: Repeating a status of type _received_ or _read_ has no ill-effects.
 *
 * When the user opens a 1:1 conversation or marks it as _read_ by another
 * mechanism:
 *
 * 1. Let `message-ids` be all message IDs associated to messages of the
 *    conversation that require _automatic_ delivery receipts and which are
 *    not yet marked as _read_.
 * 2. Split the provided `message-ids` into bundles of at most 875 message
 *    IDs and let `message-ids-bundles` be the result.¹
 * 3. Run the _Messages Submit Steps_ with `messages` set to create one or
 *    more [`delivery-receipt`](ref:e2e.delivery-receipt)s from
 *    `message-ids-bundles` with status `0x02`.
 *
 * ¹: Each message ID has 8 bytes, divided by at most 7000 bytes.
 *
 * When reflected from another device as an incoming 1:1 message:
 *
 * 1. Run the _Common Incoming Delivery Receipt Steps_.
 *
 * When reflected from another device as an outgoing 1:1 message:
 *
 * 1. Run the _Common Outgoing Delivery Receipt Steps_.
 *
 * When receiving this message as a 1:1 message:
 *
 * 1. Run the _Common Incoming Delivery Receipt Steps_.
 *
 * When reflected from another device as an incoming group message (wrapped
 * by [`group-member-container`](ref:e2e.group-member-container)):
 *
 * 1. If `status` is not `0x03` or `0x04` (i.e. not a reaction), log a
 *    notice, discard the message and abort these steps.
 * 2. Run the _Common Incoming Delivery Receipt Steps_.
 *
 * When reflected from another device as an outgoing group message (wrapped
 * by [`group-member-container`](ref:e2e.group-member-container)):
 *
 * 1. If `status` is not `0x03` or `0x04` (i.e. not a reaction), log a
 *    notice, discard the message and abort these steps.
 * 2. Run the _Common Outgoing Delivery Receipt 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. If `status` is not `0x03` or `0x04` (i.e. not a reaction), log a
 *    notice, discard the message and abort these steps.
 * 3. Run the _Common Incoming Delivery Receipt Steps_.
 *
 * The following steps are defined as the _Common Incoming Delivery Receipt
 * Steps_:
 *
 * 1. For each `message-id` of `message-ids`:
 *    1. Lookup the associated message for `message-id` in the associated
 *       conversation and let `referred-message` be the result.
 *    2. If `referred-message` is not defined, abort these sub-steps.
 *    3. If the associated conversation is a 1:1 conversation and the
 *       original sender of `referred-message` is not the user, abort these
 *       sub-steps.
 *    4. If `status` is `0x01` or `0x02` (i.e. a delivery receipt) and
 *       `referred-message` allows for delivery receipts (see the associated
 *       _Delivery receipts_ property), apply and replace the delivery
 *       receipt of the sender to `referred-message` with the associated
 *       timestamp set to the message's (the `delivery-receipt`'s)
 *       `created-at`.
 *    5. If `status` is `0x03` or `0x04` (i.e. a reaction) and
 *       `referred-message` is reactable (see the associated _Reactions_
 *       property), apply and replace any existing reaction of the sender to
 *       `referred-message` with the reaction timestamp set to the message's
 *       (the `delivery-receipt`'s) `created-at`.¹
 *
 * The following steps are defined as the _Common Outgoing Delivery Receipt
 * Steps_:
 *
 * 1. For each `message-id` of `message-ids`:
 *    1. Lookup the associated message for `message-id` in the associated
 *       conversation and let `referred-message` be the result.
 *    2. If `referred-message` is not defined, abort these sub-steps.
 *    3. If the associated conversation is a 1:1 conversation and the
 *       original sender of `referred-message` is the user, abort these
 *       sub-steps.
 *    4. If `status` is `0x01` or `0x02` (i.e. a delivery receipt) and
 *       `referred-message` allows for delivery receipts (see the associated
 *       _Delivery receipts_ property), apply and replace the delivery
 *       receipt of the user to `referred-message` with the associated
 *       timestamp set to the message's (the `delivery-receipt`'s)
 *       `created-at`.
 *    5. If `status` is `0x03` or `0x04` (i.e. a reaction) and
 *       `referred-message` is reactable (see the associated _Reactions_
 *       property), apply and replace any existing reaction of the user to
 *       `referred-message` with the reaction timestamp set to the message's
 *       (the `delivery-receipt`'s) `created-at`.¹
 *
 * ¹: Note that the deprecated reactions transmitted by a `delivery-receipt`
 * always replace **all existing reactions** of the respective sender,
 * including new-style reactions transmitted by a `csp_e2e.Reaction` message.
 */
export interface DeliveryReceiptLike {
    /**
     * Message status:
     *
     * - `0x01`: Message was received.
     * - `0x02`: Message was read.
     * - `0x03`: **Deprecated** Maps to the 👍 emoji (`1F44D`).
     * - `0x04`: **Deprecated** Maps to the 👎 emoji (`1F44E`).
     *
     * Note that only the `0x01` and `0x02` variants are considered true
     * _delivery receipts_ whereas the deprecated `0x03` and `0x04` variants
     * are considered _reactions_ (just like `csp_e2e.Reaction`).
     *
     * The following replacement logic is to be applied on a message's
     * status when displayed:
     *
     * 1. `0x02` replaces groups listed below,
     * 2. `0x01` replaces the unlisted _created_ status.
     */
    readonly status: types.u8;

    /**
     * One or more `message-id`s whose status should be updated.
     */
    readonly messageIds: Iterable<types.u64>;
}

/**
 * Encodable of {@link DeliveryReceiptLike}.
 */
interface DeliveryReceiptEncodable_ {
    /**
     * 'status' field value or encoder. See {@link DeliveryReceiptLike#status} for
     * the field's description.
     */
    readonly status: types.u8;

    /**
     * 'message-ids' field value or encoder. See {@link DeliveryReceiptLike#messageIds} for
     * the field's description.
     */
    readonly messageIds: types.BoundedIterable<types.u64>;
}

/**
 * New-type for DeliveryReceiptEncodable.
 */
export type DeliveryReceiptEncodable = types.WeakOpaque<
    DeliveryReceiptEncodable_,
    {readonly DeliveryReceiptEncodable: unique symbol}
>;

/** @inheritdoc */
export class DeliveryReceipt extends base.Struct implements DeliveryReceiptLike {
    private readonly _array: Uint8Array;
    private readonly _view: DataView;

    /**
     * Create a DeliveryReceipt from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
        this._view = new DataView(array.buffer, array.byteOffset, array.byteLength);
    }

    /**
     * Decode a delivery-receipt struct from an array.
     *
     * @param array Array to decode from.
     * @returns DeliveryReceipt instance.
     */
    public static decode(array: Uint8Array): DeliveryReceipt {
        return new DeliveryReceipt(array);
    }

    /**
     * Encode a delivery-receipt struct into an array.
     *
     * @param struct DeliveryReceiptEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<DeliveryReceiptEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        const view = new DataView(array.buffer, array.byteOffset, array.byteLength);
        let offset = 1;

        // Encode `status`
        array[0] = struct.status;

        // Encode `message-ids`
        offset += utils.encodeIterable(struct.messageIds, offset, 8, (o, v) => {
            view.setBigUint64(o, v, true);
        });

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a delivery-receipt struct into an
     * array.
     *
     * @param struct DeliveryReceiptEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<DeliveryReceiptEncodable, 'byteLength'>,
    ): types.u53 {
        let offset = 1;
        offset += struct.messageIds.length * 8;
        return offset;
    }

    /**
     * 'status' field accessor. See {@link DeliveryReceiptLike#status} for the
     * field's description.
     */
    public get status(): types.u8 {
        return utils.unwrap(this._array[0]);
    }

    /**
     * 'message-ids' field accessor. See {@link DeliveryReceiptLike#messageIds} for the
     * field's description.
     */
    public get messageIds(): Iterable<types.u64> {
        return utils.decodeIterable(this._view, 1, 8, (o) => this._view.getBigUint64(o, true));
    }

    /**
     * Create a snapshot of DeliveryReceiptLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns DeliveryReceiptLike snapshot.
     */
    public snapshot(): DeliveryReceiptLike {
        return {
            status: this.status,
            messageIds: this.messageIds,
        };
    }

    /**
     * Create a clone of DeliveryReceiptLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns DeliveryReceiptLike clone.
     */
    public clone(): DeliveryReceipt {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new DeliveryReceipt(array);
    }
}

/**
 * Indicates whether a contact is currently typing.
 *
 * **Properties**:
 * - Kind: 1:1
 * - Flags:
 *   - `0x02`: No server queuing.
 *   - `0x04`: No server acknowledgement.
 * - User profile distribution: No
 * - Exempt from blocking: No
 * - Implicit _direct_ contact creation: No
 * - Protect against replay: No¹
 * - Unarchive: No
 * - Bump _last update_: No
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: No
 *   - _Sent_ update: No
 * - Delivery receipts: No
 * - Reactions: No
 * - When rejected: N/A (ignored)
 * - Edit applies to: N/A
 * - Deletable by: N/A
 * - Send to Threema Gateway ID group creator: N/A
 *
 * ¹: It is deemed acceptable if the _typing_ indicator in the UI is replayed
 * since there is no further consequence.
 *
 * When the user is currently _typing_ while composing a **new**¹ message in
 * an associated conversation:
 *
 * 1. Schedule a volatile task to run the _Bundled Messages Send Steps_ with
 *    the following properties:
 *    - `id` set to a random message ID,
 *    - `created-at` set to the current timestamp,
 *    - `receivers` set to the targeted receiver,
 *    - to construct this message with `is-typing` set to `1`.
 * 2. Start a _user is typing_ timer in the conversation to rerun these
 *    steps in 10s.
 *
 * When the user stopped _typing_ while composing a message in an associated
 * conversation, or when the user left the conversation view:
 *
 * 1. If no _user is typing_ timer is running for the conversation, abort
 *    these steps.
 * 2. Stop the _user is typing_ timer of the conversation.
 * 3. Schedule a volatile task to run the _Bundled Messages Send Steps_ with
 *    the following properties:
 *    - `id` set to a random message ID,
 *    - `created-at` set to the current timestamp,
 *    - `receivers` set to the targeted receiver,
 *    - to construct this message with `is-typing` set to `0`.
 *
 * When reflected from another device as an incoming message:
 *
 * 1. Run the _Common Typing Indicator Receive Steps_.
 *
 * When receiving this message:
 *
 * 1. Run the _Common Typing Indicator Receive Steps_.
 *
 * The following steps are defined as the _Common Typing Indicator Receive
 * Steps_:
 *
 * 1. If `is-typing` is `1`, start a timer to display that the sender is
 *    typing in the associated conversation for the next 15s.
 * 2. If `is-typing` is `0`, cancel any running timer displaying that the
 *    sender is typing in the associated conversation.
 *
 * ¹: Editing a message may not trigger _typing_.
 */
export interface TypingIndicatorLike {
    /**
     * Set to `1` in case the contact is currently typing or `0` in
     * case the contact stopped typing. Other values are invalid.
     */
    readonly isTyping: types.u8;
}

/**
 * Encodable of {@link TypingIndicatorLike}.
 */
interface TypingIndicatorEncodable_ {
    /**
     * 'is-typing' field value or encoder. See {@link TypingIndicatorLike#isTyping} for
     * the field's description.
     */
    readonly isTyping: types.u8;
}

/**
 * New-type for TypingIndicatorEncodable.
 */
export type TypingIndicatorEncodable = types.WeakOpaque<
    TypingIndicatorEncodable_,
    {readonly TypingIndicatorEncodable: unique symbol}
>;

/** @inheritdoc */
export class TypingIndicator extends base.Struct implements TypingIndicatorLike {
    private readonly _array: Uint8Array;

    /**
     * Create a TypingIndicator from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a typing-indicator struct from an array.
     *
     * @param array Array to decode from.
     * @returns TypingIndicator instance.
     */
    public static decode(array: Uint8Array): TypingIndicator {
        return new TypingIndicator(array);
    }

    /**
     * Encode a typing-indicator struct into an array.
     *
     * @param struct TypingIndicatorEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<TypingIndicatorEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        // Encode `is-typing`
        array[0] = struct.isTyping;

        return array.subarray(0, 1);
    }

    /**
     * Get the amount of bytes that would be written when encoding a typing-indicator struct into an
     * array.
     *
     * @param struct TypingIndicatorEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<TypingIndicatorEncodable, 'byteLength'>,
    ): types.u53 {
        return 1;
    }

    /**
     * 'is-typing' field accessor. See {@link TypingIndicatorLike#isTyping} for the
     * field's description.
     */
    public get isTyping(): types.u8 {
        return utils.unwrap(this._array[0]);
    }

    /**
     * Create a snapshot of TypingIndicatorLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns TypingIndicatorLike snapshot.
     */
    public snapshot(): TypingIndicatorLike {
        return {
            isTyping: this.isTyping,
        };
    }

    /**
     * Create a clone of TypingIndicatorLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns TypingIndicatorLike clone.
     */
    public clone(): TypingIndicator {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new TypingIndicator(array);
    }
}

/**
 * Set the profile picture of a contact or a group.
 *
 * **Properties (1:1)**:
 * - Kind: 1:1
 * - Flags: None
 * - User profile distribution: No (obviously)
 * - 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
 * - When rejected: N/A (ignored)
 * - Edit applies to: N/A (can just send another `set-profile-picture`)
 * - Deletable by: N/A (can just send a `delete-profile-picture`)
 * - Send to Threema Gateway ID group creator: N/A
 *
 * **Properties (Group)**:
 * - Kind: Group
 * - Flags: None
 * - User profile distribution: No (obviously)
 * - Exempt from blocking: Yes
 * - 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
 * - When rejected: N/A¹
 * - Edit applies to: N/A (can just send another `set-profile-picture`)
 * - Deletable by: N/A (can just send a `delete-profile-picture`)
 * - Send to Threema Gateway ID group creator: N/A
 *
 * ¹: For the group creator it will be handled as if `group-sync-request` was
 * received, re-sending the group profile picture state, implicitly triggered
 * by FS `Reject` receive steps.
 *
 * The profile picture must be in JPEG format, is uploaded to the blob
 * server and encrypted by:
 *
 *     XSalsa20-Poly1305(key=<set-profile-picture.key>, nonce=00..01)
 *
 * When reflected from another device as an outgoing 1:1 message:
 *
 * 1. Update the most recently distributed profile picture cache for the
 *    contact to the enclosed blob ID.
 *
 * When receiving this message as a 1:1 message:
 *
 * 1. Download the picture from the blob server but do not request the blob
 *    to be removed. Store the profile picture.
 * 2. Store the picture as the _contact-defined_ profile picture and run the
 *    _Contact Profile Picture Selection Steps_.
 *
 * When receiving this message as a group message (wrapped by
 * [`group-creator-container`](ref:e2e.group-creator-container)):
 *
 * 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the
 *    message has been discarded, abort these steps.
 * 2. Download the picture from the blob server but do not request the blob
 *    to be removed. Let `profile-picture` be the result.
 * 3. Let `group` be a snapshot of the current group state.
 * 4. If `group.profile-picture` is defined and equals `profile-picture`
 *    (i.e. no changes), discard the message and abort these steps.
 * 5. (MD) Run the following sub-steps:
 *    1. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
 *       precondition:
 *       1. If the group does not exist or the group is marked as _left_, log
 *          a warning that a group sync race occurred, discard the message
 *          and abort these steps.
 *    2. (MD) Let `group` be a snapshot of the current group state.
 *    3. (MD) If `group.profile-picture` is defined and equals
 *       `profile-picture`, log a warning that a group sync race occurred.
 *    4. (MD) Reflect a `GroupSync.Update` with `group` set to contain
 *       `profile_picture` set to `profile-picture.
 *    5. (MD) Commit the transaction and await acknowledgement.
 * 6. Store the profile picture and and apply it to the group.
 */
export interface SetProfilePictureLike {
    /**
     * Blob ID to obtain the image data.
     */
    readonly pictureBlobId: Uint8Array;

    /**
     * Profile picture size in bytes.
     */
    readonly pictureSize: types.u32;

    /**
     * Random symmetric key used to encrypt the image data.
     */
    readonly key: Uint8Array;
}

/**
 * Encodable of {@link SetProfilePictureLike}.
 */
interface SetProfilePictureEncodable_ {
    /**
     * 'picture-blob-id' field value or encoder. See {@link SetProfilePictureLike#pictureBlobId} for
     * the field's description.
     */
    readonly pictureBlobId: Uint8Array | types.ByteLengthEncoder;

    /**
     * 'picture-size' field value or encoder. See {@link SetProfilePictureLike#pictureSize} for
     * the field's description.
     */
    readonly pictureSize: types.u32;

    /**
     * 'key' field value or encoder. See {@link SetProfilePictureLike#key} for
     * the field's description.
     */
    readonly key: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for SetProfilePictureEncodable.
 */
export type SetProfilePictureEncodable = types.WeakOpaque<
    SetProfilePictureEncodable_,
    {readonly SetProfilePictureEncodable: unique symbol}
>;

/** @inheritdoc */
export class SetProfilePicture extends base.Struct implements SetProfilePictureLike {
    private readonly _array: Uint8Array;
    private readonly _view: DataView;

    /**
     * Create a SetProfilePicture from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
        this._view = new DataView(array.buffer, array.byteOffset, array.byteLength);
    }

    /**
     * Decode a set-profile-picture struct from an array.
     *
     * @param array Array to decode from.
     * @returns SetProfilePicture instance.
     */
    public static decode(array: Uint8Array): SetProfilePicture {
        return new SetProfilePicture(array);
    }

    /**
     * Encode a set-profile-picture struct into an array.
     *
     * @param struct SetProfilePictureEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<SetProfilePictureEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        const view = new DataView(array.buffer, array.byteOffset, array.byteLength);

        // Encode `picture-blob-id`
        utils.encodeBytes(struct.pictureBlobId, array, 0);

        // Encode `picture-size`
        view.setUint32(16, struct.pictureSize, true);

        // Encode `key`
        utils.encodeBytes(struct.key, array, 20);

        return array.subarray(0, 52);
    }

    /**
     * Get the amount of bytes that would be written when encoding a set-profile-picture struct into an
     * array.
     *
     * @param struct SetProfilePictureEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<SetProfilePictureEncodable, 'byteLength'>,
    ): types.u53 {
        return 52;
    }

    /**
     * 'picture-blob-id' field accessor. See {@link SetProfilePictureLike#pictureBlobId} for the
     * field's description.
     */
    public get pictureBlobId(): Uint8Array {
        const offset = 0;
        return this._array.subarray(offset, offset + 16);
    }

    /**
     * 'picture-size' field accessor. See {@link SetProfilePictureLike#pictureSize} for the
     * field's description.
     */
    public get pictureSize(): types.u32 {
        return this._view.getUint32(16, true);
    }

    /**
     * 'key' field accessor. See {@link SetProfilePictureLike#key} for the
     * field's description.
     */
    public get key(): Uint8Array {
        const offset = 20;
        return this._array.subarray(offset, offset + 32);
    }

    /**
     * Create a snapshot of SetProfilePictureLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns SetProfilePictureLike snapshot.
     */
    public snapshot(): SetProfilePictureLike {
        return {
            pictureBlobId: this.pictureBlobId,
            pictureSize: this.pictureSize,
            key: this.key,
        };
    }

    /**
     * Create a clone of SetProfilePictureLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns SetProfilePictureLike clone.
     */
    public clone(): SetProfilePicture {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new SetProfilePicture(array);
    }
}

/**
 * Delete the profile picture of a contact.
 *
 * **Properties (1:1)**:
 * - Kind: 1:1
 * - Flags: None
 * - User profile distribution: No (obviously)
 * - 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
 * - When rejected: N/A (ignored)
 * - Edit applies to: N/A (can just send another `delete-profile-picture`)
 * - Deletable by: N/A
 * - Send to Threema Gateway ID group creator: N/A
 *
 * **Properties (Group)**:
 * - Kind: Group
 * - Flags: None
 * - User profile distribution: No (obviously)
 * - Exempt from blocking: Yes
 * - 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
 * - When rejected: N/A¹
 * - Edit applies to: N/A (can just send another `delete-profile-picture`)
 * - Deletable by: N/A
 * - Send to Threema Gateway ID group creator: N/A
 *
 * ¹: For the group creator it will be handled as if `group-sync-request` was
 * received, re-sending the group profile picture state, implicitly triggered
 * by FS `Reject` receive steps.
 *
 * When reflected from another device as an incoming 1:1 message:
 *
 * 1. Remove the _contact-defined_ profile picture and run the _Contact
 *    Profile Picture Selection Steps_.
 *
 * When reflected from another device as an outgoing 1:1 message:
 *
 * 1. Update the most recently distributed profile picture cache for the
 *    contact to a _remove_ mark with the reflected timestamp.
 *
 * When receiving this message as a 1:1 message:
 *
 * 1. Remove the _contact-defined_ profile picture and run the _Contact
 *    Profile Picture Selection Steps_.
 *
 * When receiving this message as a group message (wrapped by
 * [`group-creator-container`](ref:e2e.group-creator-container)):
 *
 * 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the
 *    message has been discarded, abort these steps.
 * 2. Let `group` be a snapshot of the current group state.
 * 3. If `group.profile-picture` is not defined (i.e. no change), discard the
 *    message and abort these steps.
 * 4. (MD) Run the following sub-steps:
 *    1. Begin a transaction with scope `GROUP_SYNC` and the following
 *       precondition:
 *       1. If the group does not exist or the group is marked as _left_, log
 *          a warning that a group sync race occurred, discard the message
 *          and abort these steps.
 *    2. Let `group` be a snapshot of the current group state.
 *    3. If `group.profile-picture` is not defined, log a warning that a
 *       group sync race occurred.
 *    4. Reflect a `GroupSync.Update` with `group` set to contain
 *       `profile_picture` set to be removed.
 *    5. Commit the transaction and await acknowledgement.
 * 5. Remove the profile picture of the group.
 */
export interface DeleteProfilePictureLike {}

/**
 * Encodable of {@link DeleteProfilePictureLike}.
 */
interface DeleteProfilePictureEncodable_ {}

/**
 * New-type for DeleteProfilePictureEncodable.
 */
export type DeleteProfilePictureEncodable = types.WeakOpaque<
    DeleteProfilePictureEncodable_,
    {readonly DeleteProfilePictureEncodable: unique symbol}
>;

/** @inheritdoc */
export class DeleteProfilePicture extends base.Struct implements DeleteProfilePictureLike {
    private readonly _array: Uint8Array;

    /**
     * Create a DeleteProfilePicture from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a delete-profile-picture struct from an array.
     *
     * @param array Array to decode from.
     * @returns DeleteProfilePicture instance.
     */
    public static decode(array: Uint8Array): DeleteProfilePicture {
        return new DeleteProfilePicture(array);
    }

    /**
     * Encode a delete-profile-picture struct into an array.
     *
     * @param struct DeleteProfilePictureEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<DeleteProfilePictureEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        return array.subarray(0, 0);
    }

    /**
     * Get the amount of bytes that would be written when encoding a delete-profile-picture struct into an
     * array.
     *
     * @param struct DeleteProfilePictureEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<DeleteProfilePictureEncodable, 'byteLength'>,
    ): types.u53 {
        return 0;
    }

    /**
     * Create a snapshot of DeleteProfilePictureLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns DeleteProfilePictureLike snapshot.
     */
    public snapshot(): DeleteProfilePictureLike {
        return {};
    }

    /**
     * Create a clone of DeleteProfilePictureLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns DeleteProfilePictureLike clone.
     */
    public clone(): DeleteProfilePicture {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new DeleteProfilePicture(array);
    }
}

/**
 * Request a contact's profile picture.
 *
 * Note that this message does not result in the profile picture being sent
 * immediately in reply to this message. Instead, it will be sent the next
 * time that contact sends a message to the user (if one is set, and if the
 * user is eligible for receiving the profile picture).
 *
 * **Properties**:
 * - Kind: 1:1
 * - Flags: None
 * - 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: No
 *   - _Sent_ update: No
 * - Delivery receipts: No
 * - Reactions: No
 * - When rejected: N/A (ignored)
 * - Edit applies to: N/A
 * - Deletable by: N/A
 * - Send to Threema Gateway ID group creator: N/A
 *
 * When reflected from another device as an incoming message:
 *
 * 1. Run the _Common Request Profile Picture Receive Steps_.
 *
 * When receiving this message:
 *
 * 1. Run the _Common Request Profile Picture Receive Steps_.
 *
 * The following steps are defined as the _Common Request Profile Picture
 * Receive Steps_:
 *
 * 1. Purge the most recently distributed profile picture cache for the
 *    sender.
 */
export interface ContactRequestProfilePictureLike {}

/**
 * Encodable of {@link ContactRequestProfilePictureLike}.
 */
interface ContactRequestProfilePictureEncodable_ {}

/**
 * New-type for ContactRequestProfilePictureEncodable.
 */
export type ContactRequestProfilePictureEncodable = types.WeakOpaque<
    ContactRequestProfilePictureEncodable_,
    {readonly ContactRequestProfilePictureEncodable: unique symbol}
>;

/** @inheritdoc */
export class ContactRequestProfilePicture
    extends base.Struct
    implements ContactRequestProfilePictureLike
{
    private readonly _array: Uint8Array;

    /**
     * Create a ContactRequestProfilePicture from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a contact-request-profile-picture struct from an array.
     *
     * @param array Array to decode from.
     * @returns ContactRequestProfilePicture instance.
     */
    public static decode(array: Uint8Array): ContactRequestProfilePicture {
        return new ContactRequestProfilePicture(array);
    }

    /**
     * Encode a contact-request-profile-picture struct into an array.
     *
     * @param struct ContactRequestProfilePictureEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<ContactRequestProfilePictureEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        return array.subarray(0, 0);
    }

    /**
     * Get the amount of bytes that would be written when encoding a contact-request-profile-picture struct into an
     * array.
     *
     * @param struct ContactRequestProfilePictureEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<ContactRequestProfilePictureEncodable, 'byteLength'>,
    ): types.u53 {
        return 0;
    }

    /**
     * Create a snapshot of ContactRequestProfilePictureLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns ContactRequestProfilePictureLike snapshot.
     */
    public snapshot(): ContactRequestProfilePictureLike {
        return {};
    }

    /**
     * Create a clone of ContactRequestProfilePictureLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns ContactRequestProfilePictureLike clone.
     */
    public clone(): ContactRequestProfilePicture {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new ContactRequestProfilePicture(array);
    }
}

/**
 * Announces the group setup to all participants. The group creator is
 * always a member of the group and must not be included in the member
 * list.
 *
 * This is sent by the creator to create a new group, as well as update and
 * disband an existing group. The group creator sends this message to all
 * current (including those to be removed) and newly added group members.
 * The group creator may also send this to a single receiver in special
 * cases.
 *
 * Since the group creator is not allowed to leave the group, the only way
 * for it to stop being a member is by sending a `group-setup` with an
 * empty members list and thereby disbanding the group.
 *
 * **Properties**:
 * - Kind: Group
 * - Flags: None
 * - User profile distribution: Yes
 * - Exempt from blocking: Yes
 * - Implicit _direct_ contact creation: Yes
 * - Protect against replay: Yes
 * - Unarchive: No¹
 * - Bump _last update_: No²
 * - Reflect:
 *   - Incoming: Yes
 *   - Outgoing: Yes
 *   - _Sent_ update: No
 * - Delivery receipts: N/A
 * - Reactions: No
 * - When rejected: N/A³
 * - Edit applies to: N/A (can just send another `group-setup`)
 * - Deletable by: N/A (can just send another `group-setup`)
 * - Send to Threema Gateway ID group creator: N/A
 *
 * ¹: A newly created group's conversation visibility is implicitly _normal_
 * and therefore not _archived_. For the sake of simplicity and
 * sender/receiver symmetry, further updates to the group should not alter
 * the conversation visibility.
 *
 * ²: A newly created group is implicitly created with _last update_ set to
 * the current timestamp. For the sake of simplicity and sender/receiver
 * symmetry, no further updates to the group should bump _last update_.
 *
 * ³: For the group creator it will be handled as if `group-sync-request` was
 * received, re-sending the group state, implicitly triggered by FS `Reject`
 * receive steps.
 *
 * When receiving this message as a group message (wrapped by
 * [`group-creator-container`](ref:e2e.group-creator-container)):
 *
 * 1. Let `members` be the given member list. Remove all duplicate entries
 *    from `members`. Remove the sender (creator) from `members` if present.
 * 2. Look up the group.
 * 3. If the group could be found:
 *    1. Let `group` be a snapshot of the current group state.
 *    2. If the group is marked as _left_ and `members` is empty (i.e. no
 *       change), discard the message and abort these steps.
 *    3. If the group is not marked as _left_:
 *       1. Let `current-members` be a copy of `group.members`.
 *       2. Add the user to `current-members`.
 *       3. If `current-members` equals `members` (i.e. no change), discard
 *          the message and abort these steps.
 * 4. If `members` does not include the user:
 *    1. If the group could not be found, discard the message and abort these
 *       steps.
 *    2. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
 *       precondition:
 *       1. If the group does not exist or the group is marked as _left_,
 *          discard the message and abort these steps.
 *    3. (MD) Reflect a `GroupSync.Update` with `group` set to contain the
 *       `user_state` set to `KICKED`.
 *    4. (MD) Commit the transaction and await acknowledgement.
 *    5. If the user is currently participating in a group call of this
 *       group, trigger leaving the call.
 *    6. Mark the group as _left_.
 *    7. Persist the previous member setup so that the group can be cloned.
 *    8. Run the _Rejected Messages Refresh Steps_ for the group.
 * 5. If `members` includes the user.
 *    1.  (MD) Begin a transaction with scope `GROUP_SYNC` and the following
 *        precondition:
 *        1. If the sender (creator) contact does not exist, log an error,
 *           discard the message and abort these steps.
 *    2.  Run the _Valid Contacts Lookup Steps_ for `members` and
 *        overwrite `members` with the result.
 *    3.  For each `contact-or-init` of `members`:
 *        1. If `contact-or-init` indicates that the _contact is the user_,
 *           remove the entry from `members` and abort these sub-steps.
 *        2. If `contact-or-init` indicates that the _contact is invalid_,
 *           remove the entry from `members`, log a warning and abort these
 *           sub-steps.
 *    4.  (MD) Let `pending-reflect-acks` be an empty list.
 *    5.  For each `contact-or-init` of `members`:
 *        1. If `contact-or-init` is an existing contact, abort these
 *           sub-steps.
 *        2. (MD) Reflect a `ContactSync.Create` with `contact` set from
 *           `contact-or-init` and the following additional properties:
 *           - `created_at` set to now,
 *           - `acquaintance_level` set to `GROUP`,
 *           - all policies and categories set to their defaults.
 *        3. (MD) Add the pending reflect acknowledgement to
 *           `pending-reflect-acks`.
 *    6.  (MD) Await all `pending-reflect-acks`.
 *    7.  Let `added-members` be a copy of `members`.
 *    8.  Let `group` be a snapshot of the current group state or undefined
 *        if the group does not exist.
 *    9.  If `group` is not defined:
 *        1. Let `removed-members` be an empty list.
 *        2. (MD) Reflect a `GroupSync.Create` with `group` set to contain:
 *           - `group_identity`,
 *           - `created_at`,
 *           - `name` empty,
 *           - `user_state` set to `MEMBER`,
 *           - `profile_picture` empty,
 *           - `member_identities` from `members`,
 *           - all policies and categories set to their defaults.
 *    10. If `group` is defined:
 *        1. Remove all members from `added-members` that are in
 *           `group.members`.
 *        2. Let `removed-members` be a copy of `group.members`.
 *        3. Remove all members from `removed-members` that are in `members`.
 *        4. (MD) Reflect a `GroupSync.Update` with `member_state_changes`
 *           constructed from `added-members` and `removed-members` and
 *           `group` set to contain the following additional properties:
 *           - `user_state` set to `MEMBER`,
 *           - `member_identities` from `members`.
 *    11. (MD) Commit the transaction and await acknowledgement.
 *    12. If the user is currently participating in a group call of this
 *        group, remove all `removed-members` participants from the group
 *        call (handle them as if they left the call) and unblock all pending
 *        group call flows for `added-members`.
 *    13. Persist newly added contacts from `members`.
 *    14. Persist the newly created group or the member changes to the group.
 *        If the group was previously marked as _left_, remove the _left_
 *        mark.
 *    15. TODO(SE-510): Schedule fetching gateway-defined profile picture
 *        here for each newly added contact from `members`, if necessary.
 *    16. If `added-members` or `removed-members` is not empty, run the
 *        _Rejected Messages Refresh Steps_ for the group.
 */
export interface GroupSetupLike {
    /**
     * A set of Threema IDs defining group membership. The creator's
     * Threema ID is always inferred and must not be included in this set.
     */
    readonly members: Iterable<Uint8Array>;
}

/**
 * Encodable of {@link GroupSetupLike}.
 */
interface GroupSetupEncodable_ {
    /**
     * 'members' field value or encoder. See {@link GroupSetupLike#members} for
     * the field's description.
     */
    readonly members: types.BoundedIterable<Uint8Array | types.ByteLengthEncoder>;
}

/**
 * New-type for GroupSetupEncodable.
 */
export type GroupSetupEncodable = types.WeakOpaque<
    GroupSetupEncodable_,
    {readonly GroupSetupEncodable: unique symbol}
>;

/** @inheritdoc */
export class GroupSetup extends base.Struct implements GroupSetupLike {
    private readonly _array: Uint8Array;

    /**
     * Create a GroupSetup from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a group-setup struct from an array.
     *
     * @param array Array to decode from.
     * @returns GroupSetup instance.
     */
    public static decode(array: Uint8Array): GroupSetup {
        return new GroupSetup(array);
    }

    /**
     * Encode a group-setup struct into an array.
     *
     * @param struct GroupSetupEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<GroupSetupEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        let offset = 0;

        // Encode `members`
        offset += utils.encodeIterable(struct.members, offset, 8, (o, v) => {
            utils.encodeBytes(v, array, o);
        });

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a group-setup struct into an
     * array.
     *
     * @param struct GroupSetupEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<GroupSetupEncodable, 'byteLength'>,
    ): types.u53 {
        let offset = 0;
        offset += struct.members.length * 8;
        return offset;
    }

    /**
     * 'members' field accessor. See {@link GroupSetupLike#members} for the
     * field's description.
     */
    public get members(): Iterable<Uint8Array> {
        return utils.decodeIterable(this._array, 0, 8, (o) => this._array.subarray(o, o + 8));
    }

    /**
     * Create a snapshot of GroupSetupLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns GroupSetupLike snapshot.
     */
    public snapshot(): GroupSetupLike {
        return {
            members: this.members,
        };
    }

    /**
     * Create a clone of GroupSetupLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns GroupSetupLike clone.
     */
    public clone(): GroupSetup {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new GroupSetup(array);
    }
}

/**
 * Name (or rename) a group. Sent to all group members when the group is
 * being created for the first time or the group is being renamed. May also
 * be sent to a single receiver as a response to a
 * [`group-sync-request`](ref:e2e.group-sync-request) message.
 *
 * **Properties**:
 * - Kind: Group
 * - Flags: None
 * - User profile distribution: No
 * - Exempt from blocking: Yes
 * - 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
 * - When rejected: N/A¹
 * - Edit applies to: N/A (can just send another `group-name`)
 * - Deletable by: N/A (can just send an empty name)
 * - Send to Threema Gateway ID group creator: N/A
 *
 * ¹: For the group creator it will be handled as if `group-sync-request` was
 * received, re-sending the group name, implicitly triggered by FS `Reject`
 * receive steps.
 *
 * When receiving this message as a group message (wrapped by
 * [`group-creator-container`](ref:e2e.group-creator-container)):
 *
 * 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the
 *    message has been discarded, abort these steps.
 * 2. Let `group` be a snapshot of the current group state.
 * 3. If `group.name` equals `name` (i.e. no change), discard the message and
 *    abort these steps.
 * 4. (MD) Run the following sub-steps:
 *    1. Begin a transaction with scope `GROUP_SYNC` and the following
 *       precondition:
 *       1. If the group does not exist or the group is marked as _left_, log
 *          a warning that a group sync race occurred, discard the message
 *          and abort these steps.
 *    2. Let `group` be a snapshot of the current group state.
 *    3. If `group.name` equals `name`, log a warning that a group sync race
 *       occurred.
 *    4. Reflect a `GroupSync.Update` with `group` set to contain
 *       `name` set to `name`.
 *    5. Commit the transaction and await acknowledgement.
 * 5. Update the group's name with `name`.
 */
export interface GroupNameLike {
    /**
     * UTF-8 encoded string containing the group's name.
     */
    readonly name: Uint8Array;
}

/**
 * Encodable of {@link GroupNameLike}.
 */
interface GroupNameEncodable_ {
    /**
     * 'name' field value or encoder. See {@link GroupNameLike#name} for
     * the field's description.
     */
    readonly name: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for GroupNameEncodable.
 */
export type GroupNameEncodable = types.WeakOpaque<
    GroupNameEncodable_,
    {readonly GroupNameEncodable: unique symbol}
>;

/** @inheritdoc */
export class GroupName extends base.Struct implements GroupNameLike {
    private readonly _array: Uint8Array;

    /**
     * Create a GroupName from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a group-name struct from an array.
     *
     * @param array Array to decode from.
     * @returns GroupName instance.
     */
    public static decode(array: Uint8Array): GroupName {
        return new GroupName(array);
    }

    /**
     * Encode a group-name struct into an array.
     *
     * @param struct GroupNameEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<GroupNameEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        let offset = 0;

        // Encode `name`
        offset += utils.encodeBytes(struct.name, array, offset);

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a group-name struct into an
     * array.
     *
     * @param struct GroupNameEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<GroupNameEncodable, 'byteLength'>,
    ): types.u53 {
        let offset = 0;
        offset += utils.getByteLength(struct.name);
        return offset;
    }

    /**
     * 'name' field accessor. See {@link GroupNameLike#name} for the
     * field's description.
     */
    public get name(): Uint8Array {
        return this._array.subarray(0);
    }

    /**
     * Create a snapshot of GroupNameLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns GroupNameLike snapshot.
     */
    public snapshot(): GroupNameLike {
        return {
            name: this.name,
        };
    }

    /**
     * Create a clone of GroupNameLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns GroupNameLike clone.
     */
    public clone(): GroupName {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new GroupName(array);
    }
}

/**
 * Sent by a group member...
 *
 * * that is leaving the group. The message is sent to all other group
 *   members and the creator.
 * * in direct reply to a group message for a group that it has marked as
 *   left.
 *
 * Note: The group creator is not allowed to leave the group.
 *
 * **Properties**:
 * - Kind: Group
 * - Flags: None
 * - User profile distribution: No
 * - Exempt from blocking: Yes
 * - 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
 * - When rejected: N/A¹
 * - Edit applies to: N/A
 * - Deletable by: N/A
 * - Send to Threema Gateway ID group creator: Yes
 *
 * ¹: Re-send of `group-leave` implicitly triggered by FS `Reject` receive
 * steps due to _Common Group Receive Steps_ invocation.
 *
 * When receiving this message as a group message (wrapped by
 * [`group-member-container`](ref:e2e.group-member-container)):
 *
 * 1. If the sender is the creator of the group, log a warning, discard the
 *    message and abort these steps.
 * 2. Look up the group.
 * 3. If the group could not be found or is marked as _left_:
 *     1. If the user is the creator of the group (as alleged by the
 *        message), discard the message and abort these steps.
 *     2. Run the _Group Sync Request Steps_ for the group, discard the
 *        message and abort these steps.
 * 4. Let `group` be a snapshot of the current group state.
 * 5. If `group.members` does not include the sender, discard the message and
 *    abort these steps.
 * 6. (MD) Run the following sub-steps:
 *    1. Begin a transaction with scope `GROUP_SYNC` and the following
 *       precondition:
 *       1. If the group does not exist or the group is marked as _left_, log
 *          a warning that a group sync race occurred, discard the message
 *          and abort these steps.
 *    2. Let `group` be a snapshot of the current group state.
 *    3. If `group.members` does not include the sender, log a warning that a
 *       group sync race occurred.
 *    4. Let `updated-members` be a copy of `group.members`.
 *    5. Remove the sender from `updated-members`.
 *    6. Reflect a `GroupSync.Update` with `member_state_changes`
 *       set to the single entry of the sender leaving and `group` set to
 *       contain `member_identities` set from `updated-members`.
 *    7. Commit the transaction and await acknowledgement.
 * 7. If the user is currently participating in a group call of this group,
 *    remove the sender from the group call (handle it as if the sender left
 *    the call).
 * 8. Remove the sender from the group.
 * 9. Run the _Rejected Messages Refresh Steps_ for the group.
 */
export interface GroupLeaveLike {}

/**
 * Encodable of {@link GroupLeaveLike}.
 */
interface GroupLeaveEncodable_ {}

/**
 * New-type for GroupLeaveEncodable.
 */
export type GroupLeaveEncodable = types.WeakOpaque<
    GroupLeaveEncodable_,
    {readonly GroupLeaveEncodable: unique symbol}
>;

/** @inheritdoc */
export class GroupLeave extends base.Struct implements GroupLeaveLike {
    private readonly _array: Uint8Array;

    /**
     * Create a GroupLeave from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a group-leave struct from an array.
     *
     * @param array Array to decode from.
     * @returns GroupLeave instance.
     */
    public static decode(array: Uint8Array): GroupLeave {
        return new GroupLeave(array);
    }

    /**
     * Encode a group-leave struct into an array.
     *
     * @param struct GroupLeaveEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<GroupLeaveEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        return array.subarray(0, 0);
    }

    /**
     * Get the amount of bytes that would be written when encoding a group-leave struct into an
     * array.
     *
     * @param struct GroupLeaveEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<GroupLeaveEncodable, 'byteLength'>,
    ): types.u53 {
        return 0;
    }

    /**
     * Create a snapshot of GroupLeaveLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns GroupLeaveLike snapshot.
     */
    public snapshot(): GroupLeaveLike {
        return {};
    }

    /**
     * Create a clone of GroupLeaveLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns GroupLeaveLike clone.
     */
    public clone(): GroupLeave {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new GroupLeave(array);
    }
}

/**
 * Sent by a group member (or a device assuming to be part of the group) to
 * the group creator.
 *
 * **Properties**:
 * - Kind: Group
 * - Flags: None
 * - User profile distribution: No
 * - Exempt from blocking: Yes
 * - 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
 * - When rejected: N/A¹
 * - Edit applies to: N/A
 * - Deletable by: N/A
 * - Send to Threema Gateway ID group creator: Yes
 *
 * ¹: Implicitly ignored by FS `Reject` receive steps.
 *
 * The following steps are defined as the _Group Sync Request Steps_:
 *
 * 1. If the user is the creator of the group, log an error and abort these
 *    steps.
 * 2. If the group is marked as _recently resynced_ for the user, log a
 *    notice and abort these steps.¹
 * 3. Run the _Bundled Messages Send Steps_ with the following properties:
 *    - `id` set to a random message ID,
 *    - `created-at` set to the current timestamp,
 *    - `receivers` set to the creator of the group,
 *    - to construct this message (wrapped by
 *      [`group-member-container`](ref:e2e.group-member-container))
 * 4. Mark the group as _recently resynced_ for the user for 1h.
 *
 * When receiving this message as a group message (wrapped by
 * [`group-member-container`](ref:e2e.group-member-container)):
 *
 * 1. Look up the group. If the group could not be found, discard the message
 *    and abort these steps.
 * 2. If the user is not the creator of the group, discard the message and
 *    abort these steps.
 * 3. If the group is marked as _recently resynced_ for the sender, log a
 *    notice, discard the message and abort these steps.¹
 * 4. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
 *    precondition:
 *    1. If the group does not exist, log a warning that a group sync race
 *       occurred, discard the message and abort these steps.
 * 5. If the group is marked as _left_ or the sender is not a member of the
 *    group, run the _Bundled Messages Send Steps_ with the following
 *    properties:
 *    - `id` set to a random message ID,
 *    - `created-at` set to the current timestamp,
 *    - `receivers` set to the sender,
 *    - to construct a [`group-setup`](ref:e2e.group-setup) (wrapped by
 *      [`group-creator-container`](ref:e2e.group-creator-container)) with an
 *      empty members set.
 * 6. If the group is not marked as _left_ and the sender is a member of the
 *    group, run the _Active Group State Resync Steps_ with four random message
 *    IDs and `target-members` set to the sender.
 * 7. (MD) Commit the transaction and await acknowledgement.
 *
 * ¹: This is a precaution since a `group-sync-request` is automatically
 * triggered and creates an automatic response. This can easily lead to
 * message loops. Limiting `group-sync-request`s to once an hour per group
 * per sender/receiver breaks a potential infinite loop.
 */
export interface GroupSyncRequestLike {}

/**
 * Encodable of {@link GroupSyncRequestLike}.
 */
interface GroupSyncRequestEncodable_ {}

/**
 * New-type for GroupSyncRequestEncodable.
 */
export type GroupSyncRequestEncodable = types.WeakOpaque<
    GroupSyncRequestEncodable_,
    {readonly GroupSyncRequestEncodable: unique symbol}
>;

/** @inheritdoc */
export class GroupSyncRequest extends base.Struct implements GroupSyncRequestLike {
    private readonly _array: Uint8Array;

    /**
     * Create a GroupSyncRequest from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a group-sync-request struct from an array.
     *
     * @param array Array to decode from.
     * @returns GroupSyncRequest instance.
     */
    public static decode(array: Uint8Array): GroupSyncRequest {
        return new GroupSyncRequest(array);
    }

    /**
     * Encode a group-sync-request struct into an array.
     *
     * @param struct GroupSyncRequestEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<GroupSyncRequestEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        return array.subarray(0, 0);
    }

    /**
     * Get the amount of bytes that would be written when encoding a group-sync-request struct into an
     * array.
     *
     * @param struct GroupSyncRequestEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<GroupSyncRequestEncodable, 'byteLength'>,
    ): types.u53 {
        return 0;
    }

    /**
     * Create a snapshot of GroupSyncRequestLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns GroupSyncRequestLike snapshot.
     */
    public snapshot(): GroupSyncRequestLike {
        return {};
    }

    /**
     * Create a clone of GroupSyncRequestLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns GroupSyncRequestLike clone.
     */
    public clone(): GroupSyncRequest {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new GroupSyncRequest(array);
    }
}

/**
 * A control message from Threema Web, requesting a session to be resumed.
 *
 * **Properties (1:1)**:
 * - Kind: 1:1
 * - Flags:
 *   - `0x20`: Short-lived server queuing.
 * - 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 the sender is not `*3MAPUSH`, discard the message and abort these
 *    steps.
 * 2. Lookup the web client session associated to `wcs` and attempt to resume
 *    it.
 */
export interface WebSessionResumeLike {
    /**
     * UTF-8, JSON-encoded object with the following fields:
     *
     * - Webclient session (`'wcs'`): SHA256 hash (hex encoded) of the
     *   public permanent key of the session initiator, string.
     * - Affiliation ID (`'wca'`): An optional identifier for affiliating
     *   consecutive pushes, `string` or `null`.
     * - Affiliation ID (`'wct'`): Unix epoch timestamp of the request in
     *   seconds, `i64`.
     * - Protocol version (`'wcv'`): Version of the Threema Web protocol,
     *   `u16`.
     *
     * All fields must be part of the JSON object, even if their values are
     * nullable.
     */
    readonly pushPayload: Uint8Array;
}

/**
 * Encodable of {@link WebSessionResumeLike}.
 */
interface WebSessionResumeEncodable_ {
    /**
     * 'push-payload' field value or encoder. See {@link WebSessionResumeLike#pushPayload} for
     * the field's description.
     */
    readonly pushPayload: Uint8Array | types.ByteLengthEncoder;
}

/**
 * New-type for WebSessionResumeEncodable.
 */
export type WebSessionResumeEncodable = types.WeakOpaque<
    WebSessionResumeEncodable_,
    {readonly WebSessionResumeEncodable: unique symbol}
>;

/** @inheritdoc */
export class WebSessionResume extends base.Struct implements WebSessionResumeLike {
    private readonly _array: Uint8Array;

    /**
     * Create a WebSessionResume from an array for accessing properties.
     *
     * Note: When accessing, attributes will be decoded on-the-fly which may be expensive.
     */
    private constructor(array: Uint8Array) {
        super();
        this._array = array;
    }

    /**
     * Decode a web-session-resume struct from an array.
     *
     * @param array Array to decode from.
     * @returns WebSessionResume instance.
     */
    public static decode(array: Uint8Array): WebSessionResume {
        return new WebSessionResume(array);
    }

    /**
     * Encode a web-session-resume struct into an array.
     *
     * @param struct WebSessionResumeEncodable to encode.
     * @param array Array to encode into.
     * @returns A subarray of array containing the encoded struct.
     */
    public static encode(
        struct: types.EncoderPick<WebSessionResumeEncodable, 'encode'>,
        array: Uint8Array,
    ): Uint8Array {
        let offset = 0;

        // Encode `push-payload`
        offset += utils.encodeBytes(struct.pushPayload, array, offset);

        return array.subarray(0, offset);
    }

    /**
     * Get the amount of bytes that would be written when encoding a web-session-resume struct into an
     * array.
     *
     * @param struct WebSessionResumeEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<WebSessionResumeEncodable, 'byteLength'>,
    ): types.u53 {
        let offset = 0;
        offset += utils.getByteLength(struct.pushPayload);
        return offset;
    }

    /**
     * 'push-payload' field accessor. See {@link WebSessionResumeLike#pushPayload} for the
     * field's description.
     */
    public get pushPayload(): Uint8Array {
        return this._array.subarray(0);
    }

    /**
     * Create a snapshot of WebSessionResumeLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns WebSessionResumeLike snapshot.
     */
    public snapshot(): WebSessionResumeLike {
        return {
            pushPayload: this.pushPayload,
        };
    }

    /**
     * Create a clone of WebSessionResumeLike.
     *
     * Note: This is a deep-copy that will copy the underlying buffer.
     *
     * @returns WebSessionResumeLike clone.
     */
    public clone(): WebSessionResume {
        const array = new Uint8Array(this._array.byteLength);
        array.set(this._array);
        return new WebSessionResume(array);
    }
}
