Files
bundesmessenger-ios/Riot/Modules/Analytics/DecryptionFailureTracker.swift
JanNiklas Grabowski 6c22fa37f1 Merge commit '56d9e1f6a55a93dc71149ae429eaa615a98de0d5' into feature/6076_foss_merge
* 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
2024-08-19 12:52:38 +02:00

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
}
}
}
*/