atagen

macltok

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