// 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';

/**
 * ## Payload
 *
 * After the handshake process, payloads may be sent and received without any
 * strictly defined order.
 *
 * Note that payload structs are mandatory to encrypt and frame. To achieve
 * this, first wrap the payload struct in a
 * [`container`](ref:payload.container) struct, encrypt it and wrap the
 * encrypted bytes in a [`frame`](ref:payload.frame) struct.
 */

/**
 * Contains an encrypted [payload](ref:payload#payload) wrapped in a
 * [container](ref:payload.container).
 *
 * Direction: Client <-> Server
 */
export interface FrameLike {
    /**
     * Length of the `box` field.
     */
    readonly length: types.u16;

    /**
     * The encrypted [payload](ref:payload#payload).
     *
     * For messages from the server to the client, encrypted by:
     *
     *     XSalsa20-Poly1305(
     *       key=X25519HSalsa20(TSK.secret, TCK.public),
     *       nonce=SCK || u64-le(SSN+),
     *     )
     *
     * For messages from the  client to the server, encrypted by:
     *
     *     XSalsa20-Poly1305(
     *       key=X25519HSalsa20(TSK.secret, TCK.public),
     *       nonce=CCK || u64-le(CSN+),
     *     )
     */
    readonly box: Uint8Array;
}

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

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

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

    /**
     * Create a Frame 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 frame struct from an array.
     *
     * @param array Array to decode from.
     * @returns Frame instance.
     */
    public static decode(array: Uint8Array): Frame {
        return new Frame(array);
    }

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

        // Encode `box`
        const lengthValue = utils.encodeBytes(struct.box, array, offset);
        offset += lengthValue;

        // Encode `length`
        view.setUint16(0, lengthValue, true);

        return array.subarray(0, offset);
    }

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

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

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

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

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

/**
 * Contains an inner [payload](ref:payload#payload) struct.
 *
 * Direction: Client <-> Server
 */
export interface ContainerLike {
    /**
     * Type of the payload. Must correspond to the encoded payload struct
     * of the `data` field:
     *
     * - `0x00`: [`echo-request`](ref:payload.echo-request)
     * - `0x80`: [`echo-response`](ref:payload.echo-response)
     * - `0x01`: outgoing [`legacy-message`](ref:payload.legacy-message) or
     *   [`message-with-metadata-box`](ref:payload.message-with-metadata-box)
     * - `0x81`: outgoing [`message-ack`](ref:payload.message-ack)
     * - `0x02`: incoming [`legacy-message`](ref:payload.legacy-message) or
     *   [`message-with-metadata-box`](ref:payload.message-with-metadata-box)
     * - `0x82`: incoming [`message-ack`](ref:payload.message-ack)
     * - `0x03`: [`unblock-incoming-messages`](ref:payload.unblock-incoming-messages)
     * - `0x20`: [`set-push-notification-token`](ref:payload.set-push-notification-token)
     * - `0x21`: (obsolete, formerly used by iOS to set a push filter)
     * - `0x22`: (obsolete, formerly used by iOS to set a push sound for contacts)
     * - `0x23`: (obsolete, formerly used by iOS to set a push sound for groups)
     * - `0x24`: high-priority token for notifications that require
     *   immediate delivery (e.g. for calls) using the same struct as
     *   [`set-push-notification-token`](ref:payload.set-push-notification-token)
     * - `0x25`: [`delete-push-notification-token`](ref:payload.delete-push-notification-token)
     * - `0x30`: [`set-connection-idle-timeout`](ref:payload.set-connection-idle-timeout)
     * - `0x31`: (obsolete, formerly used to ensure that a push message is
     *    sent for all messages, regardless of the flag)
     * - `0xd0`: [`queue-send-complete`](ref:payload.queue-send-complete)
     * - `0xd1`: (obsolete, formerly used for a function similar to the
     *    device cookie)
     * - `0xd2`: [`device-cookie-change-indication`](ref:payload.device-cookie-change-indication)
     * - `0xd3`: [`clear-device-cookie-change-indication`](ref:payload.clear-device-cookie-change-indication)
     * - `0xe0`: [`close-error`](ref:payload.close-error)
     * - `0xe1`: [`alert`](ref:payload.alert)
     */
    readonly type: types.u8;

    /**
     * Reserved, currently all zeroes.
     */
    readonly reserved: Uint8Array;

    /**
     * Inner payload. Needs to be parsed according to the `type` field.
     */
    readonly data: 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;

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

    /**
     * 'data' field value or encoder. See {@link ContainerLike#data} for
     * the field's description.
     */
    readonly data: 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 = 4;

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

        // Encode `reserved`
        utils.encodeBytes(struct.reserved, array, 1);

        // Encode `data`
        offset += utils.encodeBytes(struct.data, 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 = 4;
        offset += utils.getByteLength(struct.data);
        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]);
    }

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

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

    /**
     * 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,
            reserved: this.reserved,
            data: this.data,
        };
    }

    /**
     * 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);
    }
}

/**
 * An echo request to be answered by a corresponding echo response.
 *
 * Can be used for connection keep-alive or RTT estimation.
 *
 * Direction: Client <-> Server
 *
 * [//]: # "TODO(SE-128)"
 */
export interface EchoRequestLike {
    /**
     * Data to be echoed back in the echo response.
     */
    readonly data: Uint8Array;
}

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

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

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

    /**
     * Create a EchoRequest 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 echo-request struct from an array.
     *
     * @param array Array to decode from.
     * @returns EchoRequest instance.
     */
    public static decode(array: Uint8Array): EchoRequest {
        return new EchoRequest(array);
    }

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

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

        return array.subarray(0, offset);
    }

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

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

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

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

