BLERecentTrafficMonitor: How It Monitors Activity in the Mesh Network
The BLERecentTrafficMonitor is a thread-safe Swift component that records BLE packet timestamps and answers queries about whether any traffic occurred within a configurable recent time window, enabling adaptive radio behavior in bitchat's mesh network.
The BLERecentTrafficMonitor sits at the intersection of bitchat's receive pipeline and radio controller, providing real-time visibility into mesh activity without blocking either queue. According to the permissionlesstech/bitchat source code, this lightweight monitor uses a two-layer architecture—an NSLock-protected wrapper around a timestamp-tracking struct—to safely collect and query packet history across multiple execution contexts.
Core Architecture: Monitor and Tracker
The implementation splits responsibilities between two types defined in bitchat/Services/BLE/BLEReceivePipeline.swift:
BLERecentTrafficTracker— A struct that storesDatetimestamps in an array, automatically pruning stale entriesBLERecentTrafficMonitor— A class that wraps the tracker withNSLockfor thread-safe access
BLERecentTrafficTracker: Timestamp Storage and Pruning
The tracker manages the actual data. It enforces two limits configured via TransportConfig:
| Limit | Default Purpose |
|---|---|
bleRecentPacketWindowSeconds |
Maximum age of retained timestamps |
bleRecentPacketWindowMaxCount |
Maximum number of timestamps stored |
// BLEReceivePipeline.swift lines 100-128
struct BLERecentTrafficTracker {
private var timestamps: [Date] = []
mutating func recordPacket(at date: Date) {
timestamps.append(date)
pruneOldPackets(now: date)
}
func hasTraffic(within seconds: TimeInterval, now: Date) -> Bool {
let cutoff = now.addingTimeInterval(-seconds)
return timestamps.contains { $0 >= cutoff }
}
private mutating func pruneOldPackets(now: Date) {
let cutoff = now.addingTimeInterval(-TransportConfig.bleRecentPacketWindowSeconds)
timestamps.removeAll { $0 < cutoff }
if timestamps.count > TransportConfig.bleRecentPacketWindowMaxCount {
timestamps.removeFirst(timestamps.count - TransportConfig.bleRecentPacketWindowMaxCount)
}
}
mutating func removeAll() {
timestamps.removeAll()
}
}
The tracker caps memory usage by removing both expired timestamps and excess entries, preventing unbounded growth during traffic bursts.
BLERecentTrafficMonitor: Thread-Safe Wrapper
The monitor serializes access to the tracker using NSLock, allowing concurrent recording from the BLE receive queue and querying from the radio controller queue:
// BLEReceivePipeline.swift lines 80-98
class BLERecentTrafficMonitor {
private var tracker = BLERecentTrafficTracker()
private let lock = NSLock()
func recordPacket(at date: Date) {
lock.lock()
tracker.recordPacket(at: date)
lock.unlock()
}
func hasTraffic(within seconds: TimeInterval, now: Date) -> Bool {
lock.lock()
defer { lock.unlock() }
return tracker.hasTraffic(within: seconds, now: now)
}
func removeAll() {
lock.lock()
tracker.removeAll()
lock.unlock()
}
}
The defer pattern in hasTraffic(within:now:) ensures the lock releases even if an exception occurs, preventing deadlocks.
Integration in the BLE Stack
Instantiation in BLEService
The monitor lives as a singleton property within BLEService, making it accessible to both pipeline and radio components:
// BLEService.swift lines 24-26
class BLEService {
// ...
private let recentTrafficTracker = BLERecentTrafficMonitor()
// ...
var recentTraffic: BLERecentTrafficMonitor { recentTrafficTracker }
}
This design centralizes ownership while exposing read-only access to dependents.
Recording Packets from the Receive Pipeline
After BLEReceivePipeline successfully decodes an inbound BLE frame, it immediately records the event:
// Called within BLEReceivePipeline after packet validation
let arrivalTime = Date()
BLEService.shared.recentTraffic.recordPacket(at: arrivalTime)
This happens on the BLE-specific dispatch queue, so the lock contention is minimal—typically microseconds per call.
Querying Traffic in BLERadioController
The radio controller uses the monitor to adapt its scanning strategy. Before adjusting scan intervals or entering power-saving modes, it checks recent activity:
// Example usage pattern from BLERadioController (implied by architecture)
let isMeshActive = BLEService.shared.recentTraffic.hasTraffic(
within: TransportConfig.bleRecentPacketWindowSeconds,
now: Date()
)
if isMeshActive {
// Maintain aggressive scan parameters—mesh is communicating
setScanInterval(.aggressive)
} else {
// Reduce duty cycle to conserve power
setScanInterval(.powerSave)
scheduleMaintenanceBroadcast()
}
This query pattern runs on the radio controller's queue, safely concurrent with ongoing packet recording.
Configuration and Tuning
The monitor's behavior is controlled through TransportConfig values. Adjusting these changes the trade-off between responsiveness and memory:
| Parameter | Typical Effect When Increased |
|---|---|
bleRecentPacketWindowSeconds |
Longer historical visibility, more memory per burst |
bleRecentPacketWindowMaxCount |
Higher burst tolerance, fixed upper bound on array size |
These values are read at pruning time, so changes take effect on the next recorded packet without requiring monitor restart.
Summary
- BLERecentTrafficMonitor provides thread-safe timestamp tracking for BLE mesh activity in bitchat
- Two-layer design:
BLERecentTrafficTrackerstores data;BLERecentTrafficMonitoraddsNSLockserialization - Automatic pruning enforces age and count limits via
TransportConfigparameters - Cross-queue usage: receive pipeline records packets; radio controller queries for adaptive behavior
- Zero-copy queries: the
hasTraffic(within:now:)method scans timestamps without allocation
Frequently Asked Questions
How does BLERecentTrafficMonitor handle high packet rates?
The tracker limits memory through bleRecentPacketWindowMaxCount. Once the maximum is reached, oldest entries are discarded first. The NSLock acquisition is brief—only long enough to append a Date and prune—so even bursty traffic doesn't create significant contention.
Can the monitor be reset during mesh reconnection?
Yes. Call removeAll() on the monitor to clear all timestamps. This is useful after panic recovery or explicit mesh reconfiguration when historical traffic patterns should not influence new radio decisions.
Why use NSLock instead of actor isolation?
The bitchat codebase predates Swift's structured concurrency (actor was introduced in Swift 5.5). The NSLock implementation maintains compatibility with the existing dispatch queue architecture while providing the necessary exclusion between the BLE and radio queues.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →