Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  group-call.proto  Sprache: unbekannt

 
Spracherkennung für: .proto vermutete Sprache: Abap {ABAP[102] C[100] Corba[100]} [Methode: maximale Elemente, drei Dimensionen]

// # Group Call Protocol
//
// Note that group calls are not necessarily bound to a Threema group. _Group_
// refers to a group of call participants and is a way to distinguish from 1:1
// Threema calls.
//
// There are two primary variants which use the same technology underneath:
//
// - A group call is scoped to a (Threema) group. It does not have any advanced
//   functionality such as moderation or external guests. Only one group call is
//   intended to run within a group.
// - A conference call is a more advanced type of group call and delivers more
//   advanced functionality such as moderation and join policies (e.g. requiring
//   moderator approval to join). It optionally also allows for external guests
//   to join.
//
// The theoretical maximum amount of participants is 790 (due to the way we
// derive WebRTC MIDs) but the practical limit is way below that.
//
// ## Terminology
//
// - `GCK`: Group Call Key, only used for key derivation
// - `GCKH`: Group Call Key Hash
// - `GCNHAK`: Group Call Normal Handshake Authentication Key
// - `GCHK`: Group Call Handshake Key
// - `GCSK`: Group Call State Key
// - `GCMK`: Group Call Moderation Key, only used for key derivation
// - `GCMMK`: Group Call Moderator Message Key
// - `PCK`: Participant Call Key
// - `PCMK`: Participant Call Media Key, only used for key derivation
// - `PCMK`': Ratchet iteration of PCMK
// - `PCMFK`: Participant Call Media Frame Key
// - `PCCK`: Participant Call Cookie
// - `PCSN`: Participant Call Sequence Number
// - `MFSN`: Media Frame Sequence Number
//
// ## General Information
//
// **Endianness**: All integers use little-endian encoding.
//
// **Encryption cipher**: XSalsa20-Poly1305, unless otherwise specified.
//
// **Nonce format**:
//
// - a 16 byte cookie (PCCK), followed by
// - a monotonically increasing sequence number (PCSN, u64-le).
//
// **Sequence number**: The sequence number starts with `1` and is counted
// separately for each direction (i.e. there is one sequence number counter for
// the sender and one for the receiver). We will use `PCSN+` in this document to
// denote that the counter should be increased **after** the value has been
// inserted (i.e. semantically equivalent to `x++` in many languages).
//
// Note: This format is equivalent to the CSP transport encryption.
//
// ## Key Derivation
//
// Note: All keys that are not derived from `GCK` directly will be derived using
// `GCKH` as input. This ensures that exchanged secret keys are useless if the
// Group Call ID has been exposed (unless `GCK` is also known to the attacker).
//
//     GCKH = BLAKE2b(key=GCK, salt='#', personal='3ma-call')
//
//     GCHK = BLAKE2b(key=GCK, salt='h', personal='3ma-call')
//     GCSK = BLAKE2b(key=GCK, salt='s', personal='3ma-call')
//
//     GCMMK = BLAKE2b(key=GCMK, salt='mm', personal='3ma-call', input=GCKH)
//
//     PCMK' = BLAKE2b(key=PCMK, salt="m'", personal='3ma-call')
//     PCMFK = BLAKE2b(key=PCMK, salt='mf', personal='3ma-call', input=GCKH)
//
// ## Group Call ID Derivation
//
// For group calls scoped to groups, the Group Call ID is derived by running
// BLAKE2b on specific data provided by the `GroupCallStart`:
//
//     group-call-id = BLAKE2b(
//       out-length=32,
//       salt='i',
//       personal='3ma-call',
//       input=
//            group-creator-identity
//         || group-id
//         || u8(GroupCallStart.protocol_version)
//         || GroupCallStart.gck
//         || utf8-encode(GroupCallStart.sfu_base_url),
//     )
//
// For conference calls, the Group Call ID is derived by running BLAKE2b on
// specific data provided by the `ConferenceCallInvite`:
//
//     group-call-id = BLAKE2b(
//       out-length=32,
//       salt='ic',
//       personal='3ma-call',
//       input=
//         || u8(ConferenceCallInvite.protocol_version)
//         || ConferenceCallInvite.gck
//         || ConferenceCallInvite.gcmmk_public
//         || u8(ConferenceCallInvite.guest_policy),
//         || u8(ConferenceCallInvite.join_policy),
//         || utf8-encode(ConferenceCallInvite.sfu_base_url)
//     )
//
// ## Protocol Flow
//
// ### Obtain SFU Information
//
// Before a call can be created, SFU information and an authentication token
// need to be obtained via the Directory Server API. The obtained information
// includes the following items referenced in subsequent sections:
//
// - _SFU Base URL_: Base URL used to create and distribute new calls.
// - _Allowed SFU Hostname Suffixes_: A set of allowed hostname suffixes to be
//   applied against the _SFU Base URL_ when joining calls.
// - _SFU Token_: An opaque token used to authenticate against the SFU.
//
// When receiving the SFU information, ensure the _SFU Base URL_ uses the scheme
// `https` and the included hostname ends with one of the _Allowed SFU Hostname
// Suffixes_.
//
// ### Call Peeking
//
// The following steps are defined as the _Group Call Peek Steps_:
//
// 1. Let `call` be the targeted group call.
// 2. Let `token` be the provided SFU token or be explicitly undefined (for
//    external guest usage).
// 3. If the user is currently participating in `call`, abort these steps and
//    return the current status.
// 4. _Peek_ the `call` via a `SfuHttpRequest.Peek` request with `token`. If
//    this does not result in a response within 15s, abort these steps and
//    return that the status is _unknown_.
// 5. If the server could not be reached or the received status code is not
//    `200` or if the `SfuHttpResponse.Peek` response could not be decoded:
//    1. If the received status code is `401`, abort these steps and return the
//       status _unauthorized_.
//    2. If the received status code is `404`, abort these steps and return the
//       status _not running_.
//    3. Abort these steps and return that the status is _failed_.
// 6. If the protocol version of the `call` is not supported, abort these steps
//    and return the status _unsupported_.
// 7. Return the status as provided by the `SfuHttpResponse.Peek` response.
//
// ### Scoped to Group
//
// #### Periodic Refresh
//
// The following steps are defined as the _Group Call Refresh Steps_ and will be
// applied to update the group calls that are currently considered running
// within a group, determining which one of them is the chosen call and
// potentially join the chosen call:
//
// 1.  Let `running` be the list of group calls that are currently considered
//     running within the group.
// 2.  Let `calls` be a copy of `running`.
// 3.  If the _SFU token_ would expire in the next 30s, refresh it and let
//     `token` be the result. If the _SFU Token_ refresh fails or does not yield
//     an _SFU Token_ within 15s, set `calls` to an empty list.
// 4.  For each `call` of `calls`, run the following steps (labelled
//     _peek-call_) concurrently and wait for them to return:
//     1. Run the _Group Call Peek Steps_ for `call` and `token` and let
//        `status` be the result.
//     2. If `status` contains a valid status, reset the `call`'s _failed_
//        counter to `0`, update `call` with `status` and abort the _peek-call_
//        sub-steps.
//     3. If `status` is _unauthorized_ or _not running_, remove `call` from
//        `calls` and from `running` and abort the _peek-call_ sub-steps.
//     4. If `status` is _unknown_, remove `call` from `calls` and abort the
//        _peek-call_ sub-steps.
//     5. If `status` is _unsupported_, reset the `call`'s _failed_ counter to
//        `0`, remove `call` from `calls`, log a warning that a group call with
//        an unsupported version is currently running and abort the _peek-call_
//        sub-steps.
//     6. If `status` is _failed_:
//        1. Remove `call` from `calls`.
//        2. If the `call`'s _failed_ counter is `>= 3` and the `call` was
//           received more than 10h ago, remove `call` from `running` and abort
//           the _peek-call_ sub-steps.
//        3. Increase the _failed_ counter for `call` by `1` and abort the
//           _peek-call_ sub-steps.
// 5.  If `running` is empty, cancel the timer to periodically re-run the _Group
//     Call Refresh Steps_ of this group. Otherwise, restart or schedule the
//     timer to re-run the _Group Call Refresh Steps_ of this group in 10s.
// 6.  Let `chosen-call` be any call of `calls` with the highest `started_at`
//     value (i.e. the most recently created call) as provided by the _peek_
//     result.
// 7.  If `chosen-call` is not defined, signal that no group call is currently
//     running within the group, abort these steps and return `chosen-call`.
// 8.  Signal `chosen-call` as the currently running group call within the
//     group.
// 9.  If the _Group Call Join Steps_ are currently running with a different (or
//     new) group call than `chosen-call`, cancel and restart the _Group Call
//     Join Steps_ asynchronously with the same `intent` but with the
//     `chosen-call`.
// 10. If the user is currently participating in a group call of this group that
//     is different to `chosen-call`, exit the running group call and run the
//     _Group Call Join Steps_ asynchronously with the `intent` to _only join_
//     `chosen-call`.
// 11. Return `chosen-call`.
//
// Note: The above steps have been carefully crafted to gracefully handle cases
// where the SFU of one call cannot be reached for a short period of time.
//
// When the user leaves a group call, run the _Group Call Refresh Steps_ for the
// respective group.
//
// The above described timer may be cancelled when the Threema app is inactive.
// The timer interval may be increased to 30s in case the group conversation is
// currently not visible to the user.
//
// #### Create or Join
//
// The following steps are to be run when a user wants to join a group call of a
// group where a group call is currently considered running (e.g. the user hits
// _join_ in the UI) or when the user intents to create a group call for a group
// where no group call is currently considered running (e.g. the user hits the
// _call_ button in the UI):
//
// 1. Let `intent` be the user's intent, i.e. to either _only join_ or _create
//    or join_ a group call.
// 2. Run the _Group Call Refresh Steps_ for the respective group and let `call`
//    be the result.
// 3. If `call` is undefined and `intent` is to _only join_, abort these steps
//    and notify the user that no group call is running / the group call is no
//    longer running.
// 4. If `call` is undefined, create (but don't send) a `GroupCallStart`
//    message, apply it to `call` and mark `call` as _new_.
// 5. Run the _Group Call Join Steps_ with the `intent` and `call`.
//
// The following steps are defined as the _Group Call Join Steps_ (also applied
// for creating a group call):
//
// 1.  Let `intent` be either _only join_ or _create or join_. Let `call` be the
//     given group call to be joined (or created).
// 2.   _Join_ (or implicitly create) the group call via a `SfuHttpRequest.Join`
//     request. If this does not result in a response within 10s, abort these
//     steps and notify the user.
// 3.  If the received status code is `503`, notify the user that the group call
//     is full and abort these steps.
// 4.  If the server could not be reached or the received status code is not
//     `200` or if the `SfuHttpResponse.Join` response could not be decoded,
//     abort these steps and notify the user.
// 5.  Let `response` be the decoded ``SfuHttpResponse.Join` response.
// 6.  If `response.participant_uid` is empty, abort these steps and notify the
//     user that the server does not support conference calls.
// 7.  Establish a WebRTC connection to the SFU with the information provided in
//     the `SfuHttpResponse.Join` response. Wait until the SFU sent the initial
//     `SfuToParticipant.Hello` message via the associated data channel. Let
//     `hello` be that message.
// 8.  If the `hello.participants` contains less than 4 items, set the initial
//     capture state of the microphone to _on_.
// 9.  If `call` is marked as _new_:
//     1. Optionally add an artificial wait period of 2s minus the time elapsed
//        since step 1.¹
//     2. Let `message-id` be a random message ID.
//     3. Schedule a persistent task to run he _Bundled Messages Send Steps_
//        with the following properties:
//        - `id` set to `message-id`,
//        - `receivers` set to all group members that have `GROUP_CALL_SUPPORT`,
//        - to construct a `GroupCallStart` message from `call`.
//     4. Add the created `call` to the list of group calls that are currently
//        considered running.
//     5. Asynchronously run the _Group Call Refresh Steps_.²
// 10. The group call is now considered established and should asynchronously
//     invoke the SFU to Participant and Participant to Participant flows.
//
// ¹: This prevents butter-fingered user from accidentally starting a group
// call.
//
// ²: This will initiate the refresh timer for a newly created call and signal
// it to the UI.
//
// Note: Implementations need to ensure that only one group call can be active
// at the same time in the application. This means that only one invocation of
// the _Create or Join_ flow and only one invocation of the _Group Call Join
// Steps_ can be active. Be aware that these steps can be cancelled by the user
// and by the _Group Call Refresh Steps_.
//
// ### Conference Call
//
// #### Overview: Periodic Refresh
//
// While the Conference Call Overview screen UI is visible, schedule a timer to
// run the following steps every 10s (after the previous run returned):
//
// 1. Let `calls` be a copy of the set of stored conference calls.
// 2. If the _SFU token_ would expire in the next 30s, refresh it and let
//    `token` be the result. If the _SFU Token_ refresh fails or does not yield
//    an _SFU Token_ within 15s, update the status of all `calls` to _unknown_.
// 3. For each `call` of `calls`, run the following steps (labelled _peek-call_)
//    concurrently and wait for them to return:
//    1. Run the _Group Call Peek Steps_ for `call` and `token` and let `status`
//       be the result.
//    2. If `status` contains a valid status, set `call`'s display status to
//       `status` and abort the _peek-call_ sub-steps.
//    3. If `status` is _not running_, set `call`'s display status respectively
//       and abort the _peek-call_ sub-steps.
//    4. If `status` is _unsupported_, log a warning that a conference call with
//       an unsupported version is currently running.
//    5. Set `call`'s display status to unknown.
//
// #### Join Pre-Screen: Periodic Refresh
//
// While the Join Pre-Screen UI is visible as a Threema user, schedule a timer
// to run the following steps every 10s (after the previous run returned):
//
// 1. Let `call` be the targeted conference call.
// 2. If the _SFU token_ would expire in the next 30s, refresh it and let
//    `token` be the result. If the _SFU Token_ refresh fails or does not yield
//    an _SFU Token_ within 15s, abort these steps and return that the status is
//    _unknown_.
// 3. Run the _Group Call Peek Steps_ for `call` and `token` and let `status` be
//    the result.
// 4. If `status` contains a valid status, display the current `status` and
//    abort these steps.
// 5. If `status` is _not running_, display that the conference call is not
//    currently running (but may be started by the user) and abort these steps.
// 6. Display that the current status is unknown.
//
// While the Join Pre-Screen UI is visible as an external guest, schedule a
// timer to run the following steps every 10s (after the previous run returned):
//
// 1. Let `call` be the targeted conference call.
// 2. Run the _Group Call Peek Steps_ for `call` without a token and let
//    `status` be the result.
// 3. If `status` contains a valid status, display the current `status` and
//    abort these steps.
// 4. If `status` is _not running_ or _unauthorized_, display that the
//    conference call is not currently running and abort these steps.
// 5. Display that the current status is unknown.
//
// #### Create, Start or Join
//
// When a Threema users wants to create a conference invite:
//
// 1. If the _SFU token_ is expired, refresh it and let `token` be the result.
//    If the _SFU Token_ refresh fails or does not yield an _SFU Token_ within
//    15s, abort these steps and notify the user.
// 2. Create a `ConferenceCallInvite` message with `token` and encode it as a
//    URL as defined by that message.
//
// The following steps are to be run when a Threema user wants to start or join
// a conference call:
//
// 1. If the _SFU token_ would expire in the next 15s, refresh it and let
//    `token` be the result. If the _SFU Token_ refresh fails or does not yield
//    an _SFU Token_ within 15s, abort these steps and notify the user.
// 2.  _Join_ (or implicitly start) the conference call via a
//    `SfuHttpRequest.Join` request using `token`. If this does not result in a
//    response within 10s, abort these steps and notify the user.
// 3. If the received status code is `503`, notify the user that the conference
//    call is full and abort these steps.
// 4. If the server could not be reached or the received status code is not
//    `200` or if the _Join_ response could not be decoded, abort these steps
//    and notify the user.
// 5. Establish a WebRTC connection to the SFU with the information provided in
//    the _Join_ response. Wait until the SFU sent the initial
//    `SfuToParticipant.Hello` message via the associated data channel. Let
//    `hello` be that message.
// 6. The conference call is now considered established and should
//    asynchronously invoke the SFU to Participant and Participant to
//    Participant flows.
//
// The following steps are to be run when an external guest wants to join a
// conference call:
//
// 1.  _Join_ the conference call via a `SfuHttpRequest.Join` request without
//    any SFU token. If this does not result in a response within 10s, abort
//    these steps and notify the user.
// 2. If the received status code is `401`, notify the user that the conference
//    call is currently not running and abort these steps.
// 3. If the received status code is `503`, notify the user that the conference
//    call is full and abort these steps.
// 4. If the server could not be reached or the received status code is not
//    `200` or if the _Join_ response could not be decoded, abort these steps
//    and notify the user.
// 5. Establish a WebRTC connection to the SFU with the information provided in
//    the _Join_ response. Wait until the SFU sent the initial
//    `SfuToParticipant.Hello` message via the associated data channel. Let
//    `hello` be that message.
// 6. The conference call is now considered established and should
//    asynchronously invoke the SFU to Participant and Participant to
//    Participant flows.
//
// ### SFU to Participant Flow
//
// Upon successful joining via `SfuHttpRequest.Join`, the SFU waits for the
// client to establish a WebRTC connection and then announces all participants
// to the newly joined participant in its `SfuToParticipant.Hello` message.
//
// When another participant joins or leaves, a `ParticipantJoined` or
// `ParticipantLeft` message will be sent.
//
// At any time, participants may subscribe and unsubscribe receiving microphone,
// camera and screen data from other participants.
//
// If the user is alone in a call for more than 3 minute, the call should be
// left to save resources. The SFU will automatically drop such calls after 5
// minutes but this results in non-ideal UX.
//
// ### Participant to Participant Flow
//
// Unlike the other flows, this one is more complicated and needs to be done
// separately for each other participant. During the handshake, ephemeral
// encryption keys will be established.
//
// Note that multiple participants with the same Threema ID in the same call are
// **explicitly allowed**. Not only can this happen in case the connection has
// been lost (e.g. the client already reconnected but the SFU has not detected
// connection loss yet), but it is also a feature for multi-device capable
// clients.
//
// #### Handshake
//
// When a new participant (NP) joins, it must authenticate each other existing
// participant (EP) and establish an ephemeral shared secret (`PCK`). The
// message variant used depends on the participant's role. Normal participants
// send `Hello` and `Auth` while guest participants send `GuestHello` and
// `GuestAuth`, exemplary:
//
// If both are normal participants:
//
//     NP ------ Hello -----> EP
//     NP <----- Hello ------ EP
//     NP <----- Auth ------- EP
//     NP ------ Auth ------> EP
//
// If NP is a normal participant and EP is a guest participant:
//
//     NP ------ Hello -----> EP
//     NP <--- GuestHello --- EP
//     NP <--- GuestAuth ---- EP
//     NP ------ Auth ------> EP
//
// For group calls scoped to groups:
//
// - Only handshake messages from Threema IDs that are part of the group are
//   allowed.
// - External guests are not allowed and therefore the guest handshake is not
//   allowed.
//
// For conference calls, external guests are allowed if the _guest policy_ is
// `GuestPolicy.ALLOW_GUESTS`.
//
// #### Extended Handshake
//
// In case the call is a conference call and the _join policy_ is
// `JoinPolicy.REQUIRES_MODERATOR_APPROVAL`, the handshake period is extended
// such that both participants need to provide `Approval`, exemplary:
//
// If both NP and EP are moderators:
//
//     (Handshake until both sent `Auth`/`GuestAuth`)
//     NP ---- Approval ----> EP
//     NP <--- Approval ----- EP
//
// If EP is a moderator and EP approves NP (by user interaction):
//
//     (Handshake until both sent `Auth`/`GuestAuth`)
//     NP <--- Approval ----- EP
//     NP <- GrantApproval -- EP
//     NP ---- Approval ----> EP
//
// #### Post-Handshake
//
// After the handshake, **both** sides run the following steps:
//
// 1. Subscribe to the other participant's microphone feed (i.e. send a
//    `ParticipantMicrophone` message to the SFU).
// 2. If the user is a moderator, send a `Moderator.ReportAsModerator` message
//    to the other participant.
//
// #### Join/Leave of Other Participants
//
// The following steps are defined as the _Participant Joined Steps_ and are run
// by existing participants once a new participant joined¹:
//
// 1. Set the _handshake state_ of this participant to `await-np-hello`.
// 2. Run the _Reschedule State Task Steps_.
//
// The following steps are defined as the _Participant Active Steps_ and are run
// by existing participants once a new participant's _handshake state_ moved
// into `done`:
//
// 1. Let `pcmk` be the currently _applied_ PCMK with the associated context.
// 2. If the amount of ratchet rounds for `pcmk` is `255`, abort the call with
//    an error condition and abort these steps.
// 3. Advance the ratchet of `pcmk` once (i.e. replace the key by deriving
//    PCMK') and apply for media encryption immediately. Note: Do **not** reset
//    the MFSN!
// 4. Send a _rekey_ message to the participant containing the updated `pcmk`
// 5. If the currently _pending_ PCMK of the associated context is defined, send
//    a _rekey_ message to the participant containing the _pending_ PCMK.
// 6. Send a `CaptureState` message for each device whose capturing state is not
//    _off_.
// 7. Add the participant to the call as an _active_ participant.
//
// The following steps are defined as the _Participant Left Steps_ and are run
// by existing participants when a participant leaves²:
//
// 1. Let `pending-pcmk` be the currently _pending_ PCMK of the associated
//    context.
// 2. If `pending-pcmk` exists, additionally mark `pending-pcmk` as _stale_ and
//    abort these steps.
// 3. Let `current-pcmk` be the currently _applied_ PCMK with the associated
//    context.
// 4. Set `pending-pcmk` in the following way:
//    1. Generate a new cryptographically secure random PCMK and assign it to
//       `pending-pcmk`.
//    2. Set `pending-pcmk.epoch` to `current-pcmk.epoch + 1`, wrap back to `0`
//       if it would be `256`.
//    3. Set `pending-pcmk.ratchet_counter` to `0`.
//    4. Do **not** reset the MFSN! Continue the existing MFSN counter of the
//       previous PCMK.
// 5. Send `pending-pcmk` to all participants with a _handshake state_ of `done`
//    via a _rekey_ message.
// 6. Schedule a volatile task bound to the call to run the following steps
//    after 2s:
//    1. Apply `pending-pcmk` for media encryption. This means that
//       `pending-pcmk` now replaces the _applied_ PCMK and is no longer
//       _pending_.
//    2. If `pending-pcmk` is marked as _stale_, run the parent steps from the
//       beginning.
// 7. Run the _Reschedule State Task Steps_.
//
// The following steps are defined as the _Participant Rekey Steps_ and are run
// when a participant receives a `MediaKey` message from another participant:
//
// 1. Let `current-pcmk` be the PCMK and its associated context used for the
//    participant.
// 2. Let `new-pcmk` be the media keys (PCMK) of the received message.
// 3. Store `new-pcmk` as a successor to `current-pcmk` (and any other successor
//    already stored on `current-pcmk`) and follow the description of the media
//    frame on when to apply it.
//
// ¹: The announcement of the new participant is guaranteed to be sent prior to
// any handshake messages of the new participant.
//
// ²: Re-keying after a participant leaving is therefore throttled but always
// catches up to the current participant state with a maximum delay of 4s.
//
// #### State Update
//
// One of the participants is deterministically designated to update the
// peekable call state every 10s and additionally every time a participant joins
// or leaves. If the call state has not been updated/refreshed for 30s, the SFU
// will delete it.
//
// The following steps are defined as the _Reschedule State Task Steps_:
//
// 1. Cancel any running timer to update the call state.
// 2. Let `candidates` be a list of all other participants (in any _handshake
//    _state).
// 3. Compare the user's participant ID against all `candidates`. If any of the
//    participant IDs in `candidates` is lower than the participant ID of the
//    user, abort these steps.
// 4. Send a `ParticipantToSfu.UpdateCallState` message to the SFU and schedule
//    a repetitive timer to repeat this step every 10s.
//
// Note: The above algorithm is prone to races since the handshake is
// asynchronous for each participant pair. However, this should not be an issue
// as they'd essentially post the same status (eventually).

