punchnet-macos/Tun/Super/SDLSuperClient.swift
2026-05-22 14:57:13 +08:00

232 lines
7.4 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
// 便
enum SDLSuperError: Error {
case connectionFailed(Error)
case connectionCancelled
case writeFailed(Error)
case internalError(Error)
case timeout
case decodeError(String)
case packetTooLarge
case dataStreamClosed
}
actor SDLSuperClient {
enum State {
case idle
case running
case stopped
}
private var state: State = .idle
private let frameParser: SDLSuperFrameParser
//
public var messageStream: AsyncThrowingStream<SDLQUICInboundMessage, Error>
private let messageCont: AsyncThrowingStream<SDLQUICInboundMessage, Error>.Continuation
private var isMessageContinuationFinished: Bool = false
private var readTask: Task<Void, Never>?
private var connection: NWConnection?
private let serverEndpoint: SDLConfiguration.ResolvedServerEndpoint
private let port: UInt16
init(serverEndpoint: SDLConfiguration.ResolvedServerEndpoint, port: UInt16, maxBufferSize: Int = 2 * 1024 * 1024) {
self.serverEndpoint = serverEndpoint
self.port = port
self.frameParser = SDLSuperFrameParser(maxBufferSize: maxBufferSize)
(self.messageStream, self.messageCont) = AsyncThrowingStream.makeStream(of: SDLQUICInboundMessage.self)
}
func start() {
let serverEndpoint = self.serverEndpoint
let queue = DispatchQueue(label: "com.sdl.SuperClient.queue") // 线
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
)
SDLLogger.log("[SDLSuperClient] start with tls protocol", for: .debug)
let params = NWParameters(tls: options)
// Network.framework
params.preferNoProxies = true
let connection = NWConnection(host: Self.makeEndpointHost(address: serverEndpoint.ip), port: .init(rawValue: port)!, using: params)
connection.stateUpdateHandler = { [weak self] state in
SDLLogger.log("[SDLSuperClient] new state: \(state)", for: .debug)
Task {
await self?.handleConnectionState(state: state)
}
}
connection.start(queue: queue)
self.connection = connection
}
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 func handleConnectionState(state: NWConnection.State) {
switch state {
case .ready:
self.startReadTask()
self.state = .running
case .failed(let error):
self.finishMessageContinuationIfNeed(throwing: .connectionFailed(error))
case .cancelled:
self.finishMessageContinuationIfNeed(throwing: .connectionCancelled)
default:
()
}
}
private func finishMessageContinuationIfNeed(throwing error: SDLSuperError?) {
guard !self.isMessageContinuationFinished else {
return
}
self.isMessageContinuationFinished = true
if let error {
self.messageCont.finish(throwing: error)
} else {
self.messageCont.finish()
}
}
private func startReadTask() {
self.readTask?.cancel()
self.readTask = Task {
do {
while true {
try Task.checkCancellation()
let data = try await self.readOnce()
let frames = try self.frameParser.parseFrames(data: data)
for frame in frames {
try Task.checkCancellation()
if let message = SDLSuperCodec.decode(frame: frame) {
self.messageCont.yield(message)
} else {
self.finishMessageContinuationIfNeed(throwing: .decodeError("invalid message"))
}
}
}
} catch let err {
self.finishMessageContinuationIfNeed(throwing: .internalError(err))
}
}
}
func send(type: SDLPacketType, data: Data) {
guard case .running = state, let connection = self.connection, 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 {
Task {
SDLLogger.log("[SDLSuperClient] send data get error: \(error)", for: .debug)
await self?.finishMessageContinuationIfNeed(throwing: .writeFailed(error))
}
}
})
}
private func readOnce() async throws -> Data {
guard let connection = self.connection 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.state != .stopped else {
return
}
self.state = .stopped
self.readTask?.cancel()
self.readTask = nil
let connection = self.connection
self.connection = nil
connection?.stateUpdateHandler = nil
connection?.cancel()
self.finishMessageContinuationIfNeed(throwing: nil)
SDLLogger.log("[SDLSuperClient] stopped")
}
deinit {
SDLLogger.log("[SDLSuperClient] deinit")
}
}