atagen

macltok

public
Star0
Watch0
5a52012c9b1d7ec241e293fb63039a10884d6fdc
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: src/encryption.rs

96fdf90aacd6atagen2026-09-08 08:24 UTC1
use std::sync::Arc;
96fdf90aacd6atagen2026-09-08 08:24 UTC2
96fdf90aacd6atagen2026-09-08 08:24 UTC3
use futures_util::StreamExt;
96fdf90aacd6atagen2026-09-08 08:24 UTC4
use matrix_sdk::{
96fdf90aacd6atagen2026-09-08 08:24 UTC5
    Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC6
    encryption::{
96fdf90aacd6atagen2026-09-08 08:24 UTC7
        VerificationState,
96fdf90aacd6atagen2026-09-08 08:24 UTC8
        backups::BackupState,
96fdf90aacd6atagen2026-09-08 08:24 UTC9
        recovery::RecoveryState as SdkRecoveryState,
96fdf90aacd6atagen2026-09-08 08:24 UTC10
        verification::{
96fdf90aacd6atagen2026-09-08 08:24 UTC11
            SasState, SasVerification, Verification, VerificationRequest, VerificationRequestState,
96fdf90aacd6atagen2026-09-08 08:24 UTC12
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC13
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC14
    ruma::events::key::verification::VerificationMethod,
96fdf90aacd6atagen2026-09-08 08:24 UTC15
};
96fdf90aacd6atagen2026-09-08 08:24 UTC16
use ruma::{OwnedDeviceId, UserId};
96fdf90aacd6atagen2026-09-08 08:24 UTC17
use tokio::{sync::Mutex, task::JoinHandle};
96fdf90aacd6atagen2026-09-08 08:24 UTC18
96fdf90aacd6atagen2026-09-08 08:24 UTC19
use crate::{
31ce2453c82fatagen2026-09-24 13:07 UTC20
    EncryptionState, Error, ErrorKind, Event, KeyBackupState, RecoveryState, VerificationAction,
31ce2453c82fatagen2026-09-24 13:07 UTC21
    VerificationState as UiState, VerificationTarget, model::VerificationFailure,
31ce2453c82fatagen2026-09-24 13:07 UTC22
    transport::EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC23
};
96fdf90aacd6atagen2026-09-08 08:24 UTC24
96fdf90aacd6atagen2026-09-08 08:24 UTC25
pub(crate) fn encryption_state(
96fdf90aacd6atagen2026-09-08 08:24 UTC26
    device_id: OwnedDeviceId,
96fdf90aacd6atagen2026-09-08 08:24 UTC27
    recovery: SdkRecoveryState,
96fdf90aacd6atagen2026-09-08 08:24 UTC28
    verification: VerificationState,
96fdf90aacd6atagen2026-09-08 08:24 UTC29
    backup: BackupState,
96fdf90aacd6atagen2026-09-08 08:24 UTC30
) -> EncryptionState {
96fdf90aacd6atagen2026-09-08 08:24 UTC31
    EncryptionState {
96fdf90aacd6atagen2026-09-08 08:24 UTC32
        device_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC33
        device_verified: matches!(verification, VerificationState::Verified),
96fdf90aacd6atagen2026-09-08 08:24 UTC34
        recovery: match recovery {
96fdf90aacd6atagen2026-09-08 08:24 UTC35
            SdkRecoveryState::Unknown => RecoveryState::Unknown,
96fdf90aacd6atagen2026-09-08 08:24 UTC36
            SdkRecoveryState::Disabled => RecoveryState::Disabled,
96fdf90aacd6atagen2026-09-08 08:24 UTC37
            SdkRecoveryState::Incomplete => RecoveryState::Incomplete,
96fdf90aacd6atagen2026-09-08 08:24 UTC38
            SdkRecoveryState::Enabled => RecoveryState::Enabled,
96fdf90aacd6atagen2026-09-08 08:24 UTC39
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC40
        key_backup: match backup {
96fdf90aacd6atagen2026-09-08 08:24 UTC41
            BackupState::Unknown => KeyBackupState::Unknown,
96fdf90aacd6atagen2026-09-08 08:24 UTC42
            BackupState::Creating => KeyBackupState::Creating,
96fdf90aacd6atagen2026-09-08 08:24 UTC43
            BackupState::Enabling => KeyBackupState::Enabling,
96fdf90aacd6atagen2026-09-08 08:24 UTC44
            BackupState::Resuming => KeyBackupState::Resuming,
96fdf90aacd6atagen2026-09-08 08:24 UTC45
            BackupState::Enabled => KeyBackupState::Enabled,
96fdf90aacd6atagen2026-09-08 08:24 UTC46
            BackupState::Downloading => KeyBackupState::Downloading,
96fdf90aacd6atagen2026-09-08 08:24 UTC47
            BackupState::Disabling => KeyBackupState::Disabling,
96fdf90aacd6atagen2026-09-08 08:24 UTC48
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC49
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC50
}
96fdf90aacd6atagen2026-09-08 08:24 UTC51
96fdf90aacd6atagen2026-09-08 08:24 UTC52
#[derive(Default)]
96fdf90aacd6atagen2026-09-08 08:24 UTC53
pub(crate) struct VerificationController {
96fdf90aacd6atagen2026-09-08 08:24 UTC54
    active: Arc<Mutex<VerificationSession>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC55
    task: Option<JoinHandle<()>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC56
}
96fdf90aacd6atagen2026-09-08 08:24 UTC57
96fdf90aacd6atagen2026-09-08 08:24 UTC58
#[derive(Default)]
96fdf90aacd6atagen2026-09-08 08:24 UTC59
struct VerificationSession {
96fdf90aacd6atagen2026-09-08 08:24 UTC60
    request: Option<VerificationRequest>,
96fdf90aacd6atagen2026-09-08 08:24 UTC61
    sas: Option<SasVerification>,
96fdf90aacd6atagen2026-09-08 08:24 UTC62
}
96fdf90aacd6atagen2026-09-08 08:24 UTC63
96fdf90aacd6atagen2026-09-08 08:24 UTC64
impl VerificationController {
96fdf90aacd6atagen2026-09-08 08:24 UTC65
    pub(crate) async fn apply(
96fdf90aacd6atagen2026-09-08 08:24 UTC66
        &mut self,
96fdf90aacd6atagen2026-09-08 08:24 UTC67
        sender: EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC68
        action: VerificationAction,
31ce2453c82fatagen2026-09-24 13:07 UTC69
    ) -> Result<(), Error> {
96fdf90aacd6atagen2026-09-08 08:24 UTC70
        match action {
5a52012c9b1datagen2026-09-24 23:45 UTC71
            VerificationAction::Accept => self.accept().await,
96fdf90aacd6atagen2026-09-08 08:24 UTC72
            VerificationAction::Confirm => {
96fdf90aacd6atagen2026-09-08 08:24 UTC73
                // Retain a handle across the operation: the watcher clears
96fdf90aacd6atagen2026-09-08 08:24 UTC74
                // `active` when it publishes a terminal event, but this clone
96fdf90aacd6atagen2026-09-08 08:24 UTC75
                // still observes the shared SDK state afterwards.
96fdf90aacd6atagen2026-09-08 08:24 UTC76
                let sas = self.active.lock().await.sas.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC77
                match self.confirm().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC78
                    Ok(()) => Ok(()),
31ce2453c82fatagen2026-09-24 13:07 UTC79
                    Err(error) if error.kind == ErrorKind::VerificationInactive => Err(error),
96fdf90aacd6atagen2026-09-08 08:24 UTC80
                    // The confirm advanced local SDK state before its send failed,
96fdf90aacd6atagen2026-09-08 08:24 UTC81
                    // so the retained session could report success on retry without
96fdf90aacd6atagen2026-09-08 08:24 UTC82
                    // network traffic. Best-effort cancel (no lock held across the
96fdf90aacd6atagen2026-09-08 08:24 UTC83
                    // network), clear for restart, then report the terminal state.
96fdf90aacd6atagen2026-09-08 08:24 UTC84
                    Err(error) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC85
                        // Stop the watcher first so it cannot emit a stale
96fdf90aacd6atagen2026-09-08 08:24 UTC86
                        // Confirmed from the local transition we are unwinding.
96fdf90aacd6atagen2026-09-08 08:24 UTC87
                        self.stop_task().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC88
                        // The peer can complete or cancel the verification
96fdf90aacd6atagen2026-09-08 08:24 UTC89
                        // while our own Done send fails. The stopped watcher may
96fdf90aacd6atagen2026-09-08 08:24 UTC90
                        // already have published that terminal outcome (losing the
96fdf90aacd6atagen2026-09-08 08:24 UTC91
                        // claim below); otherwise this path owns it and must emit
96fdf90aacd6atagen2026-09-08 08:24 UTC92
                        // it instead of regressing to Cancelled.
96fdf90aacd6atagen2026-09-08 08:24 UTC93
                        if sas.as_ref().is_some_and(|sas| sas.is_done()) {
96fdf90aacd6atagen2026-09-08 08:24 UTC94
                            if take_verification(&self.active).await {
96fdf90aacd6atagen2026-09-08 08:24 UTC95
                                let _ =
96fdf90aacd6atagen2026-09-08 08:24 UTC96
                                    sender.send(Event::VerificationChanged(UiState::Done)).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC97
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC98
                            return Err(error);
96fdf90aacd6atagen2026-09-08 08:24 UTC99
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC100
                        if let Some(reason) = sas
96fdf90aacd6atagen2026-09-08 08:24 UTC101
                            .as_ref()
96fdf90aacd6atagen2026-09-08 08:24 UTC102
                            .and_then(|sas| sas.cancel_info())
96fdf90aacd6atagen2026-09-08 08:24 UTC103
                            .map(|info| info.reason().to_owned())
96fdf90aacd6atagen2026-09-08 08:24 UTC104
                        {
96fdf90aacd6atagen2026-09-08 08:24 UTC105
                            if take_verification(&self.active).await {
96fdf90aacd6atagen2026-09-08 08:24 UTC106
                                let _ = sender
96fdf90aacd6atagen2026-09-08 08:24 UTC107
                                    .send(Event::VerificationChanged(UiState::Cancelled(reason)))
96fdf90aacd6atagen2026-09-08 08:24 UTC108
                                    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC109
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC110
                            return Err(error);
96fdf90aacd6atagen2026-09-08 08:24 UTC111
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC112
                        if let Some(sas) = sas {
96fdf90aacd6atagen2026-09-08 08:24 UTC113
                            if sas.cancel().await.is_err() {
96fdf90aacd6atagen2026-09-08 08:24 UTC114
                                // The best-effort unwind already advanced
96fdf90aacd6atagen2026-09-08 08:24 UTC115
                                // local state; clearing here keeps the failed
96fdf90aacd6atagen2026-09-08 08:24 UTC116
                                // cancellation from looking retryable. The
96fdf90aacd6atagen2026-09-08 08:24 UTC117
                                // send is owned only if the watcher has not
96fdf90aacd6atagen2026-09-08 08:24 UTC118
                                // already claimed the session for its own
96fdf90aacd6atagen2026-09-08 08:24 UTC119
                                // terminal outcome.
96fdf90aacd6atagen2026-09-08 08:24 UTC120
                                if take_verification(&self.active).await {
96fdf90aacd6atagen2026-09-08 08:24 UTC121
                                    let _ = sender
96fdf90aacd6atagen2026-09-08 08:24 UTC122
                                        .send(Event::VerificationChanged(UiState::Cancelled(
96fdf90aacd6atagen2026-09-08 08:24 UTC123
                                            error.to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC124
                                        )))
96fdf90aacd6atagen2026-09-08 08:24 UTC125
                                        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC126
                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC127
                                return Err(error);
96fdf90aacd6atagen2026-09-08 08:24 UTC128
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC129
                            // The peer can complete while the unwind cancel is in
96fdf90aacd6atagen2026-09-08 08:24 UTC130
                            // flight and the SDK call no-ops; the Done then owns the
96fdf90aacd6atagen2026-09-08 08:24 UTC131
                            // terminal outcome, so report it instead of attributing
96fdf90aacd6atagen2026-09-08 08:24 UTC132
                            // it to the send failure.
96fdf90aacd6atagen2026-09-08 08:24 UTC133
                            if sas.is_done() {
96fdf90aacd6atagen2026-09-08 08:24 UTC134
                                if take_verification(&self.active).await {
96fdf90aacd6atagen2026-09-08 08:24 UTC135
                                    let _ = sender
96fdf90aacd6atagen2026-09-08 08:24 UTC136
                                        .send(Event::VerificationChanged(UiState::Done))
96fdf90aacd6atagen2026-09-08 08:24 UTC137
                                        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC138
                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC139
                                return Err(error);
96fdf90aacd6atagen2026-09-08 08:24 UTC140
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC141
                            if let Some(info) = sas.cancel_info() {
96fdf90aacd6atagen2026-09-08 08:24 UTC142
                                // Our own unwind cancel just marked the flow; it does
96fdf90aacd6atagen2026-09-08 08:24 UTC143
                                // not own the outcome yet, so fall through and report
96fdf90aacd6atagen2026-09-08 08:24 UTC144
                                // the send failure as Cancelled below. Only a peer
96fdf90aacd6atagen2026-09-08 08:24 UTC145
                                // cancellation racing the unwind owns it instead.
96fdf90aacd6atagen2026-09-08 08:24 UTC146
                                if !info.cancelled_by_us() {
96fdf90aacd6atagen2026-09-08 08:24 UTC147
                                    if take_verification(&self.active).await {
96fdf90aacd6atagen2026-09-08 08:24 UTC148
                                        let _ = sender
96fdf90aacd6atagen2026-09-08 08:24 UTC149
                                            .send(Event::VerificationChanged(UiState::Cancelled(
96fdf90aacd6atagen2026-09-08 08:24 UTC150
                                                info.reason().to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC151
                                            )))
96fdf90aacd6atagen2026-09-08 08:24 UTC152
                                            .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC153
                                    }
31ce2453c82fatagen2026-09-24 13:07 UTC154
                                    return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC155
                                        ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC156
                                        info.reason().to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC157
                                    ));
96fdf90aacd6atagen2026-09-08 08:24 UTC158
                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC159
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC160
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC161
                        if take_verification(&self.active).await {
96fdf90aacd6atagen2026-09-08 08:24 UTC162
                            let _ = sender
96fdf90aacd6atagen2026-09-08 08:24 UTC163
                                .send(Event::VerificationChanged(UiState::Cancelled(
96fdf90aacd6atagen2026-09-08 08:24 UTC164
                                    error.to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC165
                                )))
96fdf90aacd6atagen2026-09-08 08:24 UTC166
                                .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC167
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC168
                        Err(error)
96fdf90aacd6atagen2026-09-08 08:24 UTC169
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC170
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC171
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC172
            VerificationAction::Reject => self.reject().await,
96fdf90aacd6atagen2026-09-08 08:24 UTC173
            VerificationAction::Cancel => {
96fdf90aacd6atagen2026-09-08 08:24 UTC174
                // The SDK no-ops a cancel of an already-settled flow, so a
96fdf90aacd6atagen2026-09-08 08:24 UTC175
                // failed claim inside `cancel` already reported the true
96fdf90aacd6atagen2026-09-08 08:24 UTC176
                // outcome as an error and owns no event here. Only the claim
96fdf90aacd6atagen2026-09-08 08:24 UTC177
                // winner emits the local Cancelled.
96fdf90aacd6atagen2026-09-08 08:24 UTC178
                let claimed = self.cancel_claimed(Some(&sender)).await?;
96fdf90aacd6atagen2026-09-08 08:24 UTC179
                if claimed {
96fdf90aacd6atagen2026-09-08 08:24 UTC180
                    sender
96fdf90aacd6atagen2026-09-08 08:24 UTC181
                        .send(Event::VerificationChanged(UiState::Cancelled(
96fdf90aacd6atagen2026-09-08 08:24 UTC182
                            "cancelled by this client".to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC183
                        )))
96fdf90aacd6atagen2026-09-08 08:24 UTC184
                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC185
                        .map_err(|source| {
31ce2453c82fatagen2026-09-24 13:07 UTC186
                            Error::with_source(
31ce2453c82fatagen2026-09-24 13:07 UTC187
                                ErrorKind::Interrupted,
96fdf90aacd6atagen2026-09-08 08:24 UTC188
                                "event receiver has closed",
96fdf90aacd6atagen2026-09-08 08:24 UTC189
                                source,
96fdf90aacd6atagen2026-09-08 08:24 UTC190
                            )
96fdf90aacd6atagen2026-09-08 08:24 UTC191
                        })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC192
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC193
                Ok(())
96fdf90aacd6atagen2026-09-08 08:24 UTC194
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC195
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC196
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC197
96fdf90aacd6atagen2026-09-08 08:24 UTC198
    pub(crate) async fn start(
96fdf90aacd6atagen2026-09-08 08:24 UTC199
        &mut self,
96fdf90aacd6atagen2026-09-08 08:24 UTC200
        client: &Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC201
        sender: EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC202
        target: VerificationTarget,
31ce2453c82fatagen2026-09-24 13:07 UTC203
    ) -> Result<(), Error> {
96fdf90aacd6atagen2026-09-08 08:24 UTC204
        client
96fdf90aacd6atagen2026-09-08 08:24 UTC205
            .encryption()
96fdf90aacd6atagen2026-09-08 08:24 UTC206
            .wait_for_e2ee_initialization_tasks()
96fdf90aacd6atagen2026-09-08 08:24 UTC207
            .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC208
        let own_user_id = client.user_id().ok_or_else(|| {
31ce2453c82fatagen2026-09-24 13:07 UTC209
            Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC210
                ErrorKind::Unauthenticated,
96fdf90aacd6atagen2026-09-08 08:24 UTC211
                "the Matrix client is not authenticated",
96fdf90aacd6atagen2026-09-08 08:24 UTC212
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC213
        })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC214
        if self.has_active_verification().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC215
            self.cancel().await?;
96fdf90aacd6atagen2026-09-08 08:24 UTC216
        } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC217
            self.stop_task().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC218
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC219
        let request = match &target {
96fdf90aacd6atagen2026-09-08 08:24 UTC220
            VerificationTarget::OwnIdentity => {
96fdf90aacd6atagen2026-09-08 08:24 UTC221
                request_identity_verification(client, own_user_id).await?
96fdf90aacd6atagen2026-09-08 08:24 UTC222
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC223
            VerificationTarget::User(user_id) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC224
                request_identity_verification(client, user_id).await?
96fdf90aacd6atagen2026-09-08 08:24 UTC225
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC226
            VerificationTarget::Device { user_id, device_id } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC227
                let device = client
96fdf90aacd6atagen2026-09-08 08:24 UTC228
                    .encryption()
96fdf90aacd6atagen2026-09-08 08:24 UTC229
                    .get_device(user_id, device_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC230
                    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC231
                    .map_err(|source| {
31ce2453c82fatagen2026-09-24 13:07 UTC232
                        Error::with_source(
31ce2453c82fatagen2026-09-24 13:07 UTC233
                            ErrorKind::Encryption,
96fdf90aacd6atagen2026-09-08 08:24 UTC234
                            "reading the verification device failed",
96fdf90aacd6atagen2026-09-08 08:24 UTC235
                            source,
96fdf90aacd6atagen2026-09-08 08:24 UTC236
                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC237
                    })?
96fdf90aacd6atagen2026-09-08 08:24 UTC238
                    .ok_or_else(|| {
31ce2453c82fatagen2026-09-24 13:07 UTC239
                        Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC240
                            ErrorKind::VerificationUnsupported,
96fdf90aacd6atagen2026-09-08 08:24 UTC241
                            "the verification device is unavailable",
96fdf90aacd6atagen2026-09-08 08:24 UTC242
                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC243
                    })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC244
                device
96fdf90aacd6atagen2026-09-08 08:24 UTC245
                    .request_verification_with_methods(vec![VerificationMethod::SasV1])
96fdf90aacd6atagen2026-09-08 08:24 UTC246
                    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC247
                    .map_err(|source| {
31ce2453c82fatagen2026-09-24 13:07 UTC248
                        Error::from_sdk("starting device verification failed", source)
96fdf90aacd6atagen2026-09-08 08:24 UTC249
                    })?
96fdf90aacd6atagen2026-09-08 08:24 UTC250
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC251
        };
5a52012c9b1datagen2026-09-24 23:45 UTC252
        // subscribed before anything can wait on the event channel, or a quick peer's
5a52012c9b1datagen2026-09-24 23:45 UTC253
        // answer lands while we're blocked and nobody ever hears it
5a52012c9b1datagen2026-09-24 23:45 UTC254
        let changes = request.changes();
96fdf90aacd6atagen2026-09-08 08:24 UTC255
        self.active.lock().await.request = Some(request.clone());
96fdf90aacd6atagen2026-09-08 08:24 UTC256
        sender
96fdf90aacd6atagen2026-09-08 08:24 UTC257
            .send(Event::VerificationTargetChanged(target))
96fdf90aacd6atagen2026-09-08 08:24 UTC258
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC259
            .map_err(|source| {
31ce2453c82fatagen2026-09-24 13:07 UTC260
                Error::with_source(ErrorKind::Interrupted, "event receiver has closed", source)
96fdf90aacd6atagen2026-09-08 08:24 UTC261
            })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC262
        sender
96fdf90aacd6atagen2026-09-08 08:24 UTC263
            .send(Event::VerificationChanged(UiState::Waiting))
96fdf90aacd6atagen2026-09-08 08:24 UTC264
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC265
            .map_err(|source| {
31ce2453c82fatagen2026-09-24 13:07 UTC266
                Error::with_source(ErrorKind::Interrupted, "event receiver has closed", source)
96fdf90aacd6atagen2026-09-08 08:24 UTC267
            })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC268
        let active = self.active.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC269
        self.task = Some(tokio::spawn(async move {
5a52012c9b1datagen2026-09-24 23:45 UTC270
            follow_request(request, changes, active, sender).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC271
        }));
96fdf90aacd6atagen2026-09-08 08:24 UTC272
        Ok(())
96fdf90aacd6atagen2026-09-08 08:24 UTC273
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC274
5a52012c9b1datagen2026-09-24 23:45 UTC275
    /// another device asked us. only taken when nothing else is in flight, and not accepted
5a52012c9b1datagen2026-09-24 23:45 UTC276
    /// until the user says so with `VerificationAction::Accept`
5a52012c9b1datagen2026-09-24 23:45 UTC277
    pub(crate) async fn adopt_incoming(
5a52012c9b1datagen2026-09-24 23:45 UTC278
        &mut self,
5a52012c9b1datagen2026-09-24 23:45 UTC279
        sender: EventSender,
5a52012c9b1datagen2026-09-24 23:45 UTC280
        request: VerificationRequest,
5a52012c9b1datagen2026-09-24 23:45 UTC281
    ) {
5a52012c9b1datagen2026-09-24 23:45 UTC282
        if request.we_started() || request.is_done() || request.cancel_info().is_some() {
5a52012c9b1datagen2026-09-24 23:45 UTC283
            return;
5a52012c9b1datagen2026-09-24 23:45 UTC284
        }
5a52012c9b1datagen2026-09-24 23:45 UTC285
        if self.has_active_verification().await {
5a52012c9b1datagen2026-09-24 23:45 UTC286
            tracing::debug!("ignoring a verification request while another is in flight");
5a52012c9b1datagen2026-09-24 23:45 UTC287
            return;
5a52012c9b1datagen2026-09-24 23:45 UTC288
        }
5a52012c9b1datagen2026-09-24 23:45 UTC289
        self.stop_task().await;
5a52012c9b1datagen2026-09-24 23:45 UTC290
        let changes = request.changes();
5a52012c9b1datagen2026-09-24 23:45 UTC291
        self.active.lock().await.request = Some(request.clone());
5a52012c9b1datagen2026-09-24 23:45 UTC292
        let incoming = UiState::Incoming {
5a52012c9b1datagen2026-09-24 23:45 UTC293
            user_id: request.other_user_id().to_owned(),
5a52012c9b1datagen2026-09-24 23:45 UTC294
            device_id: match request.state() {
5a52012c9b1datagen2026-09-24 23:45 UTC295
                VerificationRequestState::Requested {
5a52012c9b1datagen2026-09-24 23:45 UTC296
                    other_device_data, ..
5a52012c9b1datagen2026-09-24 23:45 UTC297
                }
5a52012c9b1datagen2026-09-24 23:45 UTC298
                | VerificationRequestState::Ready {
5a52012c9b1datagen2026-09-24 23:45 UTC299
                    other_device_data, ..
5a52012c9b1datagen2026-09-24 23:45 UTC300
                } => Some(other_device_data.device_id().to_owned()),
5a52012c9b1datagen2026-09-24 23:45 UTC301
                _ => None,
5a52012c9b1datagen2026-09-24 23:45 UTC302
            },
5a52012c9b1datagen2026-09-24 23:45 UTC303
        };
5a52012c9b1datagen2026-09-24 23:45 UTC304
        let target = VerificationTarget::User(request.other_user_id().to_owned());
5a52012c9b1datagen2026-09-24 23:45 UTC305
        for event in [
5a52012c9b1datagen2026-09-24 23:45 UTC306
            Event::VerificationTargetChanged(target),
5a52012c9b1datagen2026-09-24 23:45 UTC307
            Event::VerificationChanged(incoming),
5a52012c9b1datagen2026-09-24 23:45 UTC308
        ] {
5a52012c9b1datagen2026-09-24 23:45 UTC309
            if sender.send(event).await.is_err() {
5a52012c9b1datagen2026-09-24 23:45 UTC310
                return;
5a52012c9b1datagen2026-09-24 23:45 UTC311
            }
5a52012c9b1datagen2026-09-24 23:45 UTC312
        }
5a52012c9b1datagen2026-09-24 23:45 UTC313
        let active = self.active.clone();
5a52012c9b1datagen2026-09-24 23:45 UTC314
        self.task = Some(tokio::spawn(async move {
5a52012c9b1datagen2026-09-24 23:45 UTC315
            follow_request(request, changes, active, sender).await;
5a52012c9b1datagen2026-09-24 23:45 UTC316
        }));
5a52012c9b1datagen2026-09-24 23:45 UTC317
    }
5a52012c9b1datagen2026-09-24 23:45 UTC318
5a52012c9b1datagen2026-09-24 23:45 UTC319
    async fn accept(&self) -> Result<(), Error> {
5a52012c9b1datagen2026-09-24 23:45 UTC320
        let inactive = |message: &str| Error::new(ErrorKind::VerificationInactive, message);
5a52012c9b1datagen2026-09-24 23:45 UTC321
        let (request, sas) = {
5a52012c9b1datagen2026-09-24 23:45 UTC322
            let active = self.active.lock().await;
5a52012c9b1datagen2026-09-24 23:45 UTC323
            (active.request.clone(), active.sas.is_some())
5a52012c9b1datagen2026-09-24 23:45 UTC324
        };
5a52012c9b1datagen2026-09-24 23:45 UTC325
        let request = request.ok_or_else(|| inactive("no verification request is pending"))?;
5a52012c9b1datagen2026-09-24 23:45 UTC326
        if request.we_started() {
5a52012c9b1datagen2026-09-24 23:45 UTC327
            return Err(inactive("nobody asked us, we asked them"));
5a52012c9b1datagen2026-09-24 23:45 UTC328
        }
5a52012c9b1datagen2026-09-24 23:45 UTC329
        if request.is_done() {
5a52012c9b1datagen2026-09-24 23:45 UTC330
            return Err(inactive("verification is already complete"));
5a52012c9b1datagen2026-09-24 23:45 UTC331
        }
5a52012c9b1datagen2026-09-24 23:45 UTC332
        if let Some(info) = request.cancel_info() {
5a52012c9b1datagen2026-09-24 23:45 UTC333
            return Err(inactive(info.reason()));
5a52012c9b1datagen2026-09-24 23:45 UTC334
        }
5a52012c9b1datagen2026-09-24 23:45 UTC335
        if sas {
5a52012c9b1datagen2026-09-24 23:45 UTC336
            return Err(inactive("verification has already started"));
5a52012c9b1datagen2026-09-24 23:45 UTC337
        }
5a52012c9b1datagen2026-09-24 23:45 UTC338
        request
5a52012c9b1datagen2026-09-24 23:45 UTC339
            .accept_with_methods(vec![VerificationMethod::SasV1])
5a52012c9b1datagen2026-09-24 23:45 UTC340
            .await
5a52012c9b1datagen2026-09-24 23:45 UTC341
            .map_err(|source| Error::from_sdk("accepting verification request failed", source))
5a52012c9b1datagen2026-09-24 23:45 UTC342
    }
5a52012c9b1datagen2026-09-24 23:45 UTC343
31ce2453c82fatagen2026-09-24 13:07 UTC344
    async fn confirm(&self) -> Result<(), Error> {
96fdf90aacd6atagen2026-09-08 08:24 UTC345
        let sas = self.active.lock().await.sas.clone().ok_or_else(|| {
31ce2453c82fatagen2026-09-24 13:07 UTC346
            Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC347
                ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC348
                "verification has not reached comparison",
96fdf90aacd6atagen2026-09-08 08:24 UTC349
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC350
        })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC351
        // Report a settled flow truthfully instead of as "not reached
96fdf90aacd6atagen2026-09-08 08:24 UTC352
        // comparison": the SDK would no-op the confirm with success.
96fdf90aacd6atagen2026-09-08 08:24 UTC353
        if sas.is_done() {
31ce2453c82fatagen2026-09-24 13:07 UTC354
            return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC355
                ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC356
                "verification is already complete",
96fdf90aacd6atagen2026-09-08 08:24 UTC357
            ));
96fdf90aacd6atagen2026-09-08 08:24 UTC358
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC359
        if let Some(info) = sas.cancel_info() {
31ce2453c82fatagen2026-09-24 13:07 UTC360
            return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC361
                ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC362
                info.reason().to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC363
            ));
96fdf90aacd6atagen2026-09-08 08:24 UTC364
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC365
        // Only the short-auth-string stage is confirmable. The SDK reports the
96fdf90aacd6atagen2026-09-08 08:24 UTC366
        // remote-MAC-arrived state through the same presentable flag, while an
96fdf90aacd6atagen2026-09-08 08:24 UTC367
        // early confirm would locally no-op and falsely report success.
96fdf90aacd6atagen2026-09-08 08:24 UTC368
        if !sas.can_be_presented() {
31ce2453c82fatagen2026-09-24 13:07 UTC369
            return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC370
                ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC371
                "verification has not reached comparison",
96fdf90aacd6atagen2026-09-08 08:24 UTC372
            ));
96fdf90aacd6atagen2026-09-08 08:24 UTC373
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC374
        // Our own confirm can move the flow to Done, so only Done that predates
96fdf90aacd6atagen2026-09-08 08:24 UTC375
        // the send means the SDK no-op'd on a stale session; a cancellation
96fdf90aacd6atagen2026-09-08 08:24 UTC376
        // racing the send must not report success.
96fdf90aacd6atagen2026-09-08 08:24 UTC377
        let was_done = sas.is_done();
96fdf90aacd6atagen2026-09-08 08:24 UTC378
        sas.confirm()
96fdf90aacd6atagen2026-09-08 08:24 UTC379
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC380
            .map_err(|source| Error::from_sdk("confirming verification failed", source))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC381
        if sas.is_done() {
96fdf90aacd6atagen2026-09-08 08:24 UTC382
            if was_done {
31ce2453c82fatagen2026-09-24 13:07 UTC383
                return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC384
                    ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC385
                    "verification is already complete",
96fdf90aacd6atagen2026-09-08 08:24 UTC386
                ));
96fdf90aacd6atagen2026-09-08 08:24 UTC387
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC388
        } else if sas.is_cancelled() {
96fdf90aacd6atagen2026-09-08 08:24 UTC389
            let reason = sas
96fdf90aacd6atagen2026-09-08 08:24 UTC390
                .cancel_info()
96fdf90aacd6atagen2026-09-08 08:24 UTC391
                .map(|info| info.reason().to_owned())
96fdf90aacd6atagen2026-09-08 08:24 UTC392
                .unwrap_or_else(|| "verification was cancelled".to_owned());
31ce2453c82fatagen2026-09-24 13:07 UTC393
            return Err(Error::new(ErrorKind::VerificationInactive, reason));
96fdf90aacd6atagen2026-09-08 08:24 UTC394
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC395
        Ok(())
96fdf90aacd6atagen2026-09-08 08:24 UTC396
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC397
31ce2453c82fatagen2026-09-24 13:07 UTC398
    async fn reject(&self) -> Result<(), Error> {
96fdf90aacd6atagen2026-09-08 08:24 UTC399
        let sas = self.active.lock().await.sas.clone().ok_or_else(|| {
31ce2453c82fatagen2026-09-24 13:07 UTC400
            Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC401
                ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC402
                "verification has not reached comparison",
96fdf90aacd6atagen2026-09-08 08:24 UTC403
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC404
        })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC405
        // Report a settled flow truthfully instead of as "not reached
96fdf90aacd6atagen2026-09-08 08:24 UTC406
        // comparison": the SDK would no-op the mismatch with success.
96fdf90aacd6atagen2026-09-08 08:24 UTC407
        if sas.is_done() {
31ce2453c82fatagen2026-09-24 13:07 UTC408
            return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC409
                ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC410
                "verification is already complete",
96fdf90aacd6atagen2026-09-08 08:24 UTC411
            ));
96fdf90aacd6atagen2026-09-08 08:24 UTC412
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC413
        if let Some(info) = sas.cancel_info() {
31ce2453c82fatagen2026-09-24 13:07 UTC414
            return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC415
                ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC416
                info.reason().to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC417
            ));
96fdf90aacd6atagen2026-09-08 08:24 UTC418
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC419
        // Rejecting before the comparison stage would cancel a flow the user
96fdf90aacd6atagen2026-09-08 08:24 UTC420
        // never compared, so it requires the same presentable state.
96fdf90aacd6atagen2026-09-08 08:24 UTC421
        if !sas.can_be_presented() {
31ce2453c82fatagen2026-09-24 13:07 UTC422
            return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC423
                ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC424
                "verification has not reached comparison",
96fdf90aacd6atagen2026-09-08 08:24 UTC425
            ));
96fdf90aacd6atagen2026-09-08 08:24 UTC426
        }
fa9114fcf75fatagen2026-09-24 12:51 UTC427
        sas.mismatch()
fa9114fcf75fatagen2026-09-24 12:51 UTC428
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC429
            .map_err(|source| Error::from_sdk("rejecting verification failed", source))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC430
        // A peer completing the flow concurrently must not be reported as
96fdf90aacd6atagen2026-09-08 08:24 UTC431
        // rejected. A remote cancellation racing the send turns the SDK call
96fdf90aacd6atagen2026-09-08 08:24 UTC432
        // into a no-op success, so report it instead of a false success.
96fdf90aacd6atagen2026-09-08 08:24 UTC433
        if sas.is_done() {
31ce2453c82fatagen2026-09-24 13:07 UTC434
            return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC435
                ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC436
                "verification is already complete",
96fdf90aacd6atagen2026-09-08 08:24 UTC437
            ));
96fdf90aacd6atagen2026-09-08 08:24 UTC438
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC439
        if let Some(info) = sas.cancel_info() {
96fdf90aacd6atagen2026-09-08 08:24 UTC440
            // Our own mismatch just marked the flow cancelled-by-us; that is the
96fdf90aacd6atagen2026-09-08 08:24 UTC441
            // expected reject path and stays Ok. Only a peer cancel reports an
96fdf90aacd6atagen2026-09-08 08:24 UTC442
            // error.
96fdf90aacd6atagen2026-09-08 08:24 UTC443
            if !info.cancelled_by_us() {
31ce2453c82fatagen2026-09-24 13:07 UTC444
                return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC445
                    ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC446
                    info.reason().to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC447
                ));
96fdf90aacd6atagen2026-09-08 08:24 UTC448
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC449
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC450
        Ok(())
96fdf90aacd6atagen2026-09-08 08:24 UTC451
    }
31ce2453c82fatagen2026-09-24 13:07 UTC452
    pub(crate) async fn cancel(&mut self) -> Result<(), Error> {
96fdf90aacd6atagen2026-09-08 08:24 UTC453
        self.cancel_claimed(None).await.map(|_| ())
96fdf90aacd6atagen2026-09-08 08:24 UTC454
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC455
96fdf90aacd6atagen2026-09-08 08:24 UTC456
    /// Return whether this action won the claim for a local cancellation.
96fdf90aacd6atagen2026-09-08 08:24 UTC457
    /// The optional sender reports a peer terminal outcome when it races after
96fdf90aacd6atagen2026-09-08 08:24 UTC458
    /// the claim; starting a replacement verification does not publish it.
31ce2453c82fatagen2026-09-24 13:07 UTC459
    async fn cancel_claimed(&mut self, sender: Option<&EventSender>) -> Result<bool, Error> {
96fdf90aacd6atagen2026-09-08 08:24 UTC460
        let (sas, request) = {
96fdf90aacd6atagen2026-09-08 08:24 UTC461
            let active = self.active.lock().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC462
            (active.sas.clone(), active.request.clone())
96fdf90aacd6atagen2026-09-08 08:24 UTC463
        };
96fdf90aacd6atagen2026-09-08 08:24 UTC464
        if sas.is_none() && request.is_none() {
31ce2453c82fatagen2026-09-24 13:07 UTC465
            return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC466
                ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC467
                "no verification is active",
96fdf90aacd6atagen2026-09-08 08:24 UTC468
            ));
96fdf90aacd6atagen2026-09-08 08:24 UTC469
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC470
        // The watcher owns terminal emission and claims the session when it
96fdf90aacd6atagen2026-09-08 08:24 UTC471
        // publishes. If the SDK is already terminal, the watcher either already
96fdf90aacd6atagen2026-09-08 08:24 UTC472
        // reported it or is about to: leave session and watcher alone so the
96fdf90aacd6atagen2026-09-08 08:24 UTC473
        // outcome is emitted exactly once, and report it instead of letting the
96fdf90aacd6atagen2026-09-08 08:24 UTC474
        // SDK no-op success regress Done to "cancelled by this client".
96fdf90aacd6atagen2026-09-08 08:24 UTC475
        if let Some(sas) = sas.as_ref() {
96fdf90aacd6atagen2026-09-08 08:24 UTC476
            if sas.is_done() {
31ce2453c82fatagen2026-09-24 13:07 UTC477
                return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC478
                    ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC479
                    "verification is already complete",
96fdf90aacd6atagen2026-09-08 08:24 UTC480
                ));
96fdf90aacd6atagen2026-09-08 08:24 UTC481
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC482
            if let Some(info) = sas.cancel_info() {
31ce2453c82fatagen2026-09-24 13:07 UTC483
                return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC484
                    ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC485
                    info.reason().to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC486
                ));
96fdf90aacd6atagen2026-09-08 08:24 UTC487
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC488
        } else if let Some(request) = request.as_ref() {
96fdf90aacd6atagen2026-09-08 08:24 UTC489
            if request.is_done() {
31ce2453c82fatagen2026-09-24 13:07 UTC490
                return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC491
                    ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC492
                    "verification is already complete",
96fdf90aacd6atagen2026-09-08 08:24 UTC493
                ));
96fdf90aacd6atagen2026-09-08 08:24 UTC494
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC495
            if let Some(info) = request.cancel_info() {
31ce2453c82fatagen2026-09-24 13:07 UTC496
                return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC497
                    ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC498
                    info.reason().to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC499
                ));
96fdf90aacd6atagen2026-09-08 08:24 UTC500
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC501
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC502
        // Own the terminal outcome from here: claim the session before
96fdf90aacd6atagen2026-09-08 08:24 UTC503
        // stopping the watcher so a watcher that already claimed (and will
96fdf90aacd6atagen2026-09-08 08:24 UTC504
        // emit its own terminal event) turns this into a truthful error
96fdf90aacd6atagen2026-09-08 08:24 UTC505
        // instead of a duplicate local cancel; no lock is held across sends.
96fdf90aacd6atagen2026-09-08 08:24 UTC506
        if !take_verification(&self.active).await {
96fdf90aacd6atagen2026-09-08 08:24 UTC507
            // Re-check the SDK: the claim loss may be a stale session after a
96fdf90aacd6atagen2026-09-08 08:24 UTC508
            // terminal outcome, or a concurrent start of a new flow. Only the
96fdf90aacd6atagen2026-09-08 08:24 UTC509
            // terminal case reports here; a live session belongs to the new
96fdf90aacd6atagen2026-09-08 08:24 UTC510
            // flow and must not be disturbed.
96fdf90aacd6atagen2026-09-08 08:24 UTC511
            if let Some(sas) = sas.as_ref() {
96fdf90aacd6atagen2026-09-08 08:24 UTC512
                if sas.is_done() {
31ce2453c82fatagen2026-09-24 13:07 UTC513
                    return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC514
                        ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC515
                        "verification is already complete",
96fdf90aacd6atagen2026-09-08 08:24 UTC516
                    ));
96fdf90aacd6atagen2026-09-08 08:24 UTC517
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC518
                if let Some(info) = sas.cancel_info() {
31ce2453c82fatagen2026-09-24 13:07 UTC519
                    return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC520
                        ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC521
                        info.reason().to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC522
                    ));
96fdf90aacd6atagen2026-09-08 08:24 UTC523
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC524
            } else if let Some(request) = request.as_ref() {
96fdf90aacd6atagen2026-09-08 08:24 UTC525
                if request.is_done() {
31ce2453c82fatagen2026-09-24 13:07 UTC526
                    return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC527
                        ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC528
                        "verification is already complete",
96fdf90aacd6atagen2026-09-08 08:24 UTC529
                    ));
96fdf90aacd6atagen2026-09-08 08:24 UTC530
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC531
                if let Some(info) = request.cancel_info() {
31ce2453c82fatagen2026-09-24 13:07 UTC532
                    return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC533
                        ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC534
                        info.reason().to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC535
                    ));
96fdf90aacd6atagen2026-09-08 08:24 UTC536
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC537
            }
31ce2453c82fatagen2026-09-24 13:07 UTC538
            return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC539
                ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC540
                "no verification is active",
96fdf90aacd6atagen2026-09-08 08:24 UTC541
            ));
96fdf90aacd6atagen2026-09-08 08:24 UTC542
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC543
        self.stop_task().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC544
        if let Some(sas) = sas {
96fdf90aacd6atagen2026-09-08 08:24 UTC545
            let result = sas.cancel().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC546
            if sas.is_done() {
96fdf90aacd6atagen2026-09-08 08:24 UTC547
                report_claimed_terminal(sender, UiState::Done).await;
31ce2453c82fatagen2026-09-24 13:07 UTC548
                return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC549
                    ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC550
                    "verification is already complete",
96fdf90aacd6atagen2026-09-08 08:24 UTC551
                ));
96fdf90aacd6atagen2026-09-08 08:24 UTC552
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC553
            if let Some(info) = sas.cancel_info()
96fdf90aacd6atagen2026-09-08 08:24 UTC554
                && !info.cancelled_by_us()
96fdf90aacd6atagen2026-09-08 08:24 UTC555
            {
96fdf90aacd6atagen2026-09-08 08:24 UTC556
                let reason = info.reason().to_owned();
96fdf90aacd6atagen2026-09-08 08:24 UTC557
                report_claimed_terminal(sender, UiState::Cancelled(reason.clone())).await;
31ce2453c82fatagen2026-09-24 13:07 UTC558
                return Err(Error::new(ErrorKind::VerificationInactive, reason));
96fdf90aacd6atagen2026-09-08 08:24 UTC559
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC560
            if let Err(source) = result {
31ce2453c82fatagen2026-09-24 13:07 UTC561
                let error = Error::from_sdk("cancelling verification failed", source);
96fdf90aacd6atagen2026-09-08 08:24 UTC562
                report_claimed_terminal(sender, UiState::Cancelled(error.to_string())).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC563
                return Err(error);
96fdf90aacd6atagen2026-09-08 08:24 UTC564
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC565
            Ok(true)
96fdf90aacd6atagen2026-09-08 08:24 UTC566
        } else if let Some(request) = request {
96fdf90aacd6atagen2026-09-08 08:24 UTC567
            let result = request.cancel().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC568
            if request.is_done() {
96fdf90aacd6atagen2026-09-08 08:24 UTC569
                report_claimed_terminal(sender, UiState::Done).await;
31ce2453c82fatagen2026-09-24 13:07 UTC570
                return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC571
                    ErrorKind::VerificationInactive,
96fdf90aacd6atagen2026-09-08 08:24 UTC572
                    "verification is already complete",
96fdf90aacd6atagen2026-09-08 08:24 UTC573
                ));
96fdf90aacd6atagen2026-09-08 08:24 UTC574
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC575
            if let Some(info) = request.cancel_info()
96fdf90aacd6atagen2026-09-08 08:24 UTC576
                && !info.cancelled_by_us()
96fdf90aacd6atagen2026-09-08 08:24 UTC577
            {
96fdf90aacd6atagen2026-09-08 08:24 UTC578
                let reason = info.reason().to_owned();
96fdf90aacd6atagen2026-09-08 08:24 UTC579
                report_claimed_terminal(sender, UiState::Cancelled(reason.clone())).await;
31ce2453c82fatagen2026-09-24 13:07 UTC580
                return Err(Error::new(ErrorKind::VerificationInactive, reason));
96fdf90aacd6atagen2026-09-08 08:24 UTC581
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC582
            if let Err(source) = result {
31ce2453c82fatagen2026-09-24 13:07 UTC583
                let error = Error::from_sdk("cancelling verification failed", source);
96fdf90aacd6atagen2026-09-08 08:24 UTC584
                report_claimed_terminal(sender, UiState::Cancelled(error.to_string())).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC585
                return Err(error);
96fdf90aacd6atagen2026-09-08 08:24 UTC586
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC587
            Ok(true)
96fdf90aacd6atagen2026-09-08 08:24 UTC588
        } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC589
            Ok(false)
96fdf90aacd6atagen2026-09-08 08:24 UTC590
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC591
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC592
96fdf90aacd6atagen2026-09-08 08:24 UTC593
    async fn has_active_verification(&self) -> bool {
96fdf90aacd6atagen2026-09-08 08:24 UTC594
        let active = self.active.lock().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC595
        active.request.is_some() || active.sas.is_some()
96fdf90aacd6atagen2026-09-08 08:24 UTC596
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC597
96fdf90aacd6atagen2026-09-08 08:24 UTC598
    async fn stop_task(&mut self) {
96fdf90aacd6atagen2026-09-08 08:24 UTC599
        if let Some(task) = self.task.take() {
96fdf90aacd6atagen2026-09-08 08:24 UTC600
            task.abort();
96fdf90aacd6atagen2026-09-08 08:24 UTC601
            let _ = task.await;
96fdf90aacd6atagen2026-09-08 08:24 UTC602
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC603
    }
31ce2453c82fatagen2026-09-24 13:07 UTC604
}
96fdf90aacd6atagen2026-09-08 08:24 UTC605
31ce2453c82fatagen2026-09-24 13:07 UTC606
/// shutdown forgets the flow without telling the sdk, a network send could hang it
31ce2453c82fatagen2026-09-24 13:07 UTC607
impl Drop for VerificationController {
31ce2453c82fatagen2026-09-24 13:07 UTC608
    fn drop(&mut self) {
31ce2453c82fatagen2026-09-24 13:07 UTC609
        if let Some(task) = self.task.take() {
31ce2453c82fatagen2026-09-24 13:07 UTC610
            task.abort();
31ce2453c82fatagen2026-09-24 13:07 UTC611
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC612
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC613
}
96fdf90aacd6atagen2026-09-08 08:24 UTC614
96fdf90aacd6atagen2026-09-08 08:24 UTC615
async fn request_identity_verification(
96fdf90aacd6atagen2026-09-08 08:24 UTC616
    client: &Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC617
    user_id: &UserId,
31ce2453c82fatagen2026-09-24 13:07 UTC618
) -> Result<VerificationRequest, Error> {
96fdf90aacd6atagen2026-09-08 08:24 UTC619
    let identity = client
96fdf90aacd6atagen2026-09-08 08:24 UTC620
        .encryption()
96fdf90aacd6atagen2026-09-08 08:24 UTC621
        .request_user_identity(user_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC622
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC623
        .map_err(|source| Error::from_sdk("requesting the user identity failed", source))?
96fdf90aacd6atagen2026-09-08 08:24 UTC624
        .ok_or_else(|| {
31ce2453c82fatagen2026-09-24 13:07 UTC625
            Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC626
                ErrorKind::NoCrossSigningIdentity,
96fdf90aacd6atagen2026-09-08 08:24 UTC627
                "the user has no cross-signing identity",
96fdf90aacd6atagen2026-09-08 08:24 UTC628
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC629
        })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC630
    identity
96fdf90aacd6atagen2026-09-08 08:24 UTC631
        .request_verification_with_methods(vec![VerificationMethod::SasV1])
96fdf90aacd6atagen2026-09-08 08:24 UTC632
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC633
        .map_err(|source| Error::from_verification_request("starting verification failed", source))
96fdf90aacd6atagen2026-09-08 08:24 UTC634
}
96fdf90aacd6atagen2026-09-08 08:24 UTC635
5a52012c9b1datagen2026-09-24 23:45 UTC636
/// `changes` is subscribed by the caller before the request could move, and the current state
5a52012c9b1datagen2026-09-24 23:45 UTC637
/// is handled first, since the stream only reports what happens after it was taken
96fdf90aacd6atagen2026-09-08 08:24 UTC638
async fn follow_request(
96fdf90aacd6atagen2026-09-08 08:24 UTC639
    request: VerificationRequest,
5a52012c9b1datagen2026-09-24 23:45 UTC640
    changes: impl futures_util::Stream<Item = VerificationRequestState> + Unpin,
96fdf90aacd6atagen2026-09-08 08:24 UTC641
    active: Arc<Mutex<VerificationSession>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC642
    sender: EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC643
) {
5a52012c9b1datagen2026-09-24 23:45 UTC644
    let mut states = futures_util::stream::iter([request.state()]).chain(changes);
5a52012c9b1datagen2026-09-24 23:45 UTC645
    while let Some(state) = states.next().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC646
        match state {
5a52012c9b1datagen2026-09-24 23:45 UTC647
            // created is ours waiting on them, requested is theirs waiting on `Accept`
5a52012c9b1datagen2026-09-24 23:45 UTC648
            VerificationRequestState::Created { .. }
5a52012c9b1datagen2026-09-24 23:45 UTC649
            | VerificationRequestState::Requested { .. } => {}
96fdf90aacd6atagen2026-09-08 08:24 UTC650
            VerificationRequestState::Ready { .. } => match request.start_sas().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC651
                Ok(Some(sas)) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC652
                    follow_sas(sas, active.clone(), sender.clone()).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC653
                    return;
96fdf90aacd6atagen2026-09-08 08:24 UTC654
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC655
                Ok(None) => {}
96fdf90aacd6atagen2026-09-08 08:24 UTC656
                Err(error) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC657
                    tracing::warn!(error = %error, "starting SAS verification failed");
96fdf90aacd6atagen2026-09-08 08:24 UTC658
                    // Claim the session so this terminal outcome is emitted exactly
96fdf90aacd6atagen2026-09-08 08:24 UTC659
                    // once; a lost claim means an action path already owns it.
96fdf90aacd6atagen2026-09-08 08:24 UTC660
                    report_watcher_terminal(
96fdf90aacd6atagen2026-09-08 08:24 UTC661
                        &active,
96fdf90aacd6atagen2026-09-08 08:24 UTC662
                        &sender,
96fdf90aacd6atagen2026-09-08 08:24 UTC663
                        UiState::Failed(VerificationFailure::SasStartup(error.to_string())),
96fdf90aacd6atagen2026-09-08 08:24 UTC664
                        "SAS startup failure",
96fdf90aacd6atagen2026-09-08 08:24 UTC665
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC666
                    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC667
                    return;
96fdf90aacd6atagen2026-09-08 08:24 UTC668
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC669
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC670
            VerificationRequestState::Transitioned { verification } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC671
                if let Verification::SasV1(sas) = verification {
96fdf90aacd6atagen2026-09-08 08:24 UTC672
                    follow_sas(sas, active.clone(), sender.clone()).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC673
                } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC674
                    report_watcher_terminal(
96fdf90aacd6atagen2026-09-08 08:24 UTC675
                        &active,
96fdf90aacd6atagen2026-09-08 08:24 UTC676
                        &sender,
96fdf90aacd6atagen2026-09-08 08:24 UTC677
                        UiState::Cancelled(
96fdf90aacd6atagen2026-09-08 08:24 UTC678
                            "the other device selected an unsupported method".into(),
96fdf90aacd6atagen2026-09-08 08:24 UTC679
                        ),
96fdf90aacd6atagen2026-09-08 08:24 UTC680
                        "unsupported verification method",
96fdf90aacd6atagen2026-09-08 08:24 UTC681
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC682
                    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC683
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC684
                return;
96fdf90aacd6atagen2026-09-08 08:24 UTC685
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC686
            VerificationRequestState::Done => {
96fdf90aacd6atagen2026-09-08 08:24 UTC687
                report_watcher_terminal(&active, &sender, UiState::Done, "completion").await;
96fdf90aacd6atagen2026-09-08 08:24 UTC688
                return;
96fdf90aacd6atagen2026-09-08 08:24 UTC689
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC690
            VerificationRequestState::Cancelled(info) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC691
                report_watcher_terminal(
96fdf90aacd6atagen2026-09-08 08:24 UTC692
                    &active,
96fdf90aacd6atagen2026-09-08 08:24 UTC693
                    &sender,
96fdf90aacd6atagen2026-09-08 08:24 UTC694
                    UiState::Cancelled(info.reason().to_owned()),
96fdf90aacd6atagen2026-09-08 08:24 UTC695
                    "cancellation",
96fdf90aacd6atagen2026-09-08 08:24 UTC696
                )
96fdf90aacd6atagen2026-09-08 08:24 UTC697
                .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC698
                return;
96fdf90aacd6atagen2026-09-08 08:24 UTC699
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC700
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC701
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC702
    tracing::debug!("verification request watcher ended unexpectedly");
96fdf90aacd6atagen2026-09-08 08:24 UTC703
    // The stream may end after the SDK already settled without delivering the
96fdf90aacd6atagen2026-09-08 08:24 UTC704
    // terminal update; report the settled outcome instead of StreamEnded, and
96fdf90aacd6atagen2026-09-08 08:24 UTC705
    // stay silent if an action path already claimed the session.
96fdf90aacd6atagen2026-09-08 08:24 UTC706
    if request.is_done() {
96fdf90aacd6atagen2026-09-08 08:24 UTC707
        report_watcher_terminal(&active, &sender, UiState::Done, "completion").await;
96fdf90aacd6atagen2026-09-08 08:24 UTC708
    } else if let Some(info) = request.cancel_info() {
96fdf90aacd6atagen2026-09-08 08:24 UTC709
        let reason = info.reason().to_owned();
96fdf90aacd6atagen2026-09-08 08:24 UTC710
        report_watcher_terminal(&active, &sender, UiState::Cancelled(reason), "cancellation").await;
96fdf90aacd6atagen2026-09-08 08:24 UTC711
    } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC712
        report_watcher_terminal(
96fdf90aacd6atagen2026-09-08 08:24 UTC713
            &active,
96fdf90aacd6atagen2026-09-08 08:24 UTC714
            &sender,
96fdf90aacd6atagen2026-09-08 08:24 UTC715
            UiState::Failed(VerificationFailure::RequestStreamEnded),
96fdf90aacd6atagen2026-09-08 08:24 UTC716
            "request stream termination",
96fdf90aacd6atagen2026-09-08 08:24 UTC717
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC718
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC719
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC720
}
96fdf90aacd6atagen2026-09-08 08:24 UTC721
96fdf90aacd6atagen2026-09-08 08:24 UTC722
async fn follow_sas(
96fdf90aacd6atagen2026-09-08 08:24 UTC723
    sas: SasVerification,
96fdf90aacd6atagen2026-09-08 08:24 UTC724
    active: Arc<Mutex<VerificationSession>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC725
    sender: EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC726
) {
5a52012c9b1datagen2026-09-24 23:45 UTC727
    // the sdk may already have moved the flow along before we got here, a peer starting
5a52012c9b1datagen2026-09-24 23:45 UTC728
    // sas is the usual case, so take the stream first and the current state after
5a52012c9b1datagen2026-09-24 23:45 UTC729
    let changes = sas.changes();
96fdf90aacd6atagen2026-09-08 08:24 UTC730
    active.lock().await.sas = Some(sas.clone());
5a52012c9b1datagen2026-09-24 23:45 UTC731
    let mut states = futures_util::stream::iter([sas.state()]).chain(changes);
5a52012c9b1datagen2026-09-24 23:45 UTC732
    while let Some(state) = states.next().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC733
        let update = match state {
96fdf90aacd6atagen2026-09-08 08:24 UTC734
            SasState::Created { .. } | SasState::Accepted { .. } => continue,
96fdf90aacd6atagen2026-09-08 08:24 UTC735
            SasState::Started { .. } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC736
                if let Err(error) = sas.accept().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC737
                    tracing::warn!(error = %error, "accepting SAS verification failed");
96fdf90aacd6atagen2026-09-08 08:24 UTC738
                    // Claim the session so this terminal outcome is emitted exactly
96fdf90aacd6atagen2026-09-08 08:24 UTC739
                    // once; a lost claim means an action path already owns it.
96fdf90aacd6atagen2026-09-08 08:24 UTC740
                    report_watcher_terminal(
96fdf90aacd6atagen2026-09-08 08:24 UTC741
                        &active,
96fdf90aacd6atagen2026-09-08 08:24 UTC742
                        &sender,
96fdf90aacd6atagen2026-09-08 08:24 UTC743
                        UiState::Failed(VerificationFailure::SasAcceptance(error.to_string())),
96fdf90aacd6atagen2026-09-08 08:24 UTC744
                        "SAS acceptance failure",
96fdf90aacd6atagen2026-09-08 08:24 UTC745
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC746
                    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC747
                    return;
96fdf90aacd6atagen2026-09-08 08:24 UTC748
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC749
                continue;
96fdf90aacd6atagen2026-09-08 08:24 UTC750
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC751
            SasState::KeysExchanged { emojis, decimals } => UiState::Comparison {
96fdf90aacd6atagen2026-09-08 08:24 UTC752
                emojis: emojis
96fdf90aacd6atagen2026-09-08 08:24 UTC753
                    .map(|short_auth| {
96fdf90aacd6atagen2026-09-08 08:24 UTC754
                        short_auth
96fdf90aacd6atagen2026-09-08 08:24 UTC755
                            .emojis
96fdf90aacd6atagen2026-09-08 08:24 UTC756
                            .into_iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC757
                            .map(|emoji| (emoji.symbol.to_owned(), emoji.description.to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC758
                            .collect()
96fdf90aacd6atagen2026-09-08 08:24 UTC759
                    })
96fdf90aacd6atagen2026-09-08 08:24 UTC760
                    .unwrap_or_default(),
96fdf90aacd6atagen2026-09-08 08:24 UTC761
                decimals,
96fdf90aacd6atagen2026-09-08 08:24 UTC762
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC763
            SasState::Confirmed => UiState::Confirmed,
96fdf90aacd6atagen2026-09-08 08:24 UTC764
            SasState::Done { .. } => UiState::Done,
96fdf90aacd6atagen2026-09-08 08:24 UTC765
            SasState::Cancelled(info) => UiState::Cancelled(info.reason().to_owned()),
96fdf90aacd6atagen2026-09-08 08:24 UTC766
        };
96fdf90aacd6atagen2026-09-08 08:24 UTC767
        let terminal = matches!(update, UiState::Done | UiState::Cancelled(_));
96fdf90aacd6atagen2026-09-08 08:24 UTC768
        // Claim the session before the terminal send so a racing Cancel observes
96fdf90aacd6atagen2026-09-08 08:24 UTC769
        // no active session and the SDK no-op cannot regress Done back to
96fdf90aacd6atagen2026-09-08 08:24 UTC770
        // "cancelled by this client". A lost claim means an action path already
96fdf90aacd6atagen2026-09-08 08:24 UTC771
        // owns the terminal outcome, so stay silent instead of duplicating it.
96fdf90aacd6atagen2026-09-08 08:24 UTC772
        if terminal && !take_verification(&active).await {
96fdf90aacd6atagen2026-09-08 08:24 UTC773
            return;
96fdf90aacd6atagen2026-09-08 08:24 UTC774
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC775
        if sender
96fdf90aacd6atagen2026-09-08 08:24 UTC776
            .send(Event::VerificationChanged(update))
96fdf90aacd6atagen2026-09-08 08:24 UTC777
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC778
            .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC779
        {
96fdf90aacd6atagen2026-09-08 08:24 UTC780
            tracing::debug!("SAS watcher stopped because the event receiver closed");
96fdf90aacd6atagen2026-09-08 08:24 UTC781
            return;
96fdf90aacd6atagen2026-09-08 08:24 UTC782
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC783
        if terminal {
96fdf90aacd6atagen2026-09-08 08:24 UTC784
            return;
96fdf90aacd6atagen2026-09-08 08:24 UTC785
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC786
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC787
    tracing::debug!("SAS watcher ended unexpectedly");
96fdf90aacd6atagen2026-09-08 08:24 UTC788
    // The stream may end after the SDK already settled without delivering the
96fdf90aacd6atagen2026-09-08 08:24 UTC789
    // terminal update; report the settled outcome instead of StreamEnded, and
96fdf90aacd6atagen2026-09-08 08:24 UTC790
    // stay silent if an action path already claimed the session.
96fdf90aacd6atagen2026-09-08 08:24 UTC791
    if sas.is_done() {
96fdf90aacd6atagen2026-09-08 08:24 UTC792
        report_watcher_terminal(&active, &sender, UiState::Done, "completion").await;
96fdf90aacd6atagen2026-09-08 08:24 UTC793
    } else if let Some(info) = sas.cancel_info() {
96fdf90aacd6atagen2026-09-08 08:24 UTC794
        let reason = info.reason().to_owned();
96fdf90aacd6atagen2026-09-08 08:24 UTC795
        report_watcher_terminal(&active, &sender, UiState::Cancelled(reason), "cancellation").await;
96fdf90aacd6atagen2026-09-08 08:24 UTC796
    } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC797
        report_watcher_terminal(
96fdf90aacd6atagen2026-09-08 08:24 UTC798
            &active,
96fdf90aacd6atagen2026-09-08 08:24 UTC799
            &sender,
96fdf90aacd6atagen2026-09-08 08:24 UTC800
            UiState::Failed(VerificationFailure::SasStreamEnded),
96fdf90aacd6atagen2026-09-08 08:24 UTC801
            "SAS stream termination",
96fdf90aacd6atagen2026-09-08 08:24 UTC802
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC803
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC804
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC805
}
96fdf90aacd6atagen2026-09-08 08:24 UTC806
96fdf90aacd6atagen2026-09-08 08:24 UTC807
async fn report_watcher_terminal(
96fdf90aacd6atagen2026-09-08 08:24 UTC808
    active: &Arc<Mutex<VerificationSession>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC809
    sender: &EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC810
    state: UiState,
96fdf90aacd6atagen2026-09-08 08:24 UTC811
    context: &'static str,
96fdf90aacd6atagen2026-09-08 08:24 UTC812
) {
96fdf90aacd6atagen2026-09-08 08:24 UTC813
    if take_verification(active).await
96fdf90aacd6atagen2026-09-08 08:24 UTC814
        && sender
96fdf90aacd6atagen2026-09-08 08:24 UTC815
            .send(Event::VerificationChanged(state))
96fdf90aacd6atagen2026-09-08 08:24 UTC816
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC817
            .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC818
    {
96fdf90aacd6atagen2026-09-08 08:24 UTC819
        tracing::debug!(
96fdf90aacd6atagen2026-09-08 08:24 UTC820
            context,
96fdf90aacd6atagen2026-09-08 08:24 UTC821
            "verification watcher could not report terminal outcome"
96fdf90aacd6atagen2026-09-08 08:24 UTC822
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC823
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC824
}
96fdf90aacd6atagen2026-09-08 08:24 UTC825
96fdf90aacd6atagen2026-09-08 08:24 UTC826
async fn report_claimed_terminal(sender: Option<&EventSender>, state: UiState) {
96fdf90aacd6atagen2026-09-08 08:24 UTC827
    if let Some(sender) = sender {
96fdf90aacd6atagen2026-09-08 08:24 UTC828
        let _ = sender.send(Event::VerificationChanged(state)).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC829
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC830
}
96fdf90aacd6atagen2026-09-08 08:24 UTC831
96fdf90aacd6atagen2026-09-08 08:24 UTC832
/// Claim the active session for terminal emission, if one is still present.
96fdf90aacd6atagen2026-09-08 08:24 UTC833
///
96fdf90aacd6atagen2026-09-08 08:24 UTC834
/// Exactly one party (watcher or action path) wins the claim and may emit the
96fdf90aacd6atagen2026-09-08 08:24 UTC835
/// terminal event; the loser stays silent so Done is never overwritten by a
96fdf90aacd6atagen2026-09-08 08:24 UTC836
/// later local Cancelled/Failed.
96fdf90aacd6atagen2026-09-08 08:24 UTC837
async fn take_verification(active: &Arc<Mutex<VerificationSession>>) -> bool {
96fdf90aacd6atagen2026-09-08 08:24 UTC838
    let mut active = active.lock().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC839
    let had = active.sas.is_some() || active.request.is_some();
96fdf90aacd6atagen2026-09-08 08:24 UTC840
    active.sas.take();
96fdf90aacd6atagen2026-09-08 08:24 UTC841
    active.request.take();
96fdf90aacd6atagen2026-09-08 08:24 UTC842
    had
96fdf90aacd6atagen2026-09-08 08:24 UTC843
}
96fdf90aacd6atagen2026-09-08 08:24 UTC844
96fdf90aacd6atagen2026-09-08 08:24 UTC845
#[cfg(test)]
96fdf90aacd6atagen2026-09-08 08:24 UTC846
mod tests {
96fdf90aacd6atagen2026-09-08 08:24 UTC847
    use matrix_sdk::encryption::VerificationState;
96fdf90aacd6atagen2026-09-08 08:24 UTC848
    use matrix_sdk::encryption::backups::BackupState;
96fdf90aacd6atagen2026-09-08 08:24 UTC849
    use ruma::owned_device_id;
96fdf90aacd6atagen2026-09-08 08:24 UTC850
96fdf90aacd6atagen2026-09-08 08:24 UTC851
    use super::*;
96fdf90aacd6atagen2026-09-08 08:24 UTC852
96fdf90aacd6atagen2026-09-08 08:24 UTC853
    #[test]
96fdf90aacd6atagen2026-09-08 08:24 UTC854
    fn sdk_security_state_is_normalized() {
96fdf90aacd6atagen2026-09-08 08:24 UTC855
        assert_eq!(
96fdf90aacd6atagen2026-09-08 08:24 UTC856
            encryption_state(
96fdf90aacd6atagen2026-09-08 08:24 UTC857
                owned_device_id!("DEVICE"),
96fdf90aacd6atagen2026-09-08 08:24 UTC858
                SdkRecoveryState::Incomplete,
96fdf90aacd6atagen2026-09-08 08:24 UTC859
                VerificationState::Unverified,
96fdf90aacd6atagen2026-09-08 08:24 UTC860
                BackupState::Unknown,
96fdf90aacd6atagen2026-09-08 08:24 UTC861
            ),
96fdf90aacd6atagen2026-09-08 08:24 UTC862
            EncryptionState {
96fdf90aacd6atagen2026-09-08 08:24 UTC863
                device_id: owned_device_id!("DEVICE"),
96fdf90aacd6atagen2026-09-08 08:24 UTC864
                device_verified: false,
96fdf90aacd6atagen2026-09-08 08:24 UTC865
                recovery: RecoveryState::Incomplete,
96fdf90aacd6atagen2026-09-08 08:24 UTC866
                key_backup: KeyBackupState::Unknown,
96fdf90aacd6atagen2026-09-08 08:24 UTC867
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC868
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC869
        assert_eq!(
96fdf90aacd6atagen2026-09-08 08:24 UTC870
            encryption_state(
96fdf90aacd6atagen2026-09-08 08:24 UTC871
                owned_device_id!("DEVICE"),
96fdf90aacd6atagen2026-09-08 08:24 UTC872
                SdkRecoveryState::Enabled,
96fdf90aacd6atagen2026-09-08 08:24 UTC873
                VerificationState::Verified,
96fdf90aacd6atagen2026-09-08 08:24 UTC874
                BackupState::Enabled,
96fdf90aacd6atagen2026-09-08 08:24 UTC875
            ),
96fdf90aacd6atagen2026-09-08 08:24 UTC876
            EncryptionState {
96fdf90aacd6atagen2026-09-08 08:24 UTC877
                device_id: owned_device_id!("DEVICE"),
96fdf90aacd6atagen2026-09-08 08:24 UTC878
                device_verified: true,
96fdf90aacd6atagen2026-09-08 08:24 UTC879
                recovery: RecoveryState::Enabled,
96fdf90aacd6atagen2026-09-08 08:24 UTC880
                key_backup: KeyBackupState::Enabled,
96fdf90aacd6atagen2026-09-08 08:24 UTC881
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC882
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC883
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC884
96fdf90aacd6atagen2026-09-08 08:24 UTC885
    #[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC886
    async fn comparison_actions_require_an_active_sas() {
96fdf90aacd6atagen2026-09-08 08:24 UTC887
        let mut controller = VerificationController::default();
96fdf90aacd6atagen2026-09-08 08:24 UTC888
96fdf90aacd6atagen2026-09-08 08:24 UTC889
        assert_eq!(
96fdf90aacd6atagen2026-09-08 08:24 UTC890
            controller.confirm().await.unwrap_err().to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC891
            "verification has not reached comparison"
96fdf90aacd6atagen2026-09-08 08:24 UTC892
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC893
        assert_eq!(
96fdf90aacd6atagen2026-09-08 08:24 UTC894
            controller.reject().await.unwrap_err().to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC895
            "verification has not reached comparison"
96fdf90aacd6atagen2026-09-08 08:24 UTC896
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC897
        assert_eq!(
96fdf90aacd6atagen2026-09-08 08:24 UTC898
            controller.cancel().await.unwrap_err().to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC899
            "no verification is active"
96fdf90aacd6atagen2026-09-08 08:24 UTC900
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC901
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC902
}