syntax = "proto3";

package group_call;

option java_package = "ch.threema.protobuf.group_call";
option java_multiple_files = true;
option php_namespace = "Threema\\Protocols\\GroupCall";

import "common.proto";

// Current call state as announced by the designated client.
//
// Note: The `CallState` accurateness must not be relied upon as it can be out
// of date and can be replayed by the SFU.
//
// When creating this message:
//
// 1. Set `state_created_by` to the user's participant ID.
// 2. Set `state_created_at` to the current UNIX-ish timestamp in milliseconds.
// 3. Add the user to the `participants` map:
//    1. Set the `ParticipationState` to `PARTICIPATING`.
//    2. If the call is a conference call and the _join policy_ is set to
//       `JoinPolicy.REQUIRES_MODERATOR_APPROVAL` and the user does not have a
//       local _approval_ signature from a moderator, set the
//       `ParticipationState` to `AWAITING_MODERATOR_APPROVAL`.
// 4. For each other participant:
//    1. If the _handshake state_ of the participant is `post-auth` add it to
//       the `participants` map with `ParticipationState.PARTICIPATING` and
//       abort these sub-steps.
//    2. If the _handshake state_ of the participant is `await-approval`:
//       1. If the _remote approval received_ flag is present, add it to the
//          `participants` map with `ParticipationState.PARTICIPATING` and abort
//          these sub-steps.
//       2. Add it to the `participants` map with
//          `ParticipationState.AWAITING_MODERATOR_APPROVAL` and abort these
//          sub-steps.
message CallState {
  // Random amount of padding, ignored by the receiver.
  bytes padding = 1;

  // Participant ID of the designated client that created this message.
  uint32 state_created_by = 2;

  // UNIX-ish timestamp in milliseconds the designated client created this
  // message.
  uint64 state_created_at = 3;

  // Information for a single participant.
  message Participant {
    reserved 1; // Redundant participant ID

    // A participation state.
    //
    // Note: Participants that are not yet authenticated will not appear in the
    // `CallState` at all.
    enum ParticipationState {
      // Participating (i.e. active).
      PARTICIPATING = 0;

      // Waiting for approval by a moderator to participate (i.e. authenticated
      // but not active, yet).
      AWAITING_MODERATOR_APPROVAL = 1;
    }
    ParticipationState participation_state = 4;

    // A _normal_ participant, i.e. a Threema client.
    message Normal {
      // Threema ID of the sender.
      string identity = 1;

      // Nickname associated to the Threema ID (without `~` prefix).
      string nickname = 2;
    }

    // A _guest_ participant.
    message Guest {
      // The guest's self-assigned name.
      string name = 1;
    }

    // Type-specific information.
    oneof participant {
      Normal threema = 2;
      Guest guest = 3;
    }
  }

  // Information for each participant of the group call that is authenticated.
  map<uint32, Participant> participants = 4;
}