/**
 * An echo response corresponding to an echo request.
 *
 * Direction: Client <-> Server
 *
 * [//]: # "TODO(SE-128)"
 */
export interface EchoResponseLike {
    /**
     * Data echoed back from the echo request.
     */
    readonly data: Uint8Array;
}

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

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

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

    /**
     * Create a EchoResponse 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 echo-response struct from an array.
     *
     * @param array Array to decode from.
     * @returns EchoResponse instance.
     */
    public static decode(array: Uint8Array): EchoResponse {
        return new EchoResponse(array);
    }

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

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

        return array.subarray(0, offset);
    }

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

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

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

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

/**
 * An end-to-end encrypted Threema message.
 *
 * Direction: Client <-> Server
 *
 * Note: This payload is deprecated and may be phased out eventually. It
 *       will only be used in case the
 *       [`message-payload-version`](ref:handshake.message-payload-version)
 *       was not present during login or was explicitly set to the version
 *       `0x00`.
 *
 * Conversion to [`message-with-metadata-box`](ref:payload.message-with-metadata-box):
 *
 * - Copy `legacy-message.sender-nickname` to
 *   `message-with-metadata-box.legacy-sender-nickname`
 * - Copy all other fields of `legacy-message` to their respective
 *   counterparts in `message-with-metadata-box`
 * - Set `message-with-metadata-box.metadata-length` to `0`
 * - Omit `message-with-metadata-box.metadata-container` (i.e. set it to
 *   contain 0 bytes)
 * - Copy `legacy-message.message-nonce` to
 *   `message-with-metadata-box.message-and-metadata-nonce`.
 *
 * When sending or receiving this payload, convert it to a
 * `message-with-metadata-box` and handle it as defined by that struct.
 *
 * [//]: # "TODO(SE-128)"
 */
export interface LegacyMessageLike {
    /**
     * The sender's Threema ID.
     */
    readonly senderIdentity: Uint8Array;

    /**
     * The receiver's Threema ID.
     */
    readonly receiverIdentity: Uint8Array;

    /**
     * Unique message ID for each sender/receiver pair.
     *
     * Used for duplicate detection and for quotes.
     *
     * Messages sent in a group must have the same message ID for each group
     * member.
     */
    readonly messageId: types.u64;

    /**
     * Unix timestamp in seconds for when the message has been created.
     *
     * Messages sent in a group must have the same timestamp for each group
     * member.
     *
     * However, the server overrides this timestamp with the current time if
     *
     * - the declared timestamp is in the future, or
     * - the _short-lived server queuing_ flag was set (`0x20`).
     *
     * Note: The original timestamp is still available in an attached
     *       `csp-e2e.MessageMetadata`.
     */
    readonly createdAt: types.u32;

    /**
     * Flags:
     *
     * - `0x01`: Send push notification. The server will send a push message
     *   to the receiver of the message. Only use this for messages that
     *   require a notification. For example, do not set this for delivery
     *   receipts.
     * - `0x02`: No server queuing. Use this for messages that can be
     *   discarded by the chat server in case the receiver is not connected
     *   to the chat server, e.g. the _typing_ indicator.
     * - `0x04`: No server acknowledgement. Use this for messages where reliable
     *   delivery and acknowledgement is not essential, e.g. the _typing_
     *   indicator. Will not be acknowledged by the chat server when sending.
     *   No acknowledgement should be sent by the receiver to the chat
     *   server.
     * - `0x10`: Reserved (formerly _group message marker_).
     * - `0x20`: Short-lived server queuing. Messages with this flag will
     *   only be queued for 60 seconds.
     * - `0x80`: No automatic delivery receipts. A receiver of a message with this
     *   flag must not send automatic delivery receipt of type _received_
     *   (`0x01`) or _read_ (`0x02`). This is not used by the apps but can be
     *   used by Threema Gateway IDs which do not necessarily want a delivery
     *   receipt for a message.
     */
    readonly flags: types.u8;

    /**
     * Reserved, must be set to zero.
     */
    readonly reserved: types.u8;

    /**
     * Reserved for header compatibility with metadata message.
     * Must be set to zero by legacy clients.
     */
    readonly reservedMetadataLength: Uint8Array;

    /**
     * The sender's public nickname, padded with zeroes if needed.
     */
    readonly senderNickname: Uint8Array;

    /**
     * Nonce used for the message box.
     */
    readonly messageNonce: Uint8Array;

    /**
     * The message, end-to-end encrypted by:
     *
     *     XSalsa20-Poly1305(
     *       key=X25519HSalsa20(<sender.CK>.secret, <receiver.CK>.public),
     *       nonce=<message-nonce>,
     *     )
     */
    readonly messageBox: Uint8Array;
}

/**
 * Encodable of {@link LegacyMessageLike}.
 */
interface LegacyMessageEncodable_ {
    /**
     * 'sender-identity' field value or encoder. See {@link LegacyMessageLike#senderIdentity} for
     * the field's description.
     */
    readonly senderIdentity: Uint8Array | types.ByteLengthEncoder;

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

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

    /**
     * 'created-at' field value or encoder. See {@link LegacyMessageLike#createdAt} for
     * the field's description.
     */
    readonly createdAt: types.u32;

    /**
     * 'flags' field value or encoder. See {@link LegacyMessageLike#flags} for
     * the field's description.
     */
    readonly flags: types.u8;

