import Foundation import NIOCore actor SDLUDPHoleV6Session { private let onEvent: SDLUDPHoleService.EventHandler private let onData: SDLUDPHoleService.DataHandler private var udpHoleV6: SDLUDPHoleV6? init( onEvent: @escaping SDLUDPHoleService.EventHandler, onData: @escaping SDLUDPHoleService.DataHandler ) { self.onEvent = onEvent self.onData = onData } func run() async throws { let udpHoleV6 = try SDLUDPHoleV6() let localAddress = try udpHoleV6.start() self.udpHoleV6 = udpHoleV6 if let localAddress { SDLLogger.log("[SDLUDPHoleV6Session] udpHoleV6 started, on address: \(localAddress)") } else { SDLLogger.log("[SDLUDPHoleV6Session] udpHoleV6 started, no local address") } do { try await withThrowingTaskGroup(of: Void.self) { group in defer { group.cancelAll() } let onEvent = self.onEvent let onData = self.onData group.addTask { for await (remoteAddress, message) in udpHoleV6.messageStream { try Task.checkCancellation() switch message { case .control(let control): await onEvent(.packet(remoteAddress, control)) case .data(let data): await onData(data) } } } group.addTask { for await event in udpHoleV6.eventStream { try Task.checkCancellation() switch event { case .ready: SDLLogger.log("[SDLUDPHoleV6Session] udpHoleV6 ready") case .closed, .errorCaught: throw SDLContextError.udpHoleClosed } } } _ = try await group.next() } await self.stop() } catch { await self.stop() throw error } } func stop() async { let udpHoleV6 = self.udpHoleV6 self.udpHoleV6 = nil udpHoleV6?.stop() } func send(type: SDLPacketType, data: Data, remoteAddress: SocketAddress) { guard case .v6 = remoteAddress else { SDLLogger.log("[SDLUDPHoleV6Session] unsupported socket family: \(remoteAddress)", for: .debug) return } guard let udpHoleV6 else { SDLLogger.log("[SDLUDPHoleV6Session] udpHoleV6 is nil for remoteAddress: \(remoteAddress)", for: .debug) return } udpHoleV6.send(type: type, data: data, remoteAddress: remoteAddress) } }