Discard private-media callbacks during panic

This commit is contained in:
jack
2026-07-25 23:49:41 +02:00
parent 5de1c83681
commit 72855e11f8
2 changed files with 70 additions and 7 deletions
+5 -7
View File
@@ -744,9 +744,8 @@ final class BLEService: NSObject {
ingressLinks.removeAll() ingressLinks.removeAll()
recentTrafficTracker.removeAll() recentTrafficTracker.removeAll()
scheduledRelays.cancelAll() scheduledRelays.cancelAll()
let policyCompletions = pendingPrivateMediaPolicyResolutions.values.flatMap { // These callbacks belong to pre-panic transfer state. Invoking
Array($0.completions.values) // them would let queued UI work recreate or resend wiped media.
}
pendingPrivateMediaPolicyResolutions.removeAll() pendingPrivateMediaPolicyResolutions.removeAll()
privateMediaSessionGenerations.removeAll() privateMediaSessionGenerations.removeAll()
authenticatedPeerStates.removeAll() authenticatedPeerStates.removeAll()
@@ -755,14 +754,13 @@ final class BLEService: NSObject {
authenticatedPeerStateSendProgress.removeAll() authenticatedPeerStateSendProgress.removeAll()
// Let the post-panic identity publish its fresh bundle promptly. // Let the post-panic identity publish its fresh bundle promptly.
lastPrekeyBundleSentAt = nil lastPrekeyBundleSentAt = nil
return (transfers, policyCompletions) return transfers
} }
for entry in panicReset.0 { for entry in panicReset {
entry.workItems.forEach { $0.cancel() } entry.workItems.forEach { $0.cancel() }
TransferProgressManager.shared.cancel(id: entry.id) TransferProgressManager.shared.cancel(id: entry.id)
} }
completePrivateMediaPolicyResolution(panicReset.1, with: .blockedDowngrade)
bleQueue.sync { bleQueue.sync {
pendingWriteBuffers.removeAll() pendingWriteBuffers.removeAll()
@@ -1262,7 +1260,7 @@ final class BLEService: NSObject {
with policy: PrivateMediaSendPolicy with policy: PrivateMediaSendPolicy
) { ) {
guard !completions.isEmpty else { return } guard !completions.isEmpty else { return }
Task { @MainActor in notifyUI {
completions.forEach { $0(policy) } completions.forEach { $0(policy) }
} }
} }
@@ -546,6 +546,71 @@ struct PrivateMediaEndToEndTests {
bob._test_onOutboundPacket = nil bob._test_onOutboundPacket = nil
} }
@Test
func panicDropsPendingPrivateMediaPolicyCompletion() async throws {
let root = FileManager.default.temporaryDirectory
.appendingPathComponent(
"private-media-policy-panic-\(UUID().uuidString)",
isDirectory: true
)
defer { try? FileManager.default.removeItem(at: root) }
let alice = makeService(
baseDirectory: root.appendingPathComponent(
"alice",
isDirectory: true
)
)
let bob = makeService(
baseDirectory: root.appendingPathComponent(
"bob",
isDirectory: true
)
)
alice._test_seedConnectedPeer(
bob.myPeerID,
nickname: "Prerelease Bob",
capabilities: .privateMedia,
noisePublicKey: bob.noiseStaticPublicKeyData()
)
_ = try await establishSessionCapturingPeerState(
alice: alice,
bob: bob
)
#expect(
alice.privateMediaSendPolicy(to: bob.myPeerID)
== .awaitingCapabilityProof
)
let recorder = PrivateMediaPolicyRecorder()
alice.resolvePrivateMediaSendPolicy(to: bob.myPeerID) {
recorder.record($0)
}
let registered = await TestHelpers.waitUntil(
{
alice._test_hasPendingPrivateMediaPolicyResolution(
for: bob.myPeerID
)
},
timeout: TestConstants.longTimeout
)
#expect(registered)
alice.suspendForPanicReset()
alice.resetIdentityForPanic(
currentNickname: "anon",
restartServices: false
)
alice._test_forcePrivateMediaProofTimeout(for: bob.myPeerID)
await Task.yield()
#expect(recorder.snapshot() == nil)
#expect(
!alice._test_hasPendingPrivateMediaPolicyResolution(
for: bob.myPeerID
)
)
}
@Test @Test
func queuedPrivatePayloadWaitsForProofNotHandshakeCompletion() async throws { func queuedPrivatePayloadWaitsForProofNotHandshakeCompletion() async throws {
let root = FileManager.default.temporaryDirectory let root = FileManager.default.temporaryDirectory