|
|
|
@@ -99,7 +99,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
self.myPeerID = randomBytes.map { String(format: "%02x", $0) }.joined()
|
|
|
|
|
|
|
|
|
|
super.init()
|
|
|
|
|
print("[STARTUP] Generated ephemeral peer ID: \(myPeerID)")
|
|
|
|
|
// Generated ephemeral peer ID
|
|
|
|
|
|
|
|
|
|
centralManager = CBCentralManager(delegate: self, queue: nil)
|
|
|
|
|
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
|
|
|
|
@@ -200,7 +200,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
payload: Data(vm.nickname.utf8)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
print("[ANNOUNCE] Sending proactive broadcast announce with nickname: \(vm.nickname)")
|
|
|
|
|
// Sending proactive broadcast announce
|
|
|
|
|
|
|
|
|
|
// Initial send with random delay
|
|
|
|
|
let initialDelay = self.randomDelay()
|
|
|
|
@@ -326,7 +326,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
let signature: Data?
|
|
|
|
|
do {
|
|
|
|
|
signature = try self.encryptionService.sign(messageData)
|
|
|
|
|
print("[CRYPTO] Successfully signed broadcast message")
|
|
|
|
|
// Successfully signed broadcast message
|
|
|
|
|
} catch {
|
|
|
|
|
print("[CRYPTO] Failed to sign message: \(error)")
|
|
|
|
|
signature = nil
|
|
|
|
@@ -347,7 +347,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
let initialDelay = self.randomDelay()
|
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + initialDelay) { [weak self] in
|
|
|
|
|
self?.broadcastPacket(packet)
|
|
|
|
|
print("[MESSAGE] Sending: \(content) (delayed by \(Int(initialDelay * 1000))ms)")
|
|
|
|
|
// Sending message
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Retry with randomized delays for reliability
|
|
|
|
@@ -386,13 +386,13 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
// Pad message to standard block size for privacy
|
|
|
|
|
let blockSize = MessagePadding.optimalBlockSize(for: messageData.count)
|
|
|
|
|
let paddedData = MessagePadding.pad(messageData, toSize: blockSize)
|
|
|
|
|
print("[PRIVACY] Padded message from \(messageData.count) to \(paddedData.count) bytes")
|
|
|
|
|
// Padded message for privacy
|
|
|
|
|
|
|
|
|
|
// Encrypt the padded message for the recipient
|
|
|
|
|
let encryptedPayload: Data
|
|
|
|
|
do {
|
|
|
|
|
encryptedPayload = try self.encryptionService.encrypt(paddedData, for: recipientPeerID)
|
|
|
|
|
print("[CRYPTO] Successfully encrypted private message for \(recipientPeerID)")
|
|
|
|
|
// Successfully encrypted private message
|
|
|
|
|
} catch {
|
|
|
|
|
print("[CRYPTO] Failed to encrypt private message: \(error)")
|
|
|
|
|
// Don't send unencrypted private messages
|
|
|
|
@@ -403,7 +403,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
let signature: Data?
|
|
|
|
|
do {
|
|
|
|
|
signature = try self.encryptionService.sign(encryptedPayload)
|
|
|
|
|
print("[CRYPTO] Successfully signed private message")
|
|
|
|
|
// Successfully signed private message
|
|
|
|
|
} catch {
|
|
|
|
|
print("[CRYPTO] Failed to sign private message: \(error)")
|
|
|
|
|
signature = nil
|
|
|
|
@@ -420,13 +420,13 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
ttl: self.maxTTL
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
print("[PRIVATE] Sending encrypted message to \(recipientPeerID): \(content)")
|
|
|
|
|
// Sending encrypted private message
|
|
|
|
|
|
|
|
|
|
// Add random delay for timing obfuscation
|
|
|
|
|
let delay = self.randomDelay()
|
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
|
|
|
|
self?.broadcastPacket(packet)
|
|
|
|
|
print("[PRIVACY] Private message sent with \(Int(delay * 1000))ms delay")
|
|
|
|
|
// Private message sent with timing delay
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Don't call didReceiveMessage here - let the view model handle it directly
|
|
|
|
@@ -437,7 +437,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
private func sendAnnouncementToPeer(_ peerID: String) {
|
|
|
|
|
guard let vm = delegate as? ChatViewModel else { return }
|
|
|
|
|
|
|
|
|
|
print("[ANNOUNCE] Sending announce to \(peerID) with nickname: \(vm.nickname)")
|
|
|
|
|
// Sending announce to peer
|
|
|
|
|
|
|
|
|
|
// Always send announce, don't check if already announced
|
|
|
|
|
// This ensures peers get our nickname even if they reconnect
|
|
|
|
@@ -450,20 +450,20 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if let data = packet.toBinaryData() {
|
|
|
|
|
print("[ANNOUNCE] Broadcasting announce packet")
|
|
|
|
|
// Broadcasting announce packet
|
|
|
|
|
// Try both broadcast and targeted send
|
|
|
|
|
broadcastPacket(packet)
|
|
|
|
|
|
|
|
|
|
// Also try targeted send if we have the peripheral
|
|
|
|
|
if let peripheral = connectedPeripherals[peerID],
|
|
|
|
|
let characteristic = peripheral.services?.first(where: { $0.uuid == BluetoothMeshService.serviceUUID })?.characteristics?.first(where: { $0.uuid == BluetoothMeshService.characteristicUUID }) {
|
|
|
|
|
print("[ANNOUNCE] Also sending targeted announce to peripheral \(peerID)")
|
|
|
|
|
// Also sending targeted announce
|
|
|
|
|
peripheral.writeValue(data, for: characteristic, type: .withResponse)
|
|
|
|
|
} else {
|
|
|
|
|
print("[ANNOUNCE] No peripheral found for targeted send to \(peerID)")
|
|
|
|
|
// No peripheral found for targeted send
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
print("[ANNOUNCE] Failed to create binary data for announce packet")
|
|
|
|
|
// Failed to create binary data for announce packet
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
announcedToPeers.insert(peerID)
|
|
|
|
@@ -597,7 +597,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
self.favoriteMessageQueue[recipientPeerID]?.removeFirst()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
print("[CACHE] Cached message for favorite \(recipientPeerID), queue size: \(self.favoriteMessageQueue[recipientPeerID]?.count ?? 0)")
|
|
|
|
|
// Cached message for favorite
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Clean up old messages first (only for regular cache)
|
|
|
|
@@ -611,7 +611,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
self.messageCache.removeFirst()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
print("[CACHE] Cached message (type: \(packet.type)), cache size: \(self.messageCache.count)")
|
|
|
|
|
// Cached message
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -640,13 +640,13 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
messagesToSend.append(contentsOf: favoriteMessages)
|
|
|
|
|
// Clear the favorite queue after adding to send list
|
|
|
|
|
self.favoriteMessageQueue[peerID] = nil
|
|
|
|
|
print("[CACHE] Found \(favoriteMessages.count) favorite messages for \(peerID)")
|
|
|
|
|
// Found favorite messages
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Then add regular cached messages
|
|
|
|
|
messagesToSend.append(contentsOf: self.messageCache)
|
|
|
|
|
|
|
|
|
|
print("[CACHE] Sending \(messagesToSend.count) total cached messages to \(peerID)")
|
|
|
|
|
// Sending cached messages
|
|
|
|
|
|
|
|
|
|
// Send cached messages with slight delay between each
|
|
|
|
|
for (index, storedMessage) in messagesToSend.enumerated() {
|
|
|
|
@@ -669,7 +669,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
|
|
|
|
|
if let data = updatedPacket.toBinaryData() {
|
|
|
|
|
peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
|
|
|
|
|
print("[CACHE] Sent cached message \(index + 1)/\(messagesToSend.count) to \(peerID)")
|
|
|
|
|
// Sent cached message
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -709,7 +709,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
peripheral.writeValue(data, for: characteristic, type: writeType)
|
|
|
|
|
sentToPeripherals += 1
|
|
|
|
|
} else {
|
|
|
|
|
print("[BROADCAST] Peripheral \(peerID) not connected (state: \(peripheral.state.rawValue))")
|
|
|
|
|
// Peripheral not connected
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// No characteristic for peripheral
|
|
|
|
@@ -724,7 +724,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
if success {
|
|
|
|
|
// Sent to centrals
|
|
|
|
|
} else {
|
|
|
|
|
print("[BROADCAST] Failed to send to centrals - queue full, will retry on delegate callback")
|
|
|
|
|
// Failed to send to centrals - queue full
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if characteristic == nil {
|
|
|
|
@@ -737,13 +737,13 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
messageQueue.async(flags: .barrier) { [weak self] in
|
|
|
|
|
guard let self = self else { return }
|
|
|
|
|
guard packet.ttl > 0 else {
|
|
|
|
|
print("[PACKET] Dropping packet with TTL 0")
|
|
|
|
|
// Dropping packet with TTL 0
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate packet has payload
|
|
|
|
|
guard !packet.payload.isEmpty else {
|
|
|
|
|
print("[PACKET] Dropping packet with empty payload")
|
|
|
|
|
// Dropping packet with empty payload
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -797,14 +797,14 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
if let recipientID = packet.recipientID {
|
|
|
|
|
if recipientID == SpecialRecipients.broadcast {
|
|
|
|
|
// BROADCAST MESSAGE
|
|
|
|
|
print("[MESSAGE] Received broadcast message")
|
|
|
|
|
// Received broadcast message
|
|
|
|
|
|
|
|
|
|
// Verify signature if present
|
|
|
|
|
if let signature = packet.signature {
|
|
|
|
|
do {
|
|
|
|
|
let isValid = try encryptionService.verify(signature, for: packet.payload, from: senderID)
|
|
|
|
|
if !isValid {
|
|
|
|
|
print("[CRYPTO] Invalid signature from \(senderID), dropping message")
|
|
|
|
|
// Invalid signature, dropping message
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
@@ -814,7 +814,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
|
|
|
|
|
// Parse broadcast message (not encrypted)
|
|
|
|
|
if let message = BitchatMessage.fromBinaryPayload(packet.payload) {
|
|
|
|
|
print("[MESSAGE] Broadcast from \(message.sender): \(message.content)")
|
|
|
|
|
// Received broadcast message
|
|
|
|
|
|
|
|
|
|
// Store nickname mapping
|
|
|
|
|
peerNicknames[senderID] = message.sender
|
|
|
|
@@ -847,14 +847,14 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
} else if let recipientIDString = String(data: recipientID.trimmingNullBytes(), encoding: .utf8),
|
|
|
|
|
recipientIDString == myPeerID {
|
|
|
|
|
// PRIVATE MESSAGE FOR US
|
|
|
|
|
print("[MESSAGE] Received private message for us")
|
|
|
|
|
// Received private message
|
|
|
|
|
|
|
|
|
|
// Verify signature if present
|
|
|
|
|
if let signature = packet.signature {
|
|
|
|
|
do {
|
|
|
|
|
let isValid = try encryptionService.verify(signature, for: packet.payload, from: senderID)
|
|
|
|
|
if !isValid {
|
|
|
|
|
print("[CRYPTO] Invalid signature on private message from \(senderID), dropping")
|
|
|
|
|
// Invalid signature on private message
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
@@ -866,11 +866,11 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
let decryptedPayload: Data
|
|
|
|
|
do {
|
|
|
|
|
let decryptedPadded = try encryptionService.decrypt(packet.payload, from: senderID)
|
|
|
|
|
print("[CRYPTO] Successfully decrypted private message from \(senderID)")
|
|
|
|
|
// Successfully decrypted private message
|
|
|
|
|
|
|
|
|
|
// Remove padding
|
|
|
|
|
decryptedPayload = MessagePadding.unpad(decryptedPadded)
|
|
|
|
|
print("[PRIVACY] Unpadded message from \(decryptedPadded.count) to \(decryptedPayload.count) bytes")
|
|
|
|
|
// Unpadded message
|
|
|
|
|
} catch {
|
|
|
|
|
print("[CRYPTO] Failed to decrypt private message from \(senderID): \(error)")
|
|
|
|
|
return
|
|
|
|
@@ -880,11 +880,11 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
if let message = BitchatMessage.fromBinaryPayload(decryptedPayload) {
|
|
|
|
|
// Check if this is a dummy message for cover traffic
|
|
|
|
|
if message.content.hasPrefix(self.coverTrafficPrefix) {
|
|
|
|
|
print("[PRIVACY] Received and discarded cover traffic from \(senderID)")
|
|
|
|
|
// Received and discarded cover traffic
|
|
|
|
|
return // Silently discard dummy messages
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
print("[MESSAGE] Private from \(senderID): \(message.content)")
|
|
|
|
|
// Received private message
|
|
|
|
|
|
|
|
|
|
// Store nickname mapping if we don't have it
|
|
|
|
|
if peerNicknames[senderID] == nil {
|
|
|
|
@@ -909,7 +909,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
|
|
|
|
|
} else if packet.ttl > 0 {
|
|
|
|
|
// RELAY PRIVATE MESSAGE (not for us)
|
|
|
|
|
print("[MESSAGE] Relaying private message not meant for us (TTL: \(packet.ttl))")
|
|
|
|
|
// Relaying private message
|
|
|
|
|
var relayPacket = packet
|
|
|
|
|
relayPacket.ttl -= 1
|
|
|
|
|
|
|
|
|
@@ -918,7 +918,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
let publicKeyData = self.encryptionService.getPeerIdentityKey(recipientIDString) {
|
|
|
|
|
let fingerprint = self.getPublicKeyFingerprint(publicKeyData)
|
|
|
|
|
if self.delegate?.isFavorite(fingerprint: fingerprint) ?? false {
|
|
|
|
|
print("[CACHE] Caching relayed message for offline favorite: \(recipientIDString)")
|
|
|
|
|
// Caching message for offline favorite
|
|
|
|
|
self.cacheMessage(relayPacket, messageID: messageID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -934,7 +934,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
let publicKeyData = packet.payload
|
|
|
|
|
do {
|
|
|
|
|
try encryptionService.addPeerPublicKey(senderID, publicKeyData: publicKeyData)
|
|
|
|
|
print("[KEY_EXCHANGE] Successfully added public key for \(senderID)")
|
|
|
|
|
// Added public key
|
|
|
|
|
} catch {
|
|
|
|
|
print("[KEY_EXCHANGE] Failed to add public key for \(senderID): \(error)")
|
|
|
|
|
}
|
|
|
|
@@ -955,13 +955,13 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
// Remove temp mapping and add real peer ID mapping
|
|
|
|
|
self.connectedPeripherals.removeValue(forKey: tempID)
|
|
|
|
|
self.connectedPeripherals[senderID] = peripheral
|
|
|
|
|
print("[KEY_EXCHANGE] Updated peripheral mapping from temp ID \(tempID) to \(senderID)")
|
|
|
|
|
// Updated peripheral mapping
|
|
|
|
|
|
|
|
|
|
// Transfer RSSI from temp ID to peer ID
|
|
|
|
|
if let rssi = self.peripheralRSSI[tempID] {
|
|
|
|
|
self.peerRSSI[senderID] = rssi
|
|
|
|
|
self.peripheralRSSI.removeValue(forKey: tempID)
|
|
|
|
|
print("[KEY_EXCHANGE] Transferred RSSI \(rssi) to peer \(senderID)")
|
|
|
|
|
// Transferred RSSI to peer
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -975,7 +975,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send announce with our nickname immediately
|
|
|
|
|
print("[KEY_EXCHANGE] Calling sendAnnouncementToPeer for \(senderID)")
|
|
|
|
|
// Sending announcement to peer
|
|
|
|
|
self.sendAnnouncementToPeer(senderID)
|
|
|
|
|
|
|
|
|
|
// Check if this peer has cached messages (especially for favorites)
|
|
|
|
@@ -984,7 +984,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case .announce:
|
|
|
|
|
print("[ANNOUNCE] Processing announce packet, payload size: \(packet.payload.count)")
|
|
|
|
|
// Processing announce packet
|
|
|
|
|
if let nickname = String(data: packet.payload, encoding: .utf8),
|
|
|
|
|
let senderID = String(data: packet.senderID.trimmingNullBytes(), encoding: .utf8) {
|
|
|
|
|
// Received announce from \(senderID): \(nickname)
|
|
|
|
@@ -999,7 +999,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
|
|
|
|
|
// Store the nickname
|
|
|
|
|
peerNicknames[senderID] = nickname
|
|
|
|
|
print("[ANNOUNCE] Stored nickname for \(senderID): \(nickname)")
|
|
|
|
|
// Stored nickname
|
|
|
|
|
// Updated nicknames
|
|
|
|
|
|
|
|
|
|
// Note: We can't update peripheral mapping here since we don't have
|
|
|
|
@@ -1031,7 +1031,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
NotificationService.shared.sendFavoriteOnlineNotification(nickname: nickname)
|
|
|
|
|
|
|
|
|
|
// Send any cached messages for this favorite
|
|
|
|
|
print("[CACHE] Favorite peer \(senderID) came online, checking for cached messages")
|
|
|
|
|
// Favorite peer came online
|
|
|
|
|
self.sendCachedMessages(to: senderID)
|
|
|
|
|
}
|
|
|
|
|
} else if let viewModel = self.delegate as? ChatViewModel,
|
|
|
|
@@ -1050,15 +1050,15 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
} else {
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
print("[ANNOUNCE] Failed to decode announce packet - senderID or nickname invalid")
|
|
|
|
|
// Failed to decode announce packet
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case .leave:
|
|
|
|
|
print("[LEAVE] Processing leave packet")
|
|
|
|
|
// Processing leave packet
|
|
|
|
|
if let nickname = String(data: packet.payload, encoding: .utf8),
|
|
|
|
|
let senderID = String(data: packet.senderID.trimmingNullBytes(), encoding: .utf8) {
|
|
|
|
|
|
|
|
|
|
print("[LEAVE] \(nickname) (\(senderID)) is leaving")
|
|
|
|
|
// Peer is leaving
|
|
|
|
|
|
|
|
|
|
// Remove from active peers
|
|
|
|
|
activePeers.remove(senderID)
|
|
|
|
@@ -1073,17 +1073,17 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
// Clean up peer data
|
|
|
|
|
peerNicknames.removeValue(forKey: senderID)
|
|
|
|
|
} else {
|
|
|
|
|
print("[LEAVE] Failed to parse leave packet")
|
|
|
|
|
// Failed to parse leave packet
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case .fragmentStart, .fragmentContinue, .fragmentEnd:
|
|
|
|
|
let fragmentTypeStr = packet.type == MessageType.fragmentStart.rawValue ? "START" :
|
|
|
|
|
(packet.type == MessageType.fragmentContinue.rawValue ? "CONTINUE" : "END")
|
|
|
|
|
print("[PACKET] Handling fragment type: \(fragmentTypeStr) (\(packet.type)), payload size: \(packet.payload.count), from: \(peerID)")
|
|
|
|
|
// Handling fragment
|
|
|
|
|
|
|
|
|
|
// Validate fragment has minimum required size
|
|
|
|
|
if packet.payload.count < 13 {
|
|
|
|
|
print("[PACKET] Fragment payload too small: \(packet.payload.count) bytes, dropping")
|
|
|
|
|
// Fragment payload too small
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1093,7 +1093,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
var relayPacket = packet
|
|
|
|
|
relayPacket.ttl -= 1
|
|
|
|
|
if relayPacket.ttl > 0 {
|
|
|
|
|
print("[PACKET] Relaying fragment with TTL: \(relayPacket.ttl)")
|
|
|
|
|
// Relaying fragment
|
|
|
|
|
self.broadcastPacket(relayPacket)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1116,9 +1116,9 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
fullData[offset..<min(offset + maxFragmentSize, fullData.count)]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
print("[FRAGMENT] Splitting into \(fragments.count) fragments of max \(maxFragmentSize) bytes")
|
|
|
|
|
print("[FRAGMENT] Fragment ID: \(fragmentID.hexEncodedString())")
|
|
|
|
|
print("[FRAGMENT] Original packet size: \(fullData.count) bytes")
|
|
|
|
|
// Splitting into fragments
|
|
|
|
|
// Fragment ID generated
|
|
|
|
|
// Original packet size
|
|
|
|
|
|
|
|
|
|
// Optimize fragment transmission for speed
|
|
|
|
|
// Use minimal delay for BLE 5.0 which supports better throughput
|
|
|
|
@@ -1158,19 +1158,19 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
// Send fragments on background queue with calculated delay
|
|
|
|
|
messageQueue.asyncAfter(deadline: .now() + totalDelay) { [weak self] in
|
|
|
|
|
self?.broadcastPacket(fragmentPacket)
|
|
|
|
|
print("[FRAGMENT] Sent fragment \(index + 1)/\(fragments.count) type: \(fragmentType) at +\(totalDelay)s")
|
|
|
|
|
// Sent fragment
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let totalTime = Double(fragments.count - 1) * delayBetweenFragments
|
|
|
|
|
print("[FRAGMENT] Total send time: \(totalTime)s for \(fragments.count) fragments")
|
|
|
|
|
// Total fragment send time
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func handleFragment(_ packet: BitchatPacket, from peerID: String) {
|
|
|
|
|
print("[FRAGMENT] Starting to handle fragment, payload size: \(packet.payload.count)")
|
|
|
|
|
// Handling fragment
|
|
|
|
|
|
|
|
|
|
guard packet.payload.count >= 13 else {
|
|
|
|
|
print("[FRAGMENT] Payload too small: \(packet.payload.count) bytes (need at least 13)")
|
|
|
|
|
// Fragment payload too small
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1180,41 +1180,41 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
|
|
|
|
|
// Extract fragment ID as binary data (8 bytes)
|
|
|
|
|
guard payloadArray.count >= 8 else {
|
|
|
|
|
print("[FRAGMENT] Payload too small for fragment ID")
|
|
|
|
|
// Payload too small for fragment ID
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let fragmentIDData = Data(payloadArray[0..<8])
|
|
|
|
|
let fragmentID = fragmentIDData.hexEncodedString()
|
|
|
|
|
print("[FRAGMENT] Fragment ID: \(fragmentID)")
|
|
|
|
|
// Fragment ID
|
|
|
|
|
offset = 8
|
|
|
|
|
|
|
|
|
|
// Safely extract index
|
|
|
|
|
guard payloadArray.count >= offset + 2 else {
|
|
|
|
|
print("[FRAGMENT] Not enough data for index at offset \(offset)")
|
|
|
|
|
// Not enough data for index
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
let index = Int(payloadArray[offset]) << 8 | Int(payloadArray[offset + 1])
|
|
|
|
|
offset += 2
|
|
|
|
|
print("[FRAGMENT] Index: \(index)")
|
|
|
|
|
// Fragment index
|
|
|
|
|
|
|
|
|
|
// Safely extract total
|
|
|
|
|
guard payloadArray.count >= offset + 2 else {
|
|
|
|
|
print("[FRAGMENT] Not enough data for total at offset \(offset)")
|
|
|
|
|
// Not enough data for total
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
let total = Int(payloadArray[offset]) << 8 | Int(payloadArray[offset + 1])
|
|
|
|
|
offset += 2
|
|
|
|
|
print("[FRAGMENT] Total fragments: \(total)")
|
|
|
|
|
// Total fragments
|
|
|
|
|
|
|
|
|
|
// Safely extract original type
|
|
|
|
|
guard payloadArray.count >= offset + 1 else {
|
|
|
|
|
print("[FRAGMENT] Not enough data for type at offset \(offset)")
|
|
|
|
|
// Not enough data for type
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
let originalType = payloadArray[offset]
|
|
|
|
|
offset += 1
|
|
|
|
|
print("[FRAGMENT] Original type: \(originalType)")
|
|
|
|
|
// Original type
|
|
|
|
|
|
|
|
|
|
// Extract fragment data
|
|
|
|
|
let fragmentData: Data
|
|
|
|
@@ -1224,19 +1224,19 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
fragmentData = Data()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
print("[FRAGMENT] Received fragment \(index + 1)/\(total) for ID: \(fragmentID), data size: \(fragmentData.count)")
|
|
|
|
|
// Received fragment
|
|
|
|
|
|
|
|
|
|
// Initialize fragment collection if needed
|
|
|
|
|
if incomingFragments[fragmentID] == nil {
|
|
|
|
|
incomingFragments[fragmentID] = [:]
|
|
|
|
|
fragmentMetadata[fragmentID] = (originalType, total, Date())
|
|
|
|
|
print("[FRAGMENT] Started collecting fragments for ID: \(fragmentID), expecting \(total) fragments")
|
|
|
|
|
// Started collecting fragments
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Store fragment
|
|
|
|
|
incomingFragments[fragmentID]?[index] = fragmentData
|
|
|
|
|
|
|
|
|
|
print("[FRAGMENT] Progress for ID \(fragmentID): \(incomingFragments[fragmentID]?.count ?? 0)/\(total) fragments collected")
|
|
|
|
|
// Fragment collection progress
|
|
|
|
|
|
|
|
|
|
// Check if we have all fragments
|
|
|
|
|
if let fragments = incomingFragments[fragmentID],
|
|
|
|
@@ -1248,12 +1248,12 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
if let fragment = fragments[i] {
|
|
|
|
|
reassembledData.append(fragment)
|
|
|
|
|
} else {
|
|
|
|
|
print("[FRAGMENT] Missing fragment \(i) for ID: \(fragmentID)")
|
|
|
|
|
// Missing fragment
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
print("[FRAGMENT] Successfully reassembled \(total) fragments into \(reassembledData.count) bytes")
|
|
|
|
|
// Successfully reassembled fragments
|
|
|
|
|
|
|
|
|
|
// Parse and handle the reassembled packet
|
|
|
|
|
if let reassembledPacket = BitchatPacket.from(reassembledData) {
|
|
|
|
@@ -1272,7 +1272,7 @@ class BluetoothMeshService: NSObject {
|
|
|
|
|
if metadata.timestamp < cutoffTime {
|
|
|
|
|
incomingFragments.removeValue(forKey: fragID)
|
|
|
|
|
fragmentMetadata.removeValue(forKey: fragID)
|
|
|
|
|
print("[FRAGMENT] Cleaned up expired fragments for ID: \(fragID)")
|
|
|
|
|
// Cleaned up expired fragments
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -1313,7 +1313,7 @@ extension BluetoothMeshService: CBCentralManagerDelegate {
|
|
|
|
|
// Assume 8-character names are peer IDs
|
|
|
|
|
let peerID = name
|
|
|
|
|
peerRSSI[peerID] = RSSI
|
|
|
|
|
print("[BLUETOOTH] Discovered potential peer: \(peerID) with RSSI: \(RSSI) dBm (range: ~\(estimateDistance(rssi: rssiValue))m)")
|
|
|
|
|
// Discovered potential peer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Connect to any device we discover - we'll filter by service later
|
|
|
|
@@ -1340,7 +1340,7 @@ extension BluetoothMeshService: CBCentralManagerDelegate {
|
|
|
|
|
let tempID = peripheral.identifier.uuidString
|
|
|
|
|
connectedPeripherals[tempID] = peripheral
|
|
|
|
|
|
|
|
|
|
print("[BLUETOOTH] Connected to peripheral (temp ID: \(tempID)), waiting for real peer ID...")
|
|
|
|
|
// Connected to peripheral
|
|
|
|
|
|
|
|
|
|
// Request RSSI reading
|
|
|
|
|
peripheral.readRSSI()
|
|
|
|
@@ -1449,7 +1449,7 @@ extension BluetoothMeshService: CBPeripheralDelegate {
|
|
|
|
|
)
|
|
|
|
|
if let data = announcePacket.toBinaryData() {
|
|
|
|
|
peripheral.writeValue(data, for: characteristic, type: .withResponse)
|
|
|
|
|
print("[KEY_EXCHANGE] Sent targeted announce to peripheral")
|
|
|
|
|
// Sent targeted announce
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -1459,19 +1459,19 @@ extension BluetoothMeshService: CBPeripheralDelegate {
|
|
|
|
|
|
|
|
|
|
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
|
|
|
|
|
guard let data = characteristic.value else {
|
|
|
|
|
print("[PERIPHERAL] No data in characteristic")
|
|
|
|
|
// No data in characteristic
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard let packet = BitchatPacket.from(data) else {
|
|
|
|
|
print("[PERIPHERAL] Failed to parse packet from data of size: \(data.count)")
|
|
|
|
|
// Failed to parse packet
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Use the sender ID from the packet, not our local mapping which might still be a temp ID
|
|
|
|
|
let localPeerID = connectedPeripherals.first(where: { $0.value == peripheral })?.key ?? "unknown"
|
|
|
|
|
let packetSenderID = String(data: packet.senderID.trimmingNullBytes(), encoding: .utf8) ?? "unknown"
|
|
|
|
|
print("[PERIPHERAL] Received data from localPeerID: \(localPeerID), packetSenderID: \(packetSenderID), packet type: \(packet.type)")
|
|
|
|
|
// Received data from peer
|
|
|
|
|
|
|
|
|
|
// Always handle received packets
|
|
|
|
|
handleReceivedPacket(packet, from: packetSenderID, peripheral: peripheral)
|
|
|
|
@@ -1479,7 +1479,7 @@ extension BluetoothMeshService: CBPeripheralDelegate {
|
|
|
|
|
|
|
|
|
|
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
|
|
|
|
|
if let error = error {
|
|
|
|
|
print("[PERIPHERAL] Write failed: \(error)")
|
|
|
|
|
print("[ERROR] Write failed: \(error)")
|
|
|
|
|
} else {
|
|
|
|
|
// Write completed
|
|
|
|
|
}
|
|
|
|
@@ -1552,7 +1552,7 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|
|
|
|
// Try to identify peer from packet
|
|
|
|
|
let peerID = String(data: packet.senderID.trimmingNullBytes(), encoding: .utf8) ?? "unknown"
|
|
|
|
|
|
|
|
|
|
print("[PERIPHERAL_MANAGER] Received write from peer: \(peerID), packet type: \(packet.type)")
|
|
|
|
|
// Received write from peer
|
|
|
|
|
|
|
|
|
|
// Store the central for updates
|
|
|
|
|
if !subscribedCentrals.contains(request.central) {
|
|
|
|
@@ -1622,7 +1622,7 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|
|
|
|
|
|
|
|
|
if let data = keyPacket.toBinaryData() {
|
|
|
|
|
peripheral.updateValue(data, for: self.characteristic, onSubscribedCentrals: [central])
|
|
|
|
|
print("[KEY_EXCHANGE] Sent initial key exchange as peripheral to new subscriber")
|
|
|
|
|
// Sent initial key exchange
|
|
|
|
|
|
|
|
|
|
// We'll send announce when we receive their key exchange
|
|
|
|
|
}
|
|
|
|
@@ -1695,7 +1695,7 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
print("[BATTERY] Current battery level: \(Int(currentBatteryLevel * 100))%")
|
|
|
|
|
// Battery level updated
|
|
|
|
|
updateScanParametersForBattery()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1727,22 +1727,22 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|
|
|
|
// High battery: Normal operation
|
|
|
|
|
activeScanDuration = 2.0
|
|
|
|
|
scanPauseDuration = 3.0
|
|
|
|
|
print("[BATTERY] High battery mode: normal scanning")
|
|
|
|
|
// High battery mode
|
|
|
|
|
} else if currentBatteryLevel > 0.4 {
|
|
|
|
|
// Medium battery: Moderate power saving
|
|
|
|
|
activeScanDuration = 1.5
|
|
|
|
|
scanPauseDuration = 4.5
|
|
|
|
|
print("[BATTERY] Medium battery mode: moderate power saving")
|
|
|
|
|
// Medium battery mode
|
|
|
|
|
} else if currentBatteryLevel > 0.2 {
|
|
|
|
|
// Low battery: Aggressive power saving
|
|
|
|
|
activeScanDuration = 1.0
|
|
|
|
|
scanPauseDuration = 8.0
|
|
|
|
|
print("[BATTERY] Low battery mode: aggressive power saving")
|
|
|
|
|
// Low battery mode
|
|
|
|
|
} else {
|
|
|
|
|
// Critical battery: Maximum power saving
|
|
|
|
|
activeScanDuration = 0.5
|
|
|
|
|
scanPauseDuration = 15.0
|
|
|
|
|
print("[BATTERY] Critical battery mode: maximum power saving")
|
|
|
|
|
// Critical battery mode
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we're currently in a duty cycle, restart it with new parameters
|
|
|
|
@@ -1784,7 +1784,7 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|
|
|
|
|
|
|
|
|
// Skip if battery is low
|
|
|
|
|
if currentBatteryLevel < 0.2 {
|
|
|
|
|
print("[PRIVACY] Skipping cover traffic due to low battery")
|
|
|
|
|
// Skipping cover traffic due to low battery
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1794,7 +1794,7 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|
|
|
|
// Generate random dummy content
|
|
|
|
|
let dummyContent = generateDummyContent()
|
|
|
|
|
|
|
|
|
|
print("[PRIVACY] Sending cover traffic to \(randomPeer)")
|
|
|
|
|
// Sending cover traffic
|
|
|
|
|
|
|
|
|
|
// Send as a private message so it's encrypted
|
|
|
|
|
sendPrivateMessage(dummyContent, to: randomPeer, recipientNickname: peerNicknames[randomPeer] ?? "unknown")
|
|
|
|
|