diff --git a/Cargo.lock b/Cargo.lock index 60c22ba2d..c73f0e9f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2740,6 +2740,7 @@ version = "0.1.0" dependencies = [ "anyhow", "base64", + "futures-util", "hmac", "http", "js_int", diff --git a/bindings/matrix-sdk-crypto-ffi/Cargo.toml b/bindings/matrix-sdk-crypto-ffi/Cargo.toml index 1826346d8..9463082a0 100644 --- a/bindings/matrix-sdk-crypto-ffi/Cargo.toml +++ b/bindings/matrix-sdk-crypto-ffi/Cargo.toml @@ -15,6 +15,7 @@ crate-type = ["cdylib", "staticlib"] [dependencies] anyhow = "1.0.57" base64 = "0.13.0" +futures-util = "0.3.25" hmac = "0.12.1" http = "0.2.6" pbkdf2 = "0.11.0" diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 98a6e4c8f..dd13c8b54 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -45,8 +45,8 @@ use ruma::{ use serde::{Deserialize, Serialize}; pub use users::UserIdentity; pub use verification::{ - CancelInfo, ConfirmVerificationResult, QrCode, RequestVerificationResult, Sas, ScanResult, - StartSasResult, Verification, VerificationRequest, + CancelInfo, ConfirmVerificationResult, QrCode, RequestVerificationResult, Sas, SasListener, + SasState, ScanResult, StartSasResult, Verification, VerificationRequest, }; /// Struct collecting data that is important to migrate to the rust-sdk diff --git a/bindings/matrix-sdk-crypto-ffi/src/olm.udl b/bindings/matrix-sdk-crypto-ffi/src/olm.udl index bfab4abd6..cacae3dce 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/olm.udl +++ b/bindings/matrix-sdk-crypto-ffi/src/olm.udl @@ -161,6 +161,23 @@ interface Sas { sequence? get_emoji_indices(); sequence? get_decimals(); + + void changes(SasListener callback); + SasState state(); +}; + +[Enum] +interface SasState { + Started(); + Accepted(); + KeysExchanged(sequence? emojis, sequence decimals); + Confirmed(); + Done(); + Cancelled(CancelInfo cancel_info); +}; + +callback interface SasListener { + void on_change(SasState state); }; dictionary ScanResult { diff --git a/bindings/matrix-sdk-crypto-ffi/src/verification.rs b/bindings/matrix-sdk-crypto-ffi/src/verification.rs index e82cda5d9..5a8bf2250 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/verification.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/verification.rs @@ -1,9 +1,10 @@ use std::sync::Arc; use base64::{decode_config, encode_config, STANDARD_NO_PAD}; +use futures_util::{Stream, StreamExt}; use matrix_sdk_crypto::{ matrix_sdk_qrcode::QrVerificationData, CancelInfo as RustCancelInfo, QrVerification as InnerQr, - Sas as InnerSas, Verification as InnerVerification, + Sas as InnerSas, SasState as RustSasState, Verification as InnerVerification, VerificationRequest as InnerVerificationRequest, }; use ruma::events::key::verification::VerificationMethod; @@ -11,6 +12,71 @@ use tokio::runtime::Handle; use crate::{CryptoStoreError, OutgoingVerificationRequest, SignatureUploadRequest}; +/// Callback that will be passed over the FFI to report changes to a SAS +/// verification. +pub trait SasListener: Send { + /// The callback that should be called on the Rust side + /// + /// # Arguments + /// + /// * `state` - The current state of the SAS verification. + fn on_change(&self, state: SasState); +} + +impl SasListener for T +where + T: Send, +{ + fn on_change(&self, state: SasState) { + self(state) + } +} + +/// An Enum describing the state the SAS verification is in. +pub enum SasState { + /// The verification has been started, the protocols that should be used + /// have been proposed and can be accepted. + Started, + /// The verification has been accepted and both sides agreed to a set of + /// protocols that will be used for the verification process. + Accepted, + /// The public keys have been exchanged and the short auth string can be + /// presented to the user. + KeysExchanged { + /// The emojis that represent the short auth string, will be `None` if + /// the emoji SAS method wasn't part of the [`AcceptedProtocols`]. + emojis: Option>, + /// The list of decimals that represent the short auth string. + decimals: Vec, + }, + /// The verification process has been confirmed from our side, we're waiting + /// for the other side to confirm as well. + Confirmed, + /// The verification process has been successfully concluded. + Done, + /// The verification process has been cancelled. + Cancelled { + /// Information about the reason of the cancellation. + cancel_info: CancelInfo, + }, +} + +impl From for SasState { + fn from(s: RustSasState) -> Self { + match s { + RustSasState::Started { .. } => Self::Started, + RustSasState::Accepted { .. } => Self::Accepted, + RustSasState::KeysExchanged { emojis, decimals } => Self::KeysExchanged { + emojis: emojis.map(|e| e.indices.map(|i| i as i32).to_vec()), + decimals: [decimals.0.into(), decimals.1.into(), decimals.2.into()].to_vec(), + }, + RustSasState::Confirmed => Self::Confirmed, + RustSasState::Done { .. } => Self::Done, + RustSasState::Cancelled(c) => Self::Cancelled { cancel_info: c.into() }, + } + } +} + /// Enum representing the different verification flows we support. pub struct Verification { pub(crate) inner: InnerVerification, @@ -127,6 +193,81 @@ impl Sas { pub fn get_decimals(&self) -> Option> { self.inner.decimals().map(|v| [v.0.into(), v.1.into(), v.2.into()].to_vec()) } + + /// Listen for changes in the SAS verification process. + /// + /// The given callback will be called whenever the state changes. + /// + /// This method can be used to react to changes in the state of the + /// verification process, or rather the method can be used to handle + /// each step of the verification process. + /// + /// This method will spawn a tokio task on the Rust side, once we reach the + /// Done or Cancelled state, the task will stop listening for changes. + /// + /// # Flowchart + /// + /// The flow of the verification process is pictured bellow. Please note + /// that the process can be cancelled at each step of the process. + /// Either side can cancel the process. + /// + /// ```text + /// ┌───────┐ + /// │Started│ + /// └───┬───┘ + /// │ + /// ┌────⌄───┐ + /// │Accepted│ + /// └────┬───┘ + /// │ + /// ┌───────⌄──────┐ + /// │Keys Exchanged│ + /// └───────┬──────┘ + /// │ + /// ________⌄________ + /// ╱ ╲ ┌─────────┐ + /// ╱ Does the short ╲______│Cancelled│ + /// ╲ auth string match ╱ no └─────────┘ + /// ╲_________________╱ + /// │yes + /// │ + /// ┌────⌄────┐ + /// │Confirmed│ + /// └────┬────┘ + /// │ + /// ┌───⌄───┐ + /// │ Done │ + /// └───────┘ + /// ``` + pub fn changes(&self, callback: Box) { + let stream = self.inner.changes(); + + self.runtime.spawn(Self::changes_callback(stream, callback)); + } + + /// Get the current state of the SAS verification process. + pub fn state(&self) -> SasState { + self.inner.state().into() + } + + async fn changes_callback( + mut stream: impl Stream + std::marker::Unpin, + callback: Box, + ) { + while let Some(state) = stream.next().await { + // If we receive a done or a cancelled state we're at the end of our road, we + // break out of the loop to deallocate the stream and finish the + // task. + let should_break = + matches!(state, RustSasState::Done { .. } | RustSasState::Cancelled { .. }); + + callback.on_change(state.into()); + + if should_break { + break; + } + } + } } /// The `m.qr_code.scan.v1`, `m.qr_code.show.v1`, and `m.reciprocate.v1`