From bbba8667879db0b6b3e8709d74bba0704cc7998f Mon Sep 17 00:00:00 2001 From: Jon Petersson Date: Fri, 29 Sep 2023 11:05:27 +0200 Subject: [PATCH] WIP --- ios/MullvadVPN.xcodeproj/project.pbxproj | 169 ++++++- .../xcschemes/MullvadVPN.xcscheme | 11 + .../xcschemes/PacketTunnel.xcscheme | 14 + .../BlockedStateErrorMapper.swift | 4 + .../State+Extensions.swift | 57 --- .../Actor/ConfigurationBuilder.swift | 29 +- .../Actor/PacketTunnelActor+KeyPolicy.swift | 4 +- .../Actor/State+Extensions.swift | 57 +++ ios/PacketTunnelCore/Actor/State.swift | 5 +- .../IPC}/AppMessageHandler.swift | 9 +- .../Mocks/BlockedStateErrorMapperStub.swift | 2 +- .../Mocks/DefaultPathObserverFake.swift | 2 +- .../Mocks/TunnelAdapterDummy.swift | 2 +- ios/PacketTunnelCoreTests/PingerTests.swift | 3 +- .../TaskQueueTests.swift | 79 ++++ .../AppMessageHandlerTests.swift | 436 ++++++++++++++++++ ios/PacketTunnelTests/PacketTunnelTests.swift | 34 ++ 17 files changed, 826 insertions(+), 91 deletions(-) delete mode 100644 ios/PacketTunnel/PacketTunnelProvider/State+Extensions.swift rename ios/{PacketTunnel/PacketTunnelProvider => PacketTunnelCore/IPC}/AppMessageHandler.swift (91%) create mode 100644 ios/PacketTunnelCoreTests/TaskQueueTests.swift create mode 100644 ios/PacketTunnelTests/AppMessageHandlerTests.swift create mode 100644 ios/PacketTunnelTests/PacketTunnelTests.swift diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index c2000d755b68..19ea360d7ee1 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -85,7 +85,6 @@ 582AE3102440A6CA00E6733A /* InputTextFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582AE30F2440A6CA00E6733A /* InputTextFormatter.swift */; }; 582BB1AF229566420055B6EF /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1AE229566420055B6EF /* SettingsCell.swift */; }; 582BB1B1229569620055B6EF /* UINavigationBar+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1B0229569620055B6EF /* UINavigationBar+Appearance.swift */; }; - 58342C042AAB61FB003BA12D /* State+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58342C032AAB61FB003BA12D /* State+Extensions.swift */; }; 5835B7CC233B76CB0096D79F /* TunnelManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5835B7CB233B76CB0096D79F /* TunnelManager.swift */; }; 5838321B2AC1B18400EA2071 /* PacketTunnelActor+Mocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5838321A2AC1B18400EA2071 /* PacketTunnelActor+Mocks.swift */; }; 5838321D2AC1C54600EA2071 /* TaskSleepTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5838321C2AC1C54600EA2071 /* TaskSleepTests.swift */; }; @@ -118,7 +117,6 @@ 585A02EB2A4B285800C6CAFF /* UDPConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585A02EA2A4B285800C6CAFF /* UDPConnection.swift */; }; 585A02ED2A4B28F300C6CAFF /* TCPConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585A02EC2A4B28F300C6CAFF /* TCPConnection.swift */; }; 585B1FF02AB09F97008AD470 /* VPNConnectionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9F360332AAB626300F53531 /* VPNConnectionProtocol.swift */; }; - 585B1FF22AB0BC69008AD470 /* State+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585B1FF12AB0BC69008AD470 /* State+Extensions.swift */; }; 585B4B8726D9098900555C4C /* TunnelStatusNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A94AE326CFD945001CB97C /* TunnelStatusNotificationProvider.swift */; }; 585CA70F25F8C44600B47C62 /* UIMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585CA70E25F8C44600B47C62 /* UIMetrics.swift */; }; 585E820327F3285E00939F0E /* SendStoreReceiptOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585E820227F3285E00939F0E /* SendStoreReceiptOperation.swift */; }; @@ -267,7 +265,7 @@ 58C76A092A33850E00100D75 /* ApplicationTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C76A072A33850E00100D75 /* ApplicationTarget.swift */; }; 58C76A0B2A338E4300100D75 /* BackgroundTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C76A0A2A338E4300100D75 /* BackgroundTask.swift */; }; 58C774BE29A7A249003A1A56 /* CustomNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C774BD29A7A249003A1A56 /* CustomNavigationController.swift */; }; - 58C7A43E2A863F470060C66F /* PacketTunnelCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58C7A4362A863F440060C66F /* PacketTunnelCore.framework */; platformFilter = ios; }; + 58C7A43E2A863F470060C66F /* PacketTunnelCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58C7A4362A863F440060C66F /* PacketTunnelCore.framework */; }; 58C7A4462A863F490060C66F /* PacketTunnelCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 58C7A4382A863F450060C66F /* PacketTunnelCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; 58C7A4492A863F490060C66F /* PacketTunnelCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58C7A4362A863F440060C66F /* PacketTunnelCore.framework */; }; 58C7A44A2A863F490060C66F /* PacketTunnelCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 58C7A4362A863F440060C66F /* PacketTunnelCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -279,7 +277,7 @@ 58C7A4582A863FB90060C66F /* TunnelMonitorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C7A42C2A85067A0060C66F /* TunnelMonitorProtocol.swift */; }; 58C7A4592A863FB90060C66F /* WgStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A3BDAF28A1821A00C8C2C6 /* WgStats.swift */; }; 58C7A45B2A8640030060C66F /* PacketTunnelPathObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58225D272A84F23B0083D7F1 /* PacketTunnelPathObserver.swift */; }; - 58C7A45C2A8640490060C66F /* MullvadLogging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58D223F3294C8FF00029F5F8 /* MullvadLogging.framework */; platformFilter = ios; }; + 58C7A45C2A8640490060C66F /* MullvadLogging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58D223F3294C8FF00029F5F8 /* MullvadLogging.framework */; }; 58C7A4692A8643A90060C66F /* IPv4Header.h in Headers */ = {isa = PBXBuildFile; fileRef = 58218E1428B65058000C624F /* IPv4Header.h */; settings = {ATTRIBUTES = (Public, ); }; }; 58C7A46A2A8643A90060C66F /* ICMPHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = 58218E1628B65396000C624F /* ICMPHeader.h */; settings = {ATTRIBUTES = (Public, ); }; }; 58C7A4702A8649ED0060C66F /* PingerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C7A46F2A8649ED0060C66F /* PingerTests.swift */; }; @@ -451,6 +449,9 @@ 7A42DEC92A05164100B209BE /* SettingsInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A42DEC82A05164100B209BE /* SettingsInputCell.swift */; }; 7A42DECD2A09064C00B209BE /* SelectableSettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A42DECC2A09064C00B209BE /* SelectableSettingsCell.swift */; }; 7A6B4F592AB8412E00123853 /* TunnelMonitorTimings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6B4F582AB8412E00123853 /* TunnelMonitorTimings.swift */; }; + 7A6F1A272AC57071008ACF78 /* PacketTunnelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F1A262AC57071008ACF78 /* PacketTunnelTests.swift */; }; + 7A6F1A372AC57558008ACF78 /* AppMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F775422AB9E3EF00425B47 /* AppMessageHandler.swift */; }; + 7A6F1A392AC5762B008ACF78 /* State+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58342C032AAB61FB003BA12D /* State+Extensions.swift */; }; 7A7AD28D29DC677800480EF1 /* FirstTimeLaunch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */; }; 7A7AD28F29DEDB1C00480EF1 /* SettingsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7AD28E29DEDB1C00480EF1 /* SettingsHeaderView.swift */; }; 7A818F1F29F0305800C7F0F4 /* RootConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A818F1E29F0305800C7F0F4 /* RootConfiguration.swift */; }; @@ -489,6 +490,7 @@ 7ABE318D2A1CDD4500DF4963 /* UIFont+Weight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABE318C2A1CDD4500DF4963 /* UIFont+Weight.swift */; }; 7AE044BB2A935726003915D8 /* Routing.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A88DCD02A8FABBE00D2FF0E /* Routing.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7AE47E522A17972A000418DA /* AlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE47E512A17972A000418DA /* AlertViewController.swift */; }; + 7AEF7F182ACAF85B006FE45D /* AppMessageHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F1A1C2AC56C86008ACF78 /* AppMessageHandlerTests.swift */; }; 7AF6E5F02A95051E00F2679D /* RouterBlockDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF6E5EF2A95051E00F2679D /* RouterBlockDelegate.swift */; }; 7AF6E5F12A95F4A500F2679D /* DurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FBFBF0291630700020E046 /* DurationTests.swift */; }; 7AF9BE992A4E0FE900DBFEDB /* MarkdownStylingOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE982A4E0FE900DBFEDB /* MarkdownStylingOptions.swift */; }; @@ -498,7 +500,7 @@ A9467E7F2A29DEFE000DC21F /* RelayCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9467E7E2A29DEFE000DC21F /* RelayCacheTests.swift */; }; A9467E802A29E0A6000DC21F /* AddressCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */; }; A94D691A2ABAD66700413DD4 /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 58FE25E22AA72AE9003D1918 /* WireGuardKitTypes */; }; - A94D691B2ABAD66700413DD4 /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 58FE25E72AA7399D003D1918 /* WireGuardKitTypes */; }; + A94D691B2ABAD66700413DD4 /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 58FE25E72AA7399D003D1918 /* WireGuardKitTypes */; settings = {ATTRIBUTES = (Required, ); }; }; A95F86B72A1F53BA00245DAC /* URLSessionTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FAE67C28F83CA50033DD93 /* URLSessionTransport.swift */; }; A95F86B82A1F547000245DAC /* ShadowsocksProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F1FF1B29F06124007083C3 /* ShadowsocksProxy.swift */; }; A97F1F442A1F4E1A00ECEFDE /* MullvadTransport.h in Headers */ = {isa = PBXBuildFile; fileRef = A97F1F432A1F4E1A00ECEFDE /* MullvadTransport.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -841,6 +843,13 @@ remoteGlobalIDString = 58D223D4294C8E5E0029F5F8; remoteInfo = MullvadTypes; }; + 7A6F1A282AC57071008ACF78 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 58CE5E58224146200008646E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 58CE5E5F224146200008646E; + remoteInfo = MullvadVPN; + }; 7A88DCD92A8FABBE00D2FF0E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 58CE5E58224146200008646E /* Project object */; @@ -1171,7 +1180,6 @@ 585A02E82A4B283000C6CAFF /* TCPUnsafeListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TCPUnsafeListener.swift; sourceTree = ""; }; 585A02EA2A4B285800C6CAFF /* UDPConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPConnection.swift; sourceTree = ""; }; 585A02EC2A4B28F300C6CAFF /* TCPConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TCPConnection.swift; sourceTree = ""; }; - 585B1FF12AB0BC69008AD470 /* State+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "State+Extensions.swift"; sourceTree = ""; }; 585CA70E25F8C44600B47C62 /* UIMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIMetrics.swift; sourceTree = ""; }; 585DA87626B024A600B8C587 /* CachedRelays.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedRelays.swift; sourceTree = ""; }; 585DA89226B0323E00B8C587 /* TunnelProviderMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelProviderMessage.swift; sourceTree = ""; }; @@ -1428,6 +1436,9 @@ 7A42DEC82A05164100B209BE /* SettingsInputCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInputCell.swift; sourceTree = ""; }; 7A42DECC2A09064C00B209BE /* SelectableSettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableSettingsCell.swift; sourceTree = ""; }; 7A6B4F582AB8412E00123853 /* TunnelMonitorTimings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitorTimings.swift; sourceTree = ""; }; + 7A6F1A1C2AC56C86008ACF78 /* AppMessageHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMessageHandlerTests.swift; sourceTree = ""; }; + 7A6F1A242AC57071008ACF78 /* PacketTunnelTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PacketTunnelTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 7A6F1A262AC57071008ACF78 /* PacketTunnelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelTests.swift; sourceTree = ""; }; 7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstTimeLaunch.swift; sourceTree = ""; }; 7A7AD28E29DEDB1C00480EF1 /* SettingsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHeaderView.swift; sourceTree = ""; }; 7A818F1E29F0305800C7F0F4 /* RootConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootConfiguration.swift; sourceTree = ""; }; @@ -1684,6 +1695,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7A6F1A212AC57071008ACF78 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 7A88DCCB2A8FABBE00D2FF0E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2451,6 +2469,7 @@ 58C9B8C52ABB23B400040B46 /* IPC */ = { isa = PBXGroup; children = ( + 58F775422AB9E3EF00425B47 /* AppMessageHandler.swift */, 587C575226D2615F005EF767 /* PacketTunnelOptions.swift */, 5898D2B62902A9EA00EB5EBA /* PacketTunnelRelay.swift */, 585DA89826B0329200B8C587 /* PacketTunnelStatus.swift */, @@ -2525,6 +2544,7 @@ 58C7A4432A863F490060C66F /* PacketTunnelCoreTests */, 7A88DCCF2A8FABBE00D2FF0E /* Routing */, 7A88DCDD2A8FABBE00D2FF0E /* RoutingTests */, + 7A6F1A252AC57071008ACF78 /* PacketTunnelTests */, 58CE5E61224146200008646E /* Products */, 584F991F2902CBDD001F858D /* Frameworks */, ); @@ -2553,6 +2573,7 @@ 7A88DCCE2A8FABBE00D2FF0E /* Routing.framework */, 7A88DCD72A8FABBE00D2FF0E /* RoutingTests.xctest */, 58B2FDD32AA71D2A003EB5C6 /* MullvadSettings.framework */, + 7A6F1A242AC57071008ACF78 /* PacketTunnelTests.xctest */, ); name = Products; sourceTree = ""; @@ -2731,8 +2752,6 @@ isa = PBXGroup; children = ( 58F3F3692AA08E3C00D3B0A4 /* PacketTunnelProvider.swift */, - 58F775422AB9E3EF00425B47 /* AppMessageHandler.swift */, - 585B1FF12AB0BC69008AD470 /* State+Extensions.swift */, 580D6B912AB360BE00B2D6E0 /* DeviceCheck+BlockedStateReason.swift */, 5864AF7C2A9F4DC9008BC928 /* SettingsReader.swift */, 580D6B8D2AB33BBF00B2D6E0 /* BlockedStateErrorMapper.swift */, @@ -2765,6 +2784,15 @@ path = Alert; sourceTree = ""; }; + 7A6F1A252AC57071008ACF78 /* PacketTunnelTests */ = { + isa = PBXGroup; + children = ( + 7A6F1A1C2AC56C86008ACF78 /* AppMessageHandlerTests.swift */, + 7A6F1A262AC57071008ACF78 /* PacketTunnelTests.swift */, + ); + path = PacketTunnelTests; + sourceTree = ""; + }; 7A83C3FC2A55B39500DFB83A /* TestPlans */ = { isa = PBXGroup; children = ( @@ -3184,6 +3212,7 @@ buildRules = ( ); dependencies = ( + 7A6F1A3C2AC576E3008ACF78 /* PBXTargetDependency */, 58C7A4402A863F470060C66F /* PBXTargetDependency */, 58C7A4722A864B860060C66F /* PBXTargetDependency */, ); @@ -3363,6 +3392,26 @@ productReference = 58FBFBE6291622580020E046 /* MullvadRESTTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 7A6F1A232AC57071008ACF78 /* PacketTunnelTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7A6F1A2A2AC57071008ACF78 /* Build configuration list for PBXNativeTarget "PacketTunnelTests" */; + buildPhases = ( + 7A6F1A202AC57071008ACF78 /* Sources */, + 7A6F1A212AC57071008ACF78 /* Frameworks */, + 7A6F1A222AC57071008ACF78 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 7A6F1A292AC57071008ACF78 /* PBXTargetDependency */, + ); + name = PacketTunnelTests; + packageProductDependencies = ( + ); + productName = PacketTunnelTests; + productReference = 7A6F1A242AC57071008ACF78 /* PacketTunnelTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 7A88DCCD2A8FABBE00D2FF0E /* Routing */ = { isa = PBXNativeTarget; buildConfigurationList = 7A88DCE52A8FABBF00D2FF0E /* Build configuration list for PBXNativeTarget "Routing" */; @@ -3431,7 +3480,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 1430; + LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1430; ORGANIZATIONNAME = "Mullvad VPN AB"; TargetAttributes = { @@ -3506,6 +3555,9 @@ 58FBFBE5291622580020E046 = { CreatedOnToolsVersion = 14.1; }; + 7A6F1A232AC57071008ACF78 = { + CreatedOnToolsVersion = 15.0; + }; 7A88DCCD2A8FABBE00D2FF0E = { CreatedOnToolsVersion = 14.3.1; }; @@ -3537,6 +3589,7 @@ 58CE5E5F224146200008646E /* MullvadVPN */, 58FBDA9722A519BC00EB69A3 /* WireGuardGoBridge */, 58CE5E78224146470008646E /* PacketTunnel */, + 7A6F1A232AC57071008ACF78 /* PacketTunnelTests */, 58B0A29F238EE67E00BC001D /* MullvadVPNTests */, 58D0C79223F1CE7000FE9BA7 /* MullvadVPNScreenshots */, 58D223A4294C8A480029F5F8 /* Operations */, @@ -3667,6 +3720,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7A6F1A222AC57071008ACF78 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 7A88DCCC2A8FABBE00D2FF0E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -3928,6 +3988,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7A6F1A392AC5762B008ACF78 /* State+Extensions.swift in Sources */, 58FE25F42AA9D730003D1918 /* PacketTunnelActor+Extensions.swift in Sources */, 58DDA18F2ABC32380039C360 /* Timings.swift in Sources */, 58FE25DF2AA72A9B003D1918 /* RelaySelectorProtocol.swift in Sources */, @@ -3944,6 +4005,7 @@ 583832252AC318A100EA2071 /* PacketTunnelActor+ConnectionMonitoring.swift in Sources */, 58C7A4552A863FB90060C66F /* TunnelMonitor.swift in Sources */, 58C7AF182ABD84AB007EDD7A /* ProxyURLResponse.swift in Sources */, + 7A6F1A372AC57558008ACF78 /* AppMessageHandler.swift in Sources */, 58C7A4512A863FB50060C66F /* PingerProtocol.swift in Sources */, 583832292AC3DF1300EA2071 /* Command.swift in Sources */, 583832232AC3181400EA2071 /* PacketTunnelActor+ErrorState.swift in Sources */, @@ -3961,7 +4023,6 @@ 586E8DB82AAF4AC4007BF3DA /* Task+Duration.swift in Sources */, 5838322B2AC3EF9600EA2071 /* CommandChannel.swift in Sources */, 586C145A2AC4735F00245C01 /* PacketTunnelActor+Public.swift in Sources */, - 58342C042AAB61FB003BA12D /* State+Extensions.swift in Sources */, 583832272AC3193600EA2071 /* PacketTunnelActor+SleepCycle.swift in Sources */, 58FE25DC2AA72A8F003D1918 /* AnyTask.swift in Sources */, 58C7AF152ABD8480007EDD7A /* PacketTunnelRelay.swift in Sources */, @@ -3989,6 +4050,7 @@ 58FE25F22AA77674003D1918 /* SettingsReaderStub.swift in Sources */, 58F7753D2AB8473200425B47 /* BlockedStateErrorMapperStub.swift in Sources */, 58FE25D42AA729B5003D1918 /* PacketTunnelActorTests.swift in Sources */, + 7AEF7F182ACAF85B006FE45D /* AppMessageHandlerTests.swift in Sources */, 58C7A4702A8649ED0060C66F /* PingerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4230,7 +4292,6 @@ 581DA2762A1E2FD10046ED47 /* WgKeyRotation.swift in Sources */, 580810E52A30E13A00B74552 /* DeviceStateAccessorProtocol.swift in Sources */, 580810E82A30E15500B74552 /* DeviceCheckRemoteServiceProtocol.swift in Sources */, - 585B1FF22AB0BC69008AD470 /* State+Extensions.swift in Sources */, 58C9B8CE2ABB252E00040B46 /* DeviceCheck.swift in Sources */, 58915D682A25FA080066445B /* DeviceCheckRemoteService.swift in Sources */, 58E9C3842A4EF15300CFDEAC /* WireGuardAdapter+Async.swift in Sources */, @@ -4250,7 +4311,6 @@ 58CE38C728992C8700A6D6E5 /* WireGuardAdapterError+Localization.swift in Sources */, 58E511E828DDDF2400B0BCDE /* CodingErrors+CustomErrorDescription.swift in Sources */, 582403822A827E1500163DE8 /* RelaySelectorWrapper.swift in Sources */, - 58F775432AB9E3EF00425B47 /* AppMessageHandler.swift in Sources */, 58FDF2D92A0BA11A00C2B061 /* DeviceCheckOperation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4346,6 +4406,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7A6F1A202AC57071008ACF78 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7A6F1A272AC57071008ACF78 /* PacketTunnelTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 7A88DCCA2A8FABBE00D2FF0E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -4456,7 +4524,6 @@ }; 58C7A4402A863F470060C66F /* PBXTargetDependency */ = { isa = PBXTargetDependency; - platformFilter = ios; target = 58C7A4352A863F440060C66F /* PacketTunnelCore */; targetProxy = 58C7A43F2A863F470060C66F /* PBXContainerItemProxy */; }; @@ -4467,13 +4534,11 @@ }; 58C7A45F2A8640490060C66F /* PBXTargetDependency */ = { isa = PBXTargetDependency; - platformFilter = ios; target = 58D223F2294C8FF00029F5F8 /* MullvadLogging */; targetProxy = 58C7A45E2A8640490060C66F /* PBXContainerItemProxy */; }; 58C7A4722A864B860060C66F /* PBXTargetDependency */ = { isa = PBXTargetDependency; - platformFilter = ios; target = 58CE5E5F224146200008646E /* MullvadVPN */; targetProxy = 58C7A4712A864B860060C66F /* PBXContainerItemProxy */; }; @@ -4602,6 +4667,15 @@ target = 58D223D4294C8E5E0029F5F8 /* MullvadTypes */; targetProxy = 58FE65972AB1D90600E53CB5 /* PBXContainerItemProxy */; }; + 7A6F1A292AC57071008ACF78 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 58CE5E5F224146200008646E /* MullvadVPN */; + targetProxy = 7A6F1A282AC57071008ACF78 /* PBXContainerItemProxy */; + }; + 7A6F1A3C2AC576E3008ACF78 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = 7A6F1A3B2AC576E3008ACF78 /* WireGuardKitTypes */; + }; 7A88DCDA2A8FABBE00D2FF0E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 7A88DCCD2A8FABBE00D2FF0E /* Routing */; @@ -5734,6 +5808,57 @@ }; name = Release; }; + 7A6F1A2B2AC57071008ACF78 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = CKG9MXH72F; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.PacketTunnelTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 7A6F1A2C2AC57071008ACF78 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = CKG9MXH72F; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.PacketTunnelTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; 7A88DCE62A8FABBF00D2FF0E /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5808273928487E3E006B77A4 /* Base.xcconfig */; @@ -6134,6 +6259,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 7A6F1A2A2AC57071008ACF78 /* Build configuration list for PBXNativeTarget "PacketTunnelTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7A6F1A2B2AC57071008ACF78 /* Debug */, + 7A6F1A2C2AC57071008ACF78 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 7A88DCE52A8FABBF00D2FF0E /* Build configuration list for PBXNativeTarget "Routing" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -6238,6 +6372,11 @@ package = 58F097482A20C30000DA2DAD /* XCRemoteSwiftPackageReference "wireguard-apple" */; productName = WireGuardKitTypes; }; + 7A6F1A3B2AC576E3008ACF78 /* WireGuardKitTypes */ = { + isa = XCSwiftPackageProductDependency; + package = 58F097482A20C30000DA2DAD /* XCRemoteSwiftPackageReference "wireguard-apple" */; + productName = WireGuardKitTypes; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 58CE5E58224146200008646E /* Project object */; diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme index e96e9b561d23..f3982ca984cd 100644 --- a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme +++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme @@ -268,6 +268,17 @@ ReferencedContainer = "container:MullvadVPN.xcodeproj"> + + + + + + + + + + diff --git a/ios/PacketTunnel/PacketTunnelProvider/BlockedStateErrorMapper.swift b/ios/PacketTunnel/PacketTunnelProvider/BlockedStateErrorMapper.swift index a31c508288a6..bc6c713a9349 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/BlockedStateErrorMapper.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/BlockedStateErrorMapper.swift @@ -57,6 +57,10 @@ struct BlockedStateErrorMapper: BlockedStateErrorMapperProtocol { // packet tunnel provider. return .tunnelAdapter + case is PublicKeyError: + // Returned when there is an endpoint but its public key is invalid. + return .invalidPublicKey + default: // Everything else in case we introduce new errors and forget to handle them. return .unknown diff --git a/ios/PacketTunnel/PacketTunnelProvider/State+Extensions.swift b/ios/PacketTunnel/PacketTunnelProvider/State+Extensions.swift deleted file mode 100644 index 99862e152199..000000000000 --- a/ios/PacketTunnel/PacketTunnelProvider/State+Extensions.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// State+Extensions.swift -// PacketTunnel -// -// Created by pronebird on 12/09/2023. -// Copyright © 2023 Mullvad VPN AB. All rights reserved. -// - -import Foundation -import MullvadTypes -import PacketTunnelCore - -extension State { - var packetTunnelStatus: PacketTunnelStatus { - var status = PacketTunnelStatus() - - switch self { - case let .connecting(connState), - let .connected(connState), - let .reconnecting(connState), - let .disconnecting(connState): - switch connState.networkReachability { - case .reachable: - status.isNetworkReachable = true - case .unreachable: - status.isNetworkReachable = false - case .undetermined: - // TODO: fix me - status.isNetworkReachable = true - } - - status.numberOfFailedAttempts = connState.connectionAttemptCount - status.tunnelRelay = connState.selectedRelay.packetTunnelRelay - - case .disconnected, .initial: - break - - case let .error(blockedState): - status.blockedStateReason = blockedState.reason - } - - return status - } - - var relayConstraints: RelayConstraints? { - switch self { - case let .connecting(connState), let .connected(connState), let .reconnecting(connState): - return connState.relayConstraints - - case let .error(blockedState): - return blockedState.relayConstraints - - case .initial, .disconnecting, .disconnected: - return nil - } - } -} diff --git a/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift b/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift index c4a731aa7829..6ba03db89b3e 100644 --- a/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift +++ b/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift @@ -13,6 +13,15 @@ import struct WireGuardKitTypes.IPAddressRange import class WireGuardKitTypes.PrivateKey import class WireGuardKitTypes.PublicKey +/// Error returned when there is an endpoint but its public key is invalid. +public struct PublicKeyError: LocalizedError { + let endpoint: MullvadEndpoint + + public var errorDescription: String? { + "Public key is invalid, endpoint: \(endpoint)" + } +} + /// Struct building tunnel adapter configuration. struct ConfigurationBuilder { var privateKey: PrivateKey @@ -20,22 +29,28 @@ struct ConfigurationBuilder { var dns: SelectedDNSServers? var endpoint: MullvadEndpoint? - func makeConfiguration() -> TunnelAdapterConfiguration { + func makeConfiguration() throws -> TunnelAdapterConfiguration { return TunnelAdapterConfiguration( privateKey: privateKey, interfaceAddresses: interfaceAddresses, dns: dnsServers, - peer: peer + peer: try peer ) } private var peer: TunnelPeer? { - guard let endpoint else { return nil } + get throws { + guard let endpoint else { return nil } - return TunnelPeer( - endpoint: .ipv4(endpoint.ipv4Relay), - publicKey: PublicKey(rawValue: endpoint.publicKey)! - ) + guard let publicKey = PublicKey(rawValue: endpoint.publicKey) else { + throw PublicKeyError(endpoint: endpoint) + } + + return TunnelPeer( + endpoint: .ipv4(endpoint.ipv4Relay), + publicKey: publicKey + ) + } } private var dnsServers: [IPAddress] { diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift index 0cf8351523c2..3b708fb996d8 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift @@ -108,7 +108,7 @@ extension PacketTunnelActor { guard let self else { return } // Wait for key to propagate across relays. - try await Task.sleepUsingContinuousClock(for: timings.wgKeyPropagationDelay) +// try await Task.sleepUsingContinuousClock(for: timings.wgKeyPropagationDelay) // Enqueue task to change key policy. commandChannel.send(.switchKey) @@ -159,7 +159,7 @@ extension PacketTunnelActor { /** Internal helper that transitions key policy from `.usePrior` to `.useCurrent`. - - Parameter keyPolicy: a reference to key policy hend either in connection state or blocked state struct. + - Parameter keyPolicy: a reference to key policy held either in connection state or blocked state struct. - Returns: `true` when the policy was modified, otherwise `false`. */ private func setCurrentKeyPolicy(_ keyPolicy: inout KeyPolicy) -> Bool { diff --git a/ios/PacketTunnelCore/Actor/State+Extensions.swift b/ios/PacketTunnelCore/Actor/State+Extensions.swift index cfaed6cf0787..6c0fc6cc30ac 100644 --- a/ios/PacketTunnelCore/Actor/State+Extensions.swift +++ b/ios/PacketTunnelCore/Actor/State+Extensions.swift @@ -110,3 +110,60 @@ extension BlockedStateReason { } } } + +// +// State+Extensions.swift +// PacketTunnel +// +// Created by pronebird on 12/09/2023. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import MullvadTypes + +public extension State { + var packetTunnelStatus: PacketTunnelStatus { + var status = PacketTunnelStatus() + + switch self { + case let .connecting(connState), + let .connected(connState), + let .reconnecting(connState), + let .disconnecting(connState): + switch connState.networkReachability { + case .reachable: + status.isNetworkReachable = true + case .unreachable: + status.isNetworkReachable = false + case .undetermined: + // TODO: fix me + status.isNetworkReachable = true + } + + status.numberOfFailedAttempts = connState.connectionAttemptCount + status.tunnelRelay = connState.selectedRelay.packetTunnelRelay + + case .disconnected, .initial: + break + + case let .error(blockedState): + status.blockedStateReason = blockedState.reason + } + + return status + } + + var relayConstraints: RelayConstraints? { + switch self { + case let .connecting(connState), let .connected(connState), let .reconnecting(connState): + return connState.relayConstraints + + case let .error(blockedState): + return blockedState.relayConstraints + + case .initial, .disconnecting, .disconnected: + return nil + } + } +} diff --git a/ios/PacketTunnelCore/Actor/State.swift b/ios/PacketTunnelCore/Actor/State.swift index 836e742594ac..4449372762d6 100644 --- a/ios/PacketTunnelCore/Actor/State.swift +++ b/ios/PacketTunnelCore/Actor/State.swift @@ -110,7 +110,7 @@ public struct ConnectionState { /// This is primarily used by packet tunnel for updating constraints in tunnel provider. public var relayConstraints: RelayConstraints - /// Last WG key read from setings. + /// Last WG key read from settings. /// Can be `nil` if moved to `keyPolicy`. public var currentKey: PrivateKey? @@ -192,6 +192,9 @@ public enum BlockedStateReason: String, Codable, Equatable { /// Tunnel adapter error. case tunnelAdapter + /// Invalid public key. + case invalidPublicKey + /// Unidentified reason. case unknown } diff --git a/ios/PacketTunnel/PacketTunnelProvider/AppMessageHandler.swift b/ios/PacketTunnelCore/IPC/AppMessageHandler.swift similarity index 91% rename from ios/PacketTunnel/PacketTunnelProvider/AppMessageHandler.swift rename to ios/PacketTunnelCore/IPC/AppMessageHandler.swift index 952fcb4bdeb7..2d57f3d34112 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/AppMessageHandler.swift +++ b/ios/PacketTunnelCore/IPC/AppMessageHandler.swift @@ -8,17 +8,16 @@ import Foundation import MullvadLogging -import PacketTunnelCore /** Actor handling packet tunnel IPC (app) messages and patching them through to the right facility. */ -struct AppMessageHandler { +public struct AppMessageHandler { private let logger = Logger(label: "AppMessageHandler") private let packetTunnelActor: PacketTunnelActor private let urlRequestProxy: URLRequestProxy - init(packetTunnelActor: PacketTunnelActor, urlRequestProxy: URLRequestProxy) { + public init(packetTunnelActor: PacketTunnelActor, urlRequestProxy: URLRequestProxy) { self.packetTunnelActor = packetTunnelActor self.urlRequestProxy = urlRequestProxy } @@ -34,7 +33,7 @@ struct AppMessageHandler { the acknowledgment from IPC before starting next operation, hence it's critical to return as soon as possible. (See `TunnelManager.reconnectTunnel()`, `SendTunnelProviderMessageOperation`) */ - func handleAppMessage(_ messageData: Data) async -> Data? { + public func handleAppMessage(_ messageData: Data) async -> Data? { guard let message = decodeMessage(messageData) else { return nil } logger.debug("Received app message: \(message)") @@ -51,7 +50,7 @@ struct AppMessageHandler { return await encodeReply(packetTunnelActor.state.packetTunnelStatus) case .privateKeyRotation: - packetTunnelActor.notifyKeyRotation(date: nil) + packetTunnelActor.notifyKeyRotation(date: Date()) return nil case let .reconnectTunnel(selectorResult): diff --git a/ios/PacketTunnelCoreTests/Mocks/BlockedStateErrorMapperStub.swift b/ios/PacketTunnelCoreTests/Mocks/BlockedStateErrorMapperStub.swift index eecfcc692edc..936b864d4d2a 100644 --- a/ios/PacketTunnelCoreTests/Mocks/BlockedStateErrorMapperStub.swift +++ b/ios/PacketTunnelCoreTests/Mocks/BlockedStateErrorMapperStub.swift @@ -8,7 +8,7 @@ import Foundation import MullvadTypes -import PacketTunnelCore +@testable import PacketTunnelCore /// Blocked state error mapper stub that can be configured with a block to simulate a desired behavior. class BlockedStateErrorMapperStub: BlockedStateErrorMapperProtocol { diff --git a/ios/PacketTunnelCoreTests/Mocks/DefaultPathObserverFake.swift b/ios/PacketTunnelCoreTests/Mocks/DefaultPathObserverFake.swift index b237647b30f0..cf6323cb4ba5 100644 --- a/ios/PacketTunnelCoreTests/Mocks/DefaultPathObserverFake.swift +++ b/ios/PacketTunnelCoreTests/Mocks/DefaultPathObserverFake.swift @@ -8,7 +8,7 @@ import Foundation import NetworkExtension -import PacketTunnelCore +@testable import PacketTunnelCore struct NetworkPathStub: NetworkPath { var status: NetworkExtension.NWPathStatus = .satisfied diff --git a/ios/PacketTunnelCoreTests/Mocks/TunnelAdapterDummy.swift b/ios/PacketTunnelCoreTests/Mocks/TunnelAdapterDummy.swift index 4b7040f7566f..5b8557e48cd4 100644 --- a/ios/PacketTunnelCoreTests/Mocks/TunnelAdapterDummy.swift +++ b/ios/PacketTunnelCoreTests/Mocks/TunnelAdapterDummy.swift @@ -7,7 +7,7 @@ // import Foundation -import PacketTunnelCore +@testable import PacketTunnelCore /// Dummy tunnel adapter that does nothing and reports no errors. class TunnelAdapterDummy: TunnelAdapterProtocol { diff --git a/ios/PacketTunnelCoreTests/PingerTests.swift b/ios/PacketTunnelCoreTests/PingerTests.swift index d77e9c4e5df8..115232b05895 100644 --- a/ios/PacketTunnelCoreTests/PingerTests.swift +++ b/ios/PacketTunnelCoreTests/PingerTests.swift @@ -7,7 +7,8 @@ // import Network -import PacketTunnelCore +@testable import PacketTunnelCore +import WireGuardKitTypes import XCTest final class PingerTests: XCTestCase { diff --git a/ios/PacketTunnelCoreTests/TaskQueueTests.swift b/ios/PacketTunnelCoreTests/TaskQueueTests.swift new file mode 100644 index 000000000000..ab629273b400 --- /dev/null +++ b/ios/PacketTunnelCoreTests/TaskQueueTests.swift @@ -0,0 +1,79 @@ +// +// TaskQueueTests.swift +// PacketTunnelCoreTests +// +// Created by pronebird on 14/09/2023. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +@testable import PacketTunnelCore +import WireGuardKitTypes +import XCTest + +final class TaskQueueTests: XCTestCase { + func testSerialTaskExecution() async throws { + let queue = TaskQueue() + + let firstExpectation = expectation(description: "Complete first task") + let secondExpectation = expectation(description: "Complete second task") + + async let _ = queue.add(kind: .start) { + try await Task.sleep(duration: .seconds(1)) + firstExpectation.fulfill() + } + async let _ = queue.add(kind: .start) { + secondExpectation.fulfill() + } + + await fulfillment(of: [firstExpectation, secondExpectation], timeout: 2, enforceOrder: true) + } + + func testReconnectShouldNotCancelPrecedingStart() async throws { + let queue = TaskQueue() + + async let start: () = queue.add(kind: .start) { + try await Task.sleep(duration: .seconds(1)) + } + async let _ = queue.add(kind: .reconnect) {} + + do { + try await start + } catch { + XCTFail("Start task cannot be cancelled by reconnect task") + } + } + + func testCoalesceCallsToReconnect() async throws { + let queue = TaskQueue() + + async let first: () = queue.add(kind: .reconnect) { + try await Task.sleep(duration: .seconds(1)) + } + async let _ = queue.add(kind: .networkReachability) { + try await Task.sleep(duration: .seconds(1)) + } + async let _ = queue.add(kind: .reconnect) {} + + do { + try await first + XCTFail("Preceding reconnection task must be cancelled!") + } catch {} + } + + func testStopShouldCancelPrecedingTasks() async throws { + let queue = TaskQueue() + + async let first: () = queue.add(kind: .start) { + try await Task.sleep(duration: .seconds(1)) + } + async let second: () = queue.add(kind: .reconnect) { + try await Task.sleep(duration: .seconds(1)) + } + async let _ = queue.add(kind: .stop) {} + + do { + _ = try await (first, second) + XCTFail("Preceding tasks must be cancelled!") + } catch {} + } +} diff --git a/ios/PacketTunnelTests/AppMessageHandlerTests.swift b/ios/PacketTunnelTests/AppMessageHandlerTests.swift new file mode 100644 index 000000000000..a6c68ac0dbf9 --- /dev/null +++ b/ios/PacketTunnelTests/AppMessageHandlerTests.swift @@ -0,0 +1,436 @@ +// +// AppMessageHandlerTests.swift +// PacketTunnelCoreTests +// +// Created by Jon Petersson on 2023-09-28. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Combine +import Foundation +import MullvadLogging +@testable import MullvadREST +import MullvadTypes +import PacketTunnelCore +import RelaySelector +import WireGuardKitTypes +import XCTest + +final class AppMessageHandlerTests: XCTestCase { + private var stateSink: Combine.Cancellable? + + private lazy var urlRequestProxy: URLRequestProxy = { + let transportProvider = REST.AnyTransportProvider { + AnyTransport { Response(delay: 1, statusCode: 200, value: TimeResponse(dateTime: Date())) } + } + + return URLRequestProxy( + dispatchQueue: DispatchQueue(label: "AppMessageHandlerTests"), + transportProvider: transportProvider + ) + }() + + override func tearDown() async throws { + stateSink?.cancel() + } + + func testHandleAppMessageForTunnelStatus() async throws { + let actor = PacketTunnelActor.mock() + let appMessageHandler = createAppMessageHandler(actor: actor) + let reconnectingExpectation = expectation(description: "Expect reconnecting state") + + actor.start(options: StartOptions(launchSource: .app)) + actor.setErrorState(reason: .deviceRevoked) + + stateSink = await actor.$state + .receive(on: DispatchQueue.main) + .sink { newState in + switch newState { + case .error: + Task { + let reply = try? await appMessageHandler.handleAppMessage( + TunnelProviderMessage.getTunnelStatus.encode() + ) + let tunnelStatus = try? TunnelProviderReply(messageData: reply!) + + XCTAssertEqual(tunnelStatus?.value.blockedStateReason, .deviceRevoked) + reconnectingExpectation.fulfill() + } + default: + break + } + } + + await fulfillment(of: [reconnectingExpectation], timeout: 1) + } + + func testHandleAppMessageForURLRequest() async throws { + let actor = PacketTunnelActor.mock() + let appMessageHandler = createAppMessageHandler(actor: actor) + + actor.start(options: StartOptions(launchSource: .app)) + + let url = URL(string: "localhost")! + let urlRequest = ProxyURLRequest( + id: UUID(), + urlRequest: URLRequest(url: url) + )! + + let reply = try await appMessageHandler + .handleAppMessage(TunnelProviderMessage.sendURLRequest(urlRequest).encode()) + let urlResponse = try TunnelProviderReply(messageData: reply!) + + XCTAssertEqual(urlResponse.value.response?.url, url) + } + + func testHandleAppMessageForReconnectTunnel() async throws { + let actor = PacketTunnelActor.mock() + let appMessageHandler = createAppMessageHandler(actor: actor) + let reconnectingExpectation = expectation(description: "Expect reconnecting state") + + actor.start(options: StartOptions(launchSource: .app)) + + let relayConstraints = RelayConstraints(location: .only(.hostname("se", "sto", "se6-wireguard"))) + let selectorResult = try? RelaySelector.evaluate( + relays: sampleRelays, + constraints: relayConstraints, + numberOfFailedAttempts: 0 + ) + + _ = try? await appMessageHandler.handleAppMessage( + TunnelProviderMessage.reconnectTunnel(selectorResult).encode() + ) + + stateSink = await actor.$state + .receive(on: DispatchQueue.main) + .sink { newState in + switch newState { + case let .reconnecting(state): + XCTAssertEqual(state.selectedRelay.relay, selectorResult?.relay) + reconnectingExpectation.fulfill() + default: + break + } + } + + await fulfillment(of: [reconnectingExpectation], timeout: 1) + } + + func testHandleAppMessageForKeyRotation() async throws { + let actor = PacketTunnelActor.mock() + let appMessageHandler = createAppMessageHandler(actor: actor) + let connectedExpectation = expectation(description: "Expect connecting state") + + actor.start(options: StartOptions(launchSource: .app)) + + _ = try? await appMessageHandler.handleAppMessage( + TunnelProviderMessage.privateKeyRotation.encode() + ) + + stateSink = await actor.$state + .receive(on: DispatchQueue.main) + .sink { newState in + switch newState { + case let .connected(state): + XCTAssertNotNil(state.lastKeyRotation) + connectedExpectation.fulfill() + default: + break + } + } + + await fulfillment(of: [connectedExpectation], timeout: 3) + } +} + +extension AppMessageHandlerTests { + func createAppMessageHandler(actor: PacketTunnelActor) -> AppMessageHandler { + return AppMessageHandler( + packetTunnelActor: actor, + urlRequestProxy: urlRequestProxy + ) + } +} + +struct TimeResponse: Codable { + var dateTime: Date +} + +class AnyTransport: RESTTransport { + typealias CompletionHandler = (Data?, URLResponse?, Error?) -> Void + + private let handleRequest: () -> AnyResponse + + private let completionLock = NSLock() + private var completionHandlers: [UUID: CompletionHandler] = [:] + + init(block: @escaping () -> AnyResponse) { + handleRequest = block + } + + var name: String { + return "any-transport" + } + + func sendRequest( + _ request: URLRequest, + completion: @escaping (Data?, URLResponse?, Error?) -> Void + ) -> MullvadTypes.Cancellable { + let response = handleRequest() + let id = storeCompletion(completionHandler: completion) + + let dispatchWork = DispatchWorkItem { + let data = (try? response.encode()) ?? Data() + let httpResponse = HTTPURLResponse( + url: request.url!, + statusCode: response.statusCode, + httpVersion: "1.0", + headerFields: [:] + )! + self.sendCompletion(requestID: id, completion: .success((data, httpResponse))) + } + + DispatchQueue.global().asyncAfter(deadline: .now() + response.delay, execute: dispatchWork) + + return AnyCancellable { + dispatchWork.cancel() + + self.sendCompletion(requestID: id, completion: .failure(URLError(.cancelled))) + } + } + + private func storeCompletion(completionHandler: @escaping CompletionHandler) -> UUID { + return completionLock.withLock { + let id = UUID() + completionHandlers[id] = completionHandler + return id + } + } + + private func sendCompletion(requestID: UUID, completion: Result<(Data, URLResponse), Error>) { + let complationHandler = completionLock.withLock { + return completionHandlers.removeValue(forKey: requestID) + } + switch completion { + case let .success((data, response)): + complationHandler?(data, response, nil) + case let .failure(error): + complationHandler?(nil, nil, error) + } + } +} + +struct Response: AnyResponse { + var delay: TimeInterval + var statusCode: Int + var value: T + + func encode() throws -> Data { + return try REST.Coding.makeJSONEncoder().encode(value) + } +} + +protocol AnyResponse { + var delay: TimeInterval { get } + var statusCode: Int { get } + + func encode() throws -> Data +} + +private let portRanges: [[UInt16]] = [[4000, 4001], [5000, 5001]] + +private let sampleRelays = REST.ServerRelaysResponse( + locations: [ + "es-mad": REST.ServerLocation( + country: "Spain", + city: "Madrid", + latitude: 40.408566, + longitude: -3.69222 + ), + "se-got": REST.ServerLocation( + country: "Sweden", + city: "Gothenburg", + latitude: 57.70887, + longitude: 11.97456 + ), + "se-sto": REST.ServerLocation( + country: "Sweden", + city: "Stockholm", + latitude: 59.3289, + longitude: 18.0649 + ), + "ae-dxb": REST.ServerLocation( + country: "United Arab Emirates", + city: "Dubai", + latitude: 25.276987, + longitude: 55.296249 + ), + "jp-tyo": REST.ServerLocation( + country: "Japan", + city: "Tokyo", + latitude: 35.685, + longitude: 139.751389 + ), + "ca-tor": REST.ServerLocation( + country: "Canada", + city: "Toronto", + latitude: 43.666667, + longitude: -79.416667 + ), + "us-atl": REST.ServerLocation( + country: "USA", + city: "Atlanta, GA", + latitude: 40.73061, + longitude: -73.935242 + ), + "us-dal": REST.ServerLocation( + country: "USA", + city: "Dallas, TX", + latitude: 32.89748, + longitude: -97.040443 + ), + ], + wireguard: REST.ServerWireguardTunnels( + ipv4Gateway: .loopback, + ipv6Gateway: .loopback, + portRanges: portRanges, + relays: [ + REST.ServerRelay( + hostname: "es1-wireguard", + active: true, + owned: true, + location: "es-mad", + provider: "", + weight: 500, + ipv4AddrIn: .loopback, + ipv6AddrIn: .loopback, + publicKey: PrivateKey().publicKey.rawValue, + includeInCountry: true + ), + REST.ServerRelay( + hostname: "se10-wireguard", + active: true, + owned: true, + location: "se-got", + provider: "", + weight: 1000, + ipv4AddrIn: .loopback, + ipv6AddrIn: .loopback, + publicKey: PrivateKey().publicKey.rawValue, + includeInCountry: true + ), + REST.ServerRelay( + hostname: "se2-wireguard", + active: true, + owned: true, + location: "se-sto", + provider: "", + weight: 50, + ipv4AddrIn: .loopback, + ipv6AddrIn: .loopback, + publicKey: PrivateKey().publicKey.rawValue, + includeInCountry: true + ), + REST.ServerRelay( + hostname: "se6-wireguard", + active: true, + owned: true, + location: "se-sto", + provider: "", + weight: 100, + ipv4AddrIn: .loopback, + ipv6AddrIn: .loopback, + publicKey: PrivateKey().publicKey.rawValue, + includeInCountry: true + ), + REST.ServerRelay( + hostname: "us-dal-wg-001", + active: true, + owned: true, + location: "us-dal", + provider: "", + weight: 100, + ipv4AddrIn: .loopback, + ipv6AddrIn: .loopback, + publicKey: PrivateKey().publicKey.rawValue, + includeInCountry: true + ), + REST.ServerRelay( + hostname: "us-nyc-wg-301", + active: true, + owned: true, + location: "us-nyc", + provider: "", + weight: 100, + ipv4AddrIn: .loopback, + ipv6AddrIn: .loopback, + publicKey: PrivateKey().publicKey.rawValue, + includeInCountry: true + ), + ] + ), + bridge: REST.ServerBridges(shadowsocks: [ + REST.ServerShadowsocks(protocol: "tcp", port: 443, cipher: "aes-256-gcm", password: "mullvad"), + ], relays: [ + REST.BridgeRelay( + hostname: "se-sto-br-001", + active: true, + owned: true, + location: "se-sto", + provider: "31173", + ipv4AddrIn: .loopback, + weight: 100, + includeInCountry: true + ), + REST.BridgeRelay( + hostname: "jp-tyo-br-101", + active: true, + owned: true, + location: "jp-tyo", + provider: "M247", + ipv4AddrIn: .loopback, + weight: 1, + includeInCountry: true + ), + REST.BridgeRelay( + hostname: "ca-tor-ovpn-001", + active: false, + owned: false, + location: "ca-tor", + provider: "M247", + ipv4AddrIn: .loopback, + weight: 1, + includeInCountry: true + ), + REST.BridgeRelay( + hostname: "ae-dxb-ovpn-001", + active: true, + owned: false, + location: "ae-dxb", + provider: "M247", + ipv4AddrIn: .loopback, + weight: 100, + includeInCountry: true + ), + REST.BridgeRelay( + hostname: "us-atl-br-101", + active: true, + owned: false, + location: "us-atl", + provider: "100TB", + ipv4AddrIn: .loopback, + weight: 100, + includeInCountry: true + ), + REST.BridgeRelay( + hostname: "us-dal-br-101", + active: true, + owned: false, + location: "us-dal", + provider: "100TB", + ipv4AddrIn: .loopback, + weight: 100, + includeInCountry: true + ), + ]) +) diff --git a/ios/PacketTunnelTests/PacketTunnelTests.swift b/ios/PacketTunnelTests/PacketTunnelTests.swift new file mode 100644 index 000000000000..972388f2af30 --- /dev/null +++ b/ios/PacketTunnelTests/PacketTunnelTests.swift @@ -0,0 +1,34 @@ +// +// PacketTunnelTests.swift +// PacketTunnelTests +// +// Created by Jon Petersson on 2023-09-28. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import XCTest + +final class PacketTunnelTests: XCTestCase { + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + measure { + // Put the code you want to measure the time of here. + } + } +}