    /**
     * 'reserved' field value or encoder. See {@link LegacyMessageLike#reserved} for
     * the field's description.
     */
    readonly reserved: types.u8;

    /**
     * 'reserved-metadata-length' field value or encoder. See {@link LegacyMessageLike#reservedMetadataLength} for
     * the field's description.
     */
    readonly reservedMetadataLength: Uint8Array | types.ByteLengthEncoder;

    /**
     * 'sender-nickname' field value or encoder. See {@link LegacyMessageLike#senderNickname} for
     * the field's description.
     */
    readonly senderNickname: Uint8Array | types.ByteLengthEncoder;

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

    /**
     * 'message-box' field value or encoder. See {@link LegacyMessageLike#messageBox} for
     * the field's description.
     */
    readonly messageBox: Uint8Array | types.ByteLengthEncoder;
}

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

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

    /**
     * Create a LegacyMessage 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 legacy-message struct from an array.
     *
     * @param array Array to decode from.
     * @returns LegacyMessage instance.
     */
    public static decode(array: Uint8Array): LegacyMessage {
        return new LegacyMessage(array);
    }

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

        // Encode `sender-identity`
        utils.encodeBytes(struct.senderIdentity, array, 0);

        // Encode `receiver-identity`
        utils.encodeBytes(struct.receiverIdentity, array, 8);

        // Encode `message-id`
        view.setBigUint64(16, struct.messageId, true);

        // Encode `created-at`
        view.setUint32(24, struct.createdAt, true);

        // Encode `flags`
        array[28] = struct.flags;

        // Encode `reserved`
        array[29] = struct.reserved;

        // Encode `reserved-metadata-length`
        utils.encodeBytes(struct.reservedMetadataLength, array, 30);

        // Encode `sender-nickname`
        utils.encodeBytes(struct.senderNickname, array, 32);

        // Encode `message-nonce`
        utils.encodeBytes(struct.messageNonce, array, 64);

        // Encode `message-box`
        offset += utils.encodeBytes(struct.messageBox, array, offset);

        return array.subarray(0, offset);
    }

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

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

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

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

    /**
     * 'created-at' field accessor. See {@link LegacyMessageLike#createdAt} for the
     * field's description.
     */
    public get createdAt(): types.u32 {
        return this._view.getUint32(24, true);
    }

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

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

    /**
     * 'reserved-metadata-length' field accessor. See {@link LegacyMessageLike#reservedMetadataLength} for the
     * field's description.
     */
    public get reservedMetadataLength(): Uint8Array {
        const offset = 30;
        return this._array.subarray(offset, offset + 2);
    }

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

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

    /**
     * 'message-box' field accessor. See {@link LegacyMessageLike#messageBox} for the
     * field's description.
     */
    public get messageBox(): Uint8Array {
        return this._array.subarray(88);
    }

    /**
     * Create a snapshot of LegacyMessageLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns LegacyMessageLike snapshot.
     */
    public snapshot(): LegacyMessageLike {
        return {
            senderIdentity: this.senderIdentity,
            receiverIdentity: this.receiverIdentity,
            messageId: this.messageId,
            createdAt: this.createdAt,
            flags: this.flags,
            reserved: this.reserved,
            reservedMetadataLength: this.reservedMetadataLength,
            senderNickname: this.senderNickname,
            messageNonce: this.messageNonce,
            messageBox: this.messageBox,
        };
    }

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

