mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-10-25 07:41:41 +00:00
Merge 929a86ce2ba882d59209783335291b832e5dd153 into 9b84b361225facd8e623f25d76f889d3dc54a879
This commit is contained in:
commit
05d6f51885
@ -8,6 +8,7 @@ struct BLEPeripheralLinkState {
|
||||
var isConnecting: Bool
|
||||
var isConnected: Bool
|
||||
var lastConnectionAttempt: Date?
|
||||
var attemptToken: UInt64 = 0
|
||||
/// When didConnect last fired for this link. Nil for links restored
|
||||
/// already-connected (their connect predates this process), which is
|
||||
/// exactly the signal redundant-link consolidation needs: a restored
|
||||
@ -17,6 +18,23 @@ struct BLEPeripheralLinkState {
|
||||
var assembler: NotificationStreamAssembler
|
||||
}
|
||||
|
||||
struct BLEConnectTimeoutPolicy {
|
||||
/// 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,
|
||||
isConnecting: Bool,
|
||||
isConnected: Bool,
|
||||
currentAttemptToken: UInt64,
|
||||
isPeripheralConnected: Bool
|
||||
) -> Bool {
|
||||
guard isConnecting && !isConnected else { return false }
|
||||
guard !isPeripheralConnected else { return false }
|
||||
guard currentAttemptToken == capturedAttemptToken else { return false }
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
struct BLEDirectLinkState: Equatable {
|
||||
let hasPeripheral: Bool
|
||||
let hasCentral: Bool
|
||||
@ -104,7 +122,10 @@ final class BLELinkStateStore {
|
||||
return state
|
||||
}
|
||||
|
||||
func beginConnecting(to peripheral: CBPeripheral, at date: Date) {
|
||||
@discardableResult
|
||||
func beginConnecting(to peripheral: CBPeripheral, at date: Date = Date()) -> UInt64 {
|
||||
let peripheralID = peripheral.identifier.uuidString
|
||||
let nextAttemptToken = (peripherals[peripheralID]?.attemptToken ?? 0) + 1
|
||||
setPeripheralState(
|
||||
BLEPeripheralLinkState(
|
||||
peripheral: peripheral,
|
||||
@ -112,10 +133,12 @@ final class BLELinkStateStore {
|
||||
isConnecting: true,
|
||||
isConnected: false,
|
||||
lastConnectionAttempt: date,
|
||||
attemptToken: nextAttemptToken,
|
||||
assembler: NotificationStreamAssembler()
|
||||
),
|
||||
for: peripheral.identifier.uuidString
|
||||
for: peripheralID
|
||||
)
|
||||
return nextAttemptToken
|
||||
}
|
||||
|
||||
func markConnected(_ peripheral: CBPeripheral, at now: Date = Date()) {
|
||||
|
||||
@ -231,7 +231,9 @@ final class BLERadioController {
|
||||
guard delegate?.radioIsPanicSuspended() == false else { return }
|
||||
let peripheral = candidate.peripheral
|
||||
let peripheralID = candidate.peripheralID
|
||||
linkStateStore.beginConnecting(to: peripheral, at: Date())
|
||||
// Captured monotonically increasing attempt token so the timeout closure
|
||||
// below can tell this specific attempt apart from a later one.
|
||||
let attemptToken = linkStateStore.beginConnecting(to: peripheral, at: Date())
|
||||
peripheral.delegate = peripheralDelegate
|
||||
let options: [String: Any] = [
|
||||
CBConnectPeripheralOptionNotifyOnConnectionKey: true,
|
||||
@ -243,12 +245,25 @@ final class BLERadioController {
|
||||
SecureLogger.debug("\(logPrefix): \(candidate.name) [RSSI:\(candidate.rssi)]", category: .session)
|
||||
|
||||
queue.asyncAfter(deadline: .now() + TransportConfig.bleConnectTimeoutSeconds) { [weak self] in
|
||||
guard let self,
|
||||
let state = self.linkStateStore.state(forPeripheralID: peripheralID),
|
||||
state.isConnecting && !state.isConnected else { return }
|
||||
guard let self else { return }
|
||||
let state = self.linkStateStore.state(forPeripheralID: peripheralID)
|
||||
|
||||
guard peripheral.state != .connected else {
|
||||
SecureLogger.debug("⏱️ Timeout fired but peripheral already connected: \(candidate.name)", category: .session)
|
||||
// A disconnect + immediate reconnect between this attempt and now
|
||||
// starts a new attempt with an incremented attemptToken. Without
|
||||
// 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 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)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
66
bitchatTests/Services/BLEConnectTimeoutPolicyTests.swift
Normal file
66
bitchatTests/Services/BLEConnectTimeoutPolicyTests.swift
Normal file
@ -0,0 +1,66 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import bitchat
|
||||
|
||||
struct BLEConnectTimeoutPolicyTests {
|
||||
@Test
|
||||
func supersededAttemptTokenIgnoresTimeout() {
|
||||
let capturedToken: UInt64 = 1
|
||||
let currentToken: UInt64 = 2
|
||||
|
||||
let result = BLEConnectTimeoutPolicy.shouldExecuteConnectTimeout(
|
||||
capturedAttemptToken: capturedToken,
|
||||
isConnecting: true,
|
||||
isConnected: false,
|
||||
currentAttemptToken: currentToken,
|
||||
isPeripheralConnected: false
|
||||
)
|
||||
|
||||
#expect(!result)
|
||||
}
|
||||
|
||||
@Test
|
||||
func matchingAttemptTokenExecutesTimeout() {
|
||||
let token: UInt64 = 5
|
||||
|
||||
let result = BLEConnectTimeoutPolicy.shouldExecuteConnectTimeout(
|
||||
capturedAttemptToken: token,
|
||||
isConnecting: true,
|
||||
isConnected: false,
|
||||
currentAttemptToken: token,
|
||||
isPeripheralConnected: false
|
||||
)
|
||||
|
||||
#expect(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
func connectedStateIgnoresTimeout() {
|
||||
let token: UInt64 = 5
|
||||
|
||||
let result = BLEConnectTimeoutPolicy.shouldExecuteConnectTimeout(
|
||||
capturedAttemptToken: token,
|
||||
isConnecting: false,
|
||||
isConnected: true,
|
||||
currentAttemptToken: token,
|
||||
isPeripheralConnected: true
|
||||
)
|
||||
|
||||
#expect(!result)
|
||||
}
|
||||
|
||||
@Test
|
||||
func peripheralConnectedStateIgnoresTimeout() {
|
||||
let token: UInt64 = 5
|
||||
|
||||
let result = BLEConnectTimeoutPolicy.shouldExecuteConnectTimeout(
|
||||
capturedAttemptToken: token,
|
||||
isConnecting: true,
|
||||
isConnected: false,
|
||||
currentAttemptToken: token,
|
||||
isPeripheralConnected: true
|
||||
)
|
||||
|
||||
#expect(!result)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user