punchnet-macos/Tun/Super/SDLSuperClient.swift
2026-05-27 18:22:22 +08:00

280 lines
9.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// SDLSuperClient.swift
// Tun
//
// Created by on 2026/2/13.
//
import Foundation
import Network
final class SDLSuperClient: @unchecked Sendable {
private let queue = DispatchQueue(label: "com.sdl.SuperClient.queue") // 线
//
public let messageStream: AsyncThrowingStream<SDLQUICInboundMessage, Error>
private let messageCont: AsyncThrowingStream<SDLQUICInboundMessage, Error>.Continuation
private let stateLock = NSLock()
private var isStarted = false
private var isStopped = false
private var isMessageContinuationFinished = false
private let connection: NWConnection
private let maxBufferSize: Int
init(serverEndpoint: SDLConfiguration.ResolvedServerEndpoint, port: UInt16, maxBufferSize: Int = 2 * 1024 * 1024) {
self.maxBufferSize = maxBufferSize
let pairs = AsyncThrowingStream.makeStream(of: SDLQUICInboundMessage.self)
self.messageStream = pairs.stream
self.messageCont = pairs.continuation
let options = NWProtocolTLS.Options()
serverEndpoint.host.withCString {
sec_protocol_options_set_tls_server_name(options.securityProtocolOptions, $0)
}
sec_protocol_options_add_tls_application_protocol(
options.securityProtocolOptions,
"punchnet/1.0"
)
//
sec_protocol_options_set_verify_block(
options.securityProtocolOptions,
{ _, trust, complete in
//
complete(SDLSuperTLSVerifier.verify(trust: trust, host: serverEndpoint.host))
},
queue
)
let params = NWParameters(tls: options)
// Network.framework
params.preferNoProxies = true
self.connection = NWConnection(host: Self.makeEndpointHost(address: serverEndpoint.ip), port: .init(rawValue: port)!, using: params)
SDLLogger.log("[SDLSuperClient] start with tls protocol", for: .debug)
}
func run() async throws {
guard self.markStarted() else {
return
}
let stateStream = Self.makeStateStream(for: self.connection)
defer {
self.stop()
}
try await withTaskCancellationHandler {
self.connection.start(queue: self.queue)
try await self.runStateLoop(stateStream)
} onCancel: {
self.connection.cancel()
}
}
private static func makeEndpointHost(address ip: String) -> NWEndpoint.Host {
if let ipv4Address = IPv4Address(ip) {
return .ipv4(ipv4Address)
}
if let ipv6Address = IPv6Address(ip) {
return .ipv6(ipv6Address)
}
preconditionFailure("invalid super server IP: \(ip)")
}
private static func makeStateStream(for connection: NWConnection) -> AsyncThrowingStream<NWConnection.State, Error> {
return AsyncThrowingStream(bufferingPolicy: .bufferingNewest(16)) { continuation in
connection.stateUpdateHandler = { state in
SDLLogger.log("[SDLSuperClient] new state: \(state)", for: .debug)
continuation.yield(state)
switch state {
case .failed(let error):
continuation.finish(throwing: SDLSuperError.connectionFailed(error))
case .cancelled:
continuation.finish(throwing: SDLSuperError.connectionCancelled)
default:
break
}
}
continuation.onTermination = { _ in
connection.stateUpdateHandler = nil
}
}
}
private func runStateLoop(_ stateStream: AsyncThrowingStream<NWConnection.State, Error>) async throws {
do {
try await self.waitUntilReady(stateStream)
self.connection.stateUpdateHandler = nil
try await self.readLoop()
} catch is CancellationError {
throw CancellationError()
} catch let error as SDLSuperError {
self.finishMessageStream(throwing: error)
throw error
} catch {
let wrappedError = SDLSuperError.internalError(error)
self.finishMessageStream(throwing: wrappedError)
throw wrappedError
}
}
private func waitUntilReady(_ stateStream: AsyncThrowingStream<NWConnection.State, Error>) async throws {
for try await state in stateStream {
try Task.checkCancellation()
if case .ready = state {
return
}
}
throw SDLSuperError.connectionCancelled
}
private func readLoop() async throws {
let frameParser = SDLSuperFrameParser(maxBufferSize: self.maxBufferSize)
do {
while true {
try Task.checkCancellation()
let data = try await Self.readOnce(connection: self.connection)
let frames = try frameParser.parseFrames(data: data)
for frame in frames {
try Task.checkCancellation()
if let message = SDLSuperCodec.decode(frame: frame) {
self.messageCont.yield(message)
} else {
throw SDLSuperError.decodeError("invalid message")
}
}
}
} catch is CancellationError {
throw CancellationError()
} catch let error as SDLSuperError {
self.finishMessageStream(throwing: error)
throw error
} catch {
let wrappedError = SDLSuperError.internalError(error)
self.finishMessageStream(throwing: wrappedError)
throw wrappedError
}
}
func send(type: SDLPacketType, data: Data) {
guard connection.state == .ready else {
return
}
var len = UInt16(data.count + 1).bigEndian
var packet = Data(Data(bytes: &len, count: 2))
packet.append(type.rawValue)
packet.append(data)
connection.send(content: packet, completion: .contentProcessed { [weak self] error in
if let error {
SDLLogger.log("[SDLSuperClient] send data get error: \(error)", for: .debug)
self?.finishMessageStream(throwing: SDLSuperError.writeFailed(error))
}
})
}
private static func readOnce(connection: NWConnection) async throws -> Data {
guard connection.state == .ready else {
throw SDLSuperError.connectionCancelled
}
let readContinuation = OnceContinuation<Data, Error>()
return try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { cont in
readContinuation.set(cont)
connection.receive(minimumIncompleteLength: 1, maximumLength: 64 * 1024) { data, _, isComplete, error in
if let error {
readContinuation.resume(throwing: error)
return
}
if isComplete {
readContinuation.resume(throwing: SDLSuperError.dataStreamClosed)
} else {
readContinuation.resume(returning: data ?? Data())
}
}
}
} onCancel: {
readContinuation.resume(throwing: CancellationError())
}
}
func stop() {
guard self.markStopped() else {
return
}
let connection = self.connection
connection.stateUpdateHandler = nil
connection.cancel()
self.finishMessageStream()
SDLLogger.log("[SDLSuperClient] stopped")
}
private func markStarted() -> Bool {
self.stateLock.lock()
defer {
self.stateLock.unlock()
}
guard !self.isStarted, !self.isStopped else {
return false
}
self.isStarted = true
return true
}
private func markStopped() -> Bool {
self.stateLock.lock()
defer {
self.stateLock.unlock()
}
guard !self.isStopped else {
return false
}
self.isStopped = true
return true
}
private func finishMessageStream(throwing error: Error? = nil) {
self.stateLock.lock()
let shouldFinish = !self.isMessageContinuationFinished
if shouldFinish {
self.isMessageContinuationFinished = true
}
self.stateLock.unlock()
guard shouldFinish else {
return
}
if let error {
self.messageCont.finish(throwing: error)
} else {
self.messageCont.finish()
}
}
deinit {
SDLLogger.log("[SDLSuperClient] deinit")
}
}