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

/**
 * And end-to-end encrypted audio/video frame.
 *
 * Steps to extract the unencrypted header from frame data (encrypted or
 * unencrypted):
 *
 * 1. Let `data` be the given encrypted or unencrypted frame data.
 * 2. Let `offset` be `0`.
 * 3. If the codec for this frame is Opus, there is no unencrypted header.
 *    Leave `offset` at `0`.
 * 4. If the codec for this frame is VP8:
 *    1. If the LSB of the first byte is `1`, set `offset` to `10` and abort
 *       these sub-steps.
 *    2. Set `offset` to 3.
 * 5. Return a tuple of:
 *    - unencrypted-header: A view of all bytes until `offset`
 *      (i.e. `data[0..offset]`).
 *    - payload: A view of all bytes from `offset` (i.e. `data[offset..]`).
 *
 * When creating a media frame:
 *
 * 1. Let `data` be the given frame data.
 * 2. If `data` is greater than `65536 - 16 - 6` bytes, log a warning and
 *    abort these steps.
 * 3. Let `unencrypted-header` and `payload` be the result of the above
 *    described header extraction steps by applying it on `data.
 * 4. Let `frame-mfsn` be a copy of the current MFSN. Then, immediately
 *    increase MFSN by 1.
 *
 *    IMPORTANT: The MFSN **must** be guarded by a mutex if multithreading
 *    is involved. It is critical to prevent nonce reuse!
 * 5. Let `nonce` be the byte concatenation of the following items in this
 *    order:
 *    - `u32-le(frame-mfsn)`
 *    - 8 zero bytes
 * 6. Let `pcmk` be the current PCMK with the associated context.
 * 7. Let `ad` be the byte concatenation of the following items in this
 *    order:
 *    - `u8(pcmk.epoch)`
 *    - `u8(pcmk.ratchet-counter)`
 *    - `u32-le(frame-mfsn)`
 *    - `unencrypted-header`
 * 8. Encrypt the media frame and let `encrypted-payload` be the result:
 *
 *        AES-256-GCM(
 *          key=pcmk.pcmfk,
 *          nonce=nonce,
 *          auth-tag-length=16 (bytes),
 *          auth-tag-position=append,
 *          data=payload,
 *          additional-data=ad,
 *        )
 * 9. Encode the `frame` struct:
 *    - `data`: The byte concatenation of `unencrypted-header`,
 *      `encrypted-payload` (including the AES GCM authentication tag).
 *    - `footer.key-epoch`: `pcmk.epoch`
 *    - `footer.key-ratchet-counter`: `pcmk-ratchet-counter`
 *    - `footer.mfsn`: `frame-mfsn`
 * 10. Increase MFSN by `1`.
 * 11. Send the encoded `frame` struct.
 *
 * When receiving a media frame:
 *
 * 1. Let `frame` be the received struct.
 * 2. If `frame.data` is greater than `65536` bytes, log a warning and abort
 *    these steps.
 * 3. Let `unencrypted-header` and `payload` be the result of the above
 *    described header extraction steps by applying it on `frame.data`.
 * 4. Let `pcmk` be the current PCMK with the associated context for the
 *    participant that sent this frame.
 * 5. If `frame.epoch` is greater than `pcmk.epoch` or wrapped back to `0`,
 *    seek through all successors until a media key with the same `epoch`
 *    could be determined.
 *    1. If no key could be determined, discard the media frame and abort
 *       these steps.
 *    2. Replace `pcmk` with the succeeding media key that matched `epoch`.
 *       Note: An implementation **must** ensure that only succeeding keys
 *       are being used. Rolling back to a preceeding media key is
 *       forbidden.
 * 6. If `frame.ratchet-counter` is less than `pcmk.ratchet-counter`, discard
 *    the media frame and abort these steps.
 * 7. If `frame.ratchet-counter` is greater than `pcmk.ratchet-counter`,
 *    apply the necessary amount of ratchet rounds to `pcmk` so the counters
 *    are equal.
 * 8. Let `nonce` be the byte concatenation of the following items in this
 *    order:
 *    - `u32-le(frame.footer.mfsn)`
 *    - 8 zero bytes
 * 9. Let `ad` be the byte concatenation of the following items in this
 *    order:
 *    - `u8(pcmk.epoch)`
 *    - `u8(pcmk.ratchet-counter)`
 *    - `u32-le(frame.footer.mfsn)`
 *    - `unencrypted-header`
 * 10. Decrypt the media frame and let `decrypted-payload` be the result:
 *
 *         AES-256-GCM(
 *           key=pcmk.pcmfk,
 *           nonce=nonce,
 *           auth-tag-length=16 (bytes),
 *           auth-tag-position=append,
 *           data=payload,
 *           additional-data=ad,
 *         )
 * 11. If decryption failed, discard the media frame and abort these steps.
 * 12. Let `data` be the byte concatenation of `unencrypted-header` and
 *     `decrypted-payload`.
 * 13. Forward `data` to the media pipeline.
 *
 * Note: There is limited replay mitigation. The SFU is able to replay old
 * frames that were recorded in this media key epoch and ratchet iteration.
 */
