Annotate existentials with any prefix keyword

<https://github.com/swiftlang/swift-evolution/blob/main/proposals/0335-existential-any.md>
This commit is contained in:
Caleb Jasik 2025-02-19 13:06:08 -06:00
parent fa555083a5
commit 64828e3142
No known key found for this signature in database
6 changed files with 157 additions and 149 deletions

View file

@ -17,7 +17,7 @@ enum AppMessageError: Error {
extension AppMessageError: LocalizedError { extension AppMessageError: LocalizedError {
public var description: String? { public var description: String? {
switch self { switch self {
case .unknownIPCType(let command): case let .unknownIPCType(command):
return NSLocalizedString("Unknown IPC message type \(String(command))", comment: "") return NSLocalizedString("Unknown IPC message type \(String(command))", comment: "")
} }
} }
@ -57,27 +57,27 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
if ProcessInfo().isOperatingSystemAtLeast( if ProcessInfo().isOperatingSystemAtLeast(
OperatingSystemVersion(majorVersion: 17, minorVersion: 0, patchVersion: 0)) OperatingSystemVersion(majorVersion: 17, minorVersion: 0, patchVersion: 0))
{ {
manager = try await self.findManager() manager = try await findManager()
guard let foundManager = manager else { guard let foundManager = manager else {
throw VPNStartError.couldNotFindManager throw VPNStartError.couldNotFindManager
} }
self.site = try Site(manager: foundManager) site = try Site(manager: foundManager)
} else { } else {
// This does not save the manager with the site, which means we cannot update the // This does not save the manager with the site, which means we cannot update the
// vpn profile name when updates happen (rare). // vpn profile name when updates happen (rare).
self.site = try Site(proto: self.protocolConfiguration as! NETunnelProviderProtocol) site = try Site(proto: protocolConfiguration as! NETunnelProviderProtocol)
} }
config = try self.site!.getConfig() config = try site!.getConfig()
} catch { } catch {
// TODO: need a way to notify the app // TODO: need a way to notify the app
self.log.error("Failed to render config from vpn object") log.error("Failed to render config from vpn object")
throw error throw error
} }
let _site = self.site! let _site = site!
key = try _site.getKey() key = try _site.getKey()
guard let fileDescriptor = self.tunnelFileDescriptor else { guard let fileDescriptor = tunnelFileDescriptor else {
throw VPNStartError.noTunFileDescriptor throw VPNStartError.noTunFileDescriptor
} }
let tunFD = Int(fileDescriptor) let tunFD = Int(fileDescriptor)
@ -92,7 +92,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
throw err! throw err!
} }
tunnelNetworkSettings.ipv4Settings = NEIPv4Settings( tunnelNetworkSettings.ipv4Settings = NEIPv4Settings(
addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR]) addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR]
)
var routes: [NEIPv4Route] = [ var routes: [NEIPv4Route] = [
NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR) NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR)
] ]
@ -109,26 +110,28 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
tunnelNetworkSettings.ipv4Settings!.includedRoutes = routes tunnelNetworkSettings.ipv4Settings!.includedRoutes = routes
tunnelNetworkSettings.mtu = _site.mtu as NSNumber tunnelNetworkSettings.mtu = _site.mtu as NSNumber
try await self.setTunnelNetworkSettings(tunnelNetworkSettings) try await setTunnelNetworkSettings(tunnelNetworkSettings)
var nebulaErr: NSError? var nebulaErr: NSError?
self.nebula = MobileNebulaNewNebula( nebula = MobileNebulaNewNebula(
String(data: config, encoding: .utf8), key, self.site!.logFile, tunFD, &nebulaErr) String(data: config, encoding: .utf8), key, site!.logFile, tunFD, &nebulaErr
self.startNetworkMonitor() )
startNetworkMonitor()
if nebulaErr != nil { if nebulaErr != nil {
self.log.error("We had an error starting up: \(nebulaErr, privacy: .public)") log.error("We had an error starting up: \(nebulaErr, privacy: .public)")
throw nebulaErr! throw nebulaErr!
} }
self.nebula!.start() nebula!.start()
self.dnUpdater.updateSingleLoop(site: self.site!, onUpdate: self.handleDNUpdate) dnUpdater.updateSingleLoop(site: site!, onUpdate: handleDNUpdate)
} }
private func handleDNUpdate(newSite: Site) { private func handleDNUpdate(newSite: Site) {
do { do {
self.site = newSite site = newSite
try self.nebula?.reload( try nebula?.reload(
String(data: newSite.getConfig(), encoding: .utf8), key: newSite.getKey()) String(data: newSite.getConfig(), encoding: .utf8), key: newSite.getKey()
)
} catch { } catch {
log.error( log.error(
@ -143,7 +146,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
// } // }
private func findManager() async throws -> NETunnelProviderManager { private func findManager() async throws -> NETunnelProviderManager {
let targetProtoConfig = self.protocolConfiguration as? NETunnelProviderProtocol let targetProtoConfig = protocolConfiguration as? NETunnelProviderProtocol
guard let targetProviderConfig = targetProtoConfig?.providerConfiguration else { guard let targetProviderConfig = targetProtoConfig?.providerConfiguration else {
throw VPNStartError.noProviderConfig throw VPNStartError.noProviderConfig
} }
@ -168,17 +171,17 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private func startNetworkMonitor() { private func startNetworkMonitor() {
networkMonitor = NWPathMonitor() networkMonitor = NWPathMonitor()
networkMonitor!.pathUpdateHandler = self.pathUpdate networkMonitor!.pathUpdateHandler = pathUpdate
networkMonitor!.start(queue: DispatchQueue(label: "NetworkMonitor")) networkMonitor!.start(queue: DispatchQueue(label: "NetworkMonitor"))
} }
private func stopNetworkMonitor() { private func stopNetworkMonitor() {
self.networkMonitor?.cancel() networkMonitor?.cancel()
networkMonitor = nil networkMonitor = nil
} }
override func stopTunnel( override func stopTunnel(
with reason: NEProviderStopReason, completionHandler: @escaping () -> Void with _: NEProviderStopReason, completionHandler: @escaping () -> Void
) { ) {
nebula?.stop() nebula?.stop()
stopNetworkMonitor() stopNetworkMonitor()
@ -199,14 +202,14 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private func collectAddresses(endpoints: [Network.NWEndpoint]) -> String { private func collectAddresses(endpoints: [Network.NWEndpoint]) -> String {
var str: [String] = [] var str: [String] = []
endpoints.forEach { endpoint in for endpoint in endpoints {
switch endpoint { switch endpoint {
case let .hostPort(.ipv6(host), port): case let .hostPort(.ipv6(host), port):
str.append("[\(host)]:\(port)") str.append("[\(host)]:\(port)")
case let .hostPort(.ipv4(host), port): case let .hostPort(.ipv4(host), port):
str.append("\(host):\(port)") str.append("\(host):\(port)")
default: default:
return continue
} }
} }
@ -219,28 +222,28 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
return nil return nil
} }
var error: Error? var error: (any Error)?
var data: JSON? var data: JSON?
// start command has special treatment due to needing to call two completers // start command has special treatment due to needing to call two completers
if call.command == "start" { if call.command == "start" {
do { do {
try await self.start() try await start()
// No response data, this is expected on a clean start // No response data, this is expected on a clean start
return try? JSONEncoder().encode(IPCResponse.init(type: .success, message: nil)) return try? JSONEncoder().encode(IPCResponse(type: .success, message: nil))
} catch { } catch {
defer { defer {
self.cancelTunnelWithError(error) self.cancelTunnelWithError(error)
} }
return try? JSONEncoder().encode( return try? JSONEncoder().encode(
IPCResponse.init(type: .error, message: JSON(error.localizedDescription))) IPCResponse(type: .error, message: JSON(error.localizedDescription)))
} }
} }
if nebula == nil { if nebula == nil {
// Respond with an empty success message in the event a command comes in before we've truly started // Respond with an empty success message in the event a command comes in before we've truly started
log.warning("Received command but do not have a nebula instance") log.warning("Received command but do not have a nebula instance")
return try? JSONEncoder().encode(IPCResponse.init(type: .success, message: nil)) return try? JSONEncoder().encode(IPCResponse(type: .success, message: nil))
} }
// TODO: try catch over all this // TODO: try catch over all this
@ -250,41 +253,41 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
case "getHostInfo": (data, error) = getHostInfo(args: call.arguments!) case "getHostInfo": (data, error) = getHostInfo(args: call.arguments!)
case "setRemoteForTunnel": (data, error) = setRemoteForTunnel(args: call.arguments!) case "setRemoteForTunnel": (data, error) = setRemoteForTunnel(args: call.arguments!)
case "closeTunnel": (data, error) = closeTunnel(args: call.arguments!) case "closeTunnel": (data, error) = closeTunnel(args: call.arguments!)
default: default:
error = AppMessageError.unknownIPCType(command: call.command) error = AppMessageError.unknownIPCType(command: call.command)
} }
if error != nil { if error != nil {
return try? JSONEncoder().encode( return try? JSONEncoder().encode(
IPCResponse.init( IPCResponse(
type: .error, message: JSON(error?.localizedDescription ?? "Unknown error"))) type: .error, message: JSON(error?.localizedDescription ?? "Unknown error")
))
} else { } else {
return try? JSONEncoder().encode(IPCResponse.init(type: .success, message: data)) return try? JSONEncoder().encode(IPCResponse(type: .success, message: data))
} }
} }
private func listHostmap(pending: Bool) -> (JSON?, Error?) { private func listHostmap(pending: Bool) -> (JSON?, (any Error)?) {
var err: NSError? var err: NSError?
let res = nebula!.listHostmap(pending, error: &err) let res = nebula!.listHostmap(pending, error: &err)
return (JSON(res), err) return (JSON(res), err)
} }
private func getHostInfo(args: JSON) -> (JSON?, Error?) { private func getHostInfo(args: JSON) -> (JSON?, (any Error)?) {
var err: NSError? var err: NSError?
let res = nebula!.getHostInfo( let res = nebula!.getHostInfo(
byVpnIp: args["vpnIp"].string, pending: args["pending"].boolValue, error: &err) byVpnIp: args["vpnIp"].string, pending: args["pending"].boolValue, error: &err)
return (JSON(res), err) return (JSON(res), err)
} }
private func setRemoteForTunnel(args: JSON) -> (JSON?, Error?) { private func setRemoteForTunnel(args: JSON) -> (JSON?, (any Error)?) {
var err: NSError? var err: NSError?
let res = nebula!.setRemoteForTunnel( let res = nebula!.setRemoteForTunnel(
args["vpnIp"].string, addr: args["addr"].string, error: &err) args["vpnIp"].string, addr: args["addr"].string, error: &err)
return (JSON(res), err) return (JSON(res), err)
} }
private func closeTunnel(args: JSON) -> (JSON?, Error?) { private func closeTunnel(args: JSON) -> (JSON?, (any Error)?) {
let res = nebula!.closeTunnel(args["vpnIp"].string) let res = nebula!.closeTunnel(args["vpnIp"].string)
return (JSON(res), nil) return (JSON(res), nil)
} }

View file

@ -21,7 +21,7 @@ enum SiteError: Error {
extension SiteError: CustomStringConvertible { extension SiteError: CustomStringConvertible {
public var description: String { public var description: String {
switch self { switch self {
case .nonConforming(let site): case let .nonConforming(site):
return String("Non-conforming site \(String(describing: site))") return String("Non-conforming site \(String(describing: site))")
case .noCertificate: case .noCertificate:
return "No certificate found" return "No certificate found"
@ -35,15 +35,15 @@ extension SiteError: CustomStringConvertible {
return "failed to find dn credentials in keychain" return "failed to find dn credentials in keychain"
case .dnCredentialSave: case .dnCredentialSave:
return "failed to store dn credentials in keychain" return "failed to store dn credentials in keychain"
case .unexpected(_): case .unexpected:
return "An unexpected error occurred." return "An unexpected error occurred."
} }
} }
} }
enum IPCResponseType: String, Codable { enum IPCResponseType: String, Codable {
case error = "error" case error
case success = "success" case success
} }
class IPCResponse: Codable { class IPCResponse: Codable {
@ -190,8 +190,8 @@ class Site: Codable {
let proto = manager.protocolConfiguration as! NETunnelProviderProtocol let proto = manager.protocolConfiguration as! NETunnelProviderProtocol
try self.init(proto: proto) try self.init(proto: proto)
self.manager = manager self.manager = manager
self.connected = statusMap[manager.connection.status] connected = statusMap[manager.connection.status]
self.status = statusString[manager.connection.status] status = statusString[manager.connection.status]
} }
convenience init(proto: NETunnelProviderProtocol) throws { convenience init(proto: NETunnelProviderProtocol) throws {
@ -202,7 +202,7 @@ class Site: Codable {
let decoder = JSONDecoder() let decoder = JSONDecoder()
let incoming = try decoder.decode(IncomingSite.self, from: config) let incoming = try decoder.decode(IncomingSite.self, from: config)
self.init(incoming: incoming) self.init(incoming: incoming)
self.needsToMigrateToFS = true needsToMigrateToFS = true
return return
} }
@ -272,7 +272,7 @@ class Site: Codable {
ca = try JSONDecoder().decode([CertificateInfo].self, from: rawCaDetails.data(using: .utf8)!) ca = try JSONDecoder().decode([CertificateInfo].self, from: rawCaDetails.data(using: .utf8)!)
var hasErrors = false var hasErrors = false
ca.forEach { cert in for cert in ca {
if !cert.validity.valid { if !cert.validity.valid {
hasErrors = true hasErrors = true
} }
@ -288,7 +288,7 @@ class Site: Codable {
} }
do { do {
logFile = try SiteList.getSiteLogFile(id: self.id, createDir: true).path logFile = try SiteList.getSiteLogFile(id: id, createDir: true).path
} catch { } catch {
logFile = nil logFile = nil
errors.append("Unable to create the site directory: \(error.localizedDescription)") errors.append("Unable to create the site directory: \(error.localizedDescription)")
@ -344,7 +344,7 @@ class Site: Codable {
let creds = try getDNCredentials() let creds = try getDNCredentials()
creds.invalid = true creds.invalid = true
if !(try creds.save(siteID: self.id)) { if try !(creds.save(siteID: id)) {
throw SiteError.dnCredentialLoad throw SiteError.dnCredentialLoad
} }
} }
@ -353,13 +353,13 @@ class Site: Codable {
let creds = try getDNCredentials() let creds = try getDNCredentials()
creds.invalid = false creds.invalid = false
if !(try creds.save(siteID: self.id)) { if try !(creds.save(siteID: id)) {
throw SiteError.dnCredentialSave throw SiteError.dnCredentialSave
} }
} }
func getConfig() throws -> Data { func getConfig() throws -> Data {
return try self.incomingSite!.getConfig() return try incomingSite!.getConfig()
} }
// Limits what we export to the UI // Limits what we export to the UI
@ -458,12 +458,12 @@ struct IncomingSite: Codable {
func save( func save(
manager: NETunnelProviderManager?, saveToManager: Bool = true, manager: NETunnelProviderManager?, saveToManager: Bool = true,
callback: @escaping (Error?) -> Void callback: @escaping ((any Error)?) -> Void
) { ) {
let configPath: URL let configPath: URL
do { do {
configPath = try SiteList.getSiteConfigFile(id: self.id, createDir: true) configPath = try SiteList.getSiteConfigFile(id: id, createDir: true)
} catch { } catch {
callback(error) callback(error)
@ -472,22 +472,22 @@ struct IncomingSite: Codable {
log.notice("Saving to \(configPath, privacy: .public)") log.notice("Saving to \(configPath, privacy: .public)")
do { do {
if self.key != nil { if key != nil {
let data = self.key!.data(using: .utf8) let data = key!.data(using: .utf8)
if !KeyChain.save(key: "\(self.id).key", data: data!, managed: self.managed ?? false) { if !KeyChain.save(key: "\(id).key", data: data!, managed: managed ?? false) {
return callback(SiteError.keySave) return callback(SiteError.keySave)
} }
} }
do { do {
if (try self.dnCredentials?.save(siteID: self.id)) == false { if try (dnCredentials?.save(siteID: id)) == false {
return callback(SiteError.dnCredentialSave) return callback(SiteError.dnCredentialSave)
} }
} catch { } catch {
return callback(error) return callback(error)
} }
try self.getConfig().write(to: configPath) try getConfig().write(to: configPath)
} catch { } catch {
return callback(error) return callback(error)
@ -506,7 +506,7 @@ struct IncomingSite: Codable {
} }
private func saveToManager( private func saveToManager(
manager: NETunnelProviderManager?, callback: @escaping (Error?) -> Void manager: NETunnelProviderManager?, callback: @escaping ((any Error)?) -> Void
) { ) {
if manager != nil { if manager != nil {
// We need to refresh our settings to properly update config // We need to refresh our settings to properly update config
@ -524,7 +524,7 @@ struct IncomingSite: Codable {
} }
private func finishSaveToManager( private func finishSaveToManager(
manager: NETunnelProviderManager, callback: @escaping (Error?) -> Void manager: NETunnelProviderManager, callback: @escaping ((any Error)?) -> Void
) { ) {
// Stuff our details in the protocol // Stuff our details in the protocol
let proto = let proto =
@ -532,7 +532,7 @@ struct IncomingSite: Codable {
proto.providerBundleIdentifier = "net.defined.mobileNebula.NebulaNetworkExtension" proto.providerBundleIdentifier = "net.defined.mobileNebula.NebulaNetworkExtension"
// WARN: If we stop setting providerConfiguration["id"] here, we'll need to use something else to match // WARN: If we stop setting providerConfiguration["id"] here, we'll need to use something else to match
// managers in PacketTunnelProvider.findManager // managers in PacketTunnelProvider.findManager
proto.providerConfiguration = ["id": self.id] proto.providerConfiguration = ["id": id]
proto.serverAddress = "Nebula" proto.serverAddress = "Nebula"
// Finish up the manager, this is what stores everything at the system level // Finish up the manager, this is what stores everything at the system level
@ -540,11 +540,11 @@ struct IncomingSite: Codable {
// TODO: cert name? manager.protocolConfiguration?.username // TODO: cert name? manager.protocolConfiguration?.username
// TODO: This is what is shown on the vpn page. We should add more identifying details in // TODO: This is what is shown on the vpn page. We should add more identifying details in
manager.localizedDescription = self.name manager.localizedDescription = name
manager.isEnabled = true manager.isEnabled = true
manager.saveToPreferences { error in manager.saveToPreferences { error in
return callback(error) callback(error)
} }
} }
} }

View file

@ -46,10 +46,11 @@ class SiteList {
/// Gets the file that represents the site log output, $rootDir/sites/$siteID/log /// Gets the file that represents the site log output, $rootDir/sites/$siteID/log
static func getSiteLogFile(id: String, createDir: Bool) throws -> URL { static func getSiteLogFile(id: String, createDir: Bool) throws -> URL {
return try getSiteDir(id: id, create: createDir).appendingPathComponent( return try getSiteDir(id: id, create: createDir).appendingPathComponent(
"logs", isDirectory: false) "logs", isDirectory: false
)
} }
init(completion: @escaping ([String: Site]?, Error?) -> Void) { init(completion: @escaping ([String: Site]?, (any Error)?) -> Void) {
#if targetEnvironment(simulator) #if targetEnvironment(simulator)
SiteList.loadAllFromFS { sites, err in SiteList.loadAllFromFS { sites, err in
if sites != nil { if sites != nil {
@ -67,21 +68,22 @@ class SiteList {
#endif #endif
} }
private static func loadAllFromFS(completion: @escaping ([String: Site]?, Error?) -> Void) { private static func loadAllFromFS(completion: @escaping ([String: Site]?, (any Error)?) -> Void) {
let fileManager = FileManager.default let fileManager = FileManager.default
var siteDirs: [URL] var siteDirs: [URL]
var sites = [String: Site]() var sites = [String: Site]()
do { do {
siteDirs = try fileManager.contentsOfDirectory( siteDirs = try fileManager.contentsOfDirectory(
at: getSitesDir(), includingPropertiesForKeys: nil) at: getSitesDir(), includingPropertiesForKeys: nil
)
} catch { } catch {
completion(nil, error) completion(nil, error)
return return
} }
siteDirs.forEach { path in for path in siteDirs {
do { do {
let site = try Site( let site = try Site(
path: path.appendingPathComponent("config").appendingPathExtension("json")) path: path.appendingPathComponent("config").appendingPathExtension("json"))
@ -97,7 +99,9 @@ class SiteList {
completion(sites, nil) completion(sites, nil)
} }
private static func loadAllFromNETPM(completion: @escaping ([String: Site]?, Error?) -> Void) { private static func loadAllFromNETPM(
completion: @escaping ([String: Site]?, (any Error)?) -> Void
) {
var sites = [String: Site]() var sites = [String: Site]()
// dispatchGroup is used to ensure we have migrated all sites before returning them // dispatchGroup is used to ensure we have migrated all sites before returning them

View file

@ -45,21 +45,18 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
sites = Sites(messenger: controller.binaryMessenger) sites = Sites(messenger: controller.binaryMessenger)
ui = FlutterMethodChannel(name: ChannelName.vpn, binaryMessenger: controller.binaryMessenger) ui = FlutterMethodChannel(name: ChannelName.vpn, binaryMessenger: controller.binaryMessenger)
ui!.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in ui!.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
switch call.method { switch call.method {
case "nebula.parseCerts": return self.nebulaParseCerts(call: call, result: result) case "nebula.parseCerts": return self.nebulaParseCerts(call: call, result: result)
case "nebula.generateKeyPair": return self.nebulaGenerateKeyPair(result: result) case "nebula.generateKeyPair": return self.nebulaGenerateKeyPair(result: result)
case "nebula.renderConfig": return self.nebulaRenderConfig(call: call, result: result) case "nebula.renderConfig": return self.nebulaRenderConfig(call: call, result: result)
case "nebula.verifyCertAndKey": return self.nebulaVerifyCertAndKey(call: call, result: result) case "nebula.verifyCertAndKey": return self.nebulaVerifyCertAndKey(call: call, result: result)
case "dn.enroll": return self.dnEnroll(call: call, result: result) case "dn.enroll": return self.dnEnroll(call: call, result: result)
case "listSites": return self.listSites(result: result) case "listSites": return self.listSites(result: result)
case "deleteSite": return self.deleteSite(call: call, result: result) case "deleteSite": return self.deleteSite(call: call, result: result)
case "saveSite": return self.saveSite(call: call, result: result) case "saveSite": return self.saveSite(call: call, result: result)
case "startSite": return self.startSite(call: call, result: result) case "startSite": return self.startSite(call: call, result: result)
case "stopSite": return self.stopSite(call: call, result: result) case "stopSite": return self.stopSite(call: call, result: result)
case "active.listHostmap": case "active.listHostmap":
self.vpnRequest(command: "listHostmap", arguments: call.arguments, result: result) self.vpnRequest(command: "listHostmap", arguments: call.arguments, result: result)
case "active.listPendingHostmap": case "active.listPendingHostmap":
@ -70,11 +67,10 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
self.vpnRequest(command: "setRemoteForTunnel", arguments: call.arguments, result: result) self.vpnRequest(command: "setRemoteForTunnel", arguments: call.arguments, result: result)
case "active.closeTunnel": case "active.closeTunnel":
self.vpnRequest(command: "closeTunnel", arguments: call.arguments, result: result) self.vpnRequest(command: "closeTunnel", arguments: call.arguments, result: result)
default: default:
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
} }
}) }
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
@ -90,7 +86,8 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
if err != nil { if err != nil {
return result( return result(
CallFailedError( CallFailedError(
message: "Error while parsing certificate(s)", details: err!.localizedDescription)) message: "Error while parsing certificate(s)", details: err!.localizedDescription
))
} }
return result(json) return result(json)
@ -112,7 +109,8 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
return result( return result(
CallFailedError( CallFailedError(
message: "Error while verifying certificate and private key", message: "Error while verifying certificate and private key",
details: err!.localizedDescription)) details: err!.localizedDescription
))
} }
return result(valid) return result(valid)
@ -124,7 +122,8 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
if err != nil { if err != nil {
return result( return result(
CallFailedError( CallFailedError(
message: "Error while generating key pairs", details: err!.localizedDescription)) message: "Error while generating key pairs", details: err!.localizedDescription
))
} }
return result(kp) return result(kp)
@ -150,7 +149,7 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
do { do {
let site = try apiClient.enroll(code: code) let site = try apiClient.enroll(code: code)
let oldSite = self.sites?.getSite(id: site.id) let oldSite = sites?.getSite(id: site.id)
site.save(manager: oldSite?.manager) { error in site.save(manager: oldSite?.manager) { error in
if error != nil { if error != nil {
return result( return result(
@ -166,7 +165,7 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
} }
func listSites(result: @escaping FlutterResult) { func listSites(result: @escaping FlutterResult) {
self.sites?.loadSites { (sites, err) -> Void in sites?.loadSites { sites, err in
if err != nil { if err != nil {
return result( return result(
CallFailedError(message: "Failed to load site list", details: err!.localizedDescription)) CallFailedError(message: "Failed to load site list", details: err!.localizedDescription))
@ -182,7 +181,7 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
func deleteSite(call: FlutterMethodCall, result: @escaping FlutterResult) { func deleteSite(call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let id = call.arguments as? String else { return result(NoArgumentsError()) } guard let id = call.arguments as? String else { return result(NoArgumentsError()) }
// TODO: stop the site if its running currently // TODO: stop the site if its running currently
self.sites?.deleteSite(id: id) { error in sites?.deleteSite(id: id) { error in
if error != nil { if error != nil {
result( result(
CallFailedError(message: "Failed to delete site", details: error!.localizedDescription)) CallFailedError(message: "Failed to delete site", details: error!.localizedDescription))
@ -200,7 +199,7 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
return result(NoArgumentsError()) return result(NoArgumentsError())
} }
let oldSite = self.sites?.getSite(id: site.id) let oldSite = sites?.getSite(id: site.id)
site.save(manager: oldSite?.manager) { error in site.save(manager: oldSite?.manager) { error in
if error != nil { if error != nil {
return result( return result(
@ -220,10 +219,10 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
} }
#if targetEnvironment(simulator) #if targetEnvironment(simulator)
let updater = self.sites?.getUpdater(id: id) let updater = sites?.getUpdater(id: id)
updater?.update(connected: true) updater?.update(connected: true)
#else #else
let container = self.sites?.getContainer(id: id) let container = sites?.getContainer(id: id)
let manager = container?.site.manager let manager = container?.site.manager
manager?.loadFromPreferences { error in manager?.loadFromPreferences { error in
@ -235,14 +234,15 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
manager?.loadFromPreferences { error in manager?.loadFromPreferences { error in
// TODO: Handle load error // TODO: Handle load error
do { do {
container?.updater.startFunc = { () -> Void in container?.updater.startFunc = { () in
return self.vpnRequest(command: "start", arguments: args, result: result) self.vpnRequest(command: "start", arguments: args, result: result)
} }
try manager?.connection.startVPNTunnel(options: ["expectStart": NSNumber(1)]) try manager?.connection.startVPNTunnel(options: ["expectStart": NSNumber(1)])
} catch { } catch {
return result( return result(
CallFailedError( CallFailedError(
message: "Could not start site", details: error.localizedDescription)) message: "Could not start site", details: error.localizedDescription
))
} }
} }
} }
@ -256,12 +256,12 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
return result(MissingArgumentError(message: "id is a required argument")) return result(MissingArgumentError(message: "id is a required argument"))
} }
#if targetEnvironment(simulator) #if targetEnvironment(simulator)
let updater = self.sites?.getUpdater(id: id) let updater = sites?.getUpdater(id: id)
updater?.update(connected: false) updater?.update(connected: false)
#else #else
let manager = self.sites?.getSite(id: id)?.manager let manager = sites?.getSite(id: id)?.manager
manager?.loadFromPreferences { error in manager?.loadFromPreferences { _ in
// TODO: Handle load error // TODO: Handle load error
manager?.connection.stopVPNTunnel() manager?.connection.stopVPNTunnel()
@ -290,7 +290,7 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
if let session = container!.site.manager?.connection as? NETunnelProviderSession { if let session = container!.site.manager?.connection as? NETunnelProviderSession {
do { do {
try session.sendProviderMessage( try session.sendProviderMessage(
try JSONEncoder().encode(IPCRequest(command: command, arguments: JSON(args))) JSONEncoder().encode(IPCRequest(command: command, arguments: JSON(args)))
) { data in ) { data in
if data == nil { if data == nil {
return result(nil) return result(nil)
@ -318,13 +318,13 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
} }
} }
func MissingArgumentError(message: String, details: Error? = nil) -> FlutterError { func MissingArgumentError(message: String, details: (any Error)? = nil) -> FlutterError {
return FlutterError(code: "missingArgument", message: message, details: details) return FlutterError(code: "missingArgument", message: message, details: details)
} }
func NoArgumentsError( func NoArgumentsError(
message: String? = "no arguments were provided or could not be deserialized", message: String? = "no arguments were provided or could not be deserialized",
details: Error? = nil details: (any Error)? = nil
) -> FlutterError { ) -> FlutterError {
return FlutterError(code: "noArguments", message: message, details: details) return FlutterError(code: "noArguments", message: message, details: details)
} }

View file

@ -7,7 +7,7 @@ class DNUpdater {
private let log = Logger(subsystem: "net.defined.mobileNebula", category: "DNUpdater") private let log = Logger(subsystem: "net.defined.mobileNebula", category: "DNUpdater")
func updateAll(onUpdate: @escaping (Site) -> Void) { func updateAll(onUpdate: @escaping (Site) -> Void) {
_ = SiteList { (sites, _) -> Void in _ = SiteList { sites, _ in
// NEVPN seems to force us onto the main thread and we are about to make network calls that // NEVPN seems to force us onto the main thread and we are about to make network calls that
// could block for a while. Push ourselves onto another thread to avoid blocking the UI. // could block for a while. Push ourselves onto another thread to avoid blocking the UI.
Task.detached(priority: .userInitiated) { Task.detached(priority: .userInitiated) {
@ -93,14 +93,13 @@ class DNUpdater {
// From https://medium.com/over-engineering/a-background-repeating-timer-in-swift-412cecfd2ef9 // From https://medium.com/over-engineering/a-background-repeating-timer-in-swift-412cecfd2ef9
class RepeatingTimer { class RepeatingTimer {
let timeInterval: TimeInterval let timeInterval: TimeInterval
init(timeInterval: TimeInterval) { init(timeInterval: TimeInterval) {
self.timeInterval = timeInterval self.timeInterval = timeInterval
} }
private lazy var timer: DispatchSourceTimer = { private lazy var timer: any DispatchSourceTimer = {
let t = DispatchSource.makeTimerSource() let t = DispatchSource.makeTimerSource()
t.schedule(deadline: .now(), repeating: self.timeInterval) t.schedule(deadline: .now(), repeating: self.timeInterval)
t.setEventHandler(handler: { [weak self] in t.setEventHandler(handler: { [weak self] in

View file

@ -13,14 +13,14 @@ class SiteContainer {
class Sites { class Sites {
private var containers = [String: SiteContainer]() private var containers = [String: SiteContainer]()
private var messenger: FlutterBinaryMessenger? private var messenger: (any FlutterBinaryMessenger)?
init(messenger: FlutterBinaryMessenger?) { init(messenger: (any FlutterBinaryMessenger)?) {
self.messenger = messenger self.messenger = messenger
} }
func loadSites(completion: @escaping ([String: Site]?, Error?) -> Void) { func loadSites(completion: @escaping ([String: Site]?, (any Error)?) -> Void) {
_ = SiteList { (sites, err) in _ = SiteList { sites, err in
if err != nil { if err != nil {
return completion(nil, err) return completion(nil, err)
} }
@ -36,14 +36,14 @@ class Sites {
} }
let justSites = self.containers.mapValues { let justSites = self.containers.mapValues {
return $0.site $0.site
} }
completion(justSites, nil) completion(justSites, nil)
} }
} }
func deleteSite(id: String, callback: @escaping (Error?) -> Void) { func deleteSite(id: String, callback: @escaping ((any Error)?) -> Void) {
if let site = self.containers.removeValue(forKey: id) { if let site = containers.removeValue(forKey: id) {
_ = KeyChain.delete(key: "\(site.site.id).dnCredentials") _ = KeyChain.delete(key: "\(site.site.id).dnCredentials")
_ = KeyChain.delete(key: "\(site.site.id).key") _ = KeyChain.delete(key: "\(site.site.id).key")
@ -66,15 +66,15 @@ class Sites {
} }
func getSite(id: String) -> Site? { func getSite(id: String) -> Site? {
return self.containers[id]?.site return containers[id]?.site
} }
func getUpdater(id: String) -> SiteUpdater? { func getUpdater(id: String) -> SiteUpdater? {
return self.containers[id]?.updater return containers[id]?.updater
} }
func getContainer(id: String) -> SiteContainer? { func getContainer(id: String) -> SiteContainer? {
return self.containers[id] return containers[id]
} }
} }
@ -85,38 +85,39 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
private var notification: Any? private var notification: Any?
public var startFunc: (() -> Void)? public var startFunc: (() -> Void)?
private var configFd: Int32? = nil private var configFd: Int32? = nil
private var configObserver: DispatchSourceFileSystemObject? = nil private var configObserver: (any DispatchSourceFileSystemObject)? = nil
init(messenger: FlutterBinaryMessenger, site: Site) { init(messenger: any FlutterBinaryMessenger, site: Site) {
do { do {
let configPath = try SiteList.getSiteConfigFile(id: site.id, createDir: false) let configPath = try SiteList.getSiteConfigFile(id: site.id, createDir: false)
self.configFd = open(configPath.path, O_EVTONLY) configFd = open(configPath.path, O_EVTONLY)
self.configObserver = DispatchSource.makeFileSystemObjectSource( configObserver = DispatchSource.makeFileSystemObjectSource(
fileDescriptor: self.configFd!, fileDescriptor: configFd!,
eventMask: .write eventMask: .write
) )
} catch { } catch {
// SiteList.getSiteConfigFile should never throw because we are not creating it here // SiteList.getSiteConfigFile should never throw because we are not creating it here
self.configObserver = nil configObserver = nil
} }
eventChannel = FlutterEventChannel( eventChannel = FlutterEventChannel(
name: "net.defined.nebula/\(site.id)", binaryMessenger: messenger) name: "net.defined.nebula/\(site.id)", binaryMessenger: messenger
)
self.site = site self.site = site
super.init() super.init()
eventChannel.setStreamHandler(self) eventChannel.setStreamHandler(self)
self.configObserver?.setEventHandler(handler: self.configUpdated) configObserver?.setEventHandler(handler: configUpdated)
self.configObserver?.setCancelHandler { configObserver?.setCancelHandler {
if self.configFd != nil { if self.configFd != nil {
close(self.configFd!) close(self.configFd!)
} }
self.configObserver = nil self.configObserver = nil
} }
self.configObserver?.resume() configObserver?.resume()
} }
func setSite(site: Site) { func setSite(site: Site) {
@ -124,7 +125,7 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
} }
/// onListen is called when flutter code attaches an event listener /// onListen is called when flutter code attaches an event listener
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) func onListen(withArguments _: Any?, eventSink events: @escaping FlutterEventSink)
-> FlutterError? -> FlutterError?
{ {
eventSink = events eventSink = events
@ -134,19 +135,20 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
// TODO: The dn updater path seems to race to build a site that lacks a manager. The UI does not display this error // TODO: The dn updater path seems to race to build a site that lacks a manager. The UI does not display this error
// and a another listen should occur and succeed. // and a another listen should occur and succeed.
return FlutterError( return FlutterError(
code: "Internal Error", message: "Flutter manager was not present", details: nil) code: "Internal Error", message: "Flutter manager was not present", details: nil
)
} }
self.notification = NotificationCenter.default.addObserver( notification = NotificationCenter.default.addObserver(
forName: NSNotification.Name.NEVPNStatusDidChange, object: site.manager!.connection, forName: NSNotification.Name.NEVPNStatusDidChange, object: site.manager!.connection,
queue: nil queue: nil
) { n in ) { _ in
let oldConnected = self.site.connected let oldConnected = self.site.connected
self.site.status = statusString[self.site.manager!.connection.status] self.site.status = statusString[self.site.manager!.connection.status]
self.site.connected = statusMap[self.site.manager!.connection.status] self.site.connected = statusMap[self.site.manager!.connection.status]
// Check to see if we just moved to connected and if we have a start function to call when that happens // Check to see if we just moved to connected and if we have a start function to call when that happens
if self.site.connected! && oldConnected != self.site.connected && self.startFunc != nil { if self.site.connected!, oldConnected != self.site.connected, self.startFunc != nil {
self.startFunc!() self.startFunc!()
self.startFunc = nil self.startFunc = nil
} }
@ -158,9 +160,9 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
} }
/// onCancel is called when the flutter listener stops listening /// onCancel is called when the flutter listener stops listening
func onCancel(withArguments arguments: Any?) -> FlutterError? { func onCancel(withArguments _: Any?) -> FlutterError? {
if self.notification != nil { if notification != nil {
NotificationCenter.default.removeObserver(self.notification!) NotificationCenter.default.removeObserver(notification!)
} }
return nil return nil
} }
@ -175,18 +177,18 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
let encoder = JSONEncoder() let encoder = JSONEncoder()
let data = try! encoder.encode(site) let data = try! encoder.encode(site)
self.eventSink?(String(data: data, encoding: .utf8)) eventSink?(String(data: data, encoding: .utf8))
} }
private func configUpdated() { private func configUpdated() {
if self.site.connected != true { if site.connected != true {
return return
} }
guard let newSite = try? Site(manager: self.site.manager!) else { guard let newSite = try? Site(manager: site.manager!) else {
return return
} }
self.update(connected: newSite.connected ?? false, replaceSite: newSite) update(connected: newSite.connected ?? false, replaceSite: newSite)
} }
} }