Files
letro-ios/ElementX/Sources/Services/SessionVerification/SessionVerificationControllerProxy.swift
Stefan Ceriu 8b8b2bde0b Move verification request acceptance confirmation to method call response instead of delegate callback
This because the Rust side verification state machine doesn't wait for the request
to be sent and acknowledged before changing its inner state and with the automatic
starting of SAS in https://github.com/element-hq/element-x-ios/pull/5116 that creates
race conditions between `m.key.verification.ready` and `m.key.verification.start`.
2026-05-06 11:11:38 +03:00

218 lines
7.8 KiB
Swift

//
// Copyright 2025 Element Creations Ltd.
// Copyright 2022-2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import Combine
import Foundation
import MatrixRustSDK
private final class WeakSessionVerificationControllerProxy: SessionVerificationControllerDelegate {
private weak var proxy: SessionVerificationControllerProxy?
init(proxy: SessionVerificationControllerProxy) {
self.proxy = proxy
}
// MARK: - SessionVerificationControllerDelegate
func didReceiveVerificationRequest(details: MatrixRustSDK.SessionVerificationRequestDetails) {
proxy?.didReceiveVerificationRequest(details: details)
}
func didReceiveVerificationData(data: MatrixRustSDK.SessionVerificationData) {
switch data {
// We can handle only emojis for now
case .emojis(let emojis, _):
proxy?.didReceiveData(emojis)
default:
break
}
}
func didAcceptVerificationRequest() {
proxy?.didAcceptVerificationRequest()
}
func didStartSasVerification() {
proxy?.didStartSasVerification()
}
func didFail() {
proxy?.didFail()
}
func didCancel() {
proxy?.didCancel()
}
func didFinish() {
proxy?.didFinish()
}
}
class SessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol {
private let sessionVerificationController: SessionVerificationController
init(sessionVerificationController: SessionVerificationController) {
self.sessionVerificationController = sessionVerificationController
sessionVerificationController.setDelegate(delegate: WeakSessionVerificationControllerProxy(proxy: self))
}
deinit {
sessionVerificationController.setDelegate(delegate: nil)
}
let actions = PassthroughSubject<SessionVerificationControllerProxyAction, Never>()
func acknowledgeVerificationRequest(details: SessionVerificationRequestDetails) async -> Result<Void, SessionVerificationControllerProxyError> {
MXLog.info("Acknowledging verification request")
do {
try await sessionVerificationController.acknowledgeVerificationRequest(senderId: details.senderProfile.userID, flowId: details.flowID)
return .success(())
} catch {
MXLog.error("Failed requesting session verification with error: \(error)")
return .failure(.failedAcknowledgingVerificationRequest)
}
}
func acceptVerificationRequest() async -> Result<Void, SessionVerificationControllerProxyError> {
MXLog.info("Accepting verification request")
do {
try await sessionVerificationController.acceptVerificationRequest()
MXLog.info("Accepted verification request")
actions.send(.acceptedVerificationRequest)
return .success(())
} catch {
MXLog.error("Failed requesting session verification with error: \(error)")
return .failure(.failedAcceptingVerificationRequest)
}
}
func requestDeviceVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
MXLog.info("Requesting device verification")
do {
try await sessionVerificationController.requestDeviceVerification()
return .success(())
} catch {
MXLog.error("Failed requesting device verification with error: \(error)")
return .failure(.failedRequestingVerification)
}
}
func requestUserVerification(_ userID: String) async -> Result<Void, SessionVerificationControllerProxyError> {
MXLog.info("Requesting user verification")
do {
try await sessionVerificationController.requestUserVerification(userId: userID)
return .success(())
} catch {
MXLog.error("Failed requesting verification for user \(userID) with error: \(error)")
return .failure(.failedRequestingVerification)
}
}
func startSasVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
MXLog.info("Starting SAS verification")
do {
try await sessionVerificationController.startSasVerification()
return .success(())
} catch {
MXLog.error("Failed starting SAS verification with error: \(error)")
return .failure(.failedStartingSasVerification)
}
}
func approveVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
MXLog.info("Approving verification")
do {
try await sessionVerificationController.approveVerification()
return .success(())
} catch {
MXLog.error("Failed approving verification with error: \(error)")
return .failure(.failedApprovingVerification)
}
}
func declineVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
MXLog.info("Declining verification")
do {
try await sessionVerificationController.declineVerification()
return .success(())
} catch {
MXLog.error("Failed declining verification with error: \(error)")
return .failure(.failedDecliningVerification)
}
}
func cancelVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
MXLog.info("Cancelling verification")
do {
try await sessionVerificationController.cancelVerification()
return .success(())
} catch {
MXLog.error("Failed cancelling verification with error: \(error)")
return .failure(.failedCancellingVerification)
}
}
// MARK: - Private
fileprivate func didReceiveVerificationRequest(details: MatrixRustSDK.SessionVerificationRequestDetails) {
MXLog.info("Received verification request \(details)")
let details = SessionVerificationRequestDetails(senderProfile: UserProfileProxy(sdkUserProfile: details.senderProfile),
flowID: details.flowId,
deviceID: details.deviceId,
deviceDisplayName: details.deviceDisplayName,
firstSeenDate: Date(timeIntervalSince1970: TimeInterval(details.firstSeenTimestamp / 1000)))
actions.send(.receivedVerificationRequest(details: details))
}
fileprivate func didAcceptVerificationRequest() {
// Noop because the rust side state machine changes states before sending
// the actual request, leading to race conditions with the SAS verification
// startup. The `acceptedVerificationRequest` is now called from the `startSasVerification`
// method above.
}
fileprivate func didStartSasVerification() {
MXLog.info("Started SAS verification")
actions.send(.startedSasVerification)
}
fileprivate func didReceiveData(_ data: [MatrixRustSDK.SessionVerificationEmoji]) {
MXLog.info("Received verification data")
actions.send(.receivedVerificationData(data.map { emoji in
SessionVerificationEmoji(symbol: emoji.symbol(), description: emoji.description())
}))
}
fileprivate func didFail() {
actions.send(.failed)
}
fileprivate func didFinish() {
actions.send(.finished)
}
fileprivate func didCancel() {
actions.send(.cancelled)
}
}