mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 03:25:19 +00:00
[codex] Extract BLE link state store (#1310)
* Refactor BLE transport event handling * Make image output paths unique * Keep queued Nostr read receipts alive * Refine BLE ingress fanout * Rediscover BLE service after invalidation * Extract BLE notification retry buffer * Extract BLE inbound write buffer * Extract BLE fragment assembly buffer * Tidy secure log handling from device run * Extract BLE outbound fragment scheduler * Harden app CI media tests * Redact BLE message content from logs * Extract BLE Noise session queues * Fix BLE read receipt UI updates * Allow self-authored RSR ingress replies * Harden read receipt queue test timing * Extract BLE outbound policy and incoming file storage * Avoid duplicate BLE link snapshots during send * Canonicalize Nostr relay URLs * Extract BLE link state store * Extract BLE connection scheduler --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import bitchat
|
||||
|
||||
struct BLEConnectionSchedulerTests {
|
||||
@Test
|
||||
func discoveryQueuesWeakSignalCandidates() {
|
||||
let scheduler = BLEConnectionScheduler<String>(dynamicRSSIThreshold: -80)
|
||||
let now = Date()
|
||||
let candidate = makeCandidate(id: "p1", rssi: -85, now: now)
|
||||
|
||||
let decision = scheduler.handleDiscovery(
|
||||
candidate,
|
||||
connectedOrConnectingCount: 0,
|
||||
existingState: nil,
|
||||
peripheralState: .disconnected,
|
||||
now: now
|
||||
)
|
||||
|
||||
#expect(decision == .queued)
|
||||
#expect(scheduler.candidateCount == 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
func discoveryQueuesAndSchedulesRetryWhenRateLimited() {
|
||||
let scheduler = BLEConnectionScheduler<String>(connectRateLimitInterval: 1.0)
|
||||
let now = Date()
|
||||
scheduler.recordConnectionAttempt(at: now)
|
||||
|
||||
let decision = scheduler.handleDiscovery(
|
||||
makeCandidate(id: "p1", rssi: -50, now: now),
|
||||
connectedOrConnectingCount: 0,
|
||||
existingState: nil,
|
||||
peripheralState: .disconnected,
|
||||
now: now.addingTimeInterval(0.25)
|
||||
)
|
||||
|
||||
guard case .scheduleRetry(let delay) = decision else {
|
||||
Issue.record("Expected scheduleRetry, got \(decision)")
|
||||
return
|
||||
}
|
||||
#expect(delay > 0)
|
||||
#expect(scheduler.candidateCount == 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
func nextCandidateSelectsBestScoredCandidate() {
|
||||
let scheduler = BLEConnectionScheduler<String>()
|
||||
let now = Date()
|
||||
scheduler.enqueue(makeCandidate(id: "weak", rssi: -88, now: now))
|
||||
scheduler.enqueue(makeCandidate(id: "strong", rssi: -60, now: now.addingTimeInterval(1)))
|
||||
|
||||
let decision = scheduler.nextCandidate(
|
||||
connectedOrConnectingCount: 0,
|
||||
isAlreadyConnectingOrConnected: { _ in false },
|
||||
now: now.addingTimeInterval(2)
|
||||
)
|
||||
|
||||
guard case .connect(let candidate) = decision else {
|
||||
Issue.record("Expected connect decision")
|
||||
return
|
||||
}
|
||||
#expect(candidate.peripheralID == "strong")
|
||||
}
|
||||
|
||||
@Test
|
||||
func enqueueReplacesExistingPeripheralCandidate() {
|
||||
let scheduler = BLEConnectionScheduler<String>()
|
||||
let now = Date()
|
||||
scheduler.enqueue(makeCandidate(id: "same", rssi: -90, now: now))
|
||||
scheduler.enqueue(makeCandidate(id: "same", rssi: -55, now: now.addingTimeInterval(1)))
|
||||
|
||||
let decision = scheduler.nextCandidate(
|
||||
connectedOrConnectingCount: 0,
|
||||
isAlreadyConnectingOrConnected: { _ in false },
|
||||
now: now.addingTimeInterval(2)
|
||||
)
|
||||
|
||||
guard case .connect(let candidate) = decision else {
|
||||
Issue.record("Expected connect decision")
|
||||
return
|
||||
}
|
||||
#expect(scheduler.candidateCount == 0)
|
||||
#expect(candidate.peripheralID == "same")
|
||||
#expect(candidate.rssi == -55)
|
||||
}
|
||||
|
||||
@Test
|
||||
func weakTimedOutCandidateIsRequeuedWithRetryDelay() {
|
||||
let scheduler = BLEConnectionScheduler<String>(
|
||||
weakLinkCooldownSeconds: 30,
|
||||
weakLinkRSSICutoff: -90
|
||||
)
|
||||
let now = Date()
|
||||
scheduler.recordConnectionTimeout(peripheralID: "weak", at: now)
|
||||
scheduler.enqueue(makeCandidate(id: "weak", rssi: -95, now: now))
|
||||
|
||||
let decision = scheduler.nextCandidate(
|
||||
connectedOrConnectingCount: 0,
|
||||
isAlreadyConnectingOrConnected: { _ in false },
|
||||
now: now.addingTimeInterval(5)
|
||||
)
|
||||
|
||||
guard case .retryAfter(let delay) = decision else {
|
||||
Issue.record("Expected retryAfter decision")
|
||||
return
|
||||
}
|
||||
#expect(delay == 15)
|
||||
#expect(scheduler.candidateCount == 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
func rssiThresholdTightensAfterRepeatedRecentTimeouts() {
|
||||
let scheduler = BLEConnectionScheduler<String>()
|
||||
let now = Date()
|
||||
scheduler.recordConnectionTimeout(peripheralID: "p1", at: now)
|
||||
scheduler.recordConnectionTimeout(peripheralID: "p2", at: now)
|
||||
scheduler.recordConnectionTimeout(peripheralID: "p3", at: now)
|
||||
|
||||
let threshold = scheduler.updateRSSIThreshold(
|
||||
connectedCount: 1,
|
||||
connectedOrConnectingLinkCount: 1,
|
||||
now: now.addingTimeInterval(1)
|
||||
)
|
||||
|
||||
#expect(threshold == TransportConfig.bleRSSIHighTimeoutThreshold)
|
||||
#expect(scheduler.dynamicRSSIThreshold == TransportConfig.bleRSSIHighTimeoutThreshold)
|
||||
}
|
||||
|
||||
@Test
|
||||
func rssiThresholdTightensWhenCandidateQueueIsFull() {
|
||||
let scheduler = BLEConnectionScheduler<String>(candidateCap: 2)
|
||||
let now = Date()
|
||||
scheduler.enqueue(makeCandidate(id: "p1", rssi: -65, now: now))
|
||||
scheduler.enqueue(makeCandidate(id: "p2", rssi: -66, now: now))
|
||||
|
||||
let threshold = scheduler.updateRSSIThreshold(
|
||||
connectedCount: 1,
|
||||
connectedOrConnectingLinkCount: 1,
|
||||
now: now
|
||||
)
|
||||
|
||||
#expect(threshold == TransportConfig.bleRSSIConnectedThreshold)
|
||||
}
|
||||
}
|
||||
|
||||
private func makeCandidate(id: String, rssi: Int, now: Date) -> BLEConnectionCandidate<String> {
|
||||
BLEConnectionCandidate(
|
||||
peripheral: id,
|
||||
peripheralID: id,
|
||||
rssi: rssi,
|
||||
name: id,
|
||||
isConnectable: true,
|
||||
discoveredAt: now
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user