/**
 * An end-to-end encrypted Threema message with additional end-to-end
 * encrypted metadata.
 *
 * Direction: Client <-> Server
 *
 * Note: This payload will only be used in case the
 *       [`message-payload-version`](ref:handshake.message-payload-version)
 *       was set to version `0x01`.
 *
 * Conversion to [`legacy-message`](ref:payload.legacy-message):
 *
 * - Discard `message-with-metadata-box.metadata-length` and
 *   `message-with-metadata-box.metadata-container`
 * - Copy `message-with-metadata-box.legacy-sender-nickname` to
 *   `legacy-message.sender-nickname`
 * - Copy `message-with-metadata-box.message-and-metadata-nonce` to
 *   `legacy-message.message-nonce`.
 * - Copy all other fields of `message-with-metadata-box` to their
 *   respective counterparts in `legacy-message`
 *
 * Creating this payload is only allowed as part of the _Bundled Messages
 * Send Steps_.
 *
 * When receiving this payload:
 *
 * 1.  (MD) If the device is currently not declared _leader_, exceptionally
 *     abort these steps and the connection.
 * 2.  If the nonce of `message-and-metadata-nonce` has been used before, log
 *     a warning, _Acknowledge_ and discard the message and abort these steps.
 * 3.  If `receiver-identity` does not equal the user's Threema ID, log a
 *     warning, _Acknowledge_ and discard the message and abort these steps.
 * 4.  Run the _Valid Contacts Lookup Steps_ for `sender-identity` and let
 *     `contact-or-init` be the result.
 * 5.  If `contact-or-init` indicates that the _contact is the user_ or that
 *     the _contact is invalid_, log a warning, _Acknowledge_ and discard the
 *     message and abort these steps.
 * 6.  If `metadata-length` is greater zero, decrypt the `metadata-container`
 *     and let `outer-metadata` be the result. If this fails, log a warning,
 *     _Acknowledge_ and discard the message and abort these steps.
 * 7.  Decrypt the `message-box`, decode it to a
 *     [`container`](ref:payload.container) struct and let `outer` be the
 *     result. If this fails, log a warning, _Acknowledge_ and discard the
 *     message and abort these steps.
 * 8.  If `outer.type` is `0xff`, log a warning, _Acknowledge_ and
 *     discard the message and abort these steps. (Legacy logic, may be
 *     removed in the future.)
 * 9.  If `outer.type` is unknown, log a notice, _Acknowledge_ and
 *     discard the message and abort these steps.
 * 10. Decode `outer.padded-data` into the message type associated to
 *     `outer.type` and let `outer-message` be the result. If this fails, log
 *     a warning, _Acknowledge_ and discard the message and abort these
 *     steps.
 * 11. If `outer.type` is not `0xa0`, let `inner-metadata` be
 *     `outer-metadata`, let `inner-type` be `outer.type` and let
 *     `inner-message` be `outer-message`.
 * 12. If `outer.type` is `0xa0`:
 *     1. Run the receive steps associated to
 *        `csp-e2e-fs.Envelope` with the decoded `outer-message` and let
 *        `inner-metadata`, `inner-type`, `inner-message` and `fs-commit-fn`
 *        be the result. If this fails, exceptionally abort these steps and
 *        the connection. If the message has been discarded, _Acknowledge_
 *        and abort these steps.
 *     2. If `inner-metadata` is not defined, set `inner-metadata` to
 *        `outer-metadata`.
 * 13. If `message-id` does not equal `inner-metadata.message_id`, log a
 *     warning, _Acknowledge_ and discard the message and abort these steps.
 * 14. If `message-id` refers to a message that has been received previously
 *     from `sender-identity` (including group messages), log a warning,
 *     _Acknowledge_ and discard the message and abort these steps.
 * 15. If `inner-type` is not defined (i.e. handling an FS control message),
 *     log a notice, _Acknowledge_ and discard the message and abort these
 *     steps.
 * 16. If `inner-type` is unknown, log a notice, _Acknowledge_ and
 *     discard the message and abort these steps.
 * 17. If `inner-type` is `0xa0` (i.e. FS encapsulation within FS
 *     encapsulation), log a warning, _Acknowledge_ and discard the message
 *     and abort these steps.
 * 18. If `inner-type` is not exempted from blocking, run the _Identity
 *     Blocked Steps_ for `sender-identity`. If the result indicates that
 *     `sender-identity` is blocked, _Acknowledge_ and discard the message
 *     and abort these steps.
 * 19. If `sender-identity` equals `*3MAPUSH`:
 *     1. If `inner-type` is not `0xfe`, log a warning,
 *        _Acknowledge_ and discard the message and abort these steps.
 *     2. Run the receive steps associated to `inner-type` with
 *        `inner-message`. If this fails, exceptionally abort these steps and
 *        the connection. If the message has been discarded, _Acknowledge_
 *        the message and abort these steps.
 * 20. If `sender-identity` is not a _Special Contact_:
 *     1. If `inner-metadata.nickname` is defined, let `nickname` be the
 *        value of `inner-metadata.nickname`.¹
 *     2. If `inner-metadata` is not defined and _User Profile Distribution_
 *        was expected for `inner-type`, let `nickname` be the result of
 *        decoding the plaintext `legacy-sender-nickname`.¹
 *     3. If `nickname` is present, trim any excess whitespaces from the
 *        beginning and the end of `nickname`.
 *     4. If `contact-or-init` does not contain an existing contact:
 *        1. If `inner-type` does not require to create an implicit
 *           _direct_ contact, log a notice, _Acknowledge_ and discard the
 *           message and abort these steps.
 *        2. (MD) Run the following sub-steps (labelled _add-contact_):
 *           1. Begin a transaction with scope `CONTACT_SYNC` and the
 *              following precondition:
 *              1. If the contact for `sender-identity` exists, abort the
 *                 _add-contact_ sub-steps.
 *           2. Reflect a `ContactSync.Create` with `contact` set from
 *              `contact-or-init` and the following additional properties:
 *              - `created_at` set to now,
 *              - `nickname` set to `nickname`,
 *              - `acquaintance_level` set to `DIRECT`,
 *              - all policies and categories set to their defaults.
 *           3. Commit the transaction and await acknowledgement.
 *        3. If the contact for `sender-identity` does not exist, persist a
 *           new contact from `contact-or-init` and `nickname`.
 *        4. TODO(SE-510): Schedule fetching gateway-defined profile picture
 *           here, if contact was added and if necessary.
 *     5. Lookup the contact associated to `sender-identity` and let
 *        `contact` be the result (at this point, `contact` must exist).
 *     6. (MD) If the contact's nickname is different to `nickname`:
 *         1. Begin a transaction with scope `CONTACT_SYNC` and the
 *            following precondition:
 *            1. If the contact no longer exists, log an error and
 *               exceptionally abort these steps and the connection.
 *         2. Reflect a `ContactSync.Update` with `contact` including the
 *            new `nickname`.
 *         3. Commit the transaction and await acknowledgement.
 *     7. Update the contact's nickname with `nickname`. Remove the
 *        contact's nickname if `nickname` is empty.
 *     8. Run the receive steps associated to `inner-type` with
 *        `inner-message`. If this fails, exceptionally abort these steps and
 *        the connection. If the message has been discarded, _Acknowledge_
 *        the message and abort these steps.
 * 21. (MD) If the properties associated to `inner-type` require
 *     reflecting incoming messages, reflect a `d2d.IncomingMessage` from
 *     `outer-type` and `outer-message` and the associated conversation to
 *     other devices and wait for reflection acknowledgement.² If this fails,
 *     exceptionally abort these steps and the connection.³
 * 22. _Acknowledge_ the message.⁴
 * 23. If the properties associated to `type` do not require sending
 *     automatic delivery receipts or `flags` contains the _no
 *     automatic delivery receipts_ (`0x80`) flag, abort these steps.
 * 24. 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 [`delivery-receipt`](ref:e2e.delivery-receipt)
 *       message with status _received_ (`0x01`) and the respective
 *       `message-id`.
 *
 * ¹: Note that the `nickname` of `MessageMetadata` may be undefined (leading
 * to no changes) or defined but explicitly empty (leading to the nickname of
 * the contact being removed) which is an important semantic difference.
 * Unlike the legacy nickname field which always contains a value and
 * therefore cannot represent this semantic difference without having to
 * check whether _User Profile Distribution_ was required for the type.
 *
 * ²: We reflect the **outer** message container depending on the unwrapped
 * **inner** message type, so the forward security properties are untouched
 * and all other devices need to go through the same process.
 *
 * ³: Reflection needs to happen after the message has been processed and all
 * side effects have been applied. Otherwise, if the receive process is
 * interrupted and another device takes over, it would discard the message as
 * a duplicate.
 *
 * ⁴: Because the message is already acknowledged towards the server here,
 * the following steps may not get executed at all if the connection drops
 * or the execution fails. All following steps must therefore be
 * non-critical.
 *
 * The following steps are defined as _Acknowledge_ steps for an incoming
 * message:
 *
 * 1. If the steps for this message have already been invoked once, abort
 *    these steps.
 * 2. If `flags` does not contain the _no server acknowledgement_ (`0x04`)
 *    flag, send a [`message-ack`](ref:payload.message-ack) payload to the
 *    chat server with the respective `message-id`.
 * 3. If the properties associated to `inner-type` require protection against
 *    replay, mark the nonce of `message-and-metadata-nonce` as used.
 * 4. If `fs-commit-fn` is defined, run it.
 *
 * [//]: # "TODO(SE-128)"
 */