// Supported feature (to be used as a bitmask).
enum SupportedFeature {
  // Base feature support (always present).
  BASE = 0x0000;

  // Support for screen sharing.
  SCREEN_SHARE = 0x0001;
}

// Request payloads sent to the SFU as part of an HTTP request.
message SfuHttpRequest {
  // Peeks for the current state of the group call for the given Group Call ID.
  //
  // IMPORTANT: The _peek_ process is considered stable across different
  // protocol versions. Therefore, the message **should** maintain backwards
  // compatibility!
  //
  // The URL is formed in the following way:
  //
  //     <sfu_base_url>/v1/peek/<call_id-as-hex>
  //
  // When sending this request:
  //
  // 1. Use `POST` as method.
  // 2. If an SFU token has been provided, set the `Authorization` header to
  //    `ThreemaSfuToken <sfu-token>`.
  // 3. Set the encoded `SfuHttpRequest.Peek` message as body.
  //
  // When receiving this request:
  //
  // 1. If `call_id-as-hex` is not a valid Call ID, respond with status code
  //    `400` and abort these steps.
  // 2. If the `Authorization` header is present and the provided `sfu-token` in
  //    the `Authorization` header is invalid or expired, respond with status
  //    code `401` and abort these steps.
  // 3. If the `Authorization` header is not present and no call for the given
  //    decoded `call_id-as-hex` is currently running, respond with status code
  //    `401` and abort these steps.
  // 4. If the provided data is invalid, respond with status code `400` and
  //    abort these steps.
  // 5. If `call_id` does not equal the decoded `call_id-as-hex`, respond with
  //    status code `400` and abort these steps.
  // 6. If no group call for the given `call_id` is currently running, respond
  //    with status code `404` and abort these steps.
  // 7. Respond with status code `200` and an encoded `SfuHttpResponse.Peek`
  //    message as body.
  message Peek {
    // Group Call ID associated to the group call.
    bytes call_id = 1;
  }

  // Requests to join the group call with the given Group Call ID.
  //
  // The URL is formed in the following way:
  //
  //     <sfu_base_url>/v1/join/<call_id-as-hex>
  //
  // When sending this request:
  //
  // 1. Use `POST` as method.
  // 2. If an SFU token has been provided, set the `Authorization` header to
  //    `ThreemaSfuToken <sfu-token>`.
  // 3. Set the encoded `SfuHttpRequest.Join` message as body.
  //
  // When receiving this request:
  //
  // 1. If `call_id-as-hex` is not a valid Call ID, respond with status code
  //    `400` and abort these steps.
  // 2. If the `Authorization` header is present and the provided `sfu-token` in
  //    the `Authorization` header is invalid or expired, respond with status
  //    code `401` and abort these steps.
  // 3. If the `Authorization` header is not present and no call for the given
  //    decoded `call_id-as-hex` is currently running, respond with status code
  //    `401` and abort these steps.
  // 4. If the provided data is invalid, respond with status code `400` and
  //    abort these steps.
  // 5. If `call_id` does not equal the decoded `call_id-as-hex`, respond with
  //    status code `400` and abort these steps.
  // 6. If the `protocol_version` is unsupported by the SFU, respond with status
  //    code `419` and abort these steps.
  // 7. If no more participants can join the group call for the given `call_id`,
  //    respond with status code `503` and abort these steps.
  // 8. Respond with status code `200` and an encoded `SfuHttpResponse.Join`
  //    message as body.
  // 9. Once the WebRTC connection has been established, announce the newly
  //    joined participant to all other participants via the corresponding data
  //    channel. If no WebRTC connection is being established within 30s, the
  //    participant ID is no longer reserved for the client and the group call
  //    must be teared down if no other participant started joining this group
  //    call.
  message Join {
    // Group Call ID associated to the group call.
    bytes call_id = 1;

    // Protocol version the call was announced with.
    uint32 protocol_version = 2;

    // DTLS fingerprint of the x509 certificate that will be used by the client.
    //
    // Note: This is the authentication anchor for the WebRTC connection towards
    // the SFU.
    bytes dtls_fingerprint = 3;
  }
}

