263 lines
9.4 KiB
Swift
263 lines
9.4 KiB
Swift
//
|
|
// SDLContextBootstrap.swift
|
|
// Tun
|
|
//
|
|
// Created by Codex on 2026/5/28.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
final class SDLContextBootstrap: @unchecked Sendable {
|
|
private typealias StartCompletion = (Error?) -> Void
|
|
private typealias StopCompletion = () -> Void
|
|
private typealias WakeCompletion = (Error?) -> Void
|
|
|
|
private enum BootstrapCommand {
|
|
case start(config: SDLConfiguration, rsaCipher: CCRSACipher, completion: StartCompletion)
|
|
case stop(clearRuntimeConfiguration: Bool, completion: StopCompletion)
|
|
case recoverAfterWake(completion: WakeCompletion)
|
|
}
|
|
|
|
private enum RuntimeState {
|
|
case idle
|
|
case starting
|
|
case running
|
|
case stopping
|
|
}
|
|
|
|
private weak var provider: PacketTunnelProvider?
|
|
private let runtimeLock = NSLock()
|
|
private var runtimeState: RuntimeState = .idle
|
|
private var config: SDLConfiguration?
|
|
private var rsaCipher: CCRSACipher?
|
|
private var contextActor: SDLContextActor?
|
|
private var startCompletionHandler: ((Error?) -> Void)?
|
|
private let commandContinuation: AsyncStream<BootstrapCommand>.Continuation
|
|
private var commandWorker: Task<Void, Never>?
|
|
|
|
init(provider: PacketTunnelProvider) {
|
|
let commandPair = AsyncStream.makeStream(of: BootstrapCommand.self, bufferingPolicy: .unbounded)
|
|
|
|
self.provider = provider
|
|
self.commandContinuation = commandPair.continuation
|
|
self.commandWorker = Task { [weak self, stream = commandPair.stream] in
|
|
await self?.runCommandLoop(stream)
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
self.commandContinuation.finish()
|
|
self.commandWorker?.cancel()
|
|
}
|
|
|
|
func startCached(completionHandler: @escaping (Error?) -> Void) {
|
|
self.runtimeLock.lock()
|
|
let config = self.config
|
|
let rsaCipher = self.rsaCipher
|
|
self.runtimeLock.unlock()
|
|
|
|
guard let config, let rsaCipher else {
|
|
SDLLogger.fatal("[SDLContextBootstrap] startCached failed: missing cached runtime configuration", category: .app)
|
|
completionHandler(TunnelError.invalidConfiguration)
|
|
return
|
|
}
|
|
|
|
self.start(config: config, rsaCipher: rsaCipher, completionHandler: completionHandler)
|
|
}
|
|
|
|
func start(config: SDLConfiguration, rsaCipher: CCRSACipher, completionHandler: @escaping (Error?) -> Void) {
|
|
self.submit(.start(config: config, rsaCipher: rsaCipher, completion: completionHandler))
|
|
}
|
|
|
|
func stop(clearRuntimeConfiguration: Bool, completionHandler: @escaping () -> Void) {
|
|
self.submit(.stop(clearRuntimeConfiguration: clearRuntimeConfiguration, completion: completionHandler))
|
|
}
|
|
|
|
func recoverAfterWake(completionHandler: @escaping (Error?) -> Void) {
|
|
self.submit(.recoverAfterWake(completion: completionHandler))
|
|
}
|
|
|
|
func currentContextActor() -> SDLContextActor? {
|
|
self.runtimeLock.lock()
|
|
let contextActor = self.contextActor
|
|
self.runtimeLock.unlock()
|
|
return contextActor
|
|
}
|
|
|
|
private func submit(_ command: BootstrapCommand) {
|
|
self.commandContinuation.yield(command)
|
|
}
|
|
|
|
private func runCommandLoop(_ stream: AsyncStream<BootstrapCommand>) async {
|
|
for await command in stream {
|
|
self.handle(command)
|
|
}
|
|
}
|
|
|
|
private func handle(_ command: BootstrapCommand) {
|
|
switch command {
|
|
case .start(let config, let rsaCipher, let completion):
|
|
self.handleStart(config: config, rsaCipher: rsaCipher, completionHandler: completion)
|
|
case .stop(let clearRuntimeConfiguration, let completion):
|
|
self.handleStop(clearRuntimeConfiguration: clearRuntimeConfiguration, completionHandler: completion)
|
|
case .recoverAfterWake(let completion):
|
|
self.handleRecoverAfterWake(completionHandler: completion)
|
|
}
|
|
}
|
|
|
|
private func handleStart(config: SDLConfiguration, rsaCipher: CCRSACipher, completionHandler: @escaping (Error?) -> Void) {
|
|
guard let provider = self.provider else {
|
|
SDLLogger.fatal("[SDLContextBootstrap] start rejected: provider released", category: .app)
|
|
completionHandler(TunnelError.invalidContext)
|
|
return
|
|
}
|
|
|
|
self.runtimeLock.lock()
|
|
switch self.runtimeState {
|
|
case .idle:
|
|
SDLTunnelAppNotifier.shared.clear()
|
|
|
|
let contextActor = SDLContextActor(
|
|
provider: provider,
|
|
config: config,
|
|
rsaCipher: rsaCipher
|
|
)
|
|
|
|
self.config = config
|
|
self.rsaCipher = rsaCipher
|
|
self.contextActor = contextActor
|
|
self.startCompletionHandler = completionHandler
|
|
self.runtimeState = .starting
|
|
self.runtimeLock.unlock()
|
|
|
|
Task {
|
|
do {
|
|
try await contextActor.start()
|
|
self.finishContextStart(contextActor, error: nil)
|
|
} catch {
|
|
SDLLogger.fatal("[SDLContextBootstrap] context start failed: \(error)", category: .app)
|
|
self.finishContextStart(contextActor, error: error)
|
|
}
|
|
}
|
|
|
|
case .starting, .running, .stopping:
|
|
SDLLogger.fatal("[SDLContextBootstrap] start rejected: invalid runtime state \(self.runtimeState)", category: .app)
|
|
self.runtimeLock.unlock()
|
|
completionHandler(TunnelError.invalidContext)
|
|
}
|
|
}
|
|
|
|
private func handleStop(clearRuntimeConfiguration: Bool, completionHandler: @escaping () -> Void) {
|
|
self.runtimeLock.lock()
|
|
let contextActor = self.contextActor
|
|
let startCompletionHandler = self.startCompletionHandler
|
|
|
|
guard let contextActor else {
|
|
SDLLogger.fatal("[SDLContextBootstrap] stop requested while context is nil, clearRuntimeConfiguration: \(clearRuntimeConfiguration)", category: .app)
|
|
self.runtimeState = .idle
|
|
self.startCompletionHandler = nil
|
|
if clearRuntimeConfiguration {
|
|
self.config = nil
|
|
self.rsaCipher = nil
|
|
}
|
|
self.runtimeLock.unlock()
|
|
startCompletionHandler?(TunnelError.invalidContext)
|
|
completionHandler()
|
|
return
|
|
}
|
|
|
|
self.contextActor = nil
|
|
self.startCompletionHandler = nil
|
|
self.runtimeState = .stopping
|
|
if clearRuntimeConfiguration {
|
|
self.config = nil
|
|
self.rsaCipher = nil
|
|
}
|
|
self.runtimeLock.unlock()
|
|
|
|
SDLLogger.fatal("[SDLContextBootstrap] stop will stop current context, clearRuntimeConfiguration: \(clearRuntimeConfiguration)", category: .app)
|
|
startCompletionHandler?(TunnelError.invalidContext)
|
|
|
|
Task {
|
|
await contextActor.stop()
|
|
self.markContextStopped()
|
|
completionHandler()
|
|
}
|
|
}
|
|
|
|
private func handleRecoverAfterWake(completionHandler: @escaping (Error?) -> Void) {
|
|
self.runtimeLock.lock()
|
|
let runtimeState = self.runtimeState
|
|
let contextActor = self.contextActor
|
|
let config = self.config
|
|
let rsaCipher = self.rsaCipher
|
|
self.runtimeLock.unlock()
|
|
|
|
switch runtimeState {
|
|
case .running:
|
|
guard let contextActor else {
|
|
SDLLogger.fatal("[SDLContextBootstrap] recoverAfterWake failed: missing running context", category: .app)
|
|
completionHandler(TunnelError.invalidContext)
|
|
return
|
|
}
|
|
|
|
Task {
|
|
do {
|
|
try await contextActor.recoverAfterWake()
|
|
completionHandler(nil)
|
|
} catch {
|
|
SDLLogger.fatal("[SDLContextBootstrap] recoverAfterWake failed: \(error)", category: .app)
|
|
completionHandler(error)
|
|
}
|
|
}
|
|
|
|
case .idle:
|
|
guard let config, let rsaCipher else {
|
|
SDLLogger.fatal("[SDLContextBootstrap] recoverAfterWake failed: missing cached runtime configuration", category: .app)
|
|
completionHandler(TunnelError.invalidConfiguration)
|
|
return
|
|
}
|
|
|
|
SDLLogger.log("[SDLContextBootstrap] recoverAfterWake will start cached context", category: .app)
|
|
self.handleStart(config: config, rsaCipher: rsaCipher, completionHandler: completionHandler)
|
|
|
|
case .starting:
|
|
SDLLogger.log("[SDLContextBootstrap] recoverAfterWake ignored while context is starting", category: .app)
|
|
completionHandler(nil)
|
|
|
|
case .stopping:
|
|
SDLLogger.log("[SDLContextBootstrap] recoverAfterWake ignored while context is stopping", category: .app)
|
|
completionHandler(nil)
|
|
}
|
|
}
|
|
|
|
private func finishContextStart(_ contextActor: SDLContextActor, error: Error?) {
|
|
self.runtimeLock.lock()
|
|
guard self.contextActor === contextActor else {
|
|
self.runtimeLock.unlock()
|
|
return
|
|
}
|
|
|
|
let startCompletionHandler = self.startCompletionHandler
|
|
self.startCompletionHandler = nil
|
|
|
|
if let error {
|
|
self.contextActor = nil
|
|
self.runtimeState = .idle
|
|
} else {
|
|
self.runtimeState = .running
|
|
}
|
|
self.runtimeLock.unlock()
|
|
|
|
startCompletionHandler?(error)
|
|
}
|
|
|
|
private func markContextStopped() {
|
|
self.runtimeLock.lock()
|
|
if self.contextActor == nil {
|
|
self.runtimeState = .idle
|
|
}
|
|
self.runtimeLock.unlock()
|
|
}
|
|
}
|