// // ArpResolver.swift // sdlan // 1. 通过ip地址查找mac地址 // 2. 要限制单位时间内,同一个ip的查询 // Created by 安礼成 on 2025/7/14. // import Foundation import Darwin actor ArpResolver { // 增加缓存时间逻辑 struct ArpEntry { var mac: Data var expireTime: TimeInterval } private var coolingDown: [UInt32: Date] = [:] private var known_macs: [UInt32: ArpEntry] = [:] private let arpTTL: TimeInterval nonisolated private let snapshotPublisher: SnapshotPublisher private var cleanupTask: Task? init(arpTTL: TimeInterval = 300) { self.arpTTL = arpTTL self.snapshotPublisher = SnapshotPublisher(initial: ArpSnapshot.empty()) } func start() { guard self.cleanupTask == nil else { return } self.cleanupTask = Task { [weak self] in while !Task.isCancelled { try? await Task.sleep(for: .seconds(1)) await self?.cleanup() } } } func query(ip: UInt32) -> Data? { guard let entry = known_macs[ip] else { return nil } if entry.expireTime < Date().timeIntervalSince1970 { known_macs.removeValue(forKey: ip) self.publishSnapshot() return nil } return entry.mac } func append(ip: UInt32, mac: Data) { let expireAt = Date().timeIntervalSince1970 + arpTTL self.known_macs[ip] = ArpEntry(mac: mac, expireTime: expireAt) self.publishSnapshot() } func remove(ip: UInt32) { self.known_macs.removeValue(forKey: ip) self.publishSnapshot() } func dropMacs(macs: [Data]) { self.known_macs = self.known_macs.filter { !macs.contains($0.value.mac) } self.publishSnapshot() } func clear() { self.known_macs = [:] self.coolingDown = [:] self.publishSnapshot() } func stop() { self.cleanupTask?.cancel() self.cleanupTask = nil self.clear() } func makeArpRequest(targetIp: UInt32) throws -> Data? { guard self.coolingDown[targetIp] == nil else { return nil } self.coolingDown[targetIp] = Date().addingTimeInterval(3) var arpRequest = SDLArpRequest() arpRequest.targetIp = targetIp return try arpRequest.serializedData() } func handleArpResponse(arpResponse: SDLArpResponse) { let targetIp = arpResponse.targetIp let targetMac = arpResponse.targetMac if !targetMac.isEmpty { let expireAt = Date().timeIntervalSince1970 + arpTTL self.known_macs[targetIp] = ArpEntry(mac: targetMac, expireTime: expireAt) self.publishSnapshot() } } nonisolated func snapshot() -> ArpSnapshot { return self.snapshotPublisher.current() } private func cleanup() { let now = Date() self.coolingDown = self.coolingDown.filter { $0.value > now } let oldCount = self.known_macs.count self.known_macs = self.known_macs.filter { $0.value.expireTime >= now.timeIntervalSince1970 } if self.known_macs.count != oldCount { self.publishSnapshot() } } private func publishSnapshot() { self.snapshotPublisher.publish(self.compileSnapshot()) } private func compileSnapshot() -> ArpSnapshot { let now = Date().timeIntervalSince1970 let entries = self.known_macs.compactMapValues { entry in entry.expireTime >= now ? entry.mac : nil } return ArpSnapshot(entries: entries) } deinit { self.cleanupTask?.cancel() } }