mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-24 23:45:18 +00:00
* BLE background presence: pending-connect wake-on-proximity + wake-window maintenance (#1395) iOS cancels nothing for us: pending CBCentralManager connects never expire, complete whenever the peer reappears in range, and relaunch the app via the existing state-restoration path. Use that as the wake-on-proximity mechanism: - BLERecentPeripheralCache: retains handles to recently seen/dropped peripherals (LRU 16, 15 min max age to respect BLE address rotation) - On backgrounding, arm indefinite pending connects to cached peripherals within a slot budget (2 of 6 central slots reserved for live background discovery); armed entries carry lastConnectionAttempt == nil so a quick background/foreground bounce can't strand them as connecting - The 8s app-level connect timeout defers while backgrounded so discovery-driven background connects also stay pending - Foreground return cancels stale pending connects (including connecting entries rebuilt by state restoration after a relaunch) and hands control back to the scanner/scheduler - A link dropped while backgrounded re-arms after the disconnect-settle window, so a peer walking away and returning wakes us again - Packet ingress while backgrounded triggers a catch-up maintenance pass (announce/flush/drain) since the maintenance timer is suspended with the app; rate-limited to the normal 5s cadence Battery cost ~0: pending connects live in the controller's allowlist (no scanning, no app CPU), and the catch-up pass only runs inside wake windows the radio already granted. Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * Restore: seed wake-on-proximity cache and resume service discovery Field finding from on-device testing: after a state-restoration relaunch, the recent-peripheral cache starts empty, so backgrounding shortly after a restore armed no pending connects. Seed the cache from the restored peripherals — they are the freshest proximity candidates we have. Also resume service discovery for peripherals restored as connected with no characteristic: the CBCharacteristic reference dies with the old process, and without rediscovery the link sits connected-but-unusable until the peer drops it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Defer restored-link service rediscovery until poweredOn Field finding: CBPeripheral.discoverServices issued inside willRestoreState fires before the central manager reaches poweredOn — CoreBluetooth drops the command with an API MISUSE warning, leaving restored-connected links characteristic-less after all. Move the rediscovery to centralManagerDidUpdateState(.poweredOn), which restoration guarantees runs afterwards. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Let disconnect re-arms use the freed slot (Codex P2 on #1396) The background-entry arm reserves 2 of 6 central slots for live discovery, but the disconnect re-arm path shared that budget: with 4+ links remaining the budget hit zero and the just-dropped peer was never armed — defeating walk-away/walk-back re-arming in dense meshes. The disconnect path now arms with no reserve, consuming the slot the disconnect itself freed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Test Harness Guide
This test suite uses an in-memory networking harness to make end-to-end and integration tests deterministic, fast, and race-free without touching production code.
In-Memory Bus
- File:
bitchatTests/Mocks/MockBLEService.swift - Registry/Adjacency: Global
registrymapspeerIDto aMockBLEServiceinstance;adjacencyrecords simulated links between peers. - Setup: Call
MockBLEService.resetTestBus()insetUp()to clear state between tests. - Topology: Use
simulateConnectedPeer(_:)andsimulateDisconnectedPeer(_:)to add/remove links.connectFullMesh()helpers in tests build larger topologies. - Handlers: Tests can observe data via
messageDeliveryHandler(decodedBitchatMessage) andpacketDeliveryHandler(rawBitchatPacket). - De‑duplication: A thread-safe
seenMessageIDsprevents duplicate deliveries during flooding/relays.
Broadcast Flooding
- Flag:
MockBLEService.autoFloodEnabled - Intent: When
true, public broadcasts propagate across the entire connected component (ignores TTL for reach) while still de‑duping to prevent loops. - Usage: Enabled in Integration tests (
setUp) to simulate large-network broadcast; disabled in E2E tests to keep routing explicit and verify TTL behavior (seePublicChatE2ETests.testZeroTTLNotRelayed).
Rehandshake Flow (Noise)
- Why: The legacy NACK recovery path was removed; recovery now relies on Noise session rehandshake after decrypt failure or desync.
- Manager:
NoiseSessionManagermanages per-peer sessions. - Pattern: On decrypt failure, proactively clear the local session and re-initiate a handshake. The peer accepts and replaces their session.
- Test:
IntegrationTests.testRehandshakeAfterDecryptionFailure- Corrupts ciphertext to induce a decrypt error.
- Calls
removeSession(for:)on the initiator’s manager beforeinitiateHandshake(with:)to avoidalreadyEstablished. - Verifies encrypt/decrypt succeeds post-rehandshake.
Tips
- Determinism: Add small async delays only where handler installation/topology changes could race the first send.
- Scoping: Keep
autoFloodEnabledtoggled only within Integration tests; always reset intearDown()to avoid cross-test contamination. - Direct vs Relay: Private messages target a specific peer when adjacent; otherwise they are surfaced to neighbors for relay and, if known, also delivered to the target.
Quick Start
- Create nodes and connect them:
let svc = MockBLEService(); svc.myPeerID = "PEER1"svc.simulateConnectedPeer("PEER2")
- Observe messages:
svc.messageDeliveryHandler = { msg in /* asserts */ }
- Enable broadcast flooding for Integration suites only:
MockBLEService.autoFloodEnabled = true