diff --git a/WIFI_DIRECT_PLAN.md b/WIFI_DIRECT_PLAN.md new file mode 100644 index 00000000..9743f7da --- /dev/null +++ b/WIFI_DIRECT_PLAN.md @@ -0,0 +1,202 @@ +# WiFi Direct Integration Plan for BitChat + +## Overview + +WiFi Direct enables peer-to-peer WiFi connections without requiring an access point, offering significantly higher bandwidth and range compared to Bluetooth Low Energy. + +### Key Specifications +- **Range**: 100-200 meters (vs BLE's 10-30m) +- **Speed**: 250+ Mbps (vs BLE's 1-3 Mbps) +- **Power**: Higher consumption than BLE +- **Platform Support**: + - iOS: MultipeerConnectivity framework + - Android: WiFi P2P API + - macOS: Network.framework with Bonjour + +## Alternative Transport Technologies + +### Ultrasonic Communication +- **What**: Uses sound waves above human hearing (>20kHz) to transmit data +- **Range**: 1-10 meters typically +- **Speed**: ~1-10 kbps +- **Pros**: Works through thin walls, no radio interference, very low power +- **Cons**: Limited range, sensitive to noise, low bandwidth +- **Use case**: Secret communication in meetings, data transfer when radio is jammed + +### LoRa (Long Range) +- **What**: Low-power, wide-area network protocol using sub-GHz frequencies +- **Range**: 2-15 km in rural areas, 2-5 km in urban +- **Speed**: 0.3-50 kbps +- **Pros**: Incredible range, very low power, penetrates buildings well +- **Cons**: Very low bandwidth, requires special hardware, regulated frequencies +- **Use case**: Disaster relief, rural communities, sensor networks + +## Architecture Design + +### Transport Protocol Interface + +```swift +protocol TransportProtocol { + var transportType: TransportType { get } + var isAvailable: Bool { get } + var currentPeers: [PeerInfo] { get } + + func startDiscovery() + func stopDiscovery() + func send(_ packet: BitchatPacket, to peer: PeerID?) + func setDelegate(_ delegate: TransportDelegate) +} + +enum TransportType { + case bluetooth + case wifiDirect + case ultrasonic // future + case lora // future +} + +// Transport Manager to coordinate multiple transports +class TransportManager { + private var transports: [TransportProtocol] = [] + private var routingTable: [PeerID: TransportType] = [:] + + func sendOptimal(_ packet: BitchatPacket, to peer: PeerID?) { + // Choose best transport based on: + // 1. Message size + // 2. Battery level + // 3. Available transports + // 4. Peer capabilities + } +} +``` + +## Implementation Phases + +### Phase 1: Abstract Transport Layer +1. Create `TransportProtocol` interface +2. Refactor `BluetoothMeshService` to implement protocol +3. Create `TransportManager` to coordinate transports +4. Update `ChatViewModel` to use transport abstraction + +### Phase 2: WiFi Direct Transport +1. Create `WiFiDirectTransport` class +2. iOS: Use MultipeerConnectivity framework +3. macOS: Use Network.framework with Bonjour +4. Handle transport handoff (BLE → WiFi when available) + +### Phase 3: Intelligent Routing +1. Implement bandwidth detection +2. Create routing algorithm: + - Small messages (< 1KB): Use BLE (lower power) + - Large messages/files: Use WiFi Direct + - Emergency/broadcast: Use all transports +3. Add transport negotiation protocol + +### Phase 4: Advanced Features +1. File transfer with resumption +2. Video/audio streaming support +3. Hybrid mesh (some nodes BLE-only, some WiFi-capable) +4. Transport bonding (use multiple simultaneously) + +## Key Considerations + +### Battery Impact +- WiFi Direct uses significantly more power than BLE +- Only activate when: + - Large file transfer needed + - User explicitly enables + - Device is charging + - Battery > 50% + +### Discovery Strategy +- Use BLE for initial discovery (low power) +- Exchange WiFi Direct capabilities +- Establish WiFi Direct only when needed +- Fall back to BLE if WiFi fails + +### Security +- Use same encryption (X25519 + AES-256-GCM) +- Pin WiFi Direct connections with BLE-exchanged keys +- Prevent WiFi Direct spoofing attacks + +### User Experience +- Automatic transport selection +- Visual indicator showing active transport +- Manual override option +- Seamless handoff between transports + +## Proposed File Structure + +``` +bitchat/ +├── Transports/ +│ ├── TransportProtocol.swift +│ ├── TransportManager.swift +│ ├── BluetoothTransport.swift (refactored from BluetoothMeshService) +│ ├── WiFiDirectTransport.swift (new) +│ └── TransportDelegate.swift +├── Services/ +│ └── RoutingService.swift (intelligent message routing) +``` + +## Benefits + +1. **10-100x faster** file transfers +2. **Longer range** for fixed installations +3. **Video chat** capability +4. **Backwards compatible** (BLE-only devices still work) +5. **Future-proof** (easy to add more transports) + +## Implementation Notes + +### iOS MultipeerConnectivity Example + +```swift +import MultipeerConnectivity + +class WiFiDirectTransport: NSObject, TransportProtocol { + private let serviceType = "bitchat-wifi" + private var peerID: MCPeerID + private var session: MCSession + private var advertiser: MCNearbyServiceAdvertiser + private var browser: MCNearbyServiceBrowser + + func startDiscovery() { + advertiser.startAdvertisingPeer() + browser.startBrowsingForPeers() + } +} +``` + +### Message Size Routing Logic + +```swift +func selectTransport(for message: Data) -> TransportType { + let size = message.count + let batteryLevel = BatteryOptimizer.shared.batteryLevel + + if size > 10_000 && batteryLevel > 0.5 { + return .wifiDirect + } else if size < 1_000 || batteryLevel < 0.3 { + return .bluetooth + } else { + // Medium size, good battery - use faster if available + return wifiAvailable ? .wifiDirect : .bluetooth + } +} +``` + +## Testing Strategy + +1. **Unit Tests**: Mock transport implementations +2. **Integration Tests**: BLE + WiFi handoff scenarios +3. **Performance Tests**: Throughput comparison +4. **Battery Tests**: Power consumption analysis +5. **Field Tests**: Real-world range and reliability + +## Future Considerations + +- **Transport Plugins**: Allow third-party transport implementations +- **SDN Integration**: Software-defined networking for complex topologies +- **QoS**: Quality of Service for different message types +- **Compression**: Different algorithms per transport +- **Multi-path**: Send redundant copies over multiple transports \ No newline at end of file diff --git a/bitchat/Protocols/BinaryProtocol.swift b/bitchat/Protocols/BinaryProtocol.swift index 3feb0fdd..a19e33c2 100644 --- a/bitchat/Protocols/BinaryProtocol.swift +++ b/bitchat/Protocols/BinaryProtocol.swift @@ -42,12 +42,26 @@ struct BinaryProtocol { struct Flags { static let hasRecipient: UInt8 = 0x01 static let hasSignature: UInt8 = 0x02 + static let isCompressed: UInt8 = 0x04 } // Encode BitchatPacket to binary format static func encode(_ packet: BitchatPacket) -> Data? { var data = Data() + // Try to compress payload if beneficial + var payload = packet.payload + var originalPayloadSize: UInt16? = nil + var isCompressed = false + + if CompressionUtil.shouldCompress(payload), + let compressedPayload = CompressionUtil.compress(payload) { + // Store original size for decompression (2 bytes after payload) + originalPayloadSize = UInt16(payload.count) + payload = compressedPayload + isCompressed = true + } + // Header data.append(packet.version) data.append(packet.type) @@ -66,10 +80,14 @@ struct BinaryProtocol { if packet.signature != nil { flags |= Flags.hasSignature } + if isCompressed { + flags |= Flags.isCompressed + } data.append(flags) - // Payload length (2 bytes, big-endian) - let payloadLength = UInt16(packet.payload.count) + // Payload length (2 bytes, big-endian) - includes original size if compressed + let payloadDataSize = payload.count + (isCompressed ? 2 : 0) + let payloadLength = UInt16(payloadDataSize) data.append(UInt8((payloadLength >> 8) & 0xFF)) data.append(UInt8(payloadLength & 0xFF)) @@ -89,8 +107,13 @@ struct BinaryProtocol { } } - // Payload - data.append(packet.payload) + // Payload (with original size prepended if compressed) + if isCompressed, let originalSize = originalPayloadSize { + // Prepend original size (2 bytes, big-endian) + data.append(UInt8((originalSize >> 8) & 0xFF)) + data.append(UInt8(originalSize & 0xFF)) + } + data.append(payload) // Signature (if present) if let signature = packet.signature { @@ -124,6 +147,7 @@ struct BinaryProtocol { let flags = data[offset]; offset += 1 let hasRecipient = (flags & Flags.hasRecipient) != 0 let hasSignature = (flags & Flags.hasSignature) != 0 + let isCompressed = (flags & Flags.isCompressed) != 0 // Payload length let payloadLengthData = data[offset..= 2 else { return nil } + let originalSizeData = data[offset.. = [] // Track which peers have already received cached messages private var receivedMessageTimestamps: [String: Date] = [:] // Track timestamps of received messages for debugging private var recentlySentMessages: Set = [] // Short-term cache to prevent any duplicate sends + private var lastMessageFromPeer: [String: Date] = [:] // Track last message time from each peer for connection prioritization // Battery and range optimizations private var scanDutyCycleTimer: Timer? @@ -84,6 +85,10 @@ class BluetoothMeshService: NSObject { private var batteryMonitorTimer: Timer? private var currentBatteryLevel: Float = 1.0 // Default to full battery + // Battery optimizer integration + private let batteryOptimizer = BatteryOptimizer.shared + private var batteryOptimizerCancellables = Set() + // Peer list update debouncing private var peerListUpdateTimer: Timer? private let peerListUpdateDebounceInterval: TimeInterval = 0.1 // 100ms debounce for more responsive updates @@ -123,40 +128,8 @@ class BluetoothMeshService: NSObject { private let aggregationWindow: TimeInterval = 0.1 // 100ms window private let maxAggregatedMessages = 5 - // Bloom filter for efficient duplicate detection - private struct BloomFilter { - private var bitArray: [Bool] - private let size: Int = 4096 // 512 bytes - private let hashCount = 3 - - init() { - bitArray = Array(repeating: false, count: size) - } - - mutating func insert(_ item: String) { - for i in 0.. Bool { - for i in 0.. 1000 { processedMessages.removeAll() @@ -2509,84 +2499,117 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate { // MARK: - Battery Monitoring - private func startBatteryMonitoring() { - // Update battery level immediately - updateBatteryLevel() - - // Monitor battery level every 30 seconds - batteryMonitorTimer = Timer.scheduledTimer(withTimeInterval: 30.0, repeats: true) { [weak self] _ in - self?.updateBatteryLevel() - } - } - - private func updateBatteryLevel() { - #if os(iOS) - UIDevice.current.isBatteryMonitoringEnabled = true - currentBatteryLevel = UIDevice.current.batteryLevel - - // Battery level is -1 when unknown (e.g., in simulator) - if currentBatteryLevel < 0 { - currentBatteryLevel = 1.0 // Assume full battery when unknown - } - #else - // macOS battery monitoring - if let batteryInfo = getMacOSBatteryInfo() { - currentBatteryLevel = batteryInfo - } else { - currentBatteryLevel = 1.0 // Assume full battery when unknown - } - #endif - - updateScanParametersForBattery() - } - - #if os(macOS) - private func getMacOSBatteryInfo() -> Float? { - let snapshot = IOPSCopyPowerSourcesInfo().takeRetainedValue() - let sources = IOPSCopyPowerSourcesList(snapshot).takeRetainedValue() as Array - - for source in sources { - if let description = IOPSGetPowerSourceDescription(snapshot, source).takeUnretainedValue() as? [String: Any] { - if let currentCapacity = description[kIOPSCurrentCapacityKey] as? Int, - let maxCapacity = description[kIOPSMaxCapacityKey] as? Int { - return Float(currentCapacity) / Float(maxCapacity) - } + private func setupBatteryOptimizer() { + // Subscribe to power mode changes + batteryOptimizer.$currentPowerMode + .sink { [weak self] powerMode in + self?.handlePowerModeChange(powerMode) } - } - return nil + .store(in: &batteryOptimizerCancellables) + + // Subscribe to battery level changes + batteryOptimizer.$batteryLevel + .sink { [weak self] level in + self?.currentBatteryLevel = level + } + .store(in: &batteryOptimizerCancellables) + + // Initial update + handlePowerModeChange(batteryOptimizer.currentPowerMode) } - #endif - private func updateScanParametersForBattery() { - // Adaptive scanning based on battery level - // High battery (80%+): Normal scanning - // Medium battery (40-80%): Moderate power saving - // Low battery (20-40%): Aggressive power saving - // Critical battery (<20%): Maximum power saving + private func handlePowerModeChange(_ powerMode: PowerMode) { + let params = batteryOptimizer.scanParameters + activeScanDuration = params.duration + scanPauseDuration = params.pause - if currentBatteryLevel > 0.8 { - // High battery: Normal operation - activeScanDuration = 2.0 - scanPauseDuration = 3.0 - } else if currentBatteryLevel > 0.4 { - // Medium battery: Moderate power saving - activeScanDuration = 1.5 - scanPauseDuration = 4.5 - } else if currentBatteryLevel > 0.2 { - // Low battery: Aggressive power saving - activeScanDuration = 1.0 - scanPauseDuration = 8.0 - } else { - // Critical battery: Maximum power saving - activeScanDuration = 0.5 - scanPauseDuration = 15.0 + // Update max connections + let maxConnections = powerMode.maxConnections + + // If we have too many connections, disconnect from the least important ones + if connectedPeripherals.count > maxConnections { + disconnectLeastImportantPeripherals(keepCount: maxConnections) } - // If we're currently in a duty cycle, restart it with new parameters + // Update message aggregation window + aggregationWindow = powerMode.messageAggregationWindow + + // If we're currently scanning, restart with new parameters if scanDutyCycleTimer != nil { scanDutyCycleTimer?.invalidate() scheduleScanDutyCycle() } + + // Handle advertising intervals + if powerMode.advertisingInterval > 0 { + // Stop continuous advertising and use interval-based + scheduleAdvertisingCycle(interval: powerMode.advertisingInterval) + } else { + // Continuous advertising for performance mode + startAdvertisingIfNeeded() + } + } + + private func disconnectLeastImportantPeripherals(keepCount: Int) { + // Disconnect peripherals with lowest activity/importance + let sortedPeripherals = connectedPeripherals.values + .sorted { peer1, peer2 in + // Keep peripherals we've recently communicated with + let peer1Activity = lastMessageFromPeer[peer1.identifier.uuidString] ?? Date.distantPast + let peer2Activity = lastMessageFromPeer[peer2.identifier.uuidString] ?? Date.distantPast + return peer1Activity > peer2Activity + } + + // Disconnect the least active ones + let toDisconnect = sortedPeripherals.dropFirst(keepCount) + for peripheral in toDisconnect { + centralManager.cancelPeripheralConnection(peripheral) + } + } + + private var advertisingTimer: Timer? + + private func scheduleAdvertisingCycle(interval: TimeInterval) { + advertisingTimer?.invalidate() + + // Stop advertising + if isAdvertising { + peripheralManager.stopAdvertising() + isAdvertising = false + } + + // Schedule next advertising burst + advertisingTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in + self?.advertiseBurst() + } + } + + private func advertiseBurst() { + guard batteryOptimizer.currentPowerMode != .ultraLowPower || !batteryOptimizer.isInBackground else { + return // Skip advertising in ultra low power + background + } + + startAdvertisingIfNeeded() + + // Stop advertising after a short burst (1 second) + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in + if self?.batteryOptimizer.currentPowerMode.advertisingInterval ?? 0 > 0 { + self?.peripheralManager.stopAdvertising() + self?.isAdvertising = false + } + } + } + + // Legacy battery monitoring methods - kept for compatibility + // Now handled by BatteryOptimizer + private func updateBatteryLevel() { + // This method is now handled by BatteryOptimizer + // Keeping empty implementation for compatibility + } + + private func updateScanParametersForBattery() { + // This method is now handled by BatteryOptimizer through handlePowerModeChange + // Keeping empty implementation for compatibility } // MARK: - Privacy Utilities diff --git a/bitchat/Utils/BatteryOptimizer.swift b/bitchat/Utils/BatteryOptimizer.swift new file mode 100644 index 00000000..50a5394e --- /dev/null +++ b/bitchat/Utils/BatteryOptimizer.swift @@ -0,0 +1,227 @@ +// +// BatteryOptimizer.swift +// bitchat +// +// This is free and unencumbered software released into the public domain. +// For more information, see +// + +import Foundation +import UIKit +#if os(macOS) +import IOKit.ps +#endif + +enum PowerMode { + case performance // Max performance, battery drain OK + case balanced // Default balanced mode + case powerSaver // Aggressive power saving + case ultraLowPower // Emergency mode + + var scanDuration: TimeInterval { + switch self { + case .performance: return 3.0 + case .balanced: return 2.0 + case .powerSaver: return 1.0 + case .ultraLowPower: return 0.5 + } + } + + var scanPauseDuration: TimeInterval { + switch self { + case .performance: return 2.0 + case .balanced: return 3.0 + case .powerSaver: return 8.0 + case .ultraLowPower: return 20.0 + } + } + + var maxConnections: Int { + switch self { + case .performance: return 20 + case .balanced: return 10 + case .powerSaver: return 5 + case .ultraLowPower: return 2 + } + } + + var advertisingInterval: TimeInterval { + // Note: iOS doesn't let us control this directly, but we can stop/start advertising + switch self { + case .performance: return 0.0 // Continuous + case .balanced: return 5.0 // Advertise every 5 seconds + case .powerSaver: return 15.0 // Advertise every 15 seconds + 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 { + static let shared = BatteryOptimizer() + + @Published var currentPowerMode: PowerMode = .balanced + @Published var isInBackground: Bool = false + @Published var batteryLevel: Float = 1.0 + @Published var isCharging: Bool = false + + private var observers: [NSObjectProtocol] = [] + + private init() { + setupObservers() + updateBatteryStatus() + } + + deinit { + observers.forEach { NotificationCenter.default.removeObserver($0) } + } + + private func setupObservers() { + #if os(iOS) + // Monitor app state + observers.append( + NotificationCenter.default.addObserver( + forName: UIApplication.didEnterBackgroundNotification, + object: nil, + queue: .main + ) { [weak self] _ in + self?.isInBackground = true + self?.updatePowerMode() + } + ) + + observers.append( + NotificationCenter.default.addObserver( + forName: UIApplication.willEnterForegroundNotification, + object: nil, + queue: .main + ) { [weak self] _ in + self?.isInBackground = false + self?.updatePowerMode() + } + ) + + // Monitor battery + UIDevice.current.isBatteryMonitoringEnabled = true + + observers.append( + NotificationCenter.default.addObserver( + forName: UIDevice.batteryLevelDidChangeNotification, + object: nil, + queue: .main + ) { [weak self] _ in + self?.updateBatteryStatus() + } + ) + + observers.append( + NotificationCenter.default.addObserver( + forName: UIDevice.batteryStateDidChangeNotification, + object: nil, + queue: .main + ) { [weak self] _ in + self?.updateBatteryStatus() + } + ) + #endif + } + + private func updateBatteryStatus() { + #if os(iOS) + batteryLevel = UIDevice.current.batteryLevel + if batteryLevel < 0 { + batteryLevel = 1.0 // Unknown battery level + } + + isCharging = UIDevice.current.batteryState == .charging || + UIDevice.current.batteryState == .full + #elseif os(macOS) + if let info = getMacOSBatteryInfo() { + batteryLevel = info.level + isCharging = info.isCharging + } + #endif + + updatePowerMode() + } + + #if os(macOS) + private func getMacOSBatteryInfo() -> (level: Float, isCharging: Bool)? { + let snapshot = IOPSCopyPowerSourcesInfo().takeRetainedValue() + let sources = IOPSCopyPowerSourcesList(snapshot).takeRetainedValue() as Array + + for source in sources { + if let description = IOPSGetPowerSourceDescription(snapshot, source).takeUnretainedValue() as? [String: Any] { + if let currentCapacity = description[kIOPSCurrentCapacityKey] as? Int, + let maxCapacity = description[kIOPSMaxCapacityKey] as? Int { + let level = Float(currentCapacity) / Float(maxCapacity) + let isCharging = description[kIOPSPowerSourceStateKey] as? String == kIOPSACPowerValue + return (level, isCharging) + } + } + } + return nil + } + #endif + + private func updatePowerMode() { + // Determine optimal power mode based on: + // 1. Battery level + // 2. Charging status + // 3. Background/foreground state + + if isCharging { + // When charging, use performance mode unless battery is critical + currentPowerMode = batteryLevel < 0.1 ? .balanced : .performance + } else if isInBackground { + // In background, always use power saving + if batteryLevel < 0.2 { + currentPowerMode = .ultraLowPower + } else if batteryLevel < 0.5 { + currentPowerMode = .powerSaver + } else { + currentPowerMode = .balanced + } + } else { + // Foreground, not charging + if batteryLevel < 0.1 { + currentPowerMode = .ultraLowPower + } else if batteryLevel < 0.3 { + currentPowerMode = .powerSaver + } else if batteryLevel < 0.6 { + currentPowerMode = .balanced + } else { + currentPowerMode = .performance + } + } + } + + // Manual power mode override + func setPowerMode(_ mode: PowerMode) { + currentPowerMode = mode + } + + // Get current scan parameters + var scanParameters: (duration: TimeInterval, pause: TimeInterval) { + return (currentPowerMode.scanDuration, currentPowerMode.scanPauseDuration) + } + + // Should we skip non-essential operations? + var shouldSkipNonEssential: Bool { + return currentPowerMode == .ultraLowPower || + (currentPowerMode == .powerSaver && isInBackground) + } + + // Should we reduce message frequency? + var shouldThrottleMessages: Bool { + return currentPowerMode == .powerSaver || currentPowerMode == .ultraLowPower + } +} \ No newline at end of file diff --git a/bitchat/Utils/CompressionUtil.swift b/bitchat/Utils/CompressionUtil.swift new file mode 100644 index 00000000..e112e977 --- /dev/null +++ b/bitchat/Utils/CompressionUtil.swift @@ -0,0 +1,74 @@ +// +// CompressionUtil.swift +// bitchat +// +// This is free and unencumbered software released into the public domain. +// For more information, see +// + +import Foundation +import Compression + +struct CompressionUtil { + // Compression threshold - don't compress if data is smaller than this + static let compressionThreshold = 100 // bytes + + // Compress data using LZ4 algorithm (fast compression/decompression) + static func compress(_ data: Data) -> Data? { + // Skip compression for small data + guard data.count >= compressionThreshold else { return nil } + + let destinationBuffer = UnsafeMutablePointer.allocate(capacity: data.count) + defer { destinationBuffer.deallocate() } + + let compressedSize = data.withUnsafeBytes { sourceBuffer in + guard let sourcePtr = sourceBuffer.bindMemory(to: UInt8.self).baseAddress else { return 0 } + return compression_encode_buffer( + destinationBuffer, data.count, + sourcePtr, data.count, + nil, COMPRESSION_LZ4 + ) + } + + guard compressedSize > 0 && compressedSize < data.count else { return nil } + + return Data(bytes: destinationBuffer, count: compressedSize) + } + + // Decompress LZ4 compressed data + static func decompress(_ compressedData: Data, originalSize: Int) -> Data? { + let destinationBuffer = UnsafeMutablePointer.allocate(capacity: originalSize) + defer { destinationBuffer.deallocate() } + + let decompressedSize = compressedData.withUnsafeBytes { sourceBuffer in + guard let sourcePtr = sourceBuffer.bindMemory(to: UInt8.self).baseAddress else { return 0 } + return compression_decode_buffer( + destinationBuffer, originalSize, + sourcePtr, compressedData.count, + nil, COMPRESSION_LZ4 + ) + } + + guard decompressedSize > 0 else { return nil } + + return Data(bytes: destinationBuffer, count: decompressedSize) + } + + // Helper to check if compression is worth it + static func shouldCompress(_ data: Data) -> Bool { + // Don't compress if: + // 1. Data is too small + // 2. Data appears to be already compressed (high entropy) + guard data.count >= compressionThreshold else { return false } + + // Simple entropy check - count unique bytes + var byteFrequency = [UInt8: Int]() + for byte in data { + byteFrequency[byte, default: 0] += 1 + } + + // If we have very high byte diversity, data is likely already compressed + let uniqueByteRatio = Double(byteFrequency.count) / Double(min(data.count, 256)) + return uniqueByteRatio < 0.9 // Compress if less than 90% unique bytes + } +} \ No newline at end of file diff --git a/bitchat/Utils/OptimizedBloomFilter.swift b/bitchat/Utils/OptimizedBloomFilter.swift new file mode 100644 index 00000000..0cfebeb8 --- /dev/null +++ b/bitchat/Utils/OptimizedBloomFilter.swift @@ -0,0 +1,141 @@ +// +// OptimizedBloomFilter.swift +// bitchat +// +// This is free and unencumbered software released into the public domain. +// For more information, see +// + +import Foundation +import CryptoKit + +/// Optimized Bloom filter using bit-packed storage and better hash functions +struct OptimizedBloomFilter { + private var bitArray: [UInt64] + private let bitCount: Int + private let hashCount: Int + + // Statistics + private(set) var insertCount: Int = 0 + + init(expectedItems: Int = 1000, falsePositiveRate: Double = 0.01) { + // Calculate optimal bit count and hash count + let m = Double(expectedItems) * abs(log(falsePositiveRate)) / (log(2) * log(2)) + self.bitCount = Int(max(64, m.rounded())) + + let k = Double(bitCount) / Double(expectedItems) * log(2) + self.hashCount = Int(max(1, min(10, k.rounded()))) + + // Initialize bit array (64 bits per UInt64) + let arraySize = (bitCount + 63) / 64 + self.bitArray = Array(repeating: 0, count: arraySize) + } + + mutating func insert(_ item: String) { + let hashes = generateHashes(item) + + for i in 0.. Bool { + let hashes = generateHashes(item) + + for i in 0.. [Int] { + guard let data = item.data(using: .utf8) else { + return Array(repeating: 0, count: hashCount) + } + + // Use SHA256 for high-quality hash values + let hash = SHA256.hash(data: data) + let hashBytes = Array(hash) + + var hashes = [Int]() + + // Extract multiple hash values from the SHA256 output + for i in 0.. 0 else { return 0 } + + // Count set bits + var setBits = 0 + for value in bitArray { + setBits += value.nonzeroBitCount + } + + // Calculate probability: (1 - e^(-kn/m))^k + let ratio = Double(hashCount * insertCount) / Double(bitCount) + return pow(1 - exp(-ratio), Double(hashCount)) + } + + // Get memory usage in bytes + var memorySizeBytes: Int { + return bitArray.count * 8 + } +} + +// Extension for adaptive Bloom filter that adjusts based on network size +extension OptimizedBloomFilter { + static func adaptive(for networkSize: Int) -> OptimizedBloomFilter { + // Adjust parameters based on network size + let expectedItems: Int + let falsePositiveRate: Double + + switch networkSize { + case 0..<50: + expectedItems = 500 + falsePositiveRate = 0.01 + case 50..<200: + expectedItems = 2000 + falsePositiveRate = 0.02 + case 200..<500: + expectedItems = 5000 + falsePositiveRate = 0.03 + default: + expectedItems = 10000 + falsePositiveRate = 0.05 + } + + return OptimizedBloomFilter(expectedItems: expectedItems, falsePositiveRate: falsePositiveRate) + } +} \ No newline at end of file diff --git a/bitchatTests/BloomFilterTests.swift.disabled b/bitchatTests/BloomFilterTests.swift similarity index 59% rename from bitchatTests/BloomFilterTests.swift.disabled rename to bitchatTests/BloomFilterTests.swift index 9e79475c..11cbade7 100644 --- a/bitchatTests/BloomFilterTests.swift.disabled +++ b/bitchatTests/BloomFilterTests.swift @@ -12,7 +12,7 @@ import XCTest class BloomFilterTests: XCTestCase { func testBasicBloomFilter() { - let filter = BloomFilter(size: 1024, hashCount: 3) + var filter = OptimizedBloomFilter(expectedItems: 100, falsePositiveRate: 0.01) // Test insertion and lookup let testStrings = ["message1", "message2", "message3", "test123"] @@ -25,7 +25,7 @@ class BloomFilterTests: XCTestCase { } func testFalsePositiveRate() { - let filter = BloomFilter(size: 4096, hashCount: 3) + var filter = OptimizedBloomFilter(expectedItems: 100, falsePositiveRate: 0.01) let itemCount = 100 // Insert items @@ -45,13 +45,12 @@ class BloomFilterTests: XCTestCase { let falsePositiveRate = Double(falsePositives) / Double(testCount) - // With 4096 bits and 3 hash functions, for 100 items, - // false positive rate should be around 0.05% (very low) - XCTAssertLessThan(falsePositiveRate, 0.05) + // With optimized bloom filter targeting 1% false positive rate + XCTAssertLessThan(falsePositiveRate, 0.02) // Allow some margin } func testReset() { - let filter = BloomFilter(size: 1024, hashCount: 3) + var filter = OptimizedBloomFilter(expectedItems: 100, falsePositiveRate: 0.01) // Insert some items filter.insert("test1") @@ -72,26 +71,31 @@ class BloomFilterTests: XCTestCase { } func testHashDistribution() { - let filter = BloomFilter(size: 4096, hashCount: 3) + var filter = OptimizedBloomFilter(expectedItems: 1000, falsePositiveRate: 0.01) - // Insert many items and check bit distribution + // Insert many items for i in 0..<500 { filter.insert("message-\(i)") } - // Count set bits - var setBits = 0 - for i in 0..