diff --git a/bitchat/Services/BLE/BLEService.swift b/bitchat/Services/BLE/BLEService.swift index 32b758ed..33a5b7c5 100644 --- a/bitchat/Services/BLE/BLEService.swift +++ b/bitchat/Services/BLE/BLEService.swift @@ -4892,8 +4892,23 @@ extension BLEService { // A quarantined transport restored the same cryptographic // generation. Its capability proof and announce state never // became stale; only work queued while outbound keys were paused - // needs one idempotent ready transition. + // needs one idempotent ready transition. Retrying the bounded + // early-ciphertext queue is receive-side and therefore always + // safe under the restored keys. noisePacketHandler.handleSessionAuthenticated(normalizedPeerID) + #if DEBUG + _test_onPrivateMediaSessionReconciled?(normalizedPeerID) + #endif + if deferOutboundUntilConvergence { + // Timeout-restore: the counterpart may have completed the + // replacement handshake and discarded these keys, so + // encrypting the parked queues here would lose them silently. + // The restore's mandatory convergence retry — or any later + // handshake the reconnect policy initiates — re-enters this + // transition with a fresh generation and drains them under + // keys both sides hold. + return + } sendPendingMessagesAfterHandshake(for: normalizedPeerID) sendPendingNoisePayloadsAfterHandshake(for: normalizedPeerID) return diff --git a/bitchatTests/BLEServiceCoreTests.swift b/bitchatTests/BLEServiceCoreTests.swift index 1c958264..aa0a6069 100644 --- a/bitchatTests/BLEServiceCoreTests.swift +++ b/bitchatTests/BLEServiceCoreTests.swift @@ -666,6 +666,10 @@ struct BLEServiceCoreTests { ) await ble._test_drainNoiseMessagePipeline() #expect(ble.canDeliverSecurely(to: alicePeerID)) + // The establishment transition enqueues its forced announce as a + // separate serialized phase; drain again so it lands before the tap + // and cannot masquerade as restore-driven announce traffic below. + await ble._test_drainNoiseMessagePipeline() let outbound = OutboundPacketTap() ble._test_onOutboundPacket = outbound.record