Skip to content

Commit d9ceb9e

Browse files
github-actions[bot]KaterinaWirejohnxnguyen
authored
fix: trigger automatic repair for affected conversations - WPB-22447 πŸ’ (#4063)
Co-authored-by: KaterinaWire <[email protected]> Co-authored-by: John Nguyen <[email protected]>
1 parent e19e459 commit d9ceb9e

File tree

8 files changed

+207
-3
lines changed

8 files changed

+207
-3
lines changed

β€ŽWireDomain/Sources/WireDomain/Components/ClientSessionComponent.swiftβ€Ž

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,4 +856,11 @@ public final class ClientSessionComponent {
856856
let brokenGroupIds = journal[.brokenMLSGroupIDs]
857857
return brokenGroupIds.contains(groupID.description)
858858
}
859+
860+
public lazy var repairFaultyMLSRemovalKeysGenerator = RepairFaultyMLSRemovalKeysGenerator(
861+
journal: journal,
862+
repairUseCase: repairFaultyRemovalKeysUsecase,
863+
workAgent: workAgent
864+
)
865+
859866
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//
2+
// Wire
3+
// Copyright (C) 2025 Wire Swiss GmbH
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with this program. If not, see http://www.gnu.org/licenses/.
17+
//
18+
19+
import Foundation
20+
21+
/// Generates and submits a work item to repair faulty MLS removal keys if needed.
22+
///
23+
/// This generator checks the journal flag and submits the repair work item
24+
/// to the work agent if repair is required. This pattern keeps the work item internal
25+
/// while allowing it to be triggered from the session initialization or after migrations.
26+
27+
public final class RepairFaultyMLSRemovalKeysGenerator {
28+
29+
private let journal: any JournalProtocol
30+
private let repairUseCase: any RepairRemovalKeysUseCaseProtocol
31+
private let workAgent: WorkAgent
32+
33+
public init(
34+
journal: any JournalProtocol,
35+
repairUseCase: any RepairRemovalKeysUseCaseProtocol,
36+
workAgent: WorkAgent
37+
) {
38+
self.journal = journal
39+
self.repairUseCase = repairUseCase
40+
self.workAgent = workAgent
41+
}
42+
43+
/// Checks if repair is needed and submits the work item if so.
44+
///
45+
/// This method can be called multiple times safely - it only submits
46+
/// the work item if the journal flag is set.
47+
48+
public func submitWorkItemIfNeeded() {
49+
guard journal[.isRepairFaultyMLSRemovalKeysRequired] else {
50+
return
51+
}
52+
53+
let workItem = RepairFaultyMLSRemovalKeysWorkItem(
54+
journal: journal,
55+
repairUseCase: repairUseCase
56+
)
57+
58+
Task { [workAgent] in
59+
await workAgent.submitItem(workItem)
60+
}
61+
}
62+
63+
}

β€ŽWireDomain/Sources/WireDomain/Utilities/Journal/Journal.swiftβ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ public extension Journal {
137137
JournalKey.isInitialSyncRequired,
138138
JournalKey.isSyncV2Enabled,
139139
JournalKey.isBackendMLSEnabled,
140-
JournalKey.isFederationMigrationRequired
140+
JournalKey.isFederationMigrationRequired,
141+
JournalKey.isRepairFaultyMLSRemovalKeysRequired
141142

142143
].forEach {
143144
result[$0.name] = "\(self[$0] == true ? "Yes" : "No")"

β€ŽWireDomain/Sources/WireDomain/Utilities/Journal/JournalKey.swiftβ€Ž

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ public extension JournalKey where Value == Bool {
103103
defaultValue: false
104104
)
105105

106+
/// Whether faulty MLS removal keys need to be repaired.
107+
108+
static let isRepairFaultyMLSRemovalKeysRequired = Self(
109+
"isRepairFaultyMLSRemovalKeysRequired",
110+
defaultValue: false
111+
)
112+
106113
}
107114

108115
public extension JournalKey where Value == Set<String> {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//
2+
// Wire
3+
// Copyright (C) 2025 Wire Swiss GmbH
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with this program. If not, see http://www.gnu.org/licenses/.
17+
//
18+
19+
import Foundation
20+
import WireLogging
21+
22+
/// Repairs conversations with faulty MLS removal keys.
23+
///
24+
/// This work item checks if the journal flag `isRepairFaultyMLSRemovalKeysRequired`
25+
/// is set, and if so, invokes the repair use case. On successful completion,
26+
/// the flag is cleared so the repair only runs once per migration.
27+
28+
struct RepairFaultyMLSRemovalKeysWorkItem: WorkItem {
29+
30+
let id = UUID()
31+
var priority: WorkItemPriority {
32+
.low
33+
}
34+
35+
private let journal: any JournalProtocol
36+
private let repairUseCase: any RepairRemovalKeysUseCaseProtocol
37+
38+
init(
39+
journal: any JournalProtocol,
40+
repairUseCase: any RepairRemovalKeysUseCaseProtocol
41+
) {
42+
self.journal = journal
43+
self.repairUseCase = repairUseCase
44+
}
45+
46+
func start() async throws {
47+
// Check if repair is needed
48+
guard journal[.isRepairFaultyMLSRemovalKeysRequired] else {
49+
WireLogger.mls.debug(
50+
"faulty MLS removal keys repair not needed, skipping",
51+
attributes: .safePublic
52+
)
53+
return
54+
}
55+
56+
WireLogger.mls.info(
57+
"starting faulty MLS removal keys repair",
58+
attributes: .safePublic
59+
)
60+
61+
do {
62+
let result = try await repairUseCase.invoke()
63+
64+
WireLogger.mls.info(
65+
"faulty MLS removal keys repair completed: found \(result.faultyConversationsFound), repaired \(result.conversationsRepaired)",
66+
attributes: .safePublic
67+
)
68+
69+
// Clear the flag on success
70+
journal[.isRepairFaultyMLSRemovalKeysRequired] = false
71+
72+
} catch {
73+
WireLogger.mls.error(
74+
"faulty MLS removal keys repair failed: \(String(describing: error))",
75+
attributes: .safePublic
76+
)
77+
// Don't clear the flag so it will be retried
78+
throw error
79+
}
80+
}
81+
82+
}

β€ŽWireDomain/Tests/WireDomainTests/Utilities/JournalTests.swiftβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class JournalStoreTests {
7070
@Test("Values contain all declared values")
7171
func values() {
7272
// Given
73-
let exhaustiveKeysCount = 10
73+
let exhaustiveKeysCount = 11
7474
sut[.isSyncV2Enabled] = true
7575

7676
// When
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// Wire
3+
// Copyright (C) 2025 Wire Swiss GmbH
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with this program. If not, see http://www.gnu.org/licenses/.
17+
//
18+
19+
import Foundation
20+
import WireDomain
21+
22+
/// **Issue:**: Faulty MLS removal keys need repair - [WPB-22447]
23+
struct AppVersionMigration_4_12_0: AppVersionMigration {
24+
25+
let version: SemanticVersion = "4.12.0"
26+
let journal: any JournalProtocol
27+
let repairGenerator: RepairFaultyMLSRemovalKeysGenerator?
28+
29+
func perform() async throws {
30+
// Mark that faulty MLS removal keys need to be repaired
31+
journal[.isRepairFaultyMLSRemovalKeysRequired] = true
32+
33+
// Submit the work item now that the flag is set
34+
repairGenerator?.submitWorkItemIfNeeded()
35+
}
36+
37+
}

β€Žwire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession.swiftβ€Ž

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,9 @@ public final class ZMUserSession: NSObject {
669669
await clientSessionComponent.workAgent.setAutoStartEnabled(true)
670670
await clientSessionComponent.workAgent.start()
671671
clientSessionComponent.generatorsDirectory.observeSyncState()
672+
673+
// Initialize the generator to enqueue repair work item if needed
674+
clientSessionComponent.repairFaultyMLSRemovalKeysGenerator.submitWorkItemIfNeeded()
672675
}
673676
}
674677

@@ -1711,7 +1714,11 @@ extension ZMUserSession {
17111714
sessionManager: sessionManager
17121715
),
17131716
AppVersionMigration_4_3_0(coreCryptoProvider: coreCryptoProvider),
1714-
AppVersionMigration_4_10_0(journal: journal)
1717+
AppVersionMigration_4_10_0(journal: journal),
1718+
AppVersionMigration_4_12_0(
1719+
journal: journal,
1720+
repairGenerator: clientSessionComponent?.repairFaultyMLSRemovalKeysGenerator
1721+
)
17151722
]
17161723
}
17171724

0 commit comments

Comments
Β (0)