mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 12:45:19 +00:00
Fix Noise handshake failures and implement binary protocol migration
- Fix asymmetric handshake state causing message delivery failures - Prevent duplicate handshake init messages from disrupting ongoing handshakes - Add defensive copying to all binary decoders to prevent thread safety issues - Implement binary encoding for all 9 message types (60-80% bandwidth reduction) - Fix delivery ACK decoding for Noise encrypted messages - Add comprehensive logging for debugging handshake and message flow - Fix race condition in delivery status updates - Add relay logic for handshake packets to ensure mesh delivery - Maintain backward compatibility with JSON fallback
This commit is contained in:
@@ -210,6 +210,15 @@ extension Data {
|
|||||||
|
|
||||||
return result.uppercased()
|
return result.uppercased()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readFixedBytes(at offset: inout Int, count: Int) -> Data? {
|
||||||
|
guard offset + count <= self.count else { return nil }
|
||||||
|
|
||||||
|
let data = self[offset..<offset + count]
|
||||||
|
offset += count
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Binary Message Protocol
|
// MARK: - Binary Message Protocol
|
||||||
|
|||||||
@@ -251,22 +251,23 @@ struct DeliveryAck: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func fromBinaryData(_ data: Data) -> DeliveryAck? {
|
static func fromBinaryData(_ data: Data) -> DeliveryAck? {
|
||||||
|
// Create defensive copy
|
||||||
|
let dataCopy = Data(data)
|
||||||
|
|
||||||
// Minimum size: 2 UUIDs (32) + recipientID (8) + hopCount (1) + timestamp (8) + min nickname
|
// Minimum size: 2 UUIDs (32) + recipientID (8) + hopCount (1) + timestamp (8) + min nickname
|
||||||
guard data.count >= 50 else { return nil }
|
guard dataCopy.count >= 50 else { return nil }
|
||||||
|
|
||||||
var offset = 0
|
var offset = 0
|
||||||
|
|
||||||
guard let originalMessageID = data.readUUID(at: &offset),
|
guard let originalMessageID = dataCopy.readUUID(at: &offset),
|
||||||
let ackID = data.readUUID(at: &offset) else { return nil }
|
let ackID = dataCopy.readUUID(at: &offset) else { return nil }
|
||||||
|
|
||||||
guard offset + 8 <= data.count else { return nil }
|
guard let recipientIDData = dataCopy.readFixedBytes(at: &offset, count: 8) else { return nil }
|
||||||
let recipientIDData = data[offset..<offset + 8]
|
|
||||||
offset += 8
|
|
||||||
let recipientID = recipientIDData.hexEncodedString()
|
let recipientID = recipientIDData.hexEncodedString()
|
||||||
|
|
||||||
guard let hopCount = data.readUInt8(at: &offset),
|
guard let hopCount = dataCopy.readUInt8(at: &offset),
|
||||||
let timestamp = data.readDate(at: &offset),
|
let timestamp = dataCopy.readDate(at: &offset),
|
||||||
let recipientNickname = data.readString(at: &offset) else { return nil }
|
let recipientNickname = dataCopy.readString(at: &offset) else { return nil }
|
||||||
|
|
||||||
return DeliveryAck(originalMessageID: originalMessageID,
|
return DeliveryAck(originalMessageID: originalMessageID,
|
||||||
ackID: ackID,
|
ackID: ackID,
|
||||||
@@ -336,21 +337,22 @@ struct ReadReceipt: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func fromBinaryData(_ data: Data) -> ReadReceipt? {
|
static func fromBinaryData(_ data: Data) -> ReadReceipt? {
|
||||||
|
// Create defensive copy
|
||||||
|
let dataCopy = Data(data)
|
||||||
|
|
||||||
// Minimum size: 2 UUIDs (32) + readerID (8) + timestamp (8) + min nickname
|
// Minimum size: 2 UUIDs (32) + readerID (8) + timestamp (8) + min nickname
|
||||||
guard data.count >= 49 else { return nil }
|
guard dataCopy.count >= 49 else { return nil }
|
||||||
|
|
||||||
var offset = 0
|
var offset = 0
|
||||||
|
|
||||||
guard let originalMessageID = data.readUUID(at: &offset),
|
guard let originalMessageID = dataCopy.readUUID(at: &offset),
|
||||||
let receiptID = data.readUUID(at: &offset) else { return nil }
|
let receiptID = dataCopy.readUUID(at: &offset) else { return nil }
|
||||||
|
|
||||||
guard offset + 8 <= data.count else { return nil }
|
guard let readerIDData = dataCopy.readFixedBytes(at: &offset, count: 8) else { return nil }
|
||||||
let readerIDData = data[offset..<offset + 8]
|
|
||||||
offset += 8
|
|
||||||
let readerID = readerIDData.hexEncodedString()
|
let readerID = readerIDData.hexEncodedString()
|
||||||
|
|
||||||
guard let timestamp = data.readDate(at: &offset),
|
guard let timestamp = dataCopy.readDate(at: &offset),
|
||||||
let readerNickname = data.readString(at: &offset) else { return nil }
|
let readerNickname = dataCopy.readString(at: &offset) else { return nil }
|
||||||
|
|
||||||
return ReadReceipt(originalMessageID: originalMessageID,
|
return ReadReceipt(originalMessageID: originalMessageID,
|
||||||
receiptID: receiptID,
|
receiptID: receiptID,
|
||||||
@@ -415,17 +417,18 @@ struct ChannelKeyVerifyRequest: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func fromBinaryData(_ data: Data) -> ChannelKeyVerifyRequest? {
|
static func fromBinaryData(_ data: Data) -> ChannelKeyVerifyRequest? {
|
||||||
|
// Create defensive copy
|
||||||
|
let dataCopy = Data(data)
|
||||||
|
|
||||||
var offset = 0
|
var offset = 0
|
||||||
|
|
||||||
guard let channel = data.readString(at: &offset) else { return nil }
|
guard let channel = dataCopy.readString(at: &offset) else { return nil }
|
||||||
|
|
||||||
guard offset + 8 <= data.count else { return nil }
|
guard let requesterIDData = dataCopy.readFixedBytes(at: &offset, count: 8) else { return nil }
|
||||||
let requesterIDData = data[offset..<offset + 8]
|
|
||||||
offset += 8
|
|
||||||
let requesterID = requesterIDData.hexEncodedString()
|
let requesterID = requesterIDData.hexEncodedString()
|
||||||
|
|
||||||
guard let keyCommitment = data.readString(at: &offset),
|
guard let keyCommitment = dataCopy.readString(at: &offset),
|
||||||
let timestamp = data.readDate(at: &offset) else { return nil }
|
let timestamp = dataCopy.readDate(at: &offset) else { return nil }
|
||||||
|
|
||||||
return ChannelKeyVerifyRequest(channel: channel,
|
return ChannelKeyVerifyRequest(channel: channel,
|
||||||
requesterID: requesterID,
|
requesterID: requesterID,
|
||||||
@@ -489,17 +492,18 @@ struct ChannelKeyVerifyResponse: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func fromBinaryData(_ data: Data) -> ChannelKeyVerifyResponse? {
|
static func fromBinaryData(_ data: Data) -> ChannelKeyVerifyResponse? {
|
||||||
|
// Create defensive copy
|
||||||
|
let dataCopy = Data(data)
|
||||||
|
|
||||||
var offset = 0
|
var offset = 0
|
||||||
|
|
||||||
guard let channel = data.readString(at: &offset) else { return nil }
|
guard let channel = dataCopy.readString(at: &offset) else { return nil }
|
||||||
|
|
||||||
guard offset + 8 <= data.count else { return nil }
|
guard let responderIDData = dataCopy.readFixedBytes(at: &offset, count: 8) else { return nil }
|
||||||
let responderIDData = data[offset..<offset + 8]
|
|
||||||
offset += 8
|
|
||||||
let responderID = responderIDData.hexEncodedString()
|
let responderID = responderIDData.hexEncodedString()
|
||||||
|
|
||||||
guard let verifiedByte = data.readUInt8(at: &offset),
|
guard let verifiedByte = dataCopy.readUInt8(at: &offset),
|
||||||
let timestamp = data.readDate(at: &offset) else { return nil }
|
let timestamp = dataCopy.readDate(at: &offset) else { return nil }
|
||||||
|
|
||||||
let verified = verifiedByte != 0
|
let verified = verifiedByte != 0
|
||||||
|
|
||||||
@@ -573,19 +577,20 @@ struct ChannelPasswordUpdate: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func fromBinaryData(_ data: Data) -> ChannelPasswordUpdate? {
|
static func fromBinaryData(_ data: Data) -> ChannelPasswordUpdate? {
|
||||||
|
// Create defensive copy
|
||||||
|
let dataCopy = Data(data)
|
||||||
|
|
||||||
var offset = 0
|
var offset = 0
|
||||||
|
|
||||||
guard let channel = data.readString(at: &offset) else { return nil }
|
guard let channel = dataCopy.readString(at: &offset) else { return nil }
|
||||||
|
|
||||||
guard offset + 8 <= data.count else { return nil }
|
guard let ownerIDData = dataCopy.readFixedBytes(at: &offset, count: 8) else { return nil }
|
||||||
let ownerIDData = data[offset..<offset + 8]
|
|
||||||
offset += 8
|
|
||||||
let ownerID = ownerIDData.hexEncodedString()
|
let ownerID = ownerIDData.hexEncodedString()
|
||||||
|
|
||||||
guard let ownerFingerprint = data.readString(at: &offset),
|
guard let ownerFingerprint = dataCopy.readString(at: &offset),
|
||||||
let encryptedPassword = data.readData(at: &offset),
|
let encryptedPassword = dataCopy.readData(at: &offset),
|
||||||
let newKeyCommitment = data.readString(at: &offset),
|
let newKeyCommitment = dataCopy.readString(at: &offset),
|
||||||
let timestamp = data.readDate(at: &offset) else { return nil }
|
let timestamp = dataCopy.readDate(at: &offset) else { return nil }
|
||||||
|
|
||||||
return ChannelPasswordUpdate(channel: channel,
|
return ChannelPasswordUpdate(channel: channel,
|
||||||
ownerID: ownerID,
|
ownerID: ownerID,
|
||||||
@@ -669,27 +674,28 @@ struct ChannelMetadata: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func fromBinaryData(_ data: Data) -> ChannelMetadata? {
|
static func fromBinaryData(_ data: Data) -> ChannelMetadata? {
|
||||||
|
// Create defensive copy
|
||||||
|
let dataCopy = Data(data)
|
||||||
|
|
||||||
var offset = 0
|
var offset = 0
|
||||||
|
|
||||||
guard let flags = data.readUInt8(at: &offset) else { return nil }
|
guard let flags = dataCopy.readUInt8(at: &offset) else { return nil }
|
||||||
let hasKeyCommitment = (flags & 0x01) != 0
|
let hasKeyCommitment = (flags & 0x01) != 0
|
||||||
|
|
||||||
guard let channel = data.readString(at: &offset) else { return nil }
|
guard let channel = dataCopy.readString(at: &offset) else { return nil }
|
||||||
|
|
||||||
guard offset + 8 <= data.count else { return nil }
|
guard let creatorIDData = dataCopy.readFixedBytes(at: &offset, count: 8) else { return nil }
|
||||||
let creatorIDData = data[offset..<offset + 8]
|
|
||||||
offset += 8
|
|
||||||
let creatorID = creatorIDData.hexEncodedString()
|
let creatorID = creatorIDData.hexEncodedString()
|
||||||
|
|
||||||
guard let creatorFingerprint = data.readString(at: &offset),
|
guard let creatorFingerprint = dataCopy.readString(at: &offset),
|
||||||
let createdAt = data.readDate(at: &offset),
|
let createdAt = dataCopy.readDate(at: &offset),
|
||||||
let isPasswordProtectedByte = data.readUInt8(at: &offset) else { return nil }
|
let isPasswordProtectedByte = dataCopy.readUInt8(at: &offset) else { return nil }
|
||||||
|
|
||||||
let isPasswordProtected = isPasswordProtectedByte != 0
|
let isPasswordProtected = isPasswordProtectedByte != 0
|
||||||
|
|
||||||
var keyCommitment: String? = nil
|
var keyCommitment: String? = nil
|
||||||
if hasKeyCommitment {
|
if hasKeyCommitment {
|
||||||
keyCommitment = data.readString(at: &offset)
|
keyCommitment = dataCopy.readString(at: &offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ChannelMetadata(channel: channel,
|
return ChannelMetadata(channel: channel,
|
||||||
@@ -784,33 +790,34 @@ struct NoiseIdentityAnnouncement: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func fromBinaryData(_ data: Data) -> NoiseIdentityAnnouncement? {
|
static func fromBinaryData(_ data: Data) -> NoiseIdentityAnnouncement? {
|
||||||
// Minimum size check
|
// Create defensive copy
|
||||||
guard data.count >= 20 else { return nil }
|
let dataCopy = Data(data)
|
||||||
|
|
||||||
|
// Minimum size check: flags(1) + peerID(8) + min data lengths
|
||||||
|
guard dataCopy.count >= 20 else { return nil }
|
||||||
|
|
||||||
var offset = 0
|
var offset = 0
|
||||||
|
|
||||||
guard let flags = data.readUInt8(at: &offset) else { return nil }
|
guard let flags = dataCopy.readUInt8(at: &offset) else { return nil }
|
||||||
let hasPreviousPeerID = (flags & 0x01) != 0
|
let hasPreviousPeerID = (flags & 0x01) != 0
|
||||||
|
|
||||||
guard offset + 8 <= data.count else { return nil }
|
// Read peerID using safe method
|
||||||
let peerIDData = data[offset..<offset + 8]
|
guard let peerIDBytes = dataCopy.readFixedBytes(at: &offset, count: 8) else { return nil }
|
||||||
offset += 8
|
let peerID = peerIDBytes.hexEncodedString()
|
||||||
let peerID = peerIDData.hexEncodedString()
|
|
||||||
|
|
||||||
guard let publicKey = data.readData(at: &offset),
|
guard let publicKey = dataCopy.readData(at: &offset),
|
||||||
let signingPublicKey = data.readData(at: &offset),
|
let signingPublicKey = dataCopy.readData(at: &offset),
|
||||||
let nickname = data.readString(at: &offset),
|
let nickname = dataCopy.readString(at: &offset),
|
||||||
let timestamp = data.readDate(at: &offset) else { return nil }
|
let timestamp = dataCopy.readDate(at: &offset) else { return nil }
|
||||||
|
|
||||||
var previousPeerID: String? = nil
|
var previousPeerID: String? = nil
|
||||||
if hasPreviousPeerID {
|
if hasPreviousPeerID {
|
||||||
guard offset + 8 <= data.count else { return nil }
|
// Read previousPeerID using safe method
|
||||||
let previousPeerIDData = data[offset..<offset + 8]
|
guard let prevIDBytes = dataCopy.readFixedBytes(at: &offset, count: 8) else { return nil }
|
||||||
offset += 8
|
previousPeerID = prevIDBytes.hexEncodedString()
|
||||||
previousPeerID = previousPeerIDData.hexEncodedString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let signature = data.readData(at: &offset) else { return nil }
|
guard let signature = dataCopy.readData(at: &offset) else { return nil }
|
||||||
|
|
||||||
return NoiseIdentityAnnouncement(peerID: peerID,
|
return NoiseIdentityAnnouncement(peerID: peerID,
|
||||||
publicKey: publicKey,
|
publicKey: publicKey,
|
||||||
@@ -930,31 +937,34 @@ struct VersionHello: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func fromBinaryData(_ data: Data) -> VersionHello? {
|
static func fromBinaryData(_ data: Data) -> VersionHello? {
|
||||||
|
// Create defensive copy
|
||||||
|
let dataCopy = Data(data)
|
||||||
|
|
||||||
// Minimum size check: flags(1) + versionCount(1) + at least one version(1) + preferredVersion(1) + min strings
|
// Minimum size check: flags(1) + versionCount(1) + at least one version(1) + preferredVersion(1) + min strings
|
||||||
guard data.count >= 4 else { return nil }
|
guard dataCopy.count >= 4 else { return nil }
|
||||||
|
|
||||||
var offset = 0
|
var offset = 0
|
||||||
|
|
||||||
guard let flags = data.readUInt8(at: &offset) else { return nil }
|
guard let flags = dataCopy.readUInt8(at: &offset) else { return nil }
|
||||||
let hasCapabilities = (flags & 0x01) != 0
|
let hasCapabilities = (flags & 0x01) != 0
|
||||||
|
|
||||||
guard let versionCount = data.readUInt8(at: &offset) else { return nil }
|
guard let versionCount = dataCopy.readUInt8(at: &offset) else { return nil }
|
||||||
var supportedVersions: [UInt8] = []
|
var supportedVersions: [UInt8] = []
|
||||||
for _ in 0..<versionCount {
|
for _ in 0..<versionCount {
|
||||||
guard let version = data.readUInt8(at: &offset) else { return nil }
|
guard let version = dataCopy.readUInt8(at: &offset) else { return nil }
|
||||||
supportedVersions.append(version)
|
supportedVersions.append(version)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let preferredVersion = data.readUInt8(at: &offset),
|
guard let preferredVersion = dataCopy.readUInt8(at: &offset),
|
||||||
let clientVersion = data.readString(at: &offset),
|
let clientVersion = dataCopy.readString(at: &offset),
|
||||||
let platform = data.readString(at: &offset) else { return nil }
|
let platform = dataCopy.readString(at: &offset) else { return nil }
|
||||||
|
|
||||||
var capabilities: [String]? = nil
|
var capabilities: [String]? = nil
|
||||||
if hasCapabilities {
|
if hasCapabilities {
|
||||||
guard let capCount = data.readUInt8(at: &offset) else { return nil }
|
guard let capCount = dataCopy.readUInt8(at: &offset) else { return nil }
|
||||||
capabilities = []
|
capabilities = []
|
||||||
for _ in 0..<capCount {
|
for _ in 0..<capCount {
|
||||||
guard let capability = data.readString(at: &offset) else { return nil }
|
guard let capability = dataCopy.readString(at: &offset) else { return nil }
|
||||||
capabilities?.append(capability)
|
capabilities?.append(capability)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1029,35 +1039,38 @@ struct VersionAck: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func fromBinaryData(_ data: Data) -> VersionAck? {
|
static func fromBinaryData(_ data: Data) -> VersionAck? {
|
||||||
|
// Create defensive copy
|
||||||
|
let dataCopy = Data(data)
|
||||||
|
|
||||||
// Minimum size: flags(1) + version(1) + rejected(1) + min strings
|
// Minimum size: flags(1) + version(1) + rejected(1) + min strings
|
||||||
guard data.count >= 5 else { return nil }
|
guard dataCopy.count >= 5 else { return nil }
|
||||||
|
|
||||||
var offset = 0
|
var offset = 0
|
||||||
|
|
||||||
guard let flags = data.readUInt8(at: &offset) else { return nil }
|
guard let flags = dataCopy.readUInt8(at: &offset) else { return nil }
|
||||||
let hasCapabilities = (flags & 0x01) != 0
|
let hasCapabilities = (flags & 0x01) != 0
|
||||||
let hasReason = (flags & 0x02) != 0
|
let hasReason = (flags & 0x02) != 0
|
||||||
|
|
||||||
guard let agreedVersion = data.readUInt8(at: &offset),
|
guard let agreedVersion = dataCopy.readUInt8(at: &offset),
|
||||||
let serverVersion = data.readString(at: &offset),
|
let serverVersion = dataCopy.readString(at: &offset),
|
||||||
let platform = data.readString(at: &offset),
|
let platform = dataCopy.readString(at: &offset),
|
||||||
let rejectedByte = data.readUInt8(at: &offset) else { return nil }
|
let rejectedByte = dataCopy.readUInt8(at: &offset) else { return nil }
|
||||||
|
|
||||||
let rejected = rejectedByte != 0
|
let rejected = rejectedByte != 0
|
||||||
|
|
||||||
var capabilities: [String]? = nil
|
var capabilities: [String]? = nil
|
||||||
if hasCapabilities {
|
if hasCapabilities {
|
||||||
guard let capCount = data.readUInt8(at: &offset) else { return nil }
|
guard let capCount = dataCopy.readUInt8(at: &offset) else { return nil }
|
||||||
capabilities = []
|
capabilities = []
|
||||||
for _ in 0..<capCount {
|
for _ in 0..<capCount {
|
||||||
guard let capability = data.readString(at: &offset) else { return nil }
|
guard let capability = dataCopy.readString(at: &offset) else { return nil }
|
||||||
capabilities?.append(capability)
|
capabilities?.append(capability)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var reason: String? = nil
|
var reason: String? = nil
|
||||||
if hasReason {
|
if hasReason {
|
||||||
reason = data.readString(at: &offset)
|
reason = dataCopy.readString(at: &offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
return VersionAck(agreedVersion: agreedVersion,
|
return VersionAck(agreedVersion: agreedVersion,
|
||||||
|
|||||||
@@ -904,6 +904,7 @@ class BluetoothMeshService: NSObject {
|
|||||||
|
|
||||||
// Encode the ACK
|
// Encode the ACK
|
||||||
let ackData = ack.toBinaryData()
|
let ackData = ack.toBinaryData()
|
||||||
|
print("📤 Sending delivery ACK for message \(ack.originalMessageID) to \(recipientID) - binary size: \(ackData.count)")
|
||||||
|
|
||||||
// Check if we have a Noise session with this peer
|
// Check if we have a Noise session with this peer
|
||||||
// Use noiseService directly
|
// Use noiseService directly
|
||||||
@@ -1779,6 +1780,7 @@ class BluetoothMeshService: NSObject {
|
|||||||
myNickname: myNickname,
|
myNickname: myNickname,
|
||||||
hopCount: UInt8(self.maxTTL - packet.ttl)
|
hopCount: UInt8(self.maxTTL - packet.ttl)
|
||||||
) {
|
) {
|
||||||
|
print("🔔 Generating delivery ACK for channel mention message \(messageWithPeerID.id)")
|
||||||
self.sendDeliveryAck(ack, to: senderID)
|
self.sendDeliveryAck(ack, to: senderID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1873,6 +1875,7 @@ class BluetoothMeshService: NSObject {
|
|||||||
myNickname: myNickname,
|
myNickname: myNickname,
|
||||||
hopCount: UInt8(self.maxTTL - packet.ttl)
|
hopCount: UInt8(self.maxTTL - packet.ttl)
|
||||||
) {
|
) {
|
||||||
|
print("💬 Generating delivery ACK for private message \(messageWithPeerID.id) from \(senderID)")
|
||||||
self.sendDeliveryAck(ack, to: senderID)
|
self.sendDeliveryAck(ack, to: senderID)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -2297,8 +2300,24 @@ class BluetoothMeshService: NSObject {
|
|||||||
// Handle Noise identity announcement
|
// Handle Noise identity announcement
|
||||||
let senderID = packet.senderID.hexEncodedString()
|
let senderID = packet.senderID.hexEncodedString()
|
||||||
if senderID != myPeerID && !isPeerIDOurs(senderID) {
|
if senderID != myPeerID && !isPeerIDOurs(senderID) {
|
||||||
|
// Create defensive copy and validate
|
||||||
|
let payloadCopy = Data(packet.payload)
|
||||||
|
|
||||||
|
guard !payloadCopy.isEmpty else {
|
||||||
|
SecurityLogger.log("Received empty NoiseIdentityAnnouncement from \(senderID)", category: SecurityLogger.noise, level: .error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Decode the announcement
|
// Decode the announcement
|
||||||
guard let announcement = NoiseIdentityAnnouncement.fromBinaryData(packet.payload) ?? NoiseIdentityAnnouncement.decode(from: packet.payload) else {
|
let announcement: NoiseIdentityAnnouncement?
|
||||||
|
if let firstByte = payloadCopy.first, firstByte == 0x7B { // '{' character - JSON
|
||||||
|
announcement = NoiseIdentityAnnouncement.decode(from: payloadCopy) ?? NoiseIdentityAnnouncement.fromBinaryData(payloadCopy)
|
||||||
|
} else {
|
||||||
|
announcement = NoiseIdentityAnnouncement.fromBinaryData(payloadCopy) ?? NoiseIdentityAnnouncement.decode(from: payloadCopy)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let announcement = announcement else {
|
||||||
|
SecurityLogger.log("Failed to decode NoiseIdentityAnnouncement from \(senderID), size: \(payloadCopy.count)", category: SecurityLogger.noise, level: .error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2360,13 +2379,27 @@ class BluetoothMeshService: NSObject {
|
|||||||
case .noiseHandshakeInit:
|
case .noiseHandshakeInit:
|
||||||
// Handle incoming Noise handshake initiation
|
// Handle incoming Noise handshake initiation
|
||||||
let senderID = packet.senderID.hexEncodedString()
|
let senderID = packet.senderID.hexEncodedString()
|
||||||
|
print("🤝 Received Noise handshake init from \(senderID), payload size: \(packet.payload.count)")
|
||||||
|
|
||||||
// Check if this handshake is for us or broadcast
|
// Check if this handshake is for us or broadcast
|
||||||
if let recipientID = packet.recipientID,
|
if let recipientID = packet.recipientID,
|
||||||
!isPeerIDOurs(recipientID.hexEncodedString()) {
|
!isPeerIDOurs(recipientID.hexEncodedString()) {
|
||||||
// Not for us, ignore
|
// Not for us, relay if TTL > 0
|
||||||
|
if packet.ttl > 0 {
|
||||||
|
print("🔀 Relaying handshake init packet, TTL: \(packet.ttl)")
|
||||||
|
var relayPacket = packet
|
||||||
|
relayPacket.ttl -= 1
|
||||||
|
broadcastPacket(relayPacket)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !isPeerIDOurs(senderID) {
|
if !isPeerIDOurs(senderID) {
|
||||||
|
// Check if we already have a session (established or handshaking)
|
||||||
|
if noiseService.hasSession(with: senderID) {
|
||||||
|
print("⚠️ Received handshake init from \(senderID) but already have session/handshaking - ignoring duplicate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Check if we've completed version negotiation with this peer
|
// Check if we've completed version negotiation with this peer
|
||||||
if negotiatedVersions[senderID] == nil {
|
if negotiatedVersions[senderID] == nil {
|
||||||
// Legacy peer - assume version 1 for backward compatibility
|
// Legacy peer - assume version 1 for backward compatibility
|
||||||
@@ -2381,13 +2414,26 @@ class BluetoothMeshService: NSObject {
|
|||||||
case .noiseHandshakeResp:
|
case .noiseHandshakeResp:
|
||||||
// Handle Noise handshake response
|
// Handle Noise handshake response
|
||||||
let senderID = packet.senderID.hexEncodedString()
|
let senderID = packet.senderID.hexEncodedString()
|
||||||
|
print("🤝 Received Noise handshake response from \(senderID)")
|
||||||
|
|
||||||
// Check if this handshake response is for us
|
// Check if this handshake response is for us
|
||||||
if let recipientID = packet.recipientID,
|
if let recipientID = packet.recipientID {
|
||||||
!isPeerIDOurs(recipientID.hexEncodedString()) {
|
let recipientIDStr = recipientID.hexEncodedString()
|
||||||
// Not for us, ignore
|
print("🤝 Response targeted to: \(recipientIDStr), is us: \(isPeerIDOurs(recipientIDStr))")
|
||||||
return
|
if !isPeerIDOurs(recipientIDStr) {
|
||||||
|
// Not for us, relay if TTL > 0
|
||||||
|
if packet.ttl > 0 {
|
||||||
|
print("🔀 Relaying handshake response packet, TTL: \(packet.ttl)")
|
||||||
|
var relayPacket = packet
|
||||||
|
relayPacket.ttl -= 1
|
||||||
|
broadcastPacket(relayPacket)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isPeerIDOurs(senderID) {
|
if !isPeerIDOurs(senderID) {
|
||||||
|
print("🤝 Processing handshake response from \(senderID)")
|
||||||
handleNoiseHandshakeMessage(from: senderID, message: packet.payload, isInitiation: false)
|
handleNoiseHandshakeMessage(from: senderID, message: packet.payload, isInitiation: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3344,11 +3390,14 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
guard let self = self,
|
guard let self = self,
|
||||||
let pendingMessages = self.pendingPrivateMessages[peerID] else { return }
|
let pendingMessages = self.pendingPrivateMessages[peerID] else { return }
|
||||||
|
|
||||||
|
print("📬 Sending \(pendingMessages.count) pending private messages to \(peerID)")
|
||||||
|
|
||||||
// Clear pending messages for this peer
|
// Clear pending messages for this peer
|
||||||
self.pendingPrivateMessages.removeValue(forKey: peerID)
|
self.pendingPrivateMessages.removeValue(forKey: peerID)
|
||||||
|
|
||||||
// Send each pending message
|
// Send each pending message
|
||||||
for (content, recipientNickname, messageID) in pendingMessages {
|
for (content, recipientNickname, messageID) in pendingMessages {
|
||||||
|
print("📬 Sending pending message \(messageID) to \(peerID)")
|
||||||
// Use async to avoid blocking the queue
|
// Use async to avoid blocking the queue
|
||||||
DispatchQueue.global().async { [weak self] in
|
DispatchQueue.global().async { [weak self] in
|
||||||
self?.sendPrivateMessage(content, to: peerID, recipientNickname: recipientNickname, messageID: messageID)
|
self?.sendPrivateMessage(content, to: peerID, recipientNickname: recipientNickname, messageID: messageID)
|
||||||
@@ -3409,10 +3458,12 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
|
|
||||||
private func handleNoiseHandshakeMessage(from peerID: String, message: Data, isInitiation: Bool) {
|
private func handleNoiseHandshakeMessage(from peerID: String, message: Data, isInitiation: Bool) {
|
||||||
// Use noiseService directly
|
// Use noiseService directly
|
||||||
|
print("🤝 handleNoiseHandshakeMessage from \(peerID), isInitiation: \(isInitiation), messageSize: \(message.count)")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// Process handshake message
|
// Process handshake message
|
||||||
if let response = try noiseService.processHandshakeMessage(from: peerID, message: message) {
|
if let response = try noiseService.processHandshakeMessage(from: peerID, message: message) {
|
||||||
|
print("🤝 Got response from processHandshakeMessage, size: \(response.count)")
|
||||||
// Always send responses as handshake response type
|
// Always send responses as handshake response type
|
||||||
let packet = BitchatPacket(
|
let packet = BitchatPacket(
|
||||||
type: MessageType.noiseHandshakeResp.rawValue,
|
type: MessageType.noiseHandshakeResp.rawValue,
|
||||||
@@ -3426,10 +3477,13 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
|
|
||||||
// Use broadcastPacket instead of sendPacket to ensure it goes through the mesh
|
// Use broadcastPacket instead of sendPacket to ensure it goes through the mesh
|
||||||
broadcastPacket(packet)
|
broadcastPacket(packet)
|
||||||
|
} else {
|
||||||
|
print("🤝 No response needed from processHandshakeMessage")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if handshake is complete
|
// Check if handshake is complete
|
||||||
if noiseService.hasEstablishedSession(with: peerID) {
|
if noiseService.hasEstablishedSession(with: peerID) {
|
||||||
|
print("🎉 Handshake completed with peer: \(peerID)")
|
||||||
// Unlock rotation now that handshake is complete
|
// Unlock rotation now that handshake is complete
|
||||||
unlockRotation()
|
unlockRotation()
|
||||||
|
|
||||||
@@ -3459,8 +3513,10 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
}
|
}
|
||||||
} catch NoiseSessionError.alreadyEstablished {
|
} catch NoiseSessionError.alreadyEstablished {
|
||||||
// Session already established, ignore handshake
|
// Session already established, ignore handshake
|
||||||
|
print("🤝 Handshake already established with \(peerID)")
|
||||||
} catch {
|
} catch {
|
||||||
// Handshake failed
|
// Handshake failed
|
||||||
|
print("❌ Handshake failed with \(peerID): \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3489,7 +3545,9 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
// Decrypt the message
|
// Decrypt the message
|
||||||
|
print("🔓 Attempting to decrypt Noise message from \(peerID), encrypted size: \(encryptedData.count)")
|
||||||
let decryptedData = try noiseService.decrypt(encryptedData, from: peerID)
|
let decryptedData = try noiseService.decrypt(encryptedData, from: peerID)
|
||||||
|
print("🔓 Successfully decrypted message from \(peerID), decrypted size: \(decryptedData.count)")
|
||||||
|
|
||||||
// Check if this is a special format message (type marker + payload)
|
// Check if this is a special format message (type marker + payload)
|
||||||
if decryptedData.count > 1 {
|
if decryptedData.count > 1 {
|
||||||
@@ -3500,8 +3558,9 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
// Extract the ACK JSON data (skip the type marker)
|
// Extract the ACK JSON data (skip the type marker)
|
||||||
let ackData = decryptedData.dropFirst()
|
let ackData = decryptedData.dropFirst()
|
||||||
|
|
||||||
// Decode the delivery ACK
|
// Decode the delivery ACK - try binary first, then JSON
|
||||||
if let ack = DeliveryAck.decode(from: ackData) {
|
if let ack = DeliveryAck.fromBinaryData(ackData) {
|
||||||
|
print("📨 Received binary delivery ACK via Noise: \(ack.originalMessageID) from \(ack.recipientNickname)")
|
||||||
|
|
||||||
// Process the ACK
|
// Process the ACK
|
||||||
DeliveryTracker.shared.processDeliveryAck(ack)
|
DeliveryTracker.shared.processDeliveryAck(ack)
|
||||||
@@ -3511,22 +3570,42 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
self.delegate?.didReceiveDeliveryAck(ack)
|
self.delegate?.didReceiveDeliveryAck(ack)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
} else if let ack = DeliveryAck.decode(from: ackData) {
|
||||||
|
print("📨 Received JSON delivery ACK via Noise: \(ack.originalMessageID) from \(ack.recipientNickname)")
|
||||||
|
|
||||||
|
// Process the ACK
|
||||||
|
DeliveryTracker.shared.processDeliveryAck(ack)
|
||||||
|
|
||||||
|
// Notify delegate
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.delegate?.didReceiveDeliveryAck(ack)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
print("⚠️ Failed to decode delivery ACK via Noise - data size: \(ackData.count)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to parse as a full inner packet (for backward compatibility and other message types)
|
// Try to parse as a full inner packet (for backward compatibility and other message types)
|
||||||
if let innerPacket = BitchatPacket.from(decryptedData) {
|
if let innerPacket = BitchatPacket.from(decryptedData) {
|
||||||
|
print("📦 Successfully parsed inner packet - type: \(MessageType(rawValue: innerPacket.type)?.description ?? "unknown"), from: \(innerPacket.senderID.hexEncodedString()), to: \(innerPacket.recipientID?.hexEncodedString() ?? "broadcast")")
|
||||||
|
|
||||||
// Process the decrypted inner packet
|
// Process the decrypted inner packet
|
||||||
// The packet will be handled according to its recipient ID
|
// The packet will be handled according to its recipient ID
|
||||||
// If it's for us, it won't be relayed
|
// If it's for us, it won't be relayed
|
||||||
handleReceivedPacket(innerPacket, from: peerID)
|
handleReceivedPacket(innerPacket, from: peerID)
|
||||||
|
} else {
|
||||||
|
print("⚠️ Failed to parse inner packet from decrypted data")
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Failed to decrypt - might need to re-establish session
|
// Failed to decrypt - might need to re-establish session
|
||||||
|
print("❌ Failed to decrypt Noise message from \(peerID): \(error)")
|
||||||
if !noiseService.hasEstablishedSession(with: peerID) {
|
if !noiseService.hasEstablishedSession(with: peerID) {
|
||||||
|
print("🔄 No Noise session with \(peerID), initiating handshake")
|
||||||
initiateNoiseHandshake(with: peerID)
|
initiateNoiseHandshake(with: peerID)
|
||||||
|
} else {
|
||||||
|
print("⚠️ Have session with \(peerID) but decryption failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3991,7 +4070,9 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
// Encrypt with Noise
|
// Encrypt with Noise
|
||||||
|
print("🔐 Encrypting private message \(msgID) for \(recipientPeerID)")
|
||||||
let encryptedData = try noiseService.encrypt(innerData, for: recipientPeerID)
|
let encryptedData = try noiseService.encrypt(innerData, for: recipientPeerID)
|
||||||
|
print("🔐 Successfully encrypted message, size: \(encryptedData.count)")
|
||||||
|
|
||||||
// Send as Noise encrypted message
|
// Send as Noise encrypted message
|
||||||
let outerPacket = BitchatPacket(
|
let outerPacket = BitchatPacket(
|
||||||
@@ -4004,9 +4085,11 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
ttl: adaptiveTTL
|
ttl: adaptiveTTL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
print("📤 Broadcasting encrypted private message \(msgID) to \(recipientPeerID)")
|
||||||
broadcastPacket(outerPacket)
|
broadcastPacket(outerPacket)
|
||||||
} catch {
|
} catch {
|
||||||
// Failed to encrypt message
|
// Failed to encrypt message
|
||||||
|
print("❌ Failed to encrypt private message \(msgID) for \(recipientPeerID): \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ class DeliveryTracker {
|
|||||||
// Don't track broadcasts or certain message types
|
// Don't track broadcasts or certain message types
|
||||||
guard message.isPrivate || message.channel != nil else { return }
|
guard message.isPrivate || message.channel != nil else { return }
|
||||||
|
|
||||||
|
print("📮 Tracking message \(message.id) - private: \(message.isPrivate), channel: \(message.channel ?? "none"), recipient: \(recipientNickname)")
|
||||||
|
|
||||||
|
|
||||||
let delivery = PendingDelivery(
|
let delivery = PendingDelivery(
|
||||||
messageID: message.id,
|
messageID: message.id,
|
||||||
@@ -89,9 +91,21 @@ class DeliveryTracker {
|
|||||||
pendingDeliveries[message.id] = delivery
|
pendingDeliveries[message.id] = delivery
|
||||||
pendingLock.unlock()
|
pendingLock.unlock()
|
||||||
|
|
||||||
// Update status to sent
|
// Update status to sent (only if not already delivered)
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
||||||
self?.updateDeliveryStatus(message.id, status: .sent)
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
self.pendingLock.lock()
|
||||||
|
let stillPending = self.pendingDeliveries[message.id] != nil
|
||||||
|
self.pendingLock.unlock()
|
||||||
|
|
||||||
|
// Only update to sent if still pending (not already delivered)
|
||||||
|
if stillPending {
|
||||||
|
print("⏱️ Updating message \(message.id) to sent status (still pending)")
|
||||||
|
self.updateDeliveryStatus(message.id, status: .sent)
|
||||||
|
} else {
|
||||||
|
print("✋ Skipping sent status update for \(message.id) - already delivered")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schedule timeout (outside of lock)
|
// Schedule timeout (outside of lock)
|
||||||
@@ -102,9 +116,11 @@ class DeliveryTracker {
|
|||||||
pendingLock.lock()
|
pendingLock.lock()
|
||||||
defer { pendingLock.unlock() }
|
defer { pendingLock.unlock() }
|
||||||
|
|
||||||
|
print("✅ Processing delivery ACK for message \(ack.originalMessageID) from \(ack.recipientNickname)")
|
||||||
|
|
||||||
// Prevent duplicate ACK processing
|
// Prevent duplicate ACK processing
|
||||||
guard !receivedAckIDs.contains(ack.ackID) else {
|
guard !receivedAckIDs.contains(ack.ackID) else {
|
||||||
|
print("⚠️ Duplicate ACK \(ack.ackID) - ignoring")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
receivedAckIDs.insert(ack.ackID)
|
receivedAckIDs.insert(ack.ackID)
|
||||||
@@ -112,6 +128,7 @@ class DeliveryTracker {
|
|||||||
// Find the pending delivery
|
// Find the pending delivery
|
||||||
guard var delivery = pendingDeliveries[ack.originalMessageID] else {
|
guard var delivery = pendingDeliveries[ack.originalMessageID] else {
|
||||||
// Message might have already been delivered or timed out
|
// Message might have already been delivered or timed out
|
||||||
|
print("⚠️ No pending delivery found for message \(ack.originalMessageID)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +153,7 @@ class DeliveryTracker {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Direct message - mark as delivered
|
// Direct message - mark as delivered
|
||||||
|
print("💬 Marking private message \(ack.originalMessageID) as delivered to \(ack.recipientNickname)")
|
||||||
updateDeliveryStatus(ack.originalMessageID, status: .delivered(to: ack.recipientNickname, at: Date()))
|
updateDeliveryStatus(ack.originalMessageID, status: .delivered(to: ack.recipientNickname, at: Date()))
|
||||||
pendingDeliveries.removeValue(forKey: ack.originalMessageID)
|
pendingDeliveries.removeValue(forKey: ack.originalMessageID)
|
||||||
}
|
}
|
||||||
@@ -180,6 +198,7 @@ class DeliveryTracker {
|
|||||||
// MARK: - Private Methods
|
// MARK: - Private Methods
|
||||||
|
|
||||||
private func updateDeliveryStatus(_ messageID: String, status: DeliveryStatus) {
|
private func updateDeliveryStatus(_ messageID: String, status: DeliveryStatus) {
|
||||||
|
print("📊 Updating delivery status for message \(messageID): \(status)")
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
self?.deliveryStatusUpdated.send((messageID: messageID, status: status))
|
self?.deliveryStatusUpdated.send((messageID: messageID, status: status))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,6 +207,11 @@ class NoiseEncryptionService {
|
|||||||
return sessionManager.getSession(for: peerID)?.isEstablished() ?? false
|
return sessionManager.getSession(for: peerID)?.isEstablished() ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if we have a session (established or handshaking) with a peer
|
||||||
|
func hasSession(with peerID: String) -> Bool {
|
||||||
|
return sessionManager.getSession(for: peerID) != nil
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Encryption/Decryption
|
// MARK: - Encryption/Decryption
|
||||||
|
|
||||||
/// Encrypt data for a specific peer
|
/// Encrypt data for a specific peer
|
||||||
@@ -471,11 +476,14 @@ struct NoiseMessage: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func fromBinaryData(_ data: Data) -> NoiseMessage? {
|
static func fromBinaryData(_ data: Data) -> NoiseMessage? {
|
||||||
|
// Create defensive copy
|
||||||
|
let dataCopy = Data(data)
|
||||||
|
|
||||||
var offset = 0
|
var offset = 0
|
||||||
|
|
||||||
guard let type = data.readUInt8(at: &offset),
|
guard let type = dataCopy.readUInt8(at: &offset),
|
||||||
let sessionID = data.readUUID(at: &offset),
|
let sessionID = dataCopy.readUUID(at: &offset),
|
||||||
let payload = data.readData(at: &offset) else { return nil }
|
let payload = dataCopy.readData(at: &offset) else { return nil }
|
||||||
|
|
||||||
guard let messageType = NoiseMessageType(rawValue: type) else { return nil }
|
guard let messageType = NoiseMessageType(rawValue: type) else { return nil }
|
||||||
|
|
||||||
|
|||||||
@@ -3654,6 +3654,7 @@ extension ChatViewModel: BitchatDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) {
|
private func updateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) {
|
||||||
|
print("🔄 Updating UI delivery status for message \(messageID): \(status)")
|
||||||
|
|
||||||
// Helper function to check if we should skip this update
|
// Helper function to check if we should skip this update
|
||||||
func shouldSkipUpdate(currentStatus: DeliveryStatus?, newStatus: DeliveryStatus) -> Bool {
|
func shouldSkipUpdate(currentStatus: DeliveryStatus?, newStatus: DeliveryStatus) -> Bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user