export interface MessageWithMetadataBoxLike {
    /**
     * The sender's Threema ID.
     */
    readonly senderIdentity: Uint8Array;

    /**
     * The receiver's Threema ID.
     */
    readonly receiverIdentity: Uint8Array;

    /**
     * Unique message ID for each sender/receiver pair.
     *
     * Used for duplicate detection and for quotes.
     *
     * Messages sent in a group must have the same message ID for each group
     * member.
     */
    readonly messageId: types.u64;

    /**
     * Unix timestamp in seconds for when the message has been created.
     *
     * Messages sent in a group must have the same timestamp for each group
     * member.
     *
     * However, the server overrides this timestamp with the current time if
     *
     * - the declared timestamp is in the future, or
     * - the _short-lived server queuing_ flag was set (`0x20`).
     *
     * Note: The original timestamp is still available in an attached
     *       `csp-e2e.MessageMetadata`.
     */
    readonly createdAt: types.u32;

    /**
     * Flags:
     *
     * - `0x01`: Send push notification. The server will send a push message
     *   to the receiver of the message. Only use this for messages that
     *   require a notification. For example, do not set this for delivery
     *   receipts.
     * - `0x02`: No server queuing. Use this for messages that can be
     *   discarded by the chat server in case the receiver is not connected
     *   to the chat server, e.g. the _typing_ indicator.
     * - `0x04`: No server acknowledgement. Use this for messages where reliable
     *   delivery and acknowledgement is not essential, e.g. the _typing_
     *   indicator. Will not be acknowledged by the chat server when sending.
     *   No acknowledgement should be sent by the receiver to the chat
     *   server.
     * - `0x10`: Reserved (formerly _group message marker_).
     * - `0x20`: Short-lived server queuing. Messages with this flag will
     *   only be queued for 60 seconds.
     * - `0x80`: No automatic delivery receipts. A receiver of a message with this
     *   flag must not send automatic delivery receipt of type _received_
     *   (`0x01`) or _read_ (`0x02`). This is not used by the apps but can be
     *   used by Threema Gateway IDs which do not necessarily want a delivery
     *   receipt for a message.
     */
    readonly flags: types.u8;

    /**
     * Reserved, must be set to zero.
     */
    readonly reserved: types.u8;

    /**
     * Length of the metadata box. In case it is zero, no metadata is
     * present (for compatibility with clients using
     * [`legacy-message`](ref:payload.legacy-message)).
     *
     * Note: For outgoing messages, a metadata box should always be present.
     */
    readonly metadataLength: types.u16;

    /**
     * Backwards compatibility field for the sender's public nickname.
     * Padded with zeroes if needed.
     *
     * When sending a message towards a Threema Gateway ID (starts with a
     * `*`), add the same nickname as included in the encrypted metadata box.
     * Otherwise, set it to all zeroes.
     *
     * Note: The backwards compatibility for Threema Gateway IDs will be
     * removed eventually!
     */
    readonly legacySenderNickname: Uint8Array;

    /**
     * Metadata associated to the message. Must be ignored in case
     * `metadata-length` is zero.
     *
     * Message Metadata Key (`MMK`) derivation:
     *
     *     S = X25519HSalsa20(<sender.CK>.secret, <receiver.CK>.public)
     *     MMK = BLAKE2b(key=S, salt='mm', personal='3ma-csp')
     *
     * The encoded `csp-e2e.MessageMetadata` is then encrypted in the
     * following way:
     *
     *     XSalsa20-Poly1305(
     *       key=MMK,
     *       nonce=<message-with-metadata-box.message-and-metadata-nonce>,
     *     )
     */
    readonly metadataContainer: Uint8Array;