// Response payloads sent back from the SFU as part of an HTTP request.
message SfuHttpResponse {
  // Information returned for a running group call.
  //
  // IMPORTANT: The _peek_ process is considered stable across different
  // protocol versions. Therefore, the message **should** maintain backwards
  // compatibility!
  //
  // Note: The included `CallState` information may not be accurate and should
  // not be relied upon.
  message Peek {
    // Unix-ish timestamp in milliseconds for when the first participant joined
    // the Group Call ID and therefore started the group call.
    uint64 started_at = 1;

    // Maximum amount of participants allowed in the group call.
    uint32 max_participants = 2;

    // Call state (`CallState`), encrypted by `GCSK.secret` and prefixed with a
    // random nonce.
    //
    // Not provided in case the call is currently running but no participant has
    // sent a call state to the SFU, or if the call state expired.
    //
    // The content of the call state is protocol version dependent and should
    // therefore be ignored if a client does not support the particular protocol
    // version the group call is associated with.
    optional bytes encrypted_call_state = 3;
  }

  // Information returned when joining a group call.
  //
  // When receiving this response, initiate the WebRTC connection to the SFU and
  // consider the connection established when the `SfuToParticipant.Hello`
  // message has been received on the associated data channel.
  message Join {
    // Unix-ish timestamp in milliseconds for when the first participant joined
    // the Group Call ID and therefore started the group call.
    uint64 started_at = 1;

    // Maximum amount of participants allowed in the group call.
    uint32 max_participants = 2;

    // Participant ID assigned to the client.
    //
    // Note: The client needs to know the participant ID early to derive MIDs
    // required to be present in the O/A SDP.
    uint32 participant_id = 3;

    // Participant UID, 16 byte unique random id, randomly generated by the SFU
    // and assigned to the client.
    //
    // Since SFU version v1.4.0. Not present (i.e. empty) otherwise.
    //
    // Note: Unused for group calls scope to groups, required for conference
    // calls.
    bytes participant_uid = 10;

    // Address the SFU is listening for a WebRTC connection.
    message Address {
      // Protocol.
      enum Protocol { UDP = 0; }
      Protocol protocol = 1;

      // Port.
      uint32 port = 2;

      // IPv4 or IPv6 address.
      string ip = 3;
    }

    // List of addresses the SFU listens for a WebRTC connection.
    //
    // Note: One UDP IPv4 address is mandatory! One IPv6 address is recommended.
    repeated Address addresses = 4;

    // ICE username fragment for the WebRTC connection.
    string ice_username_fragment = 5;

    // ICE password for the WebRTC connection.
    string ice_password = 6;

    // DTLS fingerprint of the x509 certificate that will be used by the SFU.
    //
    // Note: This is the authentication anchor for the WebRTC connection towards
    // the SFU.
    bytes dtls_fingerprint = 7;

    // RTP header extension ID mapping.
    //
    // Since SFU version v1.3.0: Not present otherwise. Fall back to the
    // previous hard-coded RTP header extension ID parameter mapping if not
    // provided.
    //
    // Note: The same ID is applied for header extensions used for both
    // microphone and camera/screen.
    message RtpHeaderExtensionIds {
      // urn:ietf:params:rtp-hdrext:sdes:mid
      uint32 mid = 1;

      // urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
      uint32 rtp_stream_id = 2;

      // urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
      uint32 repaired_rtp_stream_id = 3;

      // http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
      uint32 absolute_send_time = 4;

      // http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
      uint32 transport_wide_congestion_control_01 = 5;

      // urn:3gpp:video-orientation
      uint32 video_orientation = 6;

      // urn:ietf:params:rtp-hdrext:toffset
      uint32 time_offset = 7;
    }
    RtpHeaderExtensionIds rtp_header_extension_ids = 9;

    // Supported features bitmask (see `SupportedFeature`).
    uint64 supported_features = 8;
  }
}

