Refactor BLEConnectTimeoutPolicy to use primitive attributes and eliminate CBPeripheral test mock crash

This commit is contained in:
krishrathi1 2026-08-29 23:01:53 +05:30
parent 55d7cd460f
commit 929a86ce2b
3 changed files with 34 additions and 61 deletions

View File

@ -19,17 +19,18 @@ struct BLEPeripheralLinkState {
}
struct BLEConnectTimeoutPolicy {
/// Pure decision: given a captured attempt token and current link state,
/// Pure decision: given a captured attempt token and current link state attributes,
/// returns true if the timeout closure should act on this peripheral attempt.
static func shouldExecuteConnectTimeout(
capturedAttemptToken: UInt64,
state: BLEPeripheralLinkState?,
isConnecting: Bool,
isConnected: Bool,
currentAttemptToken: UInt64,
isPeripheralConnected: Bool
) -> Bool {
guard let state = state else { return false }
guard state.isConnecting && !state.isConnected else { return false }
guard isConnecting && !isConnected else { return false }
guard !isPeripheralConnected else { return false }
guard state.attemptToken == capturedAttemptToken else { return false }
guard currentAttemptToken == capturedAttemptToken else { return false }
return true
}
}

View File

@ -253,11 +253,14 @@ final class BLERadioController {
// this check, this now-stale timeout would cancel that newer,
// still-live attempt out from under it and apply a
// connection-timeout penalty to a peer that never actually timed out.
guard BLEConnectTimeoutPolicy.shouldExecuteConnectTimeout(
capturedAttemptToken: attemptToken,
state: state,
isPeripheralConnected: peripheral.state == .connected
) else {
guard let state,
BLEConnectTimeoutPolicy.shouldExecuteConnectTimeout(
capturedAttemptToken: attemptToken,
isConnecting: state.isConnecting,
isConnected: state.isConnected,
currentAttemptToken: state.attemptToken,
isPeripheralConnected: peripheral.state == .connected
) else {
if let state, state.attemptToken != attemptToken {
SecureLogger.debug("⏱️ Timeout fired for a superseded connection attempt, ignoring: \(candidate.name)", category: .session)
}

View File

@ -1,39 +1,18 @@
import Foundation
import CoreBluetooth
import Testing
@testable import bitchat
struct BLEConnectTimeoutPolicyTests {
@Test
func validConnectingAttemptTriggersTimeout() {
let token: UInt64 = 42
// When state is nil -> false
#expect(!BLEConnectTimeoutPolicy.shouldExecuteConnectTimeout(
capturedAttemptToken: token,
state: nil,
isPeripheralConnected: false
))
}
@Test
func supersededAttemptTokenIgnoresTimeout() {
let capturedToken: UInt64 = 1
let currentToken: UInt64 = 2
let mockState = BLEPeripheralLinkState(
peripheral: CBPeripheralMock.create(),
characteristic: nil,
isConnecting: true,
isConnected: false,
lastConnectionAttempt: Date(),
attemptToken: currentToken,
assembler: NotificationStreamAssembler()
)
let result = BLEConnectTimeoutPolicy.shouldExecuteConnectTimeout(
capturedAttemptToken: capturedToken,
state: mockState,
isConnecting: true,
isConnected: false,
currentAttemptToken: currentToken,
isPeripheralConnected: false
)
@ -44,19 +23,11 @@ struct BLEConnectTimeoutPolicyTests {
func matchingAttemptTokenExecutesTimeout() {
let token: UInt64 = 5
let mockState = BLEPeripheralLinkState(
peripheral: CBPeripheralMock.create(),
characteristic: nil,
isConnecting: true,
isConnected: false,
lastConnectionAttempt: Date(),
attemptToken: token,
assembler: NotificationStreamAssembler()
)
let result = BLEConnectTimeoutPolicy.shouldExecuteConnectTimeout(
capturedAttemptToken: token,
state: mockState,
isConnecting: true,
isConnected: false,
currentAttemptToken: token,
isPeripheralConnected: false
)
@ -67,31 +38,29 @@ struct BLEConnectTimeoutPolicyTests {
func connectedStateIgnoresTimeout() {
let token: UInt64 = 5
let mockState = BLEPeripheralLinkState(
peripheral: CBPeripheralMock.create(),
characteristic: nil,
let result = BLEConnectTimeoutPolicy.shouldExecuteConnectTimeout(
capturedAttemptToken: token,
isConnecting: false,
isConnected: true,
lastConnectionAttempt: Date(),
attemptToken: token,
assembler: NotificationStreamAssembler()
currentAttemptToken: token,
isPeripheralConnected: true
)
#expect(!result)
}
@Test
func peripheralConnectedStateIgnoresTimeout() {
let token: UInt64 = 5
let result = BLEConnectTimeoutPolicy.shouldExecuteConnectTimeout(
capturedAttemptToken: token,
state: mockState,
isConnecting: true,
isConnected: false,
currentAttemptToken: token,
isPeripheralConnected: true
)
#expect(!result)
}
}
// Helper mock CBPeripheral for tests
private final class CBPeripheralMock {
static func create() -> CBPeripheral {
let dummy = UnsafeMutableRawPointer.allocate(byteCount: 256, alignment: 16)
dummy.initializeMemory(as: UInt8.self, repeating: 0, count: 256)
return Unmanaged<CBPeripheral>.fromOpaque(dummy).takeUnretainedValue()
}
}