// // SDLPuncherActor.swift // Tun // // Created by 安礼成 on 2026/1/7. // import Foundation import NIOCore actor SDLPuncherActor { nonisolated private let cooldownInterval: TimeInterval = 5 // dstMac private var coolingDown: [Data: (UInt32, Date)] = [:] private var pktId: UInt32 = 1 // 提交后还没有响应的请求 private var pendingRequests: [UInt32: RegisterRequest] = [:] private var cleanupTask: Task? struct RegisterRequest { let srcMac: Data let dstMac: Data let networkId: UInt32 } // 启动定时清理任务 func start() { self.cleanupTask?.cancel() self.cleanupTask = Task { while !Task.isCancelled { try? await Task.sleep(for: .seconds(1)) self.cleanExpiredCoolingDown() } } } func submitRegisterRequest(quicClient: SDLQUICClient?, request: RegisterRequest) { let dstMac = request.dstMac guard let quicClient, coolingDown[dstMac] == nil else { return } let pktId = self.nextPacketId() coolingDown[dstMac] = (pktId, Date().addingTimeInterval(cooldownInterval)) // 触发一次打洞 var queryInfo = SDLQueryInfo() queryInfo.pktID = pktId queryInfo.dstMac = request.dstMac self.pendingRequests[pktId] = request if let queryData = try? queryInfo.serializedData() { quicClient.send(type: .queryInfo, data: queryData) } } func handlePeerInfo(using udpHole: SDLUDPHole?, peerInfo: SDLPeerInfo) async { guard let udpHole, let request = pendingRequests.removeValue(forKey: peerInfo.pktID), let remoteAddress = try? await peerInfo.v4Info.socketAddress() else { return } SDLLogger.shared.log("[SDLContext] hole sock address: \(remoteAddress)", level: .debug) // 发送register包 var register = SDLRegister() register.networkID = request.networkId register.srcMac = request.srcMac register.dstMac = request.dstMac if let registerData = try? register.serializedData() { udpHole.send(type: .register, data: registerData, remoteAddress: remoteAddress) } } private func nextPacketId() -> UInt32 { let pktId = self.pktId self.pktId &+= 1 if self.pktId == 0 { self.pktId = 1 } return pktId } private func cleanExpiredCoolingDown() { let date = Date() for (key, (pktId, expireAt)) in coolingDown { if expireAt < date { self.coolingDown.removeValue(forKey: key) self.pendingRequests.removeValue(forKey: pktId) } } } deinit { self.cleanupTask?.cancel() self.cleanupTask = nil } }