mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-24 23:45:18 +00:00
* 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>
157 lines
5.2 KiB
Swift
157 lines
5.2 KiB
Swift
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
|
|
)
|
|
}
|