summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjack <212554440+jackjackbits@users.noreply.github.com>2025-08-12 20:08:37 +0200
committerGitHub <noreply@github.com>2025-08-12 20:08:37 +0200
commitdb3c3b77f59aa7cde7cf8487f8c587817ff38433 (patch)
tree87aac32f72d2889404e19106cfbe02808ab121e9
parent26bcdf72d7b38cf1786185c645210f546a5b342d (diff)
Remove dead store-and-forward and message aggregation code (#438)
- Remove completely unused message aggregation system (100% dead code) - Remove broken store-and-forward implementation that only worked for relayed messages to offline favorites - Update documentation to reflect actual functionality - Net reduction of 312 lines of unmaintained code Co-authored-by: jack <jackjackbits@users.noreply.github.com>
-rw-r--r--AI_CONTEXT.md2
-rw-r--r--README.md2
-rw-r--r--bitchat/Services/BluetoothMeshService.swift303
-rw-r--r--bitchat/Utils/BatteryOptimizer.swift9
4 files changed, 2 insertions, 314 deletions
diff --git a/AI_CONTEXT.md b/AI_CONTEXT.md
index 57ae963..89728e6 100644
--- a/AI_CONTEXT.md
+++ b/AI_CONTEXT.md
@@ -10,7 +10,6 @@ BitChat is a decentralized, peer-to-peer messaging application that works over B
- **Bluetooth Mesh Networking**: Multi-hop message relay over BLE
- **Privacy-First Design**: No accounts, no persistent identifiers
- **End-to-End Encryption**: Uses Noise Protocol Framework for private messages
-- **Store & Forward**: Messages cached for offline peers
- **IRC-Style Commands**: Familiar `/msg`, `/who` interface
- **Cross-Platform**: Native iOS and macOS support
- **Nostr Integration**: Seamless fallback for mutual favorites when out of Bluetooth range
@@ -142,7 +141,6 @@ BitChat is a decentralized, peer-to-peer messaging application that works over B
- **No Long-Term Identifiers**: Enhances privacy and deniability
### 3. Mesh Networking
-- **Store & Forward**: Essential for intermittent connectivity
- **TTL-Based Routing**: Prevents infinite loops in mesh
- **Bloom Filters**: Efficient duplicate detection
diff --git a/README.md b/README.md
index 4b2c319..b080a50 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,6 @@ This project is released into the public domain. See the [LICENSE](LICENSE) file
- **Decentralized Mesh Network**: Automatic peer discovery and multi-hop message relay over Bluetooth LE
- **Privacy First**: No accounts, no phone numbers, no persistent identifiers
- **Private Message End-to-End Encryption**: [Noise Protocol](http://noiseprotocol.org)
-- **Store & Forward**: Messages cached for offline peers and delivered when they reconnect
- **IRC-Style Commands**: Familiar `/slap`, `/msg`, `/who` style interface
- **Universal App**: Native support for iOS and macOS
- **Emergency Wipe**: Triple-tap to instantly clear all data
@@ -41,7 +40,6 @@ bitchat uses an efficient binary protocol optimized for Bluetooth LE:
### Mesh Networking
- Each device acts as both client and peripheral
- Automatic peer discovery and connection management
-- Store-and-forward for offline message delivery
- Adaptive duty cycling for battery optimization
For detailed protocol documentation, see the [Technical Whitepaper](WHITEPAPER.md).
diff --git a/bitchat/Services/BluetoothMeshService.swift b/bitchat/Services/BluetoothMeshService.swift
index 3ae0f6f..d4e9acd 100644
--- a/bitchat/Services/BluetoothMeshService.swift
+++ b/bitchat/Services/BluetoothMeshService.swift
@@ -531,22 +531,9 @@ class BluetoothMeshService: NSObject {
private var peerLastSeenTimestamps = LRUCache<String, Date>(maxSize: 100) // Bounded cache for peer timestamps
private var cleanupTimer: Timer? // Timer to clean up stale peers
- // MARK: - Store-and-Forward Cache
+ // MARK: - Message Tracking
- // Store-and-forward message cache
- private struct StoredMessage {
- let packet: BitchatPacket
- let timestamp: Date
- let messageID: String
- let isForFavorite: Bool // Messages for favorites stored indefinitely
- }
- private var messageCache: [StoredMessage] = []
- private let messageCacheTimeout: TimeInterval = 43200 // 12 hours for regular peers
- private let maxCachedMessages = 100 // For regular peers
- private let maxCachedMessagesForFavorites = 1000 // Much larger cache for favorites
- private var favoriteMessageQueue: [String: [StoredMessage]] = [:] // Per-favorite message queues
private let deliveredMessages = BoundedSet<String>(maxSize: 5000) // Bounded to prevent memory growth
- private var cachedMessagesSentToPeer = Set<String>() // Track which peers have already received cached messages
private let receivedMessageTimestamps = LRUCache<String, Date>(maxSize: 1000) // Bounded cache
private let recentlySentMessages = BoundedSet<String>(maxSize: 500) // Short-term bounded cache
private let lastMessageFromPeer = LRUCache<String, Date>(maxSize: 100) // Bounded cache
@@ -732,13 +719,6 @@ class BluetoothMeshService: NSObject {
private var relayProbability: Double = 1.0 // Start at 100%, decrease with peer count
private let minRelayProbability: Double = 0.4 // Minimum 40% relay chance - ensures coverage
- // MARK: - Message Aggregation
-
- // Message aggregation
- private var pendingMessages: [(message: BitchatPacket, destination: String?)] = []
- private var aggregationTimer: Timer?
- private var aggregationWindow: TimeInterval = 0.1 // 100ms window
- private let maxAggregatedMessages = 5
// MARK: - Bloom Filter
@@ -833,7 +813,6 @@ class BluetoothMeshService: NSObject {
// Global memory limits to prevent unbounded growth
private let maxPendingPrivateMessages = 100
- private let maxCachedMessagesSentToPeer = 1000
private let maxMemoryUsageBytes = 50 * 1024 * 1024 // 50MB limit
private var memoryCleanupTimer: Timer?
@@ -859,69 +838,6 @@ class BluetoothMeshService: NSObject {
private var advertisementData: [String: Any] = [:]
private var isAdvertising = false
- // MARK: - Message Aggregation Implementation
-
- private func startAggregationTimer() {
- aggregationTimer?.invalidate()
- aggregationTimer = Timer.scheduledTimer(withTimeInterval: aggregationWindow, repeats: false) { [weak self] _ in
- self?.flushPendingMessages()
- }
- }
-
- private func flushPendingMessages() {
- guard !pendingMessages.isEmpty else { return }
-
- messageQueue.async { [weak self] in
- guard let self = self else { return }
-
- // Group messages by destination
- var messagesByDestination: [String?: [BitchatPacket]] = [:]
-
- for (message, destination) in self.pendingMessages {
- if messagesByDestination[destination] == nil {
- messagesByDestination[destination] = []
- }
- messagesByDestination[destination]?.append(message)
- }
-
- // Send aggregated messages
- for (destination, messages) in messagesByDestination {
- if messages.count == 1 {
- // Single message, send normally
- if destination == nil {
- self.broadcastPacket(messages[0])
- } else if let dest = destination,
- let peripheral = self.connectedPeripherals[dest],
- let characteristic = self.peripheralCharacteristics[peripheral] {
- if let data = messages[0].toBinaryData() {
- self.writeToPeripheral(data, peripheral: peripheral, characteristic: characteristic, peerID: dest)
- }
- }
- } else {
- // Multiple messages - could aggregate into a single packet
- // For now, send with minimal delay between them
- for (index, message) in messages.enumerated() {
- let delay = Double(index) * 0.02 // 20ms between messages
- DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
- if destination == nil {
- self?.broadcastPacket(message)
- } else if let dest = destination,
- let peripheral = self?.connectedPeripherals[dest],
- peripheral.state == .connected,
- let characteristic = self?.peripheralCharacteristics[peripheral] {
- if let data = message.toBinaryData() {
- self?.writeToPeripheral(data, peripheral: peripheral, characteristic: characteristic, peerID: dest)
- }
- }
- }
- }
- }
- }
-
- // Clear pending messages
- self.pendingMessages.removeAll()
- }
- }
// Removed getPublicKeyFingerprint - no longer needed with Noise
@@ -1671,7 +1587,6 @@ class BluetoothMeshService: NSObject {
scanDutyCycleTimer?.invalidate()
batteryMonitorTimer?.invalidate()
bloomFilterResetTimer?.invalidate()
- aggregationTimer?.invalidate()
cleanupTimer?.invalidate()
rotationTimer?.invalidate()
memoryCleanupTimer?.invalidate()
@@ -2957,186 +2872,6 @@ class BluetoothMeshService: NSObject {
}
}
- // MARK: - Store-and-Forward Methods
-
- private func cacheMessage(_ packet: BitchatPacket, messageID: String) {
- messageQueue.async(flags: .barrier) { [weak self] in
- guard let self = self else { return }
-
- // Don't cache certain message types
- guard packet.type != MessageType.announce.rawValue,
- packet.type != MessageType.leave.rawValue,
- packet.type != MessageType.fragmentStart.rawValue,
- packet.type != MessageType.fragmentContinue.rawValue,
- packet.type != MessageType.fragmentEnd.rawValue else {
- return
- }
-
- // Don't cache broadcast messages
- if let recipientID = packet.recipientID,
- recipientID == SpecialRecipients.broadcast {
- return // Never cache broadcast messages
- }
-
- // Check if this is a private message for a favorite
- var isForFavorite = false
- if packet.type == MessageType.message.rawValue,
- let recipientID = packet.recipientID {
- let recipientPeerID = recipientID.hexEncodedString()
- // Check if recipient is a favorite via their public key fingerprint
- if let fingerprint = self.getPeerFingerprint(recipientPeerID) {
- isForFavorite = self.delegate?.isFavorite(fingerprint: fingerprint) ?? false
- }
- }
-
- // Create stored message with original packet timestamp preserved
- let storedMessage = StoredMessage(
- packet: packet,
- timestamp: Date(timeIntervalSince1970: TimeInterval(packet.timestamp) / 1000.0), // convert from milliseconds
- messageID: messageID,
- isForFavorite: isForFavorite
- )
-
-
- if isForFavorite {
- if let recipientID = packet.recipientID {
- let recipientPeerID = recipientID.hexEncodedString()
- if self.favoriteMessageQueue[recipientPeerID] == nil {
- self.favoriteMessageQueue[recipientPeerID] = []
- }
- self.favoriteMessageQueue[recipientPeerID]?.append(storedMessage)
-
- // Limit favorite queue size
- if let count = self.favoriteMessageQueue[recipientPeerID]?.count,
- count > self.maxCachedMessagesForFavorites {
- self.favoriteMessageQueue[recipientPeerID]?.removeFirst()
- }
-
- }
- } else {
- // Clean up old messages first (only for regular cache)
- self.cleanupMessageCache()
-
- // Add to regular cache
- self.messageCache.append(storedMessage)
-
- // Limit cache size
- if self.messageCache.count > self.maxCachedMessages {
- self.messageCache.removeFirst()
- }
-
- }
- }
- }
-
- private func cleanupMessageCache() {
- let cutoffTime = Date().addingTimeInterval(-messageCacheTimeout)
- // Only remove non-favorite messages that are older than timeout
- messageCache.removeAll { !$0.isForFavorite && $0.timestamp < cutoffTime }
-
- // Clean up delivered messages set periodically (keep recent 1000 entries)
- if deliveredMessages.count > 1000 {
- // Clear older entries while keeping recent ones
- deliveredMessages.removeAll()
- }
- }
-
- private func sendCachedMessages(to peerID: String) {
- messageQueue.async { [weak self] in
- guard let self = self,
- let peripheral = self.connectedPeripherals[peerID],
- let characteristic = self.peripheralCharacteristics[peripheral] else {
- return
- }
-
-
- // Check if we've already sent cached messages to this peer in this session
- if self.cachedMessagesSentToPeer.contains(peerID) {
- return // Already sent cached messages to this peer in this session
- }
-
- // Mark that we're sending cached messages to this peer
- self.cachedMessagesSentToPeer.insert(peerID)
-
- // Clean up old messages first
- self.cleanupMessageCache()
-
- var messagesToSend: [StoredMessage] = []
-
- // First, check if this peer has any favorite messages waiting
- if let favoriteMessages = self.favoriteMessageQueue[peerID] {
- // Filter out already delivered messages
- let undeliveredFavoriteMessages = favoriteMessages.filter { !self.deliveredMessages.contains($0.messageID) }
- messagesToSend.append(contentsOf: undeliveredFavoriteMessages)
- // Clear the favorite queue after adding to send list
- self.favoriteMessageQueue[peerID] = nil
- }
-
- // Filter regular cached messages for this specific recipient
- let recipientMessages = self.messageCache.filter { storedMessage in
- if self.deliveredMessages.contains(storedMessage.messageID) {
- return false
- }
- if let recipientID = storedMessage.packet.recipientID {
- let recipientPeerID = recipientID.hexEncodedString()
- return recipientPeerID == peerID
- }
- return false // Don't forward broadcast messages
- }
- messagesToSend.append(contentsOf: recipientMessages)
-
-
- // Sort messages by timestamp to ensure proper ordering
- messagesToSend.sort { $0.timestamp < $1.timestamp }
-
- if !messagesToSend.isEmpty {
- }
-
- // Mark messages as delivered immediately to prevent duplicates
- let messageIDsToRemove = messagesToSend.map { $0.messageID }
- for messageID in messageIDsToRemove {
- self.deliveredMessages.insert(messageID)
- }
-
- // Send cached messages with slight delay between each
- for (index, storedMessage) in messagesToSend.enumerated() {
- let delay = Double(index) * 0.02 // 20ms between messages for faster sync
-
- DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self, weak peripheral] in
- guard let peripheral = peripheral,
- peripheral.state == .connected else {
- return
- }
-
- // Send the original packet with preserved timestamp
- let packetToSend = storedMessage.packet
-
- if let data = packetToSend.toBinaryData(),
- characteristic.properties.contains(.writeWithoutResponse) {
- self?.writeToPeripheral(data, peripheral: peripheral, characteristic: characteristic, peerID: peerID)
- }
- }
- }
-
- // Remove sent messages immediately
- if !messageIDsToRemove.isEmpty {
- self.messageQueue.async(flags: .barrier) {
- // Remove only the messages we sent to this specific peer
- self.messageCache.removeAll { message in
- messageIDsToRemove.contains(message.messageID)
- }
-
- // Also remove from favorite queue if any
- if var favoriteQueue = self.favoriteMessageQueue[peerID] {
- favoriteQueue.removeAll { message in
- messageIDsToRemove.contains(message.messageID)
- }
- self.favoriteMessageQueue[peerID] = favoriteQueue.isEmpty ? nil : favoriteQueue
- }
- }
- }
- }
- }
private func broadcastPacket(_ packet: BitchatPacket) {
@@ -3670,15 +3405,6 @@ class BluetoothMeshService: NSObject {
var relayPacket = packet
relayPacket.ttl -= 1
- // Check if this message is for an offline favorite and cache it
- let recipientIDString = recipientID.hexEncodedString()
- if let fingerprint = self.getPeerFingerprint(recipientIDString) {
- // Only cache if recipient is a favorite AND is currently offline
- let isActive = self.peerSessions[recipientIDString]?.isActivePeer ?? false
- if (self.delegate?.isFavorite(fingerprint: fingerprint) ?? false) && !isActive {
- self.cacheMessage(relayPacket, messageID: messageID)
- }
- }
// Use constant relay prob
let relayProb = 1.0
@@ -3780,9 +3506,6 @@ class BluetoothMeshService: NSObject {
self.peripheralCharacteristics.removeValue(forKey: peripheral)
}
- // Clear cached messages tracking
- self.cachedMessagesSentToPeer.remove(stalePeerID)
-
// Remove from last seen timestamps
self.peerLastSeenTimestamps.remove(stalePeerID)
@@ -4089,9 +3812,6 @@ class BluetoothMeshService: NSObject {
if let fingerprint = self.getPeerFingerprint(senderID) {
if self.delegate?.isFavorite(fingerprint: fingerprint) ?? false {
NotificationService.shared.sendFavoriteOnlineNotification(nickname: nickname)
-
- // Send any cached messages for this favorite
- self.sendCachedMessages(to: senderID)
}
}
}
@@ -5309,9 +5029,6 @@ extension BluetoothMeshService: CBCentralManagerDelegate {
} else {
}
- // Clear cached messages tracking for this peer to allow re-sending if they reconnect
- cachedMessagesSentToPeer.remove(peerID)
-
// Peer disconnected
@@ -5801,9 +5518,6 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
disconnectLeastImportantPeripherals(keepCount: dynamicMaxConnections)
}
- // Update message aggregation window
- aggregationWindow = powerMode.messageAggregationWindow
-
// If we're currently scanning, restart with new parameters
if scanDutyCycleTimer != nil {
scanDutyCycleTimer?.invalidate()
@@ -5949,7 +5663,7 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
return min(maxMessageDelay, max(minMessageDelay, exponentialDelay))
}
- // Check if message type is high priority (should bypass aggregation)
+ // Check if message type is high priority
private func isHighPriorityMessage(type: UInt8) -> Bool {
switch MessageType(rawValue: type) {
case .noiseHandshakeInit, .noiseHandshakeResp, .protocolAck,
@@ -6091,15 +5805,6 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
category: SecureLogger.session, level: .info)
}
- // Limit cached messages sent to peer
- if self.cachedMessagesSentToPeer.count > self.maxCachedMessagesSentToPeer {
- let excess = self.cachedMessagesSentToPeer.count - self.maxCachedMessagesSentToPeer
- let toRemove = self.cachedMessagesSentToPeer.prefix(excess)
- self.cachedMessagesSentToPeer.subtract(toRemove)
- SecureLogger.log("Removed \(excess) oldest cached message tracking entries",
- category: SecureLogger.session, level: .debug)
- }
-
// Clean up pending relays
self.pendingRelaysLock.lock()
_ = self.pendingRelays.count
@@ -6144,7 +5849,6 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
}
// Other caches
- totalBytes += cachedMessagesSentToPeer.count * 50 // peerID strings
totalBytes += deliveredMessages.count * 100 // messageID strings
totalBytes += recentlySentMessages.count * 100 // messageID strings
@@ -6708,9 +6412,6 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
// Send any pending private messages
self.sendPendingPrivateMessages(to: peerID)
-
- // Send any cached store-and-forward messages
- sendCachedMessages(to: peerID)
}
} catch NoiseSessionError.alreadyEstablished {
// Session already established, ignore handshake
diff --git a/bitchat/Utils/BatteryOptimizer.swift b/bitchat/Utils/BatteryOptimizer.swift
index e88c779..6f2e887 100644
--- a/bitchat/Utils/BatteryOptimizer.swift
+++ b/bitchat/Utils/BatteryOptimizer.swift
@@ -56,15 +56,6 @@ enum PowerMode {
case .ultraLowPower: return 30.0 // Advertise every 30 seconds
}
}
-
- var messageAggregationWindow: TimeInterval {
- switch self {
- case .performance: return 0.05 // 50ms
- case .balanced: return 0.1 // 100ms
- case .powerSaver: return 0.3 // 300ms
- case .ultraLowPower: return 0.5 // 500ms
- }
- }
}
class BatteryOptimizer {