From: Alessio Nossa Date: Tue, 1 Feb 2022 12:28:33 +0000 (+0100) Subject: Implemented SetTunnelStatus intent X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bb6ea1b2f314928fcd3ccbb74fcd47e7a71a34e9;p=thirdparty%2Fwireguard-apple.git Implemented SetTunnelStatus intent Signed-off-by: Alessio Nossa --- diff --git a/Sources/Shared/Intents.intentdefinition b/Sources/Shared/Intents.intentdefinition index 590fa39..6f2d38c 100644 --- a/Sources/Shared/Intents.intentdefinition +++ b/Sources/Shared/Intents.intentdefinition @@ -3,7 +3,94 @@ INEnums - + + + INEnumDisplayName + State + INEnumDisplayNameID + kiXIKs + INEnumGeneratesHeader + + INEnumName + State + INEnumType + State + INEnumValues + + + INEnumValueDisplayName + unknown + INEnumValueDisplayNameID + 57IgER + INEnumValueName + unknown + + + INEnumValueDisplayName + on + INEnumValueDisplayNameID + tbFf8o + INEnumValueIndex + 1 + INEnumValueName + on + + + INEnumValueDisplayName + off + INEnumValueDisplayNameID + 7lY4Sc + INEnumValueIndex + 2 + INEnumValueName + off + + + + + INEnumDisplayName + Operation + INEnumDisplayNameID + urZ7WZ + INEnumGeneratesHeader + + INEnumName + Operation + INEnumType + Regular + INEnumValues + + + INEnumValueDisplayName + unknown + INEnumValueDisplayNameID + efZxZH + INEnumValueName + unknown + + + INEnumValueDisplayName + Turn + INEnumValueDisplayNameID + viQ2K7 + INEnumValueIndex + 1 + INEnumValueName + turn + + + INEnumValueDisplayName + Toggle + INEnumValueDisplayNameID + N4IzMI + INEnumValueIndex + 2 + INEnumValueName + toggle + + + + INIntentDefinitionModelVersion 1.2 INIntentDefinitionNamespace @@ -360,6 +447,271 @@ Example INIntentVerb Do + + INIntentCategory + toggle + INIntentConfigurable + + INIntentDescription + Enables or disables a tunnel. + +The action will not return an error if there are problems during the activation, it just launches the procedure. + INIntentDescriptionID + T7NEBw + INIntentEligibleForWidgets + + INIntentLastParameterTag + 4 + INIntentManagedParameterCombinations + + state,tunnel,operation + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + ${operation} ${tunnel} tunnel ${state} + INIntentParameterCombinationTitleID + juDVvv + INIntentParameterCombinationUpdatesLinked + + + tunnel,operation + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + ${operation} ${tunnel} tunnel + INIntentParameterCombinationTitleID + kMEAI0 + + + INIntentName + SetTunnelStatus + INIntentParameterCombinations + + state,tunnel,operation + + INIntentParameterCombinationIsLinked + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + ${operation} ${tunnel} tunnel ${state} + INIntentParameterCombinationTitleID + SjQ7Ur + + + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Tunnel + INIntentParameterDisplayNameID + mbhQcc + INIntentParameterDisplayPriority + 1 + INIntentParameterMetadata + + INIntentParameterMetadataCapitalization + Sentences + INIntentParameterMetadataDefaultValueID + iBD5fT + + INIntentParameterName + tunnel + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + There are ${count} options matching ‘${tunnel}’. + INIntentParameterPromptDialogFormatStringID + 73NvEc + INIntentParameterPromptDialogType + DisambiguationIntroduction + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Just to confirm, you wanted ‘${tunnel}’? + INIntentParameterPromptDialogFormatStringID + 63TirR + INIntentParameterPromptDialogType + Confirmation + + + INIntentParameterSupportsDynamicEnumeration + + INIntentParameterTag + 2 + INIntentParameterType + String + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Operation + INIntentParameterDisplayNameID + U9YFTG + INIntentParameterDisplayPriority + 2 + INIntentParameterEnumType + Operation + INIntentParameterEnumTypeNamespace + 6NREiY + INIntentParameterMetadata + + INIntentParameterMetadataDefaultValue + turn + + INIntentParameterName + operation + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + There are ${count} options matching ‘${operation}’. + INIntentParameterPromptDialogFormatStringID + SALmBF + INIntentParameterPromptDialogType + DisambiguationIntroduction + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Just to confirm, you wanted ‘${operation}’? + INIntentParameterPromptDialogFormatStringID + vB13mD + INIntentParameterPromptDialogType + Confirmation + + + INIntentParameterTag + 4 + INIntentParameterType + Integer + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + State + INIntentParameterDisplayNameID + sLIh6r + INIntentParameterDisplayPriority + 3 + INIntentParameterEnumType + State + INIntentParameterEnumTypeNamespace + 6NREiY + INIntentParameterMetadata + + INIntentParameterMetadataDefaultValue + on + + INIntentParameterName + state + INIntentParameterRelationship + + INIntentParameterRelationshipParentName + operation + INIntentParameterRelationshipPredicateName + EnumHasExactValue + INIntentParameterRelationshipPredicateValue + turn + + INIntentParameterTag + 1 + INIntentParameterType + Integer + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + INIntentResponseLastParameterTag + 1 + INIntentResponseParameters + + + INIntentResponseParameterConfigurable + + INIntentResponseParameterCustomDisambiguation + + INIntentResponseParameterDisplayName + State + INIntentResponseParameterDisplayNameID + TXqy56 + INIntentResponseParameterDisplayPriority + 1 + INIntentResponseParameterEnumType + State + INIntentResponseParameterEnumTypeNamespace + 6NREiY + INIntentResponseParameterName + state + INIntentResponseParameterSupportsResolution + + INIntentResponseParameterTag + 1 + INIntentResponseParameterType + Integer + + + + INIntentTitle + Set Tunnel Status + INIntentTitleID + HULmDn + INIntentType + Custom + INIntentVerb + Toggle + INTypes diff --git a/Sources/WireGuardIntentsExtension/Info.plist b/Sources/WireGuardIntentsExtension/Info.plist index 06ec3ab..fbe7d56 100644 --- a/Sources/WireGuardIntentsExtension/Info.plist +++ b/Sources/WireGuardIntentsExtension/Info.plist @@ -33,6 +33,7 @@ IntentsSupported GetPeersIntent + SetTunnelStatusIntent UpdateConfigurationIntent diff --git a/Sources/WireGuardIntentsExtension/IntentHandler.swift b/Sources/WireGuardIntentsExtension/IntentHandler.swift index 62eb5e2..6f19951 100644 --- a/Sources/WireGuardIntentsExtension/IntentHandler.swift +++ b/Sources/WireGuardIntentsExtension/IntentHandler.swift @@ -11,7 +11,9 @@ class IntentHandler: INExtension { } override func handler(for intent: INIntent) -> Any { - guard intent is GetPeersIntent || intent is UpdateConfigurationIntent else { + guard intent is GetPeersIntent || + intent is UpdateConfigurationIntent || + intent is SetTunnelStatusIntent else { fatalError("Unhandled intent type: \(intent)") } diff --git a/Sources/WireGuardIntentsExtension/IntentHandling.swift b/Sources/WireGuardIntentsExtension/IntentHandling.swift index 1de3d46..78004e0 100644 --- a/Sources/WireGuardIntentsExtension/IntentHandling.swift +++ b/Sources/WireGuardIntentsExtension/IntentHandling.swift @@ -15,6 +15,8 @@ class IntentHandling: NSObject { var onTunnelsManagerReady: ((TunnelsManager) -> Void)? + var onTunnelStatusActivationReturn: ((Bool) -> Void)? + override init() { super.init() @@ -27,6 +29,8 @@ class IntentHandling: NSObject { case .success(let tunnelsManager): self.tunnelsManager = tunnelsManager + self.tunnelsManager?.activationDelegate = self + self.onTunnelsManagerReady?(tunnelsManager) self.onTunnelsManagerReady = nil } @@ -170,3 +174,129 @@ extension IntentHandling: UpdateConfigurationIntentHandling { } } + +extension IntentHandling: SetTunnelStatusIntentHandling { + + @available(iOSApplicationExtension 14.0, *) + func provideTunnelOptionsCollection(for intent: SetTunnelStatusIntent, with completion: @escaping (INObjectCollection?, Error?) -> Void) { + + self.allTunnelNames { tunnelsNames in + let tunnelsNamesObjects = (tunnelsNames ?? []).map { NSString(string: $0) } + + let objectCollection = INObjectCollection(items: tunnelsNamesObjects) + completion(objectCollection, nil) + } + } + + func handle(intent: SetTunnelStatusIntent, completion: @escaping (SetTunnelStatusIntentResponse) -> Void) { + guard let tunnelName = intent.tunnel else { + return completion(SetTunnelStatusIntentResponse(code: .failure, userActivity: nil)) + } + + let setTunnelStatusResultBlock: (Bool) -> Void = { result in + if result { + completion(SetTunnelStatusIntentResponse(code: .success, userActivity: nil)) + } else { + completion(SetTunnelStatusIntentResponse(code: .failure, userActivity: nil)) + } + } + + let updateStatusBlock: (TunnelsManager) -> Void = { tunnelsManager in + guard let tunnel = tunnelsManager.tunnel(named: tunnelName) else { + completion(SetTunnelStatusIntentResponse(code: .failure, userActivity: nil)) + return + } + + let operation = intent.operation + let isOn: Bool + + if operation == .toggle { + switch tunnel.status { + case .inactive: + isOn = true + case .active: + isOn = false + default: + wg_log(.error, message: "SetTunnelStatusIntent action cannot be executed due to the current state of \(tunnelName) tunnel: \(tunnel.status)") + completion(SetTunnelStatusIntentResponse(code: .failure, userActivity: nil)) + return + } + + } else if operation == .turn { + if (tunnel.status == .inactive) || (tunnel.status == .active) { + isOn = (intent.state == .on) + + if (isOn && tunnel.status == .active) || (!isOn && tunnel.status == .inactive) { + wg_log(.debug, message: "Tunnel \(tunnelName) is already \(isOn ? "active" : "inactive")") + completion(SetTunnelStatusIntentResponse(code: .success, userActivity: nil)) + return + } + } else { + wg_log(.error, message: "SetTunnelStatusIntent action cannot be executed due to the current state of \(tunnelName) tunnel: \(tunnel.status)") + completion(SetTunnelStatusIntentResponse(code: .failure, userActivity: nil)) + return + } + + } else { + wg_log(.error, message: "Invalid 'operation' option in action") + completion(SetTunnelStatusIntentResponse(code: .failure, userActivity: nil)) + return + } + + if tunnel.hasOnDemandRules { + tunnelsManager.setOnDemandEnabled(isOn, on: tunnel) { error in + guard error == nil else { + wg_log(.error, message: "Error setting OnDemand status: \(error!.localizedDescription).") + completion(SetTunnelStatusIntentResponse(code: .failure, userActivity: nil)) + return + } + + if !isOn { + tunnelsManager.startDeactivation(of: tunnel) + } + + completion(SetTunnelStatusIntentResponse(code: .success, userActivity: nil)) + } + } else { + if isOn { + self.onTunnelStatusActivationReturn = setTunnelStatusResultBlock + tunnelsManager.startActivation(of: tunnel) + } else { + tunnelsManager.startDeactivation(of: tunnel) + completion(SetTunnelStatusIntentResponse(code: .success, userActivity: nil)) + } + } + } + + if let tunnelsManager = tunnelsManager { + updateStatusBlock(tunnelsManager) + } else { + if onTunnelsManagerReady != nil { + wg_log(.error, message: "Overriding onTunnelsManagerReady action in allTunnelPeers function. This should not happen.") + } + onTunnelsManagerReady = updateStatusBlock + } + } + +} + +extension IntentHandling: TunnelsManagerActivationDelegate { + func tunnelActivationAttemptFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationAttemptError) { + wg_log(.error, message: "Tunnel Activation Attempt Failed with error: \(error.localizedDescription)") + self.onTunnelStatusActivationReturn?(false) + } + + func tunnelActivationAttemptSucceeded(tunnel: TunnelContainer) { + // Nothing to do, we wait tunnelActivationSucceeded to be sure all activation logic has been executed + } + + func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationError) { + wg_log(.error, message: "Tunnel Activation Failed with error: \(error.localizedDescription)") + self.onTunnelStatusActivationReturn?(false) + } + + func tunnelActivationSucceeded(tunnel: TunnelContainer) { + self.onTunnelStatusActivationReturn?(true) + } + +}