// Messages sent from the SFU to a participant via a data channel.
//
// Data Channel Parameters:
//
// - `ordered`: `true`
// - `negotiated`: `true`
// - `id`: `0`
message SfuToParticipant {
  // The enveloped message from the SFU.
  //
  // When relaying a message from one participant to another, omit any
  // additional padding.
  //
  // IMPORTANT: The format of the `SfuToParticipant.Envelope` and
  // `ParticipantToSfu.Envelope` must be compatible for the relay case, so the
  // SFU can forward the data without having to re-encode.
  //
  // When receiving this message, handle the message according to the content.
  // If an unknown content variant has been received, log a warning and discard
  // the message.
  message Envelope {
    // Random amount of padding, ignored by the receiver.
    bytes padding = 1;

    oneof content {
      ParticipantToParticipant.OuterEnvelope relay = 2;
      Hello hello = 3;
      Timestamp timestamp_response = 6;
      ParticipantJoined participant_joined = 4;
      ParticipantLeft participant_left = 5;
    }
  }

  // Announces all other participants to a newly joined participant.
  //
  // When receiving this message:
  //
  // 1. If a `Hello` was received before (i.e. if the receiver is not a newly
  //    joined participant), log a warning and abort these steps.
  // 2. If the `SfuHttpResponse.Join.participant_uid` was present while joining,
  //    let `participants` be the `participants` map field.
  // 3. If the `SfuHttpResponse.Join.participant_uid` was not present while
  //    joining:
  //    1. If the group call is a conference call, abort the call with an error
  //       condition and abort these steps.
  //    2. Let `participants` be provided by the `deprecated_participant_ids`
  //       field.
  // 4. Initiate the participant to participant handshake for all
  //    `participants`.
  // 5. Run the _Reschedule State Task Steps_.
  message Hello {
    // All participants in the group call. This **excludes** the client's
    // participant ID.
    //
    // DEPRECATED: Superseded by `participants` map.
    repeated uint32 deprecated_participant_ids = 1 [deprecated = true];

    // Additional participant information of other participants.
    message Participant {
      // Participant UID, 16 byte unique random id.
      bytes participant_uid = 1;
    }

    // A map of all participant IDs to additional `Participant` information in
    // the group call. This **excludes** the client.
    //
    // Since SFU version v1.4.0. Not present (i.e. empty) otherwise. Required
    // for conference calls. Fall back to the `deprecated_participant_ids` if
    // not present for group calls scoped to groups.
    map<uint32, Participant> participants = 4;
  }

  // Announces that a new participant joined to existing participants.
  //
  // When receiving this message:
  //
  // 1. Look up the participant. If it already exists (i.e. never _left_), log a
  //    warning and abort these steps.
  // 2. If the group call is a conference call and `participant_uid` is empty,
  //    abort the call with an error condition and abort these steps.
  // 3. Run the _Participant Joined Steps_.
  message ParticipantJoined {
    // Participant ID assigned to the newly joined participant.
    uint32 participant_id = 1;

    // Participant UID assigned to the newly joined participant, 16 byte unique
    // random id.
    //
    // Since SFU version v1.4.0. Not present (i.e. empty) otherwise.
    bytes participant_uid = 2;
  }

  // Announces that a participant left to existing participants.
  //
  // When receiving this message:
  //
  // 1. Look up the participant. If it was never announced to have _joined_ by
  //    an associated `ParticipantJoined` message, log a warning and abort these
  //    steps.
  // 2. Run the _Participant Left Steps_.
  message ParticipantLeft {
    // Participant ID referring to the participant that left.
    uint32 participant_id = 1;
  }

  // Current UNIX-ish timestamp in milliseconds of the SFU.
  //
  // When receiving this message:
  //
  // 1. Resolve the first pending timestamp request with the response.
  message Timestamp { uint64 ms = 1; }
}

// Messages sent from a participant to the SFU via a data channel.
//
// Data Channel Parameters:
//
// - `ordered`: `true`
// - `negotiated`: `true`
// - `id`: `0`
message ParticipantToSfu {
  // The enveloped message towards the SFU.
  //
  // When relaying a message from one participant to another, omit any
  // additional padding.
  //
  // IMPORTANT: The format of the `SfuToParticipant.Envelope` and
  // `ParticipantToSfu.Envelope` must be compatible for the relay case, so the
  // SFU can forward the data without having to re-encode.
  //
  // When receiving this message, handle the message according to the content.
  // If an unknown content variant has been received, log a warning and discard
  // the message.
  message Envelope {
    // Random amount of padding, ignored by the receiver.
    bytes padding = 1;

    oneof content {
      ParticipantToParticipant.OuterEnvelope relay = 2;
      UpdateCallState update_call_state = 3;
      RequestTimestamp request_timestamp = 7;
      ParticipantMicrophone request_participant_microphone = 6;
      ParticipantCamera request_participant_camera = 4;
      ParticipantScreen request_participant_screen = 5;
    }
  }

  // Request the current timestamp of the SFU.
  //
  // When receiving this message:
  //
  // 1. Respond with a `TimestampResponse` message.
  message RequestTimestamp {}

  // Update the call state that can be retrieved via a _peek_.
  //
  // Note: Only the currently designated client should send this to the SFU.
  //
  // When receiving this message:
  //
  // 1. Store the encrypted call state and make it accessible via _peek_ HTTP
  //    requests.
  // 2. Start a timer to purge the call state after 30s. Subsequent
  //    `UpdateCallState` messages will update the call state and reset the
  //    timer.
  message UpdateCallState {
    // Call state (`CallState`), encrypted by `GCSK` and prefixed with
    // a random nonce.
    bytes encrypted_call_state = 1;
  }

  // Subscribe or unsubscribe to a participant's microphone feed.
  //
  // When receiving this message:
  //
  // 1. If the `participant_id` refers to the sender's participant ID or an
  //    unknown participant ID, discard the message and abort these steps.
  // 2. If `subscribe` is set, forward the microphone feed to the client that
  //    fits best to the provided parameters.
  // 3. If `unsubscribe` is set, stop forwarding microphone feed of this
  //    participant to the client.
  message ParticipantMicrophone {
    // Participant ID whose microphone feed should be subscribed or unsubscribed
    // from.
    uint32 participant_id = 1;

    // Subscribe to a participant's microphone feed.
    message Subscribe {}

    // Unsubscribe a participant's microphone feed.
    message Unsubscribe {}

    oneof action {
      Subscribe subscribe = 2;
      Unsubscribe unsubscribe = 3;
    }
  }

  // Subscribe or unsubscribe to a participant's camera feed.
  //
  // When receiving this message:
  //
  // 1. If the `participant_id` refers to the sender's participant ID or an
  //    unknown participant ID, discard the message and abort these steps.
  // 2. If `subscribe` is set, forward the camera feed to the client that fits
  //    best to the provided parameters.
  // 3. If `unsubscribe` is set, stop forwarding camera feed of this participant
  //    to the client.
  message ParticipantCamera {
    // Participant ID whose camera feed should be subscribed or unsubscribed
    // from.
    uint32 participant_id = 1;

    // Subscribe to a participant's camera feed.
    message Subscribe {
      // Desired resolution. The client should use the canvas' resolution the
      // camera feed be displayed in. The SFU will select the spatial layer that
      // fits best.
      common.Resolution desired_resolution = 1;

      // Desired frame rate. The SFU will select the temporal layer that fits
      // best.
      uint32 desired_fps = 2;
    }

    // Unsubscribe a participant's camera feed.
    message Unsubscribe {}

    oneof action {
      Subscribe subscribe = 2;
      Unsubscribe unsubscribe = 3;
    }
  }

  // Subscribe or unsubscribe to a participant's screen feed.
  //
  // Availability: If the SFU announced support for
  // `SupportedFeature.SCREEN_SHARE`.
  //
  // When receiving this message:
  //
  // 1. If the `participant_id` refers to the sender's participant ID or an
  //    unknown participant ID, discard the message and abort these steps.
  // 2. If `subscribe` is set, forward the screen feed to the client that fits
  //    best to the provided parameters.
  // 3. If `unsubscribe` is set, stop forwarding screen feed of this participant
  //    to the client.
  message ParticipantScreen {
    // Participant ID whose screen feed should be subscribed or unsubscribed
    // from.
    uint32 participant_id = 1;

    // Subscribe to a participant's screen feed.
    message Subscribe {
      // Desired resolution. The client should use the canvas' resolution the
      // screen feed be displayed in. The SFU will select the spatial layer that
      // fits best.
      common.Resolution desired_resolution = 1;

      // Desired frame rate. The SFU will select the temporal layer that fits
      // best.
      uint32 desired_fps = 2;
    }

    // Unsubscribe a participant's screen feed.
    message Unsubscribe {}

    oneof action {
      Subscribe subscribe = 2;
      Unsubscribe unsubscribe = 3;
    }
  }
}

