punchnet-macos/Tun/Punchnet/Actors/SDLPuncherActor.swift
2026-01-30 15:23:35 +08:00

111 lines
3.4 KiB
Swift

//
// SDLPuncherActor.swift
// Tun
//
// Created by on 2026/1/7.
//
import Foundation
import NIOCore
actor SDLPuncherActor {
// dstMac
private var coolingDown: Set<Data> = []
private let cooldown: Duration = .seconds(5)
private var udpHole: SDLUDPHole
private var pktId: UInt32 = 1
//
private var pendingRequests: [UInt32:RegisterRequest] = [:]
// holer
private var logger: SDLLogger
private var querySocketAddress: SocketAddress
nonisolated private let(requestStream, requestContinuation) = AsyncStream.makeStream(of: RegisterRequest.self, bufferingPolicy: .unbounded)
struct RegisterRequest {
let srcMac: Data
let dstMac: Data
let networkId: UInt32
}
init(udpHole: SDLUDPHole, querySocketAddress: SocketAddress, logger: SDLLogger) {
self.udpHole = udpHole
self.querySocketAddress = querySocketAddress
self.logger = logger
}
nonisolated func submitRegisterRequest(request: RegisterRequest) {
self.requestContinuation.yield(request)
}
func startConsuming() async {
for await request in self.requestStream {
self.submitRegisterRequest(request: request)
}
}
private func handleRegisterRequest(request: RegisterRequest) {
let dstMac = request.dstMac
guard !coolingDown.contains(dstMac) else {
return
}
//
coolingDown.insert(dstMac)
let pktId = self.pktId
self.pktId &+= 1
if self.pktId == 0 {
self.pktId = 1
}
Task {
await self.tryHole(pktId: pktId, request: request)
//
try? await Task.sleep(for: .seconds(5))
self.endCooldown(for: dstMac)
self.removePendingRequest(for: pktId)
}
}
func handlePeerInfo(peerInfo: SDLPeerInfo) async {
if let request = pendingRequests.removeValue(forKey: peerInfo.pktID) {
if let remoteAddress = peerInfo.v4Info.socketAddress() {
self.logger.log("[SDLContext] hole sock address: \(remoteAddress)", level: .debug)
// register
var register = SDLRegister()
register.networkID = request.networkId
register.srcMac = request.srcMac
register.dstMac = request.dstMac
self.udpHole.send(type: .register, data: try! register.serializedData(), remoteAddress: remoteAddress)
} else {
self.logger.log("[SDLContext] hole sock address is invalid: \(peerInfo.v4Info)", level: .warning)
}
}
}
private func endCooldown(for key: Data) {
self.coolingDown.remove(key)
}
private func removePendingRequest(for pktId: UInt32) {
self.pendingRequests.removeValue(forKey: pktId)
}
private func tryHole(pktId: UInt32, request: RegisterRequest) async {
var queryInfo = SDLQueryInfo()
queryInfo.pktID = pktId
queryInfo.dstMac = request.dstMac
self.pendingRequests[pktId] = request
if let queryData = try? queryInfo.serializedData() {
self.udpHole.send(type: .queryInfo, data: queryData, remoteAddress: self.querySocketAddress)
}
}
}