use std::time::Duration;
use futures_util::StreamExt;
use matrix_sdk::encryption::verification::{
SasState, SasVerification, Verification, VerificationRequest, VerificationRequestState,
};
use matrix_sdk::ruma::events::key::verification::request::ToDeviceKeyVerificationRequestEvent;
use matrix_sdk::ruma::events::room::message::{MessageType, OriginalSyncRoomMessageEvent};
use tokio::sync::mpsc::UnboundedSender;
use super::{failed, user_id, Client, Error};
use crate::api::types::UserId;
const PATIENCE: Duration = Duration::from_secs(120);
#[derive(Debug, Clone)]
pub struct Check {
sas: SasVerification,
pub emojis: Vec<(String, String)>,
}
impl Check {
pub async fn confirm(&self) -> Result<(), Error> {
self.sas.confirm().await.map_err(failed)?;
let mut states = self.sas.changes();
while let Some(state) = states.next().await {
match state {
SasState::Done { .. } => return Ok(()),
SasState::Cancelled(info) => return Err(failed(info.reason())),
_ => {}
}
}
Err(failed("Verification stopped"))
}
pub async fn refuse(&self) -> Result<(), Error> {
self.sas.mismatch().await.map_err(failed)
}
}
async fn emojis_of(sas: SasVerification) -> Result<Check, Error> {
let mut states = sas.changes();
if !sas.we_started() {
sas.accept().await.map_err(failed)?;
}
while sas.emoji().is_none() {
match states.next().await {
Some(SasState::Cancelled(info)) => return Err(failed(info.reason())),
Some(SasState::Done { .. }) | None => return Err(failed("Verification stopped")),
Some(_) => {}
}
}
let emojis = sas
.emoji()
.ok_or_else(|| failed("The other session cannot show emojis"))?
.iter()
.map(|e| (e.symbol.to_owned(), e.description.to_owned()))
.collect();
Ok(Check { sas, emojis })
}
async fn present(request: VerificationRequest) -> Result<Check, Error> {
let mut states = request.changes();
if !request.we_started() {
request.accept().await.map_err(failed)?;
}
while let Some(state) = states.next().await {
match state {
VerificationRequestState::Ready { .. } if request.we_started() => {
let sas = request.start_sas().await.map_err(failed)?;
let sas = sas.ok_or_else(|| failed("The other session cannot compare emojis"))?;
return emojis_of(sas).await;
}
VerificationRequestState::Transitioned {
verification: Verification::SasV1(sas),
} => return emojis_of(sas).await,
VerificationRequestState::Cancelled(info) => return Err(failed(info.reason())),
VerificationRequestState::Done => return Err(failed("Verification stopped")),
_ => {}
}
}
Err(failed("Verification stopped"))
}
async fn wait_for(request: VerificationRequest) -> Result<Check, Error> {
tokio::time::timeout(PATIENCE, present(request))
.await
.map_err(|_| failed("Timed out waiting for the other session"))?
}
impl Client {
pub async fn verify(&self) -> Result<Check, Error> {
let sdk = self.sdk();
let me = sdk.user_id().ok_or_else(|| failed("Not logged in"))?;
let encryption = sdk.encryption();
encryption
.request_user_identity(me)
.await
.map_err(failed)?
.ok_or_else(|| {
failed("This account has no cross-signing keys; set them up in another client")
})?;
let last = sdk
.devices()
.await
.map_err(failed)?
.devices
.into_iter()
.filter(|d| Some(d.device_id.as_ref()) != sdk.device_id())
.max_by_key(|d| d.last_seen_ts)
.ok_or_else(|| failed("This account has no other session to verify against"))?;
let device = encryption
.get_device(me, &last.device_id)
.await
.map_err(failed)?
.ok_or_else(|| failed("The keys of the other session are not known yet"))?;
wait_for(device.request_verification().await.map_err(failed)?).await
}
pub async fn verify_user(&self, user: &UserId) -> Result<Check, Error> {
let identity = self
.sdk()
.encryption()
.request_user_identity(&user_id(user)?)
.await
.map_err(failed)?
.ok_or_else(|| failed("This user has no cross-signing keys"))?;
wait_for(identity.request_verification().await.map_err(failed)?).await
}
pub fn watch(&self, found: UnboundedSender<Check>) {
let sdk = self.sdk();
let encryption = sdk.encryption();
let (devices, people) = (found.clone(), found);
let seen = encryption.clone();
sdk.add_event_handler(move |event: ToDeviceKeyVerificationRequestEvent| {
let encryption = seen.clone();
let found = devices.clone();
async move {
let id = &event.content.transaction_id;
if let Some(request) = encryption.get_verification_request(&event.sender, id).await
{
offered(request, found);
}
}
});
sdk.add_event_handler(move |event: OriginalSyncRoomMessageEvent| {
let encryption = encryption.clone();
let found = people.clone();
async move {
if !matches!(event.content.msgtype, MessageType::VerificationRequest(_)) {
return;
}
let request = encryption
.get_verification_request(&event.sender, &event.event_id)
.await;
if let Some(request) = request {
offered(request, found);
}
}
});
}
}
fn offered(request: VerificationRequest, found: UnboundedSender<Check>) {
tokio::spawn(async move {
if let Ok(check) = wait_for(request).await {
let _ = found.send(check);
}
});
}