// MeshProtocol.swift
// Wire protocol for the mesh network: routing, deduplication, TTL
import Foundation
// MARK: - Message Types
enum MeshMessageType: UInt8, Codable {
case chat = 0x01 // message chiffré pairwise (DM)
case keyExchange = 0x02 // annonce de clé publique
case ack = 0x03 // accusé de réception
case discovery = 0x04 // ping de découverte
case groupChat = 0x05 // message chiffré avec la clé de groupe
case groupKeyDistrib = 0x06 // distribution unicast de la clé de groupe
}
// MARK: - Mesh Envelope
struct MeshEnvelope: Codable {
let id: String // UUID – déduplication
let type: MeshMessageType
let senderID: String
let recipientID: String // peerID, groupID, ou "*"
let ttl: UInt8
let payload: Data
let timestamp: Double
static func makeChat(
senderID: String,
recipientID: String,
payload: Data,
ttl: UInt8 = 7
) -> MeshEnvelope {
MeshEnvelope(id: UUID().uuidString, type: .chat,
senderID: senderID, recipientID: recipientID,
ttl: ttl, payload: payload,
timestamp: Date().timeIntervalSince1970)
}
static func makeKeyExchange(senderID: String, publicKeyEncoded: Data) -> MeshEnvelope {
MeshEnvelope(id: UUID().uuidString, type: .keyExchange,
senderID: senderID, recipientID: "*",
ttl: 5, payload: publicKeyEncoded,
timestamp: Date().timeIntervalSince1970)
}
static func makeAck(senderID: String, recipientID: String, messageID: String) -> MeshEnvelope {
MeshEnvelope(id: UUID().uuidString, type: .ack,
senderID: senderID, recipientID: recipientID,
ttl: 5, payload: messageID.data(using: .utf8) ?? Data(),
timestamp: Date().timeIntervalSince1970)
}
static func makeGroupChat(senderID: String, groupID: String, payload: Data) -> MeshEnvelope {
MeshEnvelope(id: UUID().uuidString, type: .groupChat,
senderID: senderID, recipientID: groupID,
ttl: 7, payload: payload,
timestamp: Date().timeIntervalSince1970)
}
static func makeGroupKeyDistrib(senderID: String, recipientID: String, payload: Data) -> MeshEnvelope {
MeshEnvelope(id: UUID().uuidString, type: .groupKeyDistrib,
senderID: senderID, recipientID: recipientID,
ttl: 5, payload: payload,
timestamp: Date().timeIntervalSince1970)
}
func forwarded() -> MeshEnvelope? {
guard ttl > 1 else { return nil }
return MeshEnvelope(id: id, type: type,
senderID: senderID, recipientID: recipientID,
ttl: ttl - 1, payload: payload, timestamp: timestamp)
}
func encoded() -> Data? { try? JSONEncoder().encode(self) }
static func decode(_ data: Data) -> MeshEnvelope? { try? JSONDecoder().decode(MeshEnvelope.self, from: data) }
}
// MARK: - Chat Payload (DM pairwise)
struct ChatPayload: Codable {
let messageID: String
let encryptedMessage: EncryptedMessage
func encoded() -> Data? { try? JSONEncoder().encode(self) }
static func decode(_ data: Data) -> ChatPayload? { try? JSONDecoder().decode(ChatPayload.self, from: data) }
}
// MARK: - Group Chat Payload
struct GroupChatPayload: Codable {
let groupID: String
let messageID: String
let aesCiphertext: Data // nonce(12) + AES-GCM(groupKey, plaintext) + tag(16)
func encoded() -> Data? { try? JSONEncoder().encode(self) }
static func decode(_ data: Data) -> GroupChatPayload? { try? JSONDecoder().decode(GroupChatPayload.self, from: data) }
}
// MARK: - Group Key Distribution Payload
struct GroupKeyPayload: Codable {
let groupID: String
let groupName: String
let memberIDs: [String]
let encryptedGroupKey: EncryptedMessage // 32 octets de clé AES chiffrés avec la clé XWing du destinataire
func encoded() -> Data? { try? JSONEncoder().encode(self) }
static func decode(_ data: Data) -> GroupKeyPayload? { try? JSONDecoder().decode(GroupKeyPayload.self, from: data) }
}
// MARK: - Dedup Cache (Thread-safe LRU)
class SeenMessageCache {
private var seen = Set<String>()
private var queue = [String]()
private let maxSize = 1000
private let lock = NSLock()
func contains(_ id: String) -> Bool {
lock.lock()
defer { lock.unlock() }
return seen.contains(id)
}
func insert(_ id: String) {
lock.lock()
defer { lock.unlock() }
guard !seen.contains(id) else { return }
seen.insert(id)
queue.append(id)
if queue.count > maxSize {
let old = queue.removeFirst()
seen.remove(old)
}
}
func clear() {
lock.lock()
defer { lock.unlock() }
seen.removeAll()
queue.removeAll()
}
}