punchnet-macos/Tun/SDLRuntimeEnvironment.swift

113 lines
2.9 KiB
Swift

import Foundation
enum SDLRuntimeEnvironmentCommand {
case start(completion: @Sendable (Error?) -> Void)
case stop(completion: @Sendable () -> Void)
}
final class SDLRuntimeEnvironment {
private enum State {
case idle
case running
}
private var state: State = .idle
private var contextActor: SDLContextActor?
private var config: SDLConfiguration
private let rsaCipher: CCRSACipher
private let provider: PacketTunnelProvider
private let commandStream: AsyncStream<SDLRuntimeEnvironmentCommand>
private let commandCont: AsyncStream<SDLRuntimeEnvironmentCommand>.Continuation
private var commandTask: Task<Void, Never>?
init(config: SDLConfiguration, rsaCipher: CCRSACipher, provider: PacketTunnelProvider) {
self.config = config
self.rsaCipher = rsaCipher
self.provider = provider
let pair = AsyncStream.makeStream(of: SDLRuntimeEnvironmentCommand.self)
self.commandStream = pair.stream
self.commandCont = pair.continuation
}
func submitCommand(command: SDLRuntimeEnvironmentCommand) {
self.commandCont.yield(command)
}
func getContextActor() -> SDLContextActor? {
self.contextActor
}
func run() {
let stream = self.commandStream
self.commandTask = Task { [weak self] in
for await command in stream {
guard let self else {
break
}
await self.handle(command)
}
}
}
private func handle(_ command: SDLRuntimeEnvironmentCommand) async {
switch command {
case .start(let handler):
do {
try await self.startCommand()
handler(nil)
} catch {
handler(error)
}
case .stop(let handler):
await self.stopCommand()
handler()
}
}
private func startCommand() async throws {
switch self.state {
case .idle:
SDLTunnelAppNotifier.shared.clear()
let contextActor = SDLContextActor(
provider: provider,
config: config,
rsaCipher: self.rsaCipher
)
self.contextActor = contextActor
await contextActor.start()
self.state = .running
case .running:
SDLLogger.log("[SDLRuntimeEnvironment] is running, ignore start command")
}
}
private func stopCommand() async {
switch self.state {
case .idle:
SDLLogger.log("[SDLRuntimeEnvironment] is idle, ignore stop command")
case .running:
let contextActor = self.contextActor
self.contextActor = nil
self.state = .idle
await contextActor?.stop()
}
}
deinit {
self.commandCont.finish()
self.commandTask?.cancel()
self.commandTask = nil
}
}