Implement handshake request notifications for pending messages (#321)

- Add handshakeRequest packet type (0x25) to notify recipients about queued messages
- Create HandshakeRequest struct with binary encoding for efficient transmission
- Send handshake requests when messages are queued due to missing session
- Display notifications when someone wants to send messages
- Fix verification persistence bug by adding forceSave on app termination
- Update UI to handle optional encryption icons (hide when no handshake attempted)

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
jack
2025-07-25 14:55:26 +02:00
committed by GitHub
co-authored by jack
parent 07c5e2e097
commit 3513228736
7 changed files with 416 additions and 149 deletions
+111
View File
@@ -100,6 +100,7 @@ enum MessageType: UInt8 {
case protocolAck = 0x22 // Generic protocol acknowledgment
case protocolNack = 0x23 // Negative acknowledgment (failure)
case systemValidation = 0x24 // Session validation ping
case handshakeRequest = 0x25 // Request handshake for pending messages
var description: String {
switch self {
@@ -121,10 +122,20 @@ enum MessageType: UInt8 {
case .protocolAck: return "protocolAck"
case .protocolNack: return "protocolNack"
case .systemValidation: return "systemValidation"
case .handshakeRequest: return "handshakeRequest"
}
}
}
// Lazy handshake state tracking
enum LazyHandshakeState {
case none // No session, no handshake attempted
case handshakeQueued // User action requires handshake
case handshaking // Currently in handshake process
case established // Session ready for use
case failed(Error) // Handshake failed
}
// Special recipient ID for broadcast messages
struct SpecialRecipients {
static let broadcast = Data(repeating: 0xFF, count: 8) // All 0xFF = broadcast
@@ -360,6 +371,106 @@ struct ReadReceipt: Codable {
}
}
// Handshake request for pending messages
struct HandshakeRequest: Codable {
let requestID: String
let requesterID: String // Who needs the handshake
let requesterNickname: String // Nickname of requester
let targetID: String // Who should initiate handshake
let pendingMessageCount: UInt8 // Number of messages queued
let timestamp: Date
init(requesterID: String, requesterNickname: String, targetID: String, pendingMessageCount: UInt8) {
self.requestID = UUID().uuidString
self.requesterID = requesterID
self.requesterNickname = requesterNickname
self.targetID = targetID
self.pendingMessageCount = pendingMessageCount
self.timestamp = Date()
}
// For binary decoding
private init(requestID: String, requesterID: String, requesterNickname: String, targetID: String, pendingMessageCount: UInt8, timestamp: Date) {
self.requestID = requestID
self.requesterID = requesterID
self.requesterNickname = requesterNickname
self.targetID = targetID
self.pendingMessageCount = pendingMessageCount
self.timestamp = timestamp
}
// MARK: - Binary Encoding
func toBinaryData() -> Data {
var data = Data()
data.appendUUID(requestID)
// RequesterID as 8-byte hex string
var requesterData = Data()
var tempID = requesterID
while tempID.count >= 2 && requesterData.count < 8 {
let hexByte = String(tempID.prefix(2))
if let byte = UInt8(hexByte, radix: 16) {
requesterData.append(byte)
}
tempID = String(tempID.dropFirst(2))
}
while requesterData.count < 8 {
requesterData.append(0)
}
data.append(requesterData)
// TargetID as 8-byte hex string
var targetData = Data()
tempID = targetID
while tempID.count >= 2 && targetData.count < 8 {
let hexByte = String(tempID.prefix(2))
if let byte = UInt8(hexByte, radix: 16) {
targetData.append(byte)
}
tempID = String(tempID.dropFirst(2))
}
while targetData.count < 8 {
targetData.append(0)
}
data.append(targetData)
data.appendUInt8(pendingMessageCount)
data.appendDate(timestamp)
data.appendString(requesterNickname)
return data
}
static func fromBinaryData(_ data: Data) -> HandshakeRequest? {
// Create defensive copy
let dataCopy = Data(data)
// Minimum size: UUID (16) + requesterID (8) + targetID (8) + count (1) + timestamp (8) + min nickname
guard dataCopy.count >= 42 else { return nil }
var offset = 0
guard let requestID = dataCopy.readUUID(at: &offset) else { return nil }
guard let requesterIDData = dataCopy.readFixedBytes(at: &offset, count: 8) else { return nil }
let requesterID = requesterIDData.hexEncodedString()
guard let targetIDData = dataCopy.readFixedBytes(at: &offset, count: 8) else { return nil }
let targetID = targetIDData.hexEncodedString()
guard let pendingMessageCount = dataCopy.readUInt8(at: &offset),
let timestamp = dataCopy.readDate(at: &offset),
let requesterNickname = dataCopy.readString(at: &offset) else { return nil }
return HandshakeRequest(requestID: requestID,
requesterID: requesterID,
requesterNickname: requesterNickname,
targetID: targetID,
pendingMessageCount: pendingMessageCount,
timestamp: timestamp)
}
}
// MARK: - Protocol Acknowledgments
// Protocol-level acknowledgment for reliable delivery