export interface FrameLike {
    /**
     * This contains the following data (in this order):
     *
     * - Unencrypted frame header (if any, 0 to 10 bytes)
     * - Encrypted audio/video frame
     * - AES GCM authentication tag (16 bytes)
     * - The `footer` struct
     */
    readonly data: Uint8Array;
}

/**
 * Encodable of {@link FrameLike}.
 */
interface FrameEncodable_ {
    /**
     * 'data' field value or encoder. See {@link FrameLike#data} for
     * the field's description.
     */
    readonly data: 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;

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

    /**
     * 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 {
        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 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 = 0;
        offset += utils.getByteLength(struct.data);
        return offset;
    }

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

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

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

/**
 * Footer of an end-to-end encrypted audio/video frame.
 */
export interface FooterLike {
    /**
     * Media key epoch. This is the same value as in
     * `group-call.MediaKey.epoch`.
     */
    readonly keyEpoch: types.u8;

    /**
     * Media key ratchet counter. This is the same value as in
     * `MediaKey.ratchet_counter`.
     */
    readonly keyRatchetCounter: types.u8;

    /**
     * Sequence number of the media frame (MFSN).
     *
     * Note: Like the epoch and the ratchet counter, the MFSN is shared
     * across different media types.
     */
    readonly mfsn: types.u32;
}

/**
 * Encodable of {@link FooterLike}.
 */
interface FooterEncodable_ {
    /**
     * 'key-epoch' field value or encoder. See {@link FooterLike#keyEpoch} for
     * the field's description.
     */
    readonly keyEpoch: types.u8;

    /**
     * 'key-ratchet-counter' field value or encoder. See {@link FooterLike#keyRatchetCounter} for
     * the field's description.
     */
    readonly keyRatchetCounter: types.u8;

    /**
     * 'mfsn' field value or encoder. See {@link FooterLike#mfsn} for
     * the field's description.
     */
    readonly mfsn: types.u32;
}

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

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

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

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

        // Encode `key-epoch`
        array[0] = struct.keyEpoch;

        // Encode `key-ratchet-counter`
        array[1] = struct.keyRatchetCounter;

        // Encode `mfsn`
        view.setUint32(2, struct.mfsn, true);

        return array.subarray(0, 6);
    }

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

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

    /**
     * 'key-ratchet-counter' field accessor. See {@link FooterLike#keyRatchetCounter} for the
     * field's description.
     */
    public get keyRatchetCounter(): types.u8 {
        return utils.unwrap(this._array[1]);
    }

    /**
     * 'mfsn' field accessor. See {@link FooterLike#mfsn} for the
     * field's description.
     */
    public get mfsn(): types.u32 {
        return this._view.getUint32(2, true);
    }

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

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