mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 19:25:23 +00:00
Reset BLEService's own receipt cache on panic completion
The panic media wipe runs on the file store PanicRecoveryOperations .live() constructs, but receipt lookups are served by BLEService's own incomingFileStore, whose in-memory index survived the wipe. Drop that instance's cache in completePanicReset — after media deletion, before radio admission reopens — so the next lookup rebuilds from the wiped directory. The panic test now drives the production suspend/wipe/ complete sequence against the instance BLEService actually uses. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -268,6 +268,17 @@ struct BLEIncomingFileStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Drops THIS instance's in-memory receipt index after a panic wipe.
|
||||||
|
///
|
||||||
|
/// `panicWipe` already resets the receipt store it runs on, but the
|
||||||
|
/// production wipe runs on the `PanicRecoveryOperations.live()` file
|
||||||
|
/// store while receipt lookups are served by `BLEService`'s own
|
||||||
|
/// `incomingFileStore`. The service's panic path must invalidate its own
|
||||||
|
/// cache explicitly or pre-panic decisions survive in memory.
|
||||||
|
func resetPrivateMediaReceiptsForPanic() {
|
||||||
|
privateMediaReceipts.resetForPanic()
|
||||||
|
}
|
||||||
|
|
||||||
func privateMediaReceiptState(
|
func privateMediaReceiptState(
|
||||||
messageID: String
|
messageID: String
|
||||||
) -> BLEPrivateMediaReceiptState {
|
) -> BLEPrivateMediaReceiptState {
|
||||||
|
|||||||
@@ -739,6 +739,12 @@ final class BLEService: NSObject {
|
|||||||
|
|
||||||
/// Reopen the radio only after media deletion and recovery-marker commit.
|
/// Reopen the radio only after media deletion and recovery-marker commit.
|
||||||
func completePanicReset(restartServices: Bool) {
|
func completePanicReset(restartServices: Bool) {
|
||||||
|
// The media wipe ran on the recovery operations' own file store; this
|
||||||
|
// service's store still caches pre-panic receipt decisions (and a
|
||||||
|
// callback drained during suspension may have re-read the pre-wipe
|
||||||
|
// ledger). Drop the cache before admission reopens so the next lookup
|
||||||
|
// rebuilds from the wiped directory.
|
||||||
|
incomingFileStore.resetPrivateMediaReceiptsForPanic()
|
||||||
setPanicSuspended(false)
|
setPanicSuspended(false)
|
||||||
guard restartServices else { return }
|
guard restartServices else { return }
|
||||||
startServices()
|
startServices()
|
||||||
@@ -3413,6 +3419,15 @@ extension BLEService {
|
|||||||
capturePanicLifecycleGeneration() != nil
|
capturePanicLifecycleGeneration() != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Queries the receipt store of the service's OWN incoming-file store —
|
||||||
|
/// the instance production lookups run against — so panic tests exercise
|
||||||
|
/// the real wiring instead of a same-instance shortcut.
|
||||||
|
func _test_privateMediaReceiptState(
|
||||||
|
messageID: String
|
||||||
|
) -> BLEPrivateMediaReceiptState {
|
||||||
|
incomingFileStore.privateMediaReceiptState(messageID: messageID)
|
||||||
|
}
|
||||||
|
|
||||||
/// Models a CoreBluetooth delegate callback without requiring a physical
|
/// Models a CoreBluetooth delegate callback without requiring a physical
|
||||||
/// peripheral. The callback itself runs on `bleQueue`, exactly where the
|
/// peripheral. The callback itself runs on `bleQueue`, exactly where the
|
||||||
/// panic radio-stop barrier must linearize it.
|
/// panic radio-stop barrier must linearize it.
|
||||||
|
|||||||
@@ -882,16 +882,40 @@ struct BLEFileTransferHandlerTests {
|
|||||||
let seed = BLEPrivateMediaReceiptStore(baseDirectory: base)
|
let seed = BLEPrivateMediaReceiptStore(baseDirectory: base)
|
||||||
#expect(seed.recordDeleted(messageID: messageID))
|
#expect(seed.recordDeleted(messageID: messageID))
|
||||||
|
|
||||||
let store = BLEIncomingFileStore(baseDirectory: base)
|
// Production wiring: receipt lookups run against the service's OWN
|
||||||
|
// incoming-file store while the panic wipe runs on the separate store
|
||||||
|
// `PanicRecoveryOperations.live()` constructs. The test must reset
|
||||||
|
// the instance BLEService uses, not a same-instance shortcut.
|
||||||
|
let keychain = MockKeychain()
|
||||||
|
let identityManager = MockIdentityManager(keychain)
|
||||||
|
let service = BLEService(
|
||||||
|
keychain: keychain,
|
||||||
|
idBridge: NostrIdentityBridge(keychain: MockKeychainHelper()),
|
||||||
|
identityManager: identityManager,
|
||||||
|
initializeBluetoothManagers: false,
|
||||||
|
incomingFileStore: BLEIncomingFileStore(baseDirectory: base)
|
||||||
|
)
|
||||||
#expect(
|
#expect(
|
||||||
store.privateMediaReceiptState(messageID: messageID)
|
service._test_privateMediaReceiptState(messageID: messageID)
|
||||||
== .tombstoned
|
== .tombstoned
|
||||||
)
|
)
|
||||||
|
|
||||||
try store.panicWipe()
|
service.suspendForPanicReset()
|
||||||
|
// A receive callback drained during suspension can still consult the
|
||||||
|
// ledger and re-cache the pre-wipe decision before media deletion.
|
||||||
|
#expect(
|
||||||
|
service._test_privateMediaReceiptState(messageID: messageID)
|
||||||
|
== .tombstoned
|
||||||
|
)
|
||||||
|
|
||||||
|
// The wipe itself runs on the recovery operations' distinct store,
|
||||||
|
// exactly like ChatViewModel's panic transaction.
|
||||||
|
let recoveryStore = BLEIncomingFileStore(baseDirectory: base)
|
||||||
|
try recoveryStore.panicWipe()
|
||||||
|
service.completePanicReset(restartServices: false)
|
||||||
|
|
||||||
#expect(
|
#expect(
|
||||||
store.privateMediaReceiptState(messageID: messageID)
|
service._test_privateMediaReceiptState(messageID: messageID)
|
||||||
== .absent
|
== .absent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user