2022-03-16 17:47:41 +00:00
|
|
|
//
|
2022-03-03 17:29:41 +00:00
|
|
|
// Copyright 2021 New Vector Ltd
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
//
|
|
|
|
|
|
|
|
import MatrixSDK
|
|
|
|
import Foundation
|
|
|
|
|
|
|
|
protocol UISIDetectorDelegate: AnyObject {
|
|
|
|
var reciprocateToDeviceEventType: String { get }
|
2022-03-16 17:47:41 +00:00
|
|
|
func uisiDetected(source: UISIDetectedMessage)
|
2022-03-03 17:29:41 +00:00
|
|
|
func uisiReciprocateRequest(source: MXEvent)
|
|
|
|
}
|
|
|
|
|
2022-03-16 17:47:41 +00:00
|
|
|
struct UISIDetectedMessage {
|
2022-03-03 17:29:41 +00:00
|
|
|
let eventId: String
|
|
|
|
let roomId: String
|
|
|
|
let senderUserId: String
|
|
|
|
let senderDeviceId: String
|
|
|
|
let senderKey: String
|
|
|
|
let sessionId: String
|
|
|
|
|
2022-03-16 17:47:41 +00:00
|
|
|
static func fromEvent(event: MXEvent) -> UISIDetectedMessage {
|
|
|
|
return UISIDetectedMessage(
|
2022-03-03 17:29:41 +00:00
|
|
|
eventId: event.eventId ?? "",
|
2022-03-16 17:47:41 +00:00
|
|
|
roomId: event.roomId,
|
2022-03-03 17:29:41 +00:00
|
|
|
senderUserId: event.sender,
|
2022-03-11 16:47:08 +00:00
|
|
|
senderDeviceId: event.wireContent["device_id"] as? String ?? "",
|
|
|
|
senderKey: event.wireContent["sender_key"] as? String ?? "",
|
2022-03-16 17:47:41 +00:00
|
|
|
sessionId: event.wireContent["session_id"] as? String ?? ""
|
2022-03-03 17:29:41 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-21 14:45:54 +00:00
|
|
|
/// Detects decryption errors that occur and don't recover within a grace period.
|
|
|
|
/// see `UISIDetectorDelegate` for listening to detections.
|
2022-03-03 17:29:41 +00:00
|
|
|
class UISIDetector: MXLiveEventListener {
|
|
|
|
|
|
|
|
weak var delegate: UISIDetectorDelegate?
|
|
|
|
var enabled = false
|
|
|
|
|
2022-03-16 17:47:41 +00:00
|
|
|
var initialSyncCompleted = false
|
|
|
|
private var trackedUISIs = [String: DispatchSourceTimer]()
|
2022-03-03 17:29:41 +00:00
|
|
|
private let dispatchQueue = DispatchQueue(label: "io.element.UISIDetector.queue")
|
2022-03-16 17:47:41 +00:00
|
|
|
private static let gracePeriodSeconds = 30
|
2022-03-03 17:29:41 +00:00
|
|
|
|
2022-03-21 15:01:20 +00:00
|
|
|
// MARK: - Public
|
|
|
|
|
2022-03-16 17:47:41 +00:00
|
|
|
func onSessionStateChanged(state: MXSessionState) {
|
2022-03-03 17:29:41 +00:00
|
|
|
dispatchQueue.async {
|
2022-03-16 17:47:41 +00:00
|
|
|
self.initialSyncCompleted = state == .running
|
2022-03-03 17:29:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-16 17:47:41 +00:00
|
|
|
func onLiveEventDecryptionAttempted(event: MXEvent, result: MXEventDecryptionResult) {
|
|
|
|
guard enabled, let eventId = event.eventId, let roomId = event.roomId else { return }
|
2022-03-03 17:29:41 +00:00
|
|
|
dispatchQueue.async {
|
2022-03-16 17:47:41 +00:00
|
|
|
let trackedId = Self.trackedEventId(roomId: eventId, eventId: roomId)
|
|
|
|
|
|
|
|
if let timer = self.trackedUISIs[trackedId],
|
|
|
|
result.clearEvent != nil {
|
|
|
|
// successfully decrypted during grace period, cancel timer.
|
|
|
|
self.trackedUISIs[trackedId] = nil
|
|
|
|
timer.cancel()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
guard self.initialSyncCompleted,
|
|
|
|
result.clearEvent == nil
|
|
|
|
else { return }
|
|
|
|
|
|
|
|
// track uisi and report it only if it is not decrypted before grade period ends
|
|
|
|
let timer = DispatchSource.makeTimerSource(queue: self.dispatchQueue)
|
|
|
|
timer.schedule(deadline: .now() + .seconds(Self.gracePeriodSeconds))
|
|
|
|
timer.setEventHandler { [weak self] in
|
|
|
|
guard let self = self else { return }
|
|
|
|
self.trackedUISIs[trackedId] = nil
|
|
|
|
MXLog.verbose("[UISIDetector] onLiveEventDecryptionAttempted: Timeout on \(eventId)")
|
|
|
|
self.triggerUISI(source: UISIDetectedMessage.fromEvent(event: event))
|
2022-03-03 17:29:41 +00:00
|
|
|
}
|
2022-03-16 17:47:41 +00:00
|
|
|
self.trackedUISIs[trackedId] = timer
|
|
|
|
timer.activate()
|
2022-03-03 17:29:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func onLiveToDeviceEvent(event: MXEvent) {
|
|
|
|
guard enabled, event.type == delegate?.reciprocateToDeviceEventType else { return }
|
|
|
|
delegate?.uisiReciprocateRequest(source: event)
|
|
|
|
}
|
|
|
|
|
2022-03-21 14:45:54 +00:00
|
|
|
// MARK: - Private
|
|
|
|
|
2022-03-16 17:47:41 +00:00
|
|
|
private func triggerUISI(source: UISIDetectedMessage) {
|
2022-03-03 17:29:41 +00:00
|
|
|
guard enabled else { return }
|
2022-03-16 17:47:41 +00:00
|
|
|
MXLog.info("[UISIDetector] triggerUISI: Unable To Decrypt \(source)")
|
2022-03-03 17:29:41 +00:00
|
|
|
self.delegate?.uisiDetected(source: source)
|
|
|
|
}
|
|
|
|
|
2022-03-21 14:45:54 +00:00
|
|
|
// MARK: - Static
|
|
|
|
|
|
|
|
private static func trackedEventId(roomId: String, eventId: String) -> String {
|
2022-03-03 17:29:41 +00:00
|
|
|
return "\(roomId)-\(eventId)"
|
|
|
|
}
|
|
|
|
}
|