Gitfed
bastien-mrq/bt-message / btmessage / Mesh / MeshProtocol.swift
// 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()
    }
}