// // SDLPuncherActor.swift // Tun // // Created by 安礼成 on 2026/1/7. // import Foundation import NIOCore actor SDLPuncherActor { // 10秒内只需要提交一次查询 nonisolated private let cooldownInterval: TimeInterval = 10 // 等待peerInfo返回的超时时间 nonisolated private let peerInfoTimeout: TimeInterval = 3 struct RegisterRequest { let srcMac: Data let dstMac: Data let networkId: UInt32 } private enum RequestPhase { case waitingPeerInfo(deadline: Date) case coolingDown } private struct RequestEntry { let request: RegisterRequest let cooldownUntil: Date var phase: RequestPhase func canSubmit(at now: Date) -> Bool { return cooldownUntil <= now } func isWaitingPeerInfo(at now: Date) -> Bool { guard case .waitingPeerInfo(let deadline) = self.phase else { return false } return deadline > now } mutating func markCoolingDown() { self.phase = .coolingDown } } // dstMac private var requestEntries: [Data: RequestEntry] = [:] private var cleanupTask: Task? 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?.cleanupExpiredEntries() } } } func submitRegisterRequest(quicClient: SDLQUICClient?, request: RegisterRequest) { guard let quicClient else { return } let now = Date() self.cleanupExpiredEntries(now: now) if let entry = self.requestEntries[request.dstMac], !entry.canSubmit(at: now) { return } var queryInfo = SDLQueryInfo() queryInfo.dstMac = request.dstMac guard let queryData = try? queryInfo.serializedData() else { SDLLogger.log("[SDLPuncherActor] failed to encode queryInfo", for: .debug) return } self.requestEntries[request.dstMac] = RequestEntry( request: request, cooldownUntil: now.addingTimeInterval(self.cooldownInterval), phase: .waitingPeerInfo(deadline: now.addingTimeInterval(self.peerInfoTimeout)) ) quicClient.send(type: .queryInfo, data: queryData) } func handlePeerInfo(using udpHole: SDLUDPHole?, udpHoleV6: SDLUDPHoleV6?, peerInfo: SDLPeerInfo) async { let now = Date() self.cleanupExpiredEntries(now: now) guard var entry = self.requestEntries[peerInfo.dstMac] else { return } guard entry.isWaitingPeerInfo(at: now) else { return } entry.markCoolingDown() self.requestEntries[peerInfo.dstMac] = entry var register = SDLRegister() register.networkID = entry.request.networkId register.srcMac = entry.request.srcMac register.dstMac = entry.request.dstMac guard let registerData = try? register.serializedData() else { SDLLogger.log("[SDLPuncherActor] failed to encode register", for: .debug) return } // 并行发送register请求 if let udpHole, peerInfo.hasV4Info { if let remoteAddress = try? await peerInfo.v4Info.socketAddress() { SDLLogger.log("[SDLContext] hole sock address: \(remoteAddress)", for: .punchnet) udpHole.send(type: .register, data: registerData, remoteAddress: remoteAddress) } else { SDLLogger.log("[SDLPuncherActor] failed to resolve peerInfo.v4Info", for: .debug) } } if let udpHoleV6, peerInfo.hasV6Info { if let remoteAddress = try? await peerInfo.v6Info.socketAddress() { SDLLogger.log("[SDLContext] hole sock address v6: \(remoteAddress)", for: .punchnet) udpHoleV6.send(type: .register, data: registerData, remoteAddress: remoteAddress) } else { SDLLogger.log("[SDLPuncherActor] failed to resolve peerInfo.v6Info", for: .debug) } } } func stop() { self.cleanupTask?.cancel() self.cleanupTask = nil self.requestEntries.removeAll() } private func cleanupExpiredEntries(now: Date = Date()) { self.requestEntries = self.requestEntries.filter { _, entry in !entry.canSubmit(at: now) } } deinit { self.cleanupTask?.cancel() } }