// Messages sent from one participant to another.
//
// Note that these are relayed via `SfuToParticipant.Envelope` and
// `ParticipantToSfu.Envelope` in order to prevent races with
// `ParticipantJoined`/`ParticipantLeft`.
message ParticipantToParticipant {
  // Signature context, used as a prefix when signing to separate different use cases.
  enum SignatureContext {
    RESERVED = 0x00;
    JOIN_APPROVAL = 0x01;
    POST_AUTH_MESSAGE = 0x02;
  }

  // Used for all messages that are relayed from one participant to another via
  // the SFU.
  //
  // When receiving a relayed message:
  //
  // 1. If the `receiver` is not the user's assigned participant id, discard the
  //    message and abort these steps.
  // 2. If the `sender` is unknown, discard the message and abort these steps.
  // 3. Decrypt `encrypted_data` according to the current _handshake state_ and
  //    handle the inner envelope:
  //    - `await-ep-hello` or `await-np-hello`: Expect a
  //      `Handshake.HelloEnvelope`.
  //    - `await-auth`: Expect a `Handshake.AuthEnvelope`.
  //    - `await-approval`: Expect a `Handshake.ApprovalEnvelope`.
  //    - `done`: Expect a post-auth `Envelope`.
  message OuterEnvelope {
    // Participant ID of the sender. Checked by the SFU to be correct, dropped
    // if not.
    uint32 sender = 1;

    // Participant ID of the receiver. Checked by the SFU to exist, dropped if
    // not.
    uint32 receiver = 2;

    // The inner envelope. Always encrypted. Key and nonce are to be inferred
    // from the current _handshake state_ towards the sending participant.
    bytes encrypted_data = 4;
  }

  // Messages required for the initial lock-step handshake between participants.
  message Handshake {
    // The first message (`HelloEnvelope(Hello)` or `HelloEnvelope(GuestHello)`)
    // of both sides is always encrypted by `GCHK`, prefixed with a
    // random nonce.
    //
    // When receiving this message, handle the message according to the content.
    // If an unknown content variant has been received, log a warning, discard
    // the message and remove the participant by running the _Participant Left
    // Steps_.
    message HelloEnvelope {
      // Random amount of padding, ignored by the receiver
      bytes padding = 1;

      oneof content {
        Hello hello = 2;
        GuestHello guest_hello = 3;
      }
    }

    // The second message is encrypted in the following way:
    //
    // 1. If both user and the other participant are Threema users, let
    //    `auth-key` be the result of the following operation:
    //
    // ```text
    // Blake2b(
    //   key=X25519HSalsa20(<user.CK>.secret, <participant.CK>.public),
    //   salt='nha', personal='3ma-call', input=GCKH,
    // )
    // ```
    // 2. If the user is a Threema user and the other participant is a guest
    //    participant, let `auth-key` be the result of the following operation:
    //
    // ```text
    // Blake2b(
    //   key=X25519HSalsa20(<user.CK>.secret, <participant.PCK>.public),
    //   salt='gha', personal='3ma-call', input=GCKH,
    // )
    // ```
    // 3. If the user is a guest participant and the other participant is a
    //    Threema user, let `auth-key` be the result of the following operation:
    //
    // ```text
    // Blake2b(
    //   key=X25519HSalsa20(<user.PCK>.secret, <participant.CK>.public),
    //   salt='gha', personal='3ma-call', input=GCKH,
    // )
    // ```
    // 4. If both user and the other participant are guest participants, let
    //    `auth-key` be undefined.
    // 5. If `auth-key` is defined:
    //    1. If the sender is a Threema user, let `inner-data` be an
    //       `AuthEnvelope` with an `Auth` message. Otherwise, let `inner-data`
    //       be an `AuthEnvelope` with a `GuestAuth` message.
    //    2. Let `inner-nonce` be a random nonce.
    //    3. Let `inner-data` be encrypted by:
    //
    //    ```text
    //    XSalsa20-Poly1305(
    //      key=auth-key,
    //      nonce=inner-nonce,
    //      data=<AuthEnvelope(Auth)>,
    //    )
    //    ```
    //    3. Let `outer-data` be encrypted by:
    //
    //    ```text
    //    XSalsa20-Poly1305(
    //      key=X25519HSalsa20(<sender.PCK>.secret, <receiver.PCK>.public),
    //      nonce=<sender.PCCK> || <sender.PCSN+>,
    //      data=inner-nonce || inner-data,
    //    )
    //    ```
    //    4. Return `outer-data`.
    // 6. If `auth-key` is undefined, return an `AuthEnvelope` with a
    //    `GuestAuth` message encrypted in the following way:
    //
    // ```text
    // XSalsa20-Poly1305(
    //   key=X25519HSalsa20(<sender.PCK>.secret, <receiver.PCK>.public),
    //   nonce=<sender.PCCK> || <sender.PCSN+>,
    //   data=<AuthEnvelope(GuestAuth)>,
    // )
    // ```
    //
    // When receiving this message, handle the message according to the content.
    // If an unknown content variant has been received, log a warning, discard
    // the message and remove the participant by running the _Participant Left
    // Steps_.
    message AuthEnvelope {
      // Random amount of padding, ignored by the receiver
      bytes padding = 1;

      oneof content {
        Auth auth = 2;
        GuestAuth guest_auth = 3;
      }
    }

    // Initial handshake message.
    //
    // When creating this message as a newly joined participant towards another
    // participant:
    //
    // 1. Set the participant's _handshake state_ to `await-ep-hello`.
    // 2. Send this message.
    //
    // When receiving this message:
    //
    // 1. If the group call is scoped to a (Threema) group:
    //    1. Lookup `identity` in the associated group (including the user
    //       itself) and let `contact` be the result.
    //    2. If `contact` is undefined or marked as _invalid_, log a warning,
    //       discard the message, remove the participant by running the
    //       _Participant Left Steps_ and abort these steps.
    // 2. If the group call is a conference call:
    //    1. Run the _Valid Contacts Lookup Steps_ with `identities` set to
    //       `identity` and let `contact` be the result.
    //    2. If `contact` is marked as _invalid_, log a warning, discard the
    //       message, remove the participant by running the _Participant Left
    //       Steps_ and abort these steps.
    // 3. If the sender is a newly joined participant and therefore the
    //    _handshake state_ was set to `await-np-hello` (as described by the
    //    _Join/Leave_ section):
    //    1. If the user is a Threema user, respond by sending a `Hello`
    //       message, immediately followed by an `Auth` message.
    //    2. If the user is a guest participant, respond by sending a
    //       `GuestHello` message, immediately followed by a `GuestAuth`
    //       message.
    //    3. Set the participant's _handshake state_ to `await-auth` and abort
    //       these steps.
    // 4. If the participant's _handshake state_ is `await-ep-hello`:
    //    1. If the `pck` reflects the local PCK.public or the `pcck` reflects
    //       the local PCCK, log a warning, discard the message, remove the
    //       participant by running the _Participant Left Steps_ and abort these
    //       steps.
    //    2. If the user is a Threema user, respond by sending an `Auth`
    //       message.
    //    3. If the user is a guest participant, respond by sending a
    //       `GuestAuth` message.
    //    4. Set the participant's _handshake state_ to `await-auth` and abort
    //       these steps.
    // 5. Log a warning, discard the message, remove the participant by running
    //    the _Participant Left Steps_ and abort these steps.
    message Hello {
      // Threema ID of the sender.
      string identity = 1;

      // Nickname associated to the Threema ID (without `~` prefix).
      string nickname = 2;

      // 32 byte ephemeral public key (`PCK.public`) towards the remote
      // participant.
      //
      // Note: It is allowed to use the same `PCK` for multiple participants.
      bytes pck = 3;

      // 16 byte random cookie used for nonces by the sender in subsequent
      // messages.
      bytes pcck = 4;
    }

    // Second handshake message.
    //
    // When receiving this message:
    //
    // 1. If the participant's _handshake state_ is not `await-auth`, log a
    //    warning, discard the message, remove the participant by running the
    //    _Participant Left Steps_ and abort these steps.
    // 2. If the other participant initiated a guest handshake via a
    //    `GuestHello`, log a warning, discard the message, remove the
    //    participant by running the _Participant Left Steps_ and abort these
    //    steps.
    // 3. If the repeated `pck` does not equal the local `PCK.public` used
    //    towards this participant, log a warning, discard the message, remove
    //    the participant by running the _Participant Left Steps_ and abort
    //    these steps.
    // 4. If the repeated `pcck` does not equal the local `PCCK` used towards
    //    this participant, log a warning, discard the message, remove the
    //    participant by running the _Participant Left Steps_ and abort these
    //    steps.
    // 5. Run the _Participant Rekey Steps_ for each media key of
    //    `deprecated_early_media_keys`.
    // 6. If the call is not a conference call or the _join policy_ is not set
    //    to `JoinPolicy.REQUIRES_MODERATOR_APPROVAL`:
    //    1. Set the participant's handshake state to `done`.
    //    2. Run the _Participant Active Steps_.
    // 7. If the call is a conference call and the _join policy_ is set to
    //    `JoinPolicy.REQUIRES_MODERATOR_APPROVAL`:
    //    1. Set the participant's _handshake state_ to `await-approval`.
    //    2. If the user is a moderator or has a local _approval_ signature from
    //       a moderator, respond by sending an `Approval` message and update
    //       the _handshake state_ of `await-approval` with the _local approval
    //       sent_ flag.
    // 8. (Unreachable)
    message Auth {
      // 32 byte repeated ephemeral public key from the `Hello` message.
      //
      // Note: Repeating the sender's `PCK.public` prevents replay attacks.
      bytes pck = 1;

      // 32 byte repeated random cookie from the `Hello` message.
      //
      // Note: Repeating the sender's `PCCK` prevents replay attacks while
      // allowing the sender to use the same `PCK` for multiple participants.
      bytes pcck = 2;

      // The currently applied PCMK and any _pending_ PCMK used for media
      // encryption, specifically in that order.
      //
      // DEPRECATED: The idea behind sending PCMKs early was that participants
      // should become visible in the UI once the `Auth`/`GuestAuth` is being
      // sent, even though they are not mutually authenticated yet. This would
      // make it apparent to the user that the other participant is now able to
      // hear and see everything said and done in the call. However, it turns
      // out that in practice it is quite complicated to have a pre-full-auth
      // state that affects the UI, making this a footgun. For the sake of
      // implementation simplicity and to avoid any other weird kind of
      // pre-full-auth attacks, newer implementations MUST sent an empty list
      // and follow up by sending a `Rekey` message after mutual authentication
      // (and join approval).
      repeated MediaKey deprecated_early_media_keys = 3 [deprecated = true];
    }

    // Initial guest handshake message.
    //
    // When creating this message as a newly joined guest participant towards
    // another participant:
    //
    // 1. Set the participant's _handshake state_ to `await-ep-hello`.
    // 2. Send this message.
    //
    // When receiving this message:
    //
    // 1. If guest participants are not allowed for this call, log a warning,
    //    discard the message, remove the participant by running the
    //    _Participant Left Steps_ and abort these steps.
    // 3. If the sender is a newly joined participant and therefore the
    //    _handshake state_ was set to `await-np-hello` (as described by the
    //    _Join/Leave_ section):
    //    1. If the user is a Threema user, respond by sending a `Hello`
    //       message, immediately followed by an `Auth` message.
    //    2. If the user is a guest participant, respond by sending a
    //       `GuestHello` message, immediately followed by a `GuestAuth`
    //       message.
    //    3. Set the participant's _handshake state_ to `await-guest-auth` and
    //       abort these steps.
    // 4. If the participant's _handshake state_ is `await-ep-hello`:
    //    1. If the `pck` reflects the local PCK.public or the `pcck` reflects
    //       the local PCCK, log a warning, discard the message, remove the
    //       participant by running the _Participant Left Steps_ and abort these
    //       steps.
    //    2. If the user is a Threema user, respond by sending an `Auth`
    //       message.
    //    3. If the user is a guest participant, respond by sending a
    //       `GuestAuth` message.
    //    4. Set the participant's _handshake state_ to `await-guest-auth` and
    //       abort these steps.
    // 5. Log a warning, discard the message, remove the participant by running
    //    the _Participant Left Steps_ and abort these steps.
    message GuestHello {
      // The guest's self-assigned name.
      string name = 1;

      // 32 byte ephemeral public key (`PCK.public`) towards the remote
      // participant.
      //
      // Note: It is allowed to use the same `PCK` for multiple participants.
      bytes pck = 2;

      // 16 byte random cookie used for nonces by the sender in subsequent
      // messages.
      bytes pcck = 3;
    }

    // Second handshake message.
    //
    // When receiving this message:
    //
    // 1. If the participant's _handshake state_ is not `await-guest-auth`, log
    //    a warning, discard the message, remove the participant by running the
    //    _Participant Left Steps_ and abort these steps.
    // 2. If the other participant initiated the (normal) handshake via a
    //    `Hello`, log a warning, discard the message, remove the participant by
    //    running the _Participant Left Steps_ and abort these steps.
    // 3. If the repeated `pck` does not equal the local `PCK.public` used
    //    towards this participant, log a warning, discard the message, remove
    //    the participant by running the _Participant Left Steps_ and abort
    //    these steps.
    // 4. If the repeated `pcck` does not equal the local `PCCK` used towards
    //    this participant, log a warning, discard the message, remove the
    //    participant by running the _Participant Left Steps_ and abort these
    //    steps.
    // 5. If the call is not a conference call or the _join policy_ is not set
    //    to `JoinPolicy.REQUIRES_MODERATOR_APPROVAL`:
    //    1. Set the participant's handshake state to `done`.
    //    2. Run the _Participant Active Steps_.
    // 6. If the call is a conference call and the _join policy_ is set to
    //    `JoinPolicy.REQUIRES_MODERATOR_APPROVAL`:
    //    1. Set the participant's _handshake state_ to `await-approval`.
    //    2. If the user is a moderator or has a local _approval_ signature from
    //       a moderator, respond by sending an `Approval` message and update
    //       the _handshake state_ of `await-approval` with the _local approval
    //       sent_ flag.
    // 7. (Unreachable)
    message GuestAuth {
      // 32 byte repeated ephemeral public key from the `GuestHello` message.
      //
      // Note: Repeating the sender's `PCK.public` prevents replay attacks.
      bytes pck = 1;

      // 32 byte repeated random cookie from the `GuestHello` message.
      //
      // Note: Repeating the sender's `PCCK` prevents replay attacks while
      //       allowing the sender to use the same `PCK` for multiple
      //       participants.
      bytes pcck = 2;
    }

    // Final handshake phase for conference calls with _join policy_
    // `JoinPolicy.REQUIRES_MODERATOR_APPROVAL`, encrypted by:
    //
    // ```text
    // XSalsa20-Poly1305(
    //   key=X25519HSalsa20(<sender.PCK>.secret, <receiver.PCK>.public),
    //   nonce=<sender.PCCK> || <sender.PCSN+>,
    // )
    // ```
    //
    // When receiving this message:
    //
    // 1. If the participant's _handshake state_ is not `await-approval`, log a
    //    warning, discard the message, remove the participant by running the
    //    _Participant Left Steps_ and abort these steps.
    // 2. Handle the message according to the content. If an unknown content
    //    variant has been received, log a warning, discard the message and
    //    remove the participant by running the _Participant Left Steps_.
    message ApprovalEnvelope {
      // Random amount of padding, ignored by the receiver
      bytes padding = 1;

      oneof content {
        GrantApproval grant_approval = 2;
        Approval approval = 3;
      }
    }

    // Grants approval to join a conference call with _join policy_
    // `JoinPolicy.REQUIRES_MODERATOR_APPROVAL`.
    //
    // Note: While only a moderator should be able to grant approval, this
    // message can technically be sent by any participant.
    //
    // When the user intends to grant another participant an approval to join:
    //
    // 1. If the participant's _handshake state_ is not `await-approval`, log a
    //    warning and abort these steps.
    // 2. If the user is not a moderator, log a warning and abort these steps.
    // 3. Create and send an approval signature.
    //
    // When receiving this message:
    //
    // 1. Verify the `signature`. If it is invalid, log a warning, discard the
    //    message and abort these steps.
    // 2. If the user already has a local _approval_ signature from another
    //    moderator, discard the message and abort these steps.
    // 3. Store the `signature` as the local _approval_.
    // 4. For each other participant with a _handshake state_ of
    //    `await-approval` (including the sender of this message)¹:
    //    1. Send an `Approval` message and update the _handshake state_ of
    //       `await_approval` with the _local approval sent_ flag.
    //    2. If the _handshake state_ of `await-approval` also has the _remote
    //       approval received_ flag, set the _handshake state_ for this
    //       participant to `done` and run the _Participant Active
    //       Steps_.
    //
    // ¹: Due to step 2 and the steps defined for `Auth` and `GuestAuth`, it
    // must be assumed that the other participant's _handshake state_ of
    // `await-approval` does not have the _local approval sent_ flag.
    message GrantApproval {
      // Signature over the participant's UID:
      //
      // ```text
      // Ed25519(
      //   key=GCMMK,
      //   message=
      //        u8(SignatureContext.JOIN_APPROVAL)
      //     || <receiver.participant-uid>
      //   )
      // ```
      //
      // IMPORTANT: The `ParticipantToParticipant.ApprovalEnvelope` that
      // encapsulates this message shall be encrypted by the same `PCSN` as
      // used to sign this message.
      bytes signature = 1;
    }

    // Present approval to join a conference call with _join policy_
    // `JoinPolicy.REQUIRES_MODERATOR_APPROVAL`.
    //
    // When receiving this message:
    //
    // 1. Verify the `signature`. If it is invalid, log a warning, discard the
    //    message, remove the participant by running the _Participant Left
    //    Steps_ and abort these steps.
    // 2. Update the _handshake state_ of `await-approval` with the _remote
    //    approval received_ flag.
    // 3. If the _handshake state_ of `await-approval` also has the _local
    //    approval sent_ flag, set the _handshake state_ for this participant to
    //    `done` and run the _Participant Active Steps_.
    message Approval {
      // Signature over the participant's UID:
      //
      // ```text
      // Ed25519(
      //   key=GCMMK,
      //   message=
      //        u8(SignatureContext.JOIN_APPROVAL)
      //     || <receiver.participant-uid>
      //   )
      // ```
      //
      // IMPORTANT: The `ParticipantToParticipant.ApprovalEnvelope` that
      // encapsulates this message shall be encrypted by the same `PCSN` as
      // used to sign this message.
      bytes signature = 1;
    }
  }

  // After fulfilling either the (normal) handshake or the guest handshake, all
  // following messages are encoded in `Envelope` and encrypted by:
  //
  // ```text
  // XSalsa20-Poly1305(
  //   key=X25519HSalsa20(<sender.PCK>.secret, <receiver.PCK>.public),
  //   nonce=<sender.PCCK> || <sender.PCSN+>,
  // )
  // ```
  //
  // Note: Since guest to guest handshakes are TOFU, an attacker knowing `GCK`
  // having control over the SFU may apply a MITM attack between a guest
  // participant and another guest participant. The attacker would be able to
  // silently eavesdrop all media traffic between the two participants. This is
  // repeatable for all other participants and means the attacker is able to
  // silently eavesdrop the whole call. Therefore, if a call is not open for
  // guests, guest handshakes **must not** be accepted.
  //
  // When receiving this message:
  //
  // 1. If the participant's _handshake state_ is not `done`, log a warning,
  //    discard the message, remove the participant by running the _Participant
  //    Left Steps_ and abort these steps.
  // 2. Handle the message according to the content. If an unknown content
  //    variant has been received, log a warning and discard the message.
  message Envelope {
    // Random amount of padding, ignored by the receiver
    bytes padding = 1;

    oneof content {
      // Announces new media keys a participant will apply soon.
      MediaKey rekey = 3;

      // Announces capture state changes of a participant.
      CaptureState capture_state = 4;

      // A moderator message.
      Moderator.Envelope moderator_envelope = 2;
    }
  }

  // Messages from moderators towards participants (including other moderators).
  message Moderator {
    // A message from a moderator.
    //
    // When receiving this message:
    //
    // 1. Verify the `signature`. If it does not match, log a warning, discard
    //    the message and abort these steps.
    // 2. Handle the message according to the content. If an unknown content
    //    variant has been received, log a warning and discard the message.
    message Envelope {
      // An `Content` message. After encoding, it is signed by:
      //
      // ```text
      // Ed25519(
      //   key=GCMMK,
      //   message=
      //        u8(SignatureContext.POST_AUTH_MESSAGE)
      //     || X25519HSalsa20(<sender.PCK>.secret, <receiver.PCK>.public)
      //     || <sender.PCCK>
      //     || <sender.PCSN+>
      //     || <message>
      //   )
      // ```
      //
      // IMPORTANT: The `ParticipantToParticipant.Envelope` that encapsulates this message shall be
      // encrypted by the same `PCSN` as used to sign this message.
      message Content {
        oneof content {
          ReportAsModerator report_as_moderator = 1;
          PromoteToModerator promote_to_moderator = 2;
          ForceLeave force_leave = 3;
          ForceCaptureStateOff force_capture_state_off = 4;
        }
      }
      bytes content = 1;

      // Signature over the encoded `Content` message.
      bytes signature = 2;
    }

    // Report as a moderator.
    //
    // When receiving this message, mark the sender as a moderator in the UI.
    message ReportAsModerator {}

    // Promote the receiver to a moderator.
    //
    // Note: This is final for the duration of the Group Call. A moderator
    // cannot be demoted.
    //
    // When receiving this message:
    //
    // 1. If the user already is a moderator, abort these steps.
    // 2. Derive GCMMK and calculate the associated public key from the received
    //    `gcmk`. If it does not match the known `GCMMK.public`, log a warning
    //    and abort these steps.
    // 3. Send a `Moderator.ReportAsModerator` message to all other participants
    //    (including the sender who promoted the user to a moderator).
    // 4. Notify the user of its moderator status and enable moderation
    //    functionality in the UI.
    message PromoteToModerator { bytes gcmk = 1; }

    // Force the receiver to leave the call.
    //
    // When receiving this message, immediately leave the call.
    message ForceLeave {}

    // Force the receiver's capture device to be turned off.
    //
    // Note: This is a momentary enforcement. A participant may immediately
    //       restart capturing a device (e.g. unmute itself) and the message is
    //       not repeated towards newly joined participants.
    //
    // When receiving this message:
    //
    // 1. Look up the corresponding device. If none could be found, abort these
    //    steps.
    // 2. If the device's capture state is already _off_, abort these steps.
    // 3. Send a `CaptureState` message for the device and follow the creation
    //    steps of that message (i.e. stop capturing, etc.).
    message ForceCaptureStateOff {
      enum Device {
        // Stop capturing all devices
        ALL = 0;
        // Stop capturing the microphone (i.e. mute)
        MICROPHONE = 1;
        // Stop capturing the camera
        CAMERA = 2;
        // Stop capturing the screen
        SCREEN = 3;
      }
      Device device = 1;
    }
  }

  // Media keys a participant will use for sending.
  //
  // When receiving this message, run the _Participant Rekey Steps_.
  message MediaKey {
    // The current epoch reflecting the PCMK state.
    //
    // Initially, epoch is `0` and increases each time a participant leaves. The
    // concrete mechanism is explained in the _Join/Leave_ section.
    uint32 epoch = 1;

    // The current ratchet counter reflecting the PCMK state.
    //
    // Initially (or when a participant leaves), the ratchet counter is `0` and
    // increases each time a participant joins. The ratcheting mechanism is
    // explained in the _Join/Leave_ section.
    uint32 ratchet_counter = 2;

    // The current state of the PCMK with the applied ratchet counter.
    //
    // Initially (or when a participant leaves), PCMK is a random 32 byte secret
    // key. The concrete mechanism is explained in the _Join/Leave_ section.
    //
    // This key must be identical **towards** all participants.
    bytes pcmk = 3;
  }

  // Signals a participant's device capturing state.
  //
  // When creating this message:
  //
  // 1. Let `device` be the device whose state is to be updated.
  // 2. If `device` is to be turned _off_:
  //    1. Stop capturing from the device.
  //    2. Pause the corresponding media track.
  // 3. If `device` is to be turned _on_:
  //    1. Start capturing from the device.
  //    2. Resume the corresponding media track.
  // 4. If `device` is of type _screen_:
  //    1. Send a `RequestTimestamp` to the SFU.
  //    2. Let `context` be the corresponding `Timestamp` response.
  // 5. Send the `CaptureState` message for the `device` with the provided
  //    `context`.
  //
  // When receiving this message:
  //
  // 1. Let `device` be the device of the sender whose state has been updated.
  // 2. If `device` was turned _off_ and the user is subscribed to the given
  //    `device`'s feed:
  //    1. Stop displaying the corresponding media feed in the UI.
  //    2. Pause the corresponding media track.
  //    3. If `device` is `Microphone`, no further action is necessary.
  //    4. If `device` is `Camera`, send a `ParticipantCamera.Unsubscribe`
  //       message to the SFU.
  //    5. If `device` is `Screen`, send a `ParticipantScreen.Unsubscribe`
  //       message to the SFU.
  // 3. If `device` was turned _on_ and the user is not subscribed to the given
  //    `device`'s feed:
  //    1. Resume the corresponding media track.
  //    2. Start displaying the corresponding media feed in the UI.
  //    3. If `device` is `Microphone`, no further action is necessary.
  //    4. If `device` is `Camera`, send a `ParticipantCamera.Subscribe` message
  //       to the SFU.
  //    5. If `device` is `Screen`:
  //       1. Send a `ParticipantScreen.Subscribe` message to the SFU.
  //       2. If `device`'s `started_at` timestamp is the most recent timestamp
  //          from all currently active screen shares of all participants, set
  //          the focus to this participant's screen.
  message CaptureState {
    // Capture state of the microphone.
    message Microphone {
      oneof state {
        common.Unit on = 1;
        common.Unit off = 2;
      }
    }

    // Capture state of the camera.
    message Camera {
      oneof state {
        common.Unit on = 1;
        common.Unit off = 2;
      }
    }

    // Capture state of the screen.
    message Screen {
      message On {
        // UNIX-ish timestamp in milliseconds retrieved from the SFU for when
        // the screen share was initiated.
        uint64 started_at = 1;
      }

      oneof state {
        On on = 1;
        common.Unit off = 2;
      }
    }

    oneof state {
      Microphone microphone = 1;
      Camera camera = 2;
      Screen screen = 3;
    }
  }
}

[Dauer der Verarbeitung: 0.49 Sekunden]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104842
#Domains=499168