mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-16 06:28:27 +02:00
* commit '56d9e1f6a55a93dc71149ae429eaa615a98de0d5': (79 commits) finish version++ version++ Translated using Weblate (Hungarian) Translated using Weblate (Italian) Translated using Weblate (Ukrainian) Translated using Weblate (Hungarian) Translated using Weblate (Slovak) Translated using Weblate (Swedish) Translated using Weblate (Indonesian) Translated using Weblate (Albanian) Translated using Weblate (Estonian) Translated using Weblate (Estonian) updated the submodule updated SDK Update the SDK. (#7819) Prepare for new sprint finish version++ version++ fix Changelog. ... # Conflicts: # Config/AppVersion.xcconfig # Podfile # Podfile.lock # Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved # Riot/Managers/Settings/RiotSettings.swift # Riot/Modules/Analytics/Analytics.swift # Riot/Modules/Analytics/DecryptionFailure.swift # Riot/Modules/Analytics/PHGPostHogConfiguration.swift # Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift # Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModel.swift # Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.m # Riot/Modules/Settings/SettingsViewController.m # Riot/Utils/EventFormatter.m # Riot/Utils/Tools.m # RiotNSE/target.yml # fastlane/Fastfile # project.yml
200 lines
7.1 KiB
Swift
200 lines
7.1 KiB
Swift
//
|
|
// Copyright 2024 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.
|
|
//
|
|
|
|
/* bwi: 6035 - ignore the Swift implementation and use the old objective-c code
|
|
import Foundation
|
|
|
|
|
|
// Protocol to get the current time. Used for easy testing
|
|
protocol TimeProvider {
|
|
func nowTs() -> TimeInterval
|
|
}
|
|
|
|
class DefaultTimeProvider: TimeProvider {
|
|
|
|
func nowTs() -> TimeInterval {
|
|
return Date.now.timeIntervalSince1970
|
|
}
|
|
|
|
}
|
|
|
|
|
|
@objc
|
|
class DecryptionFailureTracker: NSObject {
|
|
|
|
let GRACE_PERIOD: TimeInterval = 4
|
|
// Call `checkFailures` every `CHECK_INTERVAL`
|
|
let CHECK_INTERVAL: TimeInterval = 15
|
|
|
|
// The maximum time to wait for a late decryption before reporting as permanent UTD
|
|
let MAX_WAIT_FOR_LATE_DECRYPTION: TimeInterval = 60
|
|
|
|
@objc weak var delegate: E2EAnalytics?
|
|
|
|
// Reported failures
|
|
var reportedFailures = [String /* eventId */: DecryptionFailure]()
|
|
|
|
// Event ids of failures that were tracked previously
|
|
var trackedEvents = Set<String>()
|
|
|
|
var checkFailuresTimer: Timer?
|
|
|
|
@objc static let sharedInstance = DecryptionFailureTracker()
|
|
|
|
var timeProvider: TimeProvider = DefaultTimeProvider()
|
|
|
|
override init() {
|
|
super.init()
|
|
|
|
NotificationCenter.default.addObserver(self,
|
|
selector: #selector(eventDidDecrypt(_:)),
|
|
name: .mxEventDidDecrypt,
|
|
object: nil)
|
|
}
|
|
|
|
@objc
|
|
func reportUnableToDecryptError(forEvent event: MXEvent, withRoomState roomState: MXRoomState, mySession: MXSession) {
|
|
if reportedFailures[event.eventId] != nil || trackedEvents.contains(event.eventId) {
|
|
return
|
|
}
|
|
guard let userId = mySession.myUserId else { return }
|
|
|
|
// Filter out "expected" UTDs
|
|
// We cannot decrypt messages sent before the user joined the room
|
|
guard let myUser = roomState.members.member(withUserId: userId) else { return }
|
|
if myUser.membership != MXMembership.join {
|
|
return
|
|
}
|
|
|
|
guard let failedEventId = event.eventId else { return }
|
|
|
|
guard let error = event.decryptionError as? NSError else { return }
|
|
|
|
let eventOrigin = event.originServerTs
|
|
let deviceTimestamp = mySession.crypto.deviceCreationTs
|
|
// If negative it's an historical event relative to the current session
|
|
let eventRelativeAgeMillis = Int(eventOrigin) - Int(deviceTimestamp)
|
|
let isSessionVerified = mySession.crypto.crossSigning.canTrustCrossSigning
|
|
|
|
var reason = DecryptionFailureReason.unspecified
|
|
|
|
if error.code == MXDecryptingErrorUnknownInboundSessionIdCode.rawValue {
|
|
reason = DecryptionFailureReason.olmKeysNotSent
|
|
} else if error.code == MXDecryptingErrorOlmCode.rawValue {
|
|
reason = DecryptionFailureReason.olmIndexError
|
|
}
|
|
|
|
let context = String(format: "code: %ld, description: %@", error.code, event.decryptionError.localizedDescription)
|
|
|
|
let failure = DecryptionFailure(failedEventId: failedEventId, reason: reason, context: context, ts: self.timeProvider.nowTs())
|
|
|
|
failure.eventLocalAgeMillis = Int(exactly: eventRelativeAgeMillis)
|
|
failure.trustOwnIdentityAtTimeOfFailure = isSessionVerified
|
|
|
|
let myDomain = userId.components(separatedBy: ":").last
|
|
failure.isMatrixOrg = myDomain == "matrix.org"
|
|
|
|
if MXTools.isMatrixUserIdentifier(event.sender) {
|
|
let senderDomain = event.sender.components(separatedBy: ":").last
|
|
failure.isFederated = senderDomain != nil && senderDomain != myDomain
|
|
}
|
|
|
|
/// XXX for future work, as for now only the event formatter reports UTDs. That means that it's only UTD ~visible to users
|
|
failure.wasVisibleToUser = true
|
|
|
|
reportedFailures[failedEventId] = failure
|
|
|
|
|
|
// Start the ticker if needed. There is no need to have a ticker if no failures are tracked
|
|
if checkFailuresTimer == nil {
|
|
self.checkFailuresTimer = Timer.scheduledTimer(withTimeInterval: CHECK_INTERVAL, repeats: true) { [weak self] _ in
|
|
self?.checkFailures()
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
@objc
|
|
func dispatch() {
|
|
self.checkFailures()
|
|
}
|
|
|
|
@objc
|
|
func eventDidDecrypt(_ notification: Notification) {
|
|
guard let event = notification.object as? MXEvent else { return }
|
|
|
|
guard let reportedFailure = self.reportedFailures[event.eventId] else { return }
|
|
|
|
let now = self.timeProvider.nowTs()
|
|
let ellapsedTime = now - reportedFailure.ts
|
|
|
|
if ellapsedTime < 4 {
|
|
// event is graced
|
|
reportedFailures.removeValue(forKey: event.eventId)
|
|
} else {
|
|
// It's a late decrypt must be reported as a late decrypt
|
|
reportedFailure.timeToDecrypt = ellapsedTime
|
|
self.delegate?.trackE2EEError(reportedFailure)
|
|
}
|
|
// Remove from reported failures
|
|
self.trackedEvents.insert(event.eventId)
|
|
reportedFailures.removeValue(forKey: event.eventId)
|
|
|
|
// Check if we still need the ticker timer
|
|
if reportedFailures.isEmpty {
|
|
// Invalidate the current timer, nothing to check for
|
|
self.checkFailuresTimer?.invalidate()
|
|
self.checkFailuresTimer = nil
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
Mark reported failures that occured before tsNow - GRACE_PERIOD as failures that should be
|
|
tracked.
|
|
*/
|
|
@objc
|
|
func checkFailures() {
|
|
guard let delegate = self.delegate else {return}
|
|
|
|
let tsNow = self.timeProvider.nowTs()
|
|
var failuresToCheck = [DecryptionFailure]()
|
|
|
|
for reportedFailure in self.reportedFailures.values {
|
|
let ellapsed = tsNow - reportedFailure.ts
|
|
if ellapsed > MAX_WAIT_FOR_LATE_DECRYPTION {
|
|
failuresToCheck.append(reportedFailure)
|
|
reportedFailure.timeToDecrypt = nil
|
|
reportedFailures.removeValue(forKey: reportedFailure.failedEventId)
|
|
trackedEvents.insert(reportedFailure.failedEventId)
|
|
}
|
|
}
|
|
|
|
for failure in failuresToCheck {
|
|
delegate.trackE2EEError(failure)
|
|
}
|
|
|
|
// Check if we still need the ticker timer
|
|
if reportedFailures.isEmpty {
|
|
// Invalidate the current timer, nothing to check for
|
|
self.checkFailuresTimer?.invalidate()
|
|
self.checkFailuresTimer = nil
|
|
}
|
|
}
|
|
|
|
}
|
|
*/
|