# Meta information
meta:
  # Document name and ID
  id: group-call
  name: Group Call Protocol

# Virtual namespace, just containing the below docstring
index: &index
  _doc: |-
    # Group Call Protocol (Supplementary)

    This is a supplementary section to the corresponding protobuf section with
    messages that use structbuf instead of protobuf. All defined messages here
    follow the same logic.

# Real-Time media end-to-end encryption
e2e: &e2e
  frame:
    _doc: |-
      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.

    fields:
      - _doc: |-
          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
        name: data
        type: b*

  footer:
    _doc: |-
      Footer of an end-to-end encrypted audio/video frame.

    fields:
      - _doc: |-
          Media key epoch. This is the same value as in
          `group-call.MediaKey.epoch`.
        name: key-epoch
        type: u8
      - _doc: |-
          Media key ratchet counter. This is the same value as in
          `MediaKey.ratchet_counter`.
        name: key-ratchet-counter
        type: u8
      - _doc: |-
          Sequence number of the media frame (MFSN).

          Note: Like the epoch and the ratchet counter, the MFSN is shared
          across different media types.
        name: mfsn
        type: u32-le

# Parsed struct namespaces (mapped into separate files)
namespaces:
  index: *index
  e2e: *e2e
