# BLERecentTrafficMonitor: How It Monitors Activity in the Mesh Network

> Discover how BLERecentTrafficMonitor tracks BLE packet timestamps to enable adaptive radio behavior in bitchat's mesh network. Learn to monitor recent activity efficiently.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: deep-dive
- Published: 2026-08-04

---

**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`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEReceivePipeline.swift):

- **`BLERecentTrafficTracker`** — A struct that stores `Date` timestamps in an array, automatically pruning stale entries
- **`BLERecentTrafficMonitor`** — A class that wraps the tracker with `NSLock` for 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 |

```swift
// 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:

```swift
// 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:

```swift
// 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:

```swift
// 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:

```swift
// 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**: `BLERecentTrafficTracker` stores data; `BLERecentTrafficMonitor` adds `NSLock` serialization
- **Automatic pruning** enforces age and count limits via `TransportConfig` parameters
- **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.