1// DHTStore.swift2// SophaxChatCore3//4// Local key-value store for DHT STORE requests.5// Keyed by full 256-bit nodeID (hex). Secondary index allows prefix lookup6// by 16-char peerID so callers need not know the full key.78import Foundation910public actor DHTStore {1112/// Default TTL — slightly longer than the 24h republish interval.13 public static let defaultTTL: TimeInterval = 25 * 60 * 601415 private struct Entry {16 let bundle: PreKeyBundle17 let expiresAt: Date18}1920/// nodeID(hex64) → Entry21 private var entries:[String: Entry]=[:]22/// peerID(hex16) → nodeID(hex64) — built from the first 16 chars of each stored nodeID.23 private var prefixIndex:[String: String]=[:]2425 public init(){}2627// MARK: - Mutations2829 public func store(nodeID: String, bundle: PreKeyBundle,30 expiresAt: Date = Date().addingTimeInterval(DHTStore.defaultTTL)){31 entries[nodeID]= Entry(bundle: bundle, expiresAt: expiresAt)32 let prefix = String(nodeID.prefix(16))33 prefixIndex[prefix]= nodeID34}3536 public func remove(nodeID: String){37 let prefix = String(nodeID.prefix(16))38 if prefixIndex[prefix]== nodeID { prefixIndex.removeValue(forKey: prefix)}39 entries.removeValue(forKey: nodeID)40}4142 public func purgeExpired(){43 let now = Date()44 let expired = entries.filter { $0.value.expiresAt < now }.map(\.key)45 for key in expired { remove(nodeID: key)}46}4748// MARK: - Queries4950/// Lookup by full 256-bit nodeID (hex64).51 public func lookup(nodeID: String)-> PreKeyBundle?{52 guard let entry = entries[nodeID], entry.expiresAt > Date() else { return nil }53 return entry.bundle54}5556/// Lookup by 16-char peerID prefix.57 public func lookupByPrefix(_ peerID: String)-> PreKeyBundle?{58 guard let nodeID = prefixIndex[peerID] else { return nil }59 return lookup(nodeID: nodeID)60}6162/// Full nodeID for a given peerID prefix, if known.63 public func nodeID(forPeerID peerID: String)-> String?{64 prefixIndex[peerID]65}6667 public func allNodeIDs()->[String]{ Array(entries.keys)}68}