    /**
     * Nonce used for the message and the metadata box.
     */
    readonly messageAndMetadataNonce: Uint8Array;

    /**
     * The message, end-to-end encrypted by:
     *
     *     XSalsa20-Poly1305(
     *       key=X25519HSalsa20(<sender.CK>.secret, <receiver.CK>.public),
     *       nonce=<message-nonce>,
     *     )
     */
    readonly messageBox: Uint8Array;
}

/**
 * Encodable of {@link MessageWithMetadataBoxLike}.
 */
interface MessageWithMetadataBoxEncodable_ {
    /**
     * 'sender-identity' field value or encoder. See {@link MessageWithMetadataBoxLike#senderIdentity} for
     * the field's description.
     */
    readonly senderIdentity: Uint8Array | types.ByteLengthEncoder;

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

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

    /**
     * 'created-at' field value or encoder. See {@link MessageWithMetadataBoxLike#createdAt} for
     * the field's description.
     */
    readonly createdAt: types.u32;

    /**
     * 'flags' field value or encoder. See {@link MessageWithMetadataBoxLike#flags} for
     * the field's description.
     */
    readonly flags: types.u8;

    /**
     * 'reserved' field value or encoder. See {@link MessageWithMetadataBoxLike#reserved} for
     * the field's description.
     */
    readonly reserved: types.u8;

    /**
     * 'legacy-sender-nickname' field value or encoder. See {@link MessageWithMetadataBoxLike#legacySenderNickname} for
     * the field's description.
     */
    readonly legacySenderNickname: Uint8Array | types.ByteLengthEncoder;

    /**
     * 'metadata-container' field value or encoder. See {@link MessageWithMetadataBoxLike#metadataContainer} for
     * the field's description.
     */
    readonly metadataContainer: Uint8Array | types.ByteLengthEncoder;

    /**
     * 'message-and-metadata-nonce' field value or encoder. See {@link MessageWithMetadataBoxLike#messageAndMetadataNonce} for
     * the field's description.
     */
    readonly messageAndMetadataNonce: Uint8Array | types.ByteLengthEncoder;

    /**
     * 'message-box' field value or encoder. See {@link MessageWithMetadataBoxLike#messageBox} for
     * the field's description.
     */
    readonly messageBox: Uint8Array | types.ByteLengthEncoder;
}

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

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

    /**
     * Create a MessageWithMetadataBox 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 message-with-metadata-box struct from an array.
     *
     * @param array Array to decode from.
     * @returns MessageWithMetadataBox instance.
     */
    public static decode(array: Uint8Array): MessageWithMetadataBox {
        return new MessageWithMetadataBox(array);
    }

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

        // Encode `sender-identity`
        utils.encodeBytes(struct.senderIdentity, array, 0);

        // Encode `receiver-identity`
        utils.encodeBytes(struct.receiverIdentity, array, 8);

        // Encode `message-id`
        view.setBigUint64(16, struct.messageId, true);

        // Encode `created-at`
        view.setUint32(24, struct.createdAt, true);

        // Encode `flags`
        array[28] = struct.flags;

        // Encode `reserved`
        array[29] = struct.reserved;

        // Encode `legacy-sender-nickname`
        utils.encodeBytes(struct.legacySenderNickname, array, 32);

        // Encode `metadata-container`
        const metadataLengthValue = utils.encodeBytes(struct.metadataContainer, array, offset);
        offset += metadataLengthValue;

        // Encode `message-and-metadata-nonce`
        utils.encodeBytes(struct.messageAndMetadataNonce, array, offset);
        offset += 24;

        // Encode `message-box`
        offset += utils.encodeBytes(struct.messageBox, array, offset);

        // Encode `metadata-length`
        view.setUint16(30, metadataLengthValue, true);

        return array.subarray(0, offset);
    }

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

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

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

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

    /**
     * 'created-at' field accessor. See {@link MessageWithMetadataBoxLike#createdAt} for the
     * field's description.
     */
    public get createdAt(): types.u32 {
        return this._view.getUint32(24, true);
    }

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

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

    /**
     * 'metadata-length' field accessor. See {@link MessageWithMetadataBoxLike#metadataLength} for the
     * field's description.
     */
    public get metadataLength(): types.u16 {
        return this._view.getUint16(30, true);
    }

    /**
     * 'legacy-sender-nickname' field accessor. See {@link MessageWithMetadataBoxLike#legacySenderNickname} for the
     * field's description.
     */
    public get legacySenderNickname(): Uint8Array {
        const offset = 32;
        return this._array.subarray(offset, offset + 32);
    }

    /**
     * 'metadata-container' field accessor. See {@link MessageWithMetadataBoxLike#metadataContainer} for the
     * field's description.
     */
    public get metadataContainer(): Uint8Array {
        const offset = 64;
        return this._array.subarray(offset, offset + this.metadataLength);
    }

    /**
     * 'message-and-metadata-nonce' field accessor. See {@link MessageWithMetadataBoxLike#messageAndMetadataNonce} for the
     * field's description.
     */
    public get messageAndMetadataNonce(): Uint8Array {
        const offset = 64 + this.metadataLength;
        return this._array.subarray(offset, offset + 24);
    }

    /**
     * 'message-box' field accessor. See {@link MessageWithMetadataBoxLike#messageBox} for the
     * field's description.
     */
    public get messageBox(): Uint8Array {
        return this._array.subarray(88 + this.metadataLength);
    }

    /**
     * Create a snapshot of MessageWithMetadataBoxLike.
     *
     * Note: This is **not** a deep-copy, so byte arrays will still be views of the underlying
     *       buffer.
     *
     * @returns MessageWithMetadataBoxLike snapshot.
     */
    public snapshot(): MessageWithMetadataBoxLike {
        return {
            senderIdentity: this.senderIdentity,
            receiverIdentity: this.receiverIdentity,
            messageId: this.messageId,
            createdAt: this.createdAt,
            flags: this.flags,
            reserved: this.reserved,
            metadataLength: this.metadataLength,
            legacySenderNickname: this.legacySenderNickname,
            metadataContainer: this.metadataContainer,
            messageAndMetadataNonce: this.messageAndMetadataNonce,
            messageBox: this.messageBox,
        };
    }

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

