mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 20:05:19 +00:00
Fix handshake deadlock after decryption failure
Always accept handshake initiations even with valid sessions, as the peer must have cleared their session for a good reason (e.g., decryption failure). This prevents deadlock where one peer has no session while the other maintains an invalid one.
This commit is contained in:
@@ -96,6 +96,11 @@ enum MessageType: UInt8 {
|
||||
case versionHello = 0x20 // Initial version announcement
|
||||
case versionAck = 0x21 // Version acknowledgment
|
||||
|
||||
// Protocol-level acknowledgments
|
||||
case protocolAck = 0x22 // Generic protocol acknowledgment
|
||||
case protocolNack = 0x23 // Negative acknowledgment (failure)
|
||||
case systemValidation = 0x24 // Session validation ping
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .announce: return "announce"
|
||||
@@ -113,6 +118,9 @@ enum MessageType: UInt8 {
|
||||
case .noiseIdentityAnnounce: return "noiseIdentityAnnounce"
|
||||
case .versionHello: return "versionHello"
|
||||
case .versionAck: return "versionAck"
|
||||
case .protocolAck: return "protocolAck"
|
||||
case .protocolNack: return "protocolNack"
|
||||
case .systemValidation: return "systemValidation"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,6 +360,172 @@ struct ReadReceipt: Codable {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Protocol Acknowledgments
|
||||
|
||||
// Protocol-level acknowledgment for reliable delivery
|
||||
struct ProtocolAck: Codable {
|
||||
let originalPacketID: String // ID of the packet being acknowledged
|
||||
let ackID: String // Unique ID for this ACK
|
||||
let senderID: String // Who sent the original packet
|
||||
let receiverID: String // Who received and is acknowledging
|
||||
let packetType: UInt8 // Type of packet being acknowledged
|
||||
let timestamp: Date // When ACK was generated
|
||||
let hopCount: UInt8 // Hops taken to reach receiver
|
||||
|
||||
init(originalPacketID: String, senderID: String, receiverID: String, packetType: UInt8, hopCount: UInt8) {
|
||||
self.originalPacketID = originalPacketID
|
||||
self.ackID = UUID().uuidString
|
||||
self.senderID = senderID
|
||||
self.receiverID = receiverID
|
||||
self.packetType = packetType
|
||||
self.timestamp = Date()
|
||||
self.hopCount = hopCount
|
||||
}
|
||||
|
||||
// Private init for binary decoding
|
||||
private init(originalPacketID: String, ackID: String, senderID: String, receiverID: String,
|
||||
packetType: UInt8, timestamp: Date, hopCount: UInt8) {
|
||||
self.originalPacketID = originalPacketID
|
||||
self.ackID = ackID
|
||||
self.senderID = senderID
|
||||
self.receiverID = receiverID
|
||||
self.packetType = packetType
|
||||
self.timestamp = timestamp
|
||||
self.hopCount = hopCount
|
||||
}
|
||||
|
||||
func toBinaryData() -> Data {
|
||||
var data = Data()
|
||||
data.appendUUID(originalPacketID)
|
||||
data.appendUUID(ackID)
|
||||
|
||||
// Sender and receiver IDs as 8-byte hex strings
|
||||
data.append(Data(hexString: senderID) ?? Data(repeating: 0, count: 8))
|
||||
data.append(Data(hexString: receiverID) ?? Data(repeating: 0, count: 8))
|
||||
|
||||
data.appendUInt8(packetType)
|
||||
data.appendUInt8(hopCount)
|
||||
data.appendDate(timestamp)
|
||||
return data
|
||||
}
|
||||
|
||||
static func fromBinaryData(_ data: Data) -> ProtocolAck? {
|
||||
let dataCopy = Data(data)
|
||||
guard dataCopy.count >= 50 else { return nil } // 2 UUIDs + 2 IDs + type + hop + timestamp
|
||||
|
||||
var offset = 0
|
||||
guard let originalPacketID = dataCopy.readUUID(at: &offset),
|
||||
let ackID = dataCopy.readUUID(at: &offset),
|
||||
let senderIDData = dataCopy.readFixedBytes(at: &offset, count: 8),
|
||||
let receiverIDData = dataCopy.readFixedBytes(at: &offset, count: 8),
|
||||
let packetType = dataCopy.readUInt8(at: &offset),
|
||||
let hopCount = dataCopy.readUInt8(at: &offset),
|
||||
let timestamp = dataCopy.readDate(at: &offset) else { return nil }
|
||||
|
||||
let senderID = senderIDData.hexEncodedString()
|
||||
let receiverID = receiverIDData.hexEncodedString()
|
||||
|
||||
return ProtocolAck(originalPacketID: originalPacketID,
|
||||
ackID: ackID,
|
||||
senderID: senderID,
|
||||
receiverID: receiverID,
|
||||
packetType: packetType,
|
||||
timestamp: timestamp,
|
||||
hopCount: hopCount)
|
||||
}
|
||||
}
|
||||
|
||||
// Protocol-level negative acknowledgment
|
||||
struct ProtocolNack: Codable {
|
||||
let originalPacketID: String // ID of the packet that failed
|
||||
let nackID: String // Unique ID for this NACK
|
||||
let senderID: String // Who sent the original packet
|
||||
let receiverID: String // Who is reporting the failure
|
||||
let packetType: UInt8 // Type of packet that failed
|
||||
let timestamp: Date // When NACK was generated
|
||||
let reason: String // Reason for failure
|
||||
let errorCode: UInt8 // Numeric error code
|
||||
|
||||
// Error codes
|
||||
enum ErrorCode: UInt8 {
|
||||
case unknown = 0
|
||||
case checksumFailed = 1
|
||||
case decryptionFailed = 2
|
||||
case malformedPacket = 3
|
||||
case unsupportedVersion = 4
|
||||
case resourceExhausted = 5
|
||||
case routingFailed = 6
|
||||
case sessionExpired = 7
|
||||
}
|
||||
|
||||
init(originalPacketID: String, senderID: String, receiverID: String,
|
||||
packetType: UInt8, reason: String, errorCode: ErrorCode = .unknown) {
|
||||
self.originalPacketID = originalPacketID
|
||||
self.nackID = UUID().uuidString
|
||||
self.senderID = senderID
|
||||
self.receiverID = receiverID
|
||||
self.packetType = packetType
|
||||
self.timestamp = Date()
|
||||
self.reason = reason
|
||||
self.errorCode = errorCode.rawValue
|
||||
}
|
||||
|
||||
// Private init for binary decoding
|
||||
private init(originalPacketID: String, nackID: String, senderID: String, receiverID: String,
|
||||
packetType: UInt8, timestamp: Date, reason: String, errorCode: UInt8) {
|
||||
self.originalPacketID = originalPacketID
|
||||
self.nackID = nackID
|
||||
self.senderID = senderID
|
||||
self.receiverID = receiverID
|
||||
self.packetType = packetType
|
||||
self.timestamp = timestamp
|
||||
self.reason = reason
|
||||
self.errorCode = errorCode
|
||||
}
|
||||
|
||||
func toBinaryData() -> Data {
|
||||
var data = Data()
|
||||
data.appendUUID(originalPacketID)
|
||||
data.appendUUID(nackID)
|
||||
|
||||
// Sender and receiver IDs as 8-byte hex strings
|
||||
data.append(Data(hexString: senderID) ?? Data(repeating: 0, count: 8))
|
||||
data.append(Data(hexString: receiverID) ?? Data(repeating: 0, count: 8))
|
||||
|
||||
data.appendUInt8(packetType)
|
||||
data.appendUInt8(errorCode)
|
||||
data.appendDate(timestamp)
|
||||
data.appendString(reason)
|
||||
return data
|
||||
}
|
||||
|
||||
static func fromBinaryData(_ data: Data) -> ProtocolNack? {
|
||||
let dataCopy = Data(data)
|
||||
guard dataCopy.count >= 52 else { return nil } // Minimum size
|
||||
|
||||
var offset = 0
|
||||
guard let originalPacketID = dataCopy.readUUID(at: &offset),
|
||||
let nackID = dataCopy.readUUID(at: &offset),
|
||||
let senderIDData = dataCopy.readFixedBytes(at: &offset, count: 8),
|
||||
let receiverIDData = dataCopy.readFixedBytes(at: &offset, count: 8),
|
||||
let packetType = dataCopy.readUInt8(at: &offset),
|
||||
let errorCode = dataCopy.readUInt8(at: &offset),
|
||||
let timestamp = dataCopy.readDate(at: &offset),
|
||||
let reason = dataCopy.readString(at: &offset) else { return nil }
|
||||
|
||||
let senderID = senderIDData.hexEncodedString()
|
||||
let receiverID = receiverIDData.hexEncodedString()
|
||||
|
||||
return ProtocolNack(originalPacketID: originalPacketID,
|
||||
nackID: nackID,
|
||||
senderID: senderID,
|
||||
receiverID: receiverID,
|
||||
packetType: packetType,
|
||||
timestamp: timestamp,
|
||||
reason: reason,
|
||||
errorCode: errorCode)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Peer Identity Rotation
|
||||
|
||||
@@ -796,6 +970,9 @@ protocol BitchatDelegate: AnyObject {
|
||||
func didReceiveDeliveryAck(_ ack: DeliveryAck)
|
||||
func didReceiveReadReceipt(_ receipt: ReadReceipt)
|
||||
func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus)
|
||||
|
||||
// Peer availability tracking
|
||||
func peerAvailabilityChanged(_ peerID: String, available: Bool)
|
||||
}
|
||||
|
||||
// Provide default implementation to make it effectively optional
|
||||
@@ -815,4 +992,8 @@ extension BitchatDelegate {
|
||||
func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) {
|
||||
// Default empty implementation
|
||||
}
|
||||
|
||||
func peerAvailabilityChanged(_ peerID: String, available: Bool) {
|
||||
// Default empty implementation
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user