// // ArpServer.swift // sdlan // 1. 通过ip地址查找mac地址 // 2. 要限制单位时间内,同一个ip的查询 // Created by 安礼成 on 2025/7/14. // import Foundation import Darwin actor ArpServer { // 增加缓存时间逻辑 struct ArpEntry { var mac: Data var expireTime: TimeInterval } private var coolingDown: [UInt32: Date] = [:] private var packetId: UInt32 = 1 private var known_macs: [UInt32: ArpEntry] = [:] private let arpTTL: TimeInterval private var cleanupTask: Task? init(arpTTL: TimeInterval = 300) { self.arpTTL = arpTTL } func start() { guard self.cleanupTask == nil else { return } self.cleanupTask = Task { while !Task.isCancelled { try? await Task.sleep(for: .seconds(1)) 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) 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) } func remove(ip: UInt32) { self.known_macs.removeValue(forKey: ip) } func dropMacs(macs: [Data]) { self.known_macs = self.known_macs.filter { !macs.contains($0.value.mac) } } func clear() { self.known_macs = [:] } func arpRequest(targetIp: UInt32, use quicClient: SDLQUICClient?) throws { guard let quicClient, self.coolingDown[targetIp] == nil else { return } let pktID = self.packetId self.packetId &+= 1 // 单位时间内指允许提交一次 self.coolingDown[targetIp] = Date().addingTimeInterval(3) // 进行arp查询 var arpRequest = SDLArpRequest() arpRequest.targetIp = targetIp arpRequest.pktID = pktID quicClient.send(type: .arpRequest, data: 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) } } private func cleanup() { let now = Date() self.coolingDown = self.coolingDown.filter { $0.value > now } } deinit { self.cleanupTask?.cancel() } }