/**
 * Acknowledges that a message has been received.
 *
 * Direction: Client <-> Server
 *
 * [//]: # "TODO(SE-128)"
 */
export interface MessageAckLike {
    /**
     * Identity of the sender for an incoming (`0x82`) message / of the
     * receiver for an outgoing (`0x81`) message.
     */
    readonly identity: Uint8Array;

    /**
     * Refers to the `message-id` of the acknowledged message.
     */
    readonly messageId: types.u64;
}

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

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

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

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

    /**
     * Create a MessageAck 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 message-ack struct from an array.
     *
     * @param array Array to decode from.
     * @returns MessageAck instance.
     */
    public static decode(array: Uint8Array): MessageAck {
        return new MessageAck(array);
    }

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

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

        // Encode `message-id`
        view.setBigUint64(8, struct.messageId, true);

        return array.subarray(0, 16);
    }

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

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

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

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

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

/**
 * Unblock incoming messages from the server. Sent by a multi-device capable
 * client once it is nominated to receive incoming messages.
 *
 * Direction: Client --> Server
 *
 * [//]: # "TODO(SE-128)"
 */
export interface UnblockIncomingMessagesLike {}

/**
 * Encodable of {@link UnblockIncomingMessagesLike}.
 */
interface UnblockIncomingMessagesEncodable_ {}

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

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

    /**
     * Create a UnblockIncomingMessages 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 unblock-incoming-messages struct from an array.
     *
     * @param array Array to decode from.
     * @returns UnblockIncomingMessages instance.
     */
    public static decode(array: Uint8Array): UnblockIncomingMessages {
        return new UnblockIncomingMessages(array);
    }

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

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

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

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

/**
 * Sets the push notification token to be used when sending a push message.
 *
 * Direction: Client --> Server
 */
export interface SetPushNotificationTokenLike {
    /**
     * Type of the push token:
     *
     * - `0x00`: No push
     * - `0x01`: APNs Production
     * - `0x02`: APNs Development
     * - `0x03`: APNs Production with `content-available` key
     * - `0x04`: APNs Development with `content-available` key
     * - `0x05`: APNs Production with `mutable-content` key
     * - `0x06`: APNs Development with `mutable-content` key
     * - `0x11`: FCM with empty payload
     * - `0x13`: HMS with empty payload
     */
    readonly type: types.u8;

    /**
     * Push token, maximum 255 bytes.
     */
    readonly token: Uint8Array;
}

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

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

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

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

    /**
     * Create a SetPushNotificationToken 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 set-push-notification-token struct from an array.
     *
     * @param array Array to decode from.
     * @returns SetPushNotificationToken instance.
     */
    public static decode(array: Uint8Array): SetPushNotificationToken {
        return new SetPushNotificationToken(array);
    }

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

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

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

        return array.subarray(0, offset);
    }

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

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

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

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

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

/**
 * Deletes push tokens for a Threema ID. Can be used when self-removing or
 * removing another device from a device group.
 *
 * Direction: Client --> Server
 *
 * When receiving this payload:
 *
 * 1. If `csp-device-ids` is empty, delete all tokens for all devices except
 *    the device sending the payload and abort these steps.
 * 2. Delete all tokens for the devices specified in `csp-device-ids`.
 */
export interface DeletePushNotificationTokenLike {
    /**
     * Delete tokens belonging to a
     * [`csp-device-id`](ref:handshake.csp-device-id) in the same device
     * group.
     */
    readonly cspDeviceIds: Iterable<types.u64>;
}

/**
 * Encodable of {@link DeletePushNotificationTokenLike}.
 */
interface DeletePushNotificationTokenEncodable_ {
    /**
     * 'csp-device-ids' field value or encoder. See {@link DeletePushNotificationTokenLike#cspDeviceIds} for
     * the field's description.
     */
    readonly cspDeviceIds: types.BoundedIterable<types.u64>;
}

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

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

    /**
     * Create a DeletePushNotificationToken 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 delete-push-notification-token struct from an array.
     *
     * @param array Array to decode from.
     * @returns DeletePushNotificationToken instance.
     */
    public static decode(array: Uint8Array): DeletePushNotificationToken {
        return new DeletePushNotificationToken(array);
    }

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

        // Encode `csp-device-ids`
        offset += utils.encodeIterable(struct.cspDeviceIds, 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 delete-push-notification-token struct into an
     * array.
     *
     * @param struct DeletePushNotificationTokenEncodable to encode.
     * @returns The amount of bytes that would be required to encode the struct.
     */
    public static byteLength(
        struct: types.EncoderPick<DeletePushNotificationTokenEncodable, 'byteLength'>,
    ): types.u53 {
        let offset = 0;
        offset += struct.cspDeviceIds.length * 8;
        return offset;
    }

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

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

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

/**
 * Request a different idle timeout than the default one of 5 minutes. The
 * new setting is valid for the connection only.
 *
 * The client must ensure that it sends echo requests or other traffic
 * frequently to keep the connection alive.
 *
 * Direction: Client --> Server
 *
 * [//]: # "TODO(SE-128)"
 */
export interface SetConnectionIdleTimeoutLike {
    /**
     * Idle timeout in seconds. Minium 30s, maximum 600s.
     */
    readonly timeout: types.u16;
}

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

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

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

    /**
     * Create a SetConnectionIdleTimeout 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-connection-idle-timeout struct from an array.
     *
     * @param array Array to decode from.
     * @returns SetConnectionIdleTimeout instance.
     */
    public static decode(array: Uint8Array): SetConnectionIdleTimeout {
        return new SetConnectionIdleTimeout(array);
    }

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

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

        return array.subarray(0, 2);
    }

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

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

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

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

/**
 * Indicates that the incoming message queue on the server has been fully
 * transmitted to the client. A client should not disconnect prior to
 * having received this payload.
 *
 * Direction: Client <-- Server
 *
 * [//]: # "TODO(SE-128)"
 */
export interface QueueSendCompleteLike {}

/**
 * Encodable of {@link QueueSendCompleteLike}.
 */
interface QueueSendCompleteEncodable_ {}

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

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

    /**
     * Create a QueueSendComplete 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 queue-send-complete struct from an array.
     *
     * @param array Array to decode from.
     * @returns QueueSendComplete instance.
     */
    public static decode(array: Uint8Array): QueueSendComplete {
        return new QueueSendComplete(array);
    }

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

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

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

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

/**
 * Indicates to the client that a device cookie mismatch has been detected
 * since the last time that the device cookie change indication has been
 * cleared (using the
 * [`clear-device-cookie-change-indication`](ref:clear-device-cookie-change-indication)
 * payload).
 *
 * The client should display a warning in form of a notification and/or
 * dialog to the user, informing them that a new and potentially unauthorized
 * device has accessed the account. When the user confirms, the client should
 * send a
 * [`clear-device-cookie-change-indication`](ref:clear-device-cookie-change-indication)
 * payload to clear the indication.
 *
 * Direction: Client <-- Server
 */
export interface DeviceCookieChangeIndicationLike {}

/**
 * Encodable of {@link DeviceCookieChangeIndicationLike}.
 */
interface DeviceCookieChangeIndicationEncodable_ {}

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

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

    /**
     * Create a DeviceCookieChangeIndication 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 device-cookie-change-indication struct from an array.
     *
     * @param array Array to decode from.
     * @returns DeviceCookieChangeIndication instance.
     */
    public static decode(array: Uint8Array): DeviceCookieChangeIndication {
        return new DeviceCookieChangeIndication(array);
    }

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

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

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

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

/**
 * Causes the server to clear the flag that triggers sending the
 * [`device-cookie-change-indication`](ref:device-cookie-change-indication)
 * on each connection.
 *
 * The flag will be set again by the server if another device cookie
 * mismatch is detected.
 *
 * Direction: Client --> Server
 */
export interface ClearDeviceCookieChangeIndicationLike {}

/**
 * Encodable of {@link ClearDeviceCookieChangeIndicationLike}.
 */
interface ClearDeviceCookieChangeIndicationEncodable_ {}

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

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

    /**
     * Create a ClearDeviceCookieChangeIndication 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 clear-device-cookie-change-indication struct from an array.
     *
     * @param array Array to decode from.
     * @returns ClearDeviceCookieChangeIndication instance.
     */
    public static decode(array: Uint8Array): ClearDeviceCookieChangeIndication {
        return new ClearDeviceCookieChangeIndication(array);
    }

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

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

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

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

/**
 * Indicates that the connection has experienced an unrecoverable error and
 * must be closed.
 *
 * Direction: Client <-- Server
 *
 * [//]: # "TODO(SE-128)"
 */
export interface CloseErrorLike {
    /**
     * Indicates whether the client is allowed to reconnect automatically
     * after the connection has been severed. This allows the server to
     * prevent infinite loops in case of a recurring error.
     *
     * Set to `0` in case the client may not reconnect automatically or any
     * other value otherwise.
     */
    readonly canReconnect: types.u8;

    /**
     * Error message (UTF-8 encoded)
     */
    readonly message: Uint8Array;
}

/**
 * Encodable of {@link CloseErrorLike}.
 */
interface CloseErrorEncodable_ {
    /**
     * 'can-reconnect' field value or encoder. See {@link CloseErrorLike#canReconnect} for
     * the field's description.
     */
    readonly canReconnect: types.u8;

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

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

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

    /**
     * Create a CloseError 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 close-error struct from an array.
     *
     * @param array Array to decode from.
     * @returns CloseError instance.
     */
    public static decode(array: Uint8Array): CloseError {
        return new CloseError(array);
    }

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

        // Encode `can-reconnect`
        array[0] = struct.canReconnect;

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

        return array.subarray(0, offset);
    }

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

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

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

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

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

/**
 * Generic alert that should be displayed in the client's user interface.
 *
 * Direction: Client <-- Server
 *
 * [//]: # "TODO(SE-128)"
 */
export interface AlertLike {
    /**
     * Alert message (UTF-8 encoded)
     */
    readonly message: Uint8Array;
}

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

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

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

    /**
     * Create a Alert 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 alert struct from an array.
     *
     * @param array Array to decode from.
     * @returns Alert instance.
     */
    public static decode(array: Uint8Array): Alert {
        return new Alert(array);
    }

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

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

        return array.subarray(0, offset);
    }

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

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

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

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