280 lines
9.0 KiB
Swift
280 lines
9.0 KiB
Swift
//
|
||
// 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")
|
||
}
|
||
|
||
}
|