From 3563eb4c76f6eaf8876d4a091643587e793304fc Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 7 Jan 2025 21:39:29 +1100 Subject: [PATCH 01/17] chore: add dylib downloader and validator --- .../Coder Desktop.xcodeproj/project.pbxproj | 381 ++++++++++--- .../xcshareddata/swiftpm/Package.resolved | 11 +- .../xcschemes/Coder Desktop.xcscheme | 17 +- .../{ProtoTests.xcscheme => VPN.xcscheme} | 45 +- Coder Desktop/Coder Desktop.xctestplan | 8 +- .../Coder DesktopRelease.entitlements | 16 + .../Coder Desktop/Coder_Desktop.entitlements | 2 + Coder Desktop/Coder Desktop/SDK/Client.swift | 6 +- Coder Desktop/VPN/Manager.swift | 20 + Coder Desktop/VPN/PacketTunnelProvider.swift | 58 +- Coder Desktop/VPN/TunnelHandle.swift | 78 +++ ..._coder_Coder_Desktop_VPN-Bridging-Header.h | 7 + Coder Desktop/VPNLib/Downloader.swift | 170 ++++++ .../{Proto => VPNLib}/Receiver.swift | 0 Coder Desktop/{Proto => VPNLib}/Sender.swift | 2 +- Coder Desktop/{Proto => VPNLib}/Speaker.swift | 12 +- Coder Desktop/VPNLib/VPNLib.h | 11 + Coder Desktop/{Proto => VPNLib}/vpn.pb.swift | 516 +++++++++--------- Coder Desktop/{Proto => VPNLib}/vpn.proto | 0 .../VPNLibTests/DownloaderTests.swift | 162 ++++++ .../ProtoTests.swift | 2 +- .../SpeakerTests.swift | 2 +- Makefile | 2 +- 23 files changed, 1143 insertions(+), 385 deletions(-) rename Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/{ProtoTests.xcscheme => VPN.xcscheme} (67%) create mode 100644 Coder Desktop/Coder Desktop/Coder DesktopRelease.entitlements create mode 100644 Coder Desktop/VPN/Manager.swift create mode 100644 Coder Desktop/VPN/TunnelHandle.swift create mode 100644 Coder Desktop/VPN/com_coder_Coder_Desktop_VPN-Bridging-Header.h create mode 100644 Coder Desktop/VPNLib/Downloader.swift rename Coder Desktop/{Proto => VPNLib}/Receiver.swift (100%) rename Coder Desktop/{Proto => VPNLib}/Sender.swift (95%) rename Coder Desktop/{Proto => VPNLib}/Speaker.swift (96%) create mode 100644 Coder Desktop/VPNLib/VPNLib.h rename Coder Desktop/{Proto => VPNLib}/vpn.pb.swift (77%) rename Coder Desktop/{Proto => VPNLib}/vpn.proto (100%) create mode 100644 Coder Desktop/VPNLibTests/DownloaderTests.swift rename Coder Desktop/{ProtoTests => VPNLibTests}/ProtoTests.swift (99%) rename Coder Desktop/{ProtoTests => VPNLibTests}/SpeakerTests.swift (99%) diff --git a/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj b/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj index e5ae28b..c114a79 100644 --- a/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj +++ b/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj @@ -11,8 +11,14 @@ 9616793D2CFF117300B2B6DF /* com.coder.Coder-Desktop.VPN.systemextension in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = 961679302CFF117300B2B6DF /* com.coder.Coder-Desktop.VPN.systemextension */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 961679532CFF207900B2B6DF /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = 961679522CFF207900B2B6DF /* SwiftProtobuf */; }; 961679552CFF207900B2B6DF /* SwiftProtobufPluginLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 961679542CFF207900B2B6DF /* SwiftProtobufPluginLibrary */; }; - AA071D2C2D1041A7008D0B72 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = 961679E22D03144900B2B6DF /* SwiftProtobuf */; }; - AA071D2D2D1041A7008D0B72 /* SwiftProtobufPluginLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 961679E42D03144C00B2B6DF /* SwiftProtobufPluginLibrary */; }; + AA3B3DA92D2D23860099996A /* VPNLib.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA3B3DA12D2D23860099996A /* VPNLib.framework */; }; + AA3B3DB42D2D23860099996A /* VPNLib.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA3B3DA12D2D23860099996A /* VPNLib.framework */; }; + AA3B3DB52D2D23860099996A /* VPNLib.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AA3B3DA12D2D23860099996A /* VPNLib.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + AA3B3DBF2D2D23AB0099996A /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = AA3B3DBE2D2D23AB0099996A /* SwiftProtobuf */; }; + AA3B3DC12D2D23AB0099996A /* SwiftProtobufPluginLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = AA3B3DC02D2D23AB0099996A /* SwiftProtobufPluginLibrary */; }; + AA3B3DCD2D2D249F0099996A /* VPNLib.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA3B3DA12D2D23860099996A /* VPNLib.framework */; }; + AA3B3DCE2D2D249F0099996A /* VPNLib.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AA3B3DA12D2D23860099996A /* VPNLib.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + AA3B3DD22D2D26700099996A /* Swifter in Frameworks */ = {isa = PBXBuildFile; productRef = AA3B3DD12D2D26700099996A /* Swifter */; }; AA8BC3392D0060A900E1ABAA /* ViewInspector in Frameworks */ = {isa = PBXBuildFile; productRef = AA8BC3382D0060A900E1ABAA /* ViewInspector */; }; AA8BC33F2D0061F200E1ABAA /* FluidMenuBarExtra in Frameworks */ = {isa = PBXBuildFile; productRef = AA8BC33E2D0061F200E1ABAA /* FluidMenuBarExtra */; }; AA8BC4CF2D00A4B700E1ABAA /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = AA8BC4CE2D00A4B700E1ABAA /* KeychainAccess */; }; @@ -41,13 +47,34 @@ remoteGlobalIDString = 9616792F2CFF117300B2B6DF; remoteInfo = VPN; }; - 961679DD2D030E1D00B2B6DF /* PBXContainerItemProxy */ = { + AA3B3DAA2D2D23860099996A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 961678F42CFF100D00B2B6DF /* Project object */; + proxyType = 1; + remoteGlobalIDString = AA3B3DA02D2D23860099996A; + remoteInfo = VPNLib; + }; + AA3B3DAC2D2D23860099996A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 961678F42CFF100D00B2B6DF /* Project object */; proxyType = 1; remoteGlobalIDString = 961678FB2CFF100D00B2B6DF; remoteInfo = "Coder Desktop"; }; + AA3B3DB22D2D23860099996A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 961678F42CFF100D00B2B6DF /* Project object */; + proxyType = 1; + remoteGlobalIDString = AA3B3DA02D2D23860099996A; + remoteInfo = VPNLib; + }; + AA3B3DCF2D2D249F0099996A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 961678F42CFF100D00B2B6DF /* Project object */; + proxyType = 1; + remoteGlobalIDString = AA3B3DA02D2D23860099996A; + remoteInfo = VPNLib; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -62,6 +89,28 @@ name = "Embed System Extensions"; runOnlyForDeploymentPostprocessing = 0; }; + AA32A0E32D2D21A3004D6733 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + AA3B3DCE2D2D249F0099996A /* VPNLib.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + AA3B3D922D2D233E0099996A /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + AA3B3DB52D2D23860099996A /* VPNLib.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -70,30 +119,27 @@ 961679192CFF100E00B2B6DF /* Coder DesktopUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Coder DesktopUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 961679302CFF117300B2B6DF /* com.coder.Coder-Desktop.VPN.systemextension */ = {isa = PBXFileReference; explicitFileType = "wrapper.system-extension"; includeInIndex = 0; path = "com.coder.Coder-Desktop.VPN.systemextension"; sourceTree = BUILT_PRODUCTS_DIR; }; 961679322CFF117300B2B6DF /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; - 961679D92D030E1D00B2B6DF /* ProtoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ProtoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + AA3B3DA12D2D23860099996A /* VPNLib.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = VPNLib.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + AA3B3DA82D2D23860099996A /* VPNLibTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VPNLibTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - 9616793E2CFF117300B2B6DF /* Exceptions for "VPN" folder in "VPN" target */ = { + AA3B3DB62D2D23860099996A /* Exceptions for "VPNLib" folder in "VPNLib" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - Info.plist, + vpn.proto, ); - target = 9616792F2CFF117300B2B6DF /* VPN */; - }; - 961679472CFF14EA00B2B6DF /* Exceptions for "Proto" folder in "VPN" target */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - Sender.swift, + publicHeaders = ( + VPNLib.h, ); - target = 9616792F2CFF117300B2B6DF /* VPN */; + target = AA3B3DA02D2D23860099996A /* VPNLib */; }; - AA071D842D1041E9008D0B72 /* Exceptions for "Proto" folder in "Coder Desktop" target */ = { + AA3C69C12D2D15D200A45481 /* Exceptions for "VPN" folder in "VPN" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - vpn.proto, + Info.plist, ); - target = 961678FB2CFF100D00B2B6DF /* Coder Desktop */; + target = 9616792F2CFF117300B2B6DF /* VPN */; }; /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -113,26 +159,25 @@ path = "Coder DesktopUITests"; sourceTree = ""; }; - 961679342CFF117300B2B6DF /* VPN */ = { + AA3B3DA22D2D23860099996A /* VPNLib */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( - 9616793E2CFF117300B2B6DF /* Exceptions for "VPN" folder in "VPN" target */, + AA3B3DB62D2D23860099996A /* Exceptions for "VPNLib" folder in "VPNLib" target */, ); - path = VPN; + path = VPNLib; sourceTree = ""; }; - 961679432CFF149000B2B6DF /* Proto */ = { + AA3B3DAE2D2D23860099996A /* VPNLibTests */ = { isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - AA071D842D1041E9008D0B72 /* Exceptions for "Proto" folder in "Coder Desktop" target */, - 961679472CFF14EA00B2B6DF /* Exceptions for "Proto" folder in "VPN" target */, - ); - path = Proto; + path = VPNLibTests; sourceTree = ""; }; - 961679DA2D030E1D00B2B6DF /* ProtoTests */ = { + AA3C69AD2D2D143400A45481 /* VPN */ = { isa = PBXFileSystemSynchronizedRootGroup; - path = ProtoTests; + exceptions = ( + AA3C69C12D2D15D200A45481 /* Exceptions for "VPN" folder in "VPN" target */, + ); + path = VPN; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ @@ -143,6 +188,7 @@ buildActionMask = 2147483647; files = ( AAD720D02D0816B200F6304D /* Alamofire in Frameworks */, + AA3B3DB42D2D23860099996A /* VPNLib.framework in Frameworks */, AA8BC4CF2D00A4B700E1ABAA /* KeychainAccess in Frameworks */, AA8BC33F2D0061F200E1ABAA /* FluidMenuBarExtra in Frameworks */, 961679552CFF207900B2B6DF /* SwiftProtobufPluginLibrary in Frameworks */, @@ -169,16 +215,26 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - AA071D2D2D1041A7008D0B72 /* SwiftProtobufPluginLibrary in Frameworks */, - AA071D2C2D1041A7008D0B72 /* SwiftProtobuf in Frameworks */, 961679332CFF117300B2B6DF /* NetworkExtension.framework in Frameworks */, + AA3B3DCD2D2D249F0099996A /* VPNLib.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA3B3D9E2D2D23860099996A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AA3B3DC12D2D23AB0099996A /* SwiftProtobufPluginLibrary in Frameworks */, + AA3B3DBF2D2D23AB0099996A /* SwiftProtobuf in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - 961679D62D030E1D00B2B6DF /* Frameworks */ = { + AA3B3DA52D2D23860099996A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + AA3B3DA92D2D23860099996A /* VPNLib.framework in Frameworks */, + AA3B3DD22D2D26700099996A /* Swifter in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -188,12 +244,12 @@ 961678F32CFF100D00B2B6DF = { isa = PBXGroup; children = ( - 961679432CFF149000B2B6DF /* Proto */, - 961679DA2D030E1D00B2B6DF /* ProtoTests */, 961678FE2CFF100D00B2B6DF /* Coder Desktop */, + AA3C69AD2D2D143400A45481 /* VPN */, 961679122CFF100E00B2B6DF /* Coder DesktopTests */, 9616791C2CFF100E00B2B6DF /* Coder DesktopUITests */, - 961679342CFF117300B2B6DF /* VPN */, + AA3B3DA22D2D23860099996A /* VPNLib */, + AA3B3DAE2D2D23860099996A /* VPNLibTests */, 961679312CFF117300B2B6DF /* Frameworks */, 961678FD2CFF100D00B2B6DF /* Products */, ); @@ -206,7 +262,8 @@ 9616790F2CFF100E00B2B6DF /* Coder DesktopTests.xctest */, 961679192CFF100E00B2B6DF /* Coder DesktopUITests.xctest */, 961679302CFF117300B2B6DF /* com.coder.Coder-Desktop.VPN.systemextension */, - 961679D92D030E1D00B2B6DF /* ProtoTests.xctest */, + AA3B3DA12D2D23860099996A /* VPNLib.framework */, + AA3B3DA82D2D23860099996A /* VPNLibTests.xctest */, ); name = Products; sourceTree = ""; @@ -221,6 +278,16 @@ }; /* End PBXGroup section */ +/* Begin PBXHeadersBuildPhase section */ + AA3B3D9C2D2D23860099996A /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + /* Begin PBXNativeTarget section */ 961678FB2CFF100D00B2B6DF /* Coder Desktop */ = { isa = PBXNativeTarget; @@ -230,16 +297,17 @@ 961678F92CFF100D00B2B6DF /* Frameworks */, 961678FA2CFF100D00B2B6DF /* Resources */, 961679422CFF117300B2B6DF /* Embed System Extensions */, + AA3B3D922D2D233E0099996A /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( AA8BC33C2D0060E700E1ABAA /* PBXTargetDependency */, 9616793C2CFF117300B2B6DF /* PBXTargetDependency */, + AA3B3DB32D2D23860099996A /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( 961678FE2CFF100D00B2B6DF /* Coder Desktop */, - 961679432CFF149000B2B6DF /* Proto */, ); name = "Coder Desktop"; packageProductDependencies = ( @@ -307,44 +375,71 @@ 9616792C2CFF117300B2B6DF /* Sources */, 9616792D2CFF117300B2B6DF /* Frameworks */, 9616792E2CFF117300B2B6DF /* Resources */, + AA32A0E32D2D21A3004D6733 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( + AA3B3DD02D2D249F0099996A /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( - 961679342CFF117300B2B6DF /* VPN */, + AA3C69AD2D2D143400A45481 /* VPN */, ); name = VPN; packageProductDependencies = ( - 961679E22D03144900B2B6DF /* SwiftProtobuf */, - 961679E42D03144C00B2B6DF /* SwiftProtobufPluginLibrary */, ); productName = VPN; productReference = 961679302CFF117300B2B6DF /* com.coder.Coder-Desktop.VPN.systemextension */; productType = "com.apple.product-type.system-extension"; }; - 961679D82D030E1D00B2B6DF /* ProtoTests */ = { + AA3B3DA02D2D23860099996A /* VPNLib */ = { isa = PBXNativeTarget; - buildConfigurationList = 961679DF2D030E1D00B2B6DF /* Build configuration list for PBXNativeTarget "ProtoTests" */; + buildConfigurationList = AA3B3DB72D2D23860099996A /* Build configuration list for PBXNativeTarget "VPNLib" */; buildPhases = ( - 961679D52D030E1D00B2B6DF /* Sources */, - 961679D62D030E1D00B2B6DF /* Frameworks */, - 961679D72D030E1D00B2B6DF /* Resources */, + AA3B3D9C2D2D23860099996A /* Headers */, + AA3B3D9D2D2D23860099996A /* Sources */, + AA3B3D9E2D2D23860099996A /* Frameworks */, + AA3B3D9F2D2D23860099996A /* Resources */, ); buildRules = ( ); dependencies = ( - 961679DE2D030E1D00B2B6DF /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( - 961679DA2D030E1D00B2B6DF /* ProtoTests */, + AA3B3DA22D2D23860099996A /* VPNLib */, ); - name = ProtoTests; + name = VPNLib; packageProductDependencies = ( + AA3B3DBE2D2D23AB0099996A /* SwiftProtobuf */, + AA3B3DC02D2D23AB0099996A /* SwiftProtobufPluginLibrary */, + ); + productName = VPNLib; + productReference = AA3B3DA12D2D23860099996A /* VPNLib.framework */; + productType = "com.apple.product-type.framework"; + }; + AA3B3DA72D2D23860099996A /* VPNLibTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = AA3B3DBA2D2D23860099996A /* Build configuration list for PBXNativeTarget "VPNLibTests" */; + buildPhases = ( + AA3B3DA42D2D23860099996A /* Sources */, + AA3B3DA52D2D23860099996A /* Frameworks */, + AA3B3DA62D2D23860099996A /* Resources */, ); - productName = ProtoTests; - productReference = 961679D92D030E1D00B2B6DF /* ProtoTests.xctest */; + buildRules = ( + ); + dependencies = ( + AA3B3DAB2D2D23860099996A /* PBXTargetDependency */, + AA3B3DAD2D2D23860099996A /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + AA3B3DAE2D2D23860099996A /* VPNLibTests */, + ); + name = VPNLibTests; + packageProductDependencies = ( + AA3B3DD12D2D26700099996A /* Swifter */, + ); + productName = VPNLibTests; + productReference = AA3B3DA82D2D23860099996A /* VPNLibTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ @@ -354,7 +449,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1610; + LastSwiftUpdateCheck = 1620; LastUpgradeCheck = 1620; TargetAttributes = { 961678FB2CFF100D00B2B6DF = { @@ -371,8 +466,11 @@ 9616792F2CFF117300B2B6DF = { CreatedOnToolsVersion = 16.1; }; - 961679D82D030E1D00B2B6DF = { - CreatedOnToolsVersion = 16.1; + AA3B3DA02D2D23860099996A = { + CreatedOnToolsVersion = 16.2; + }; + AA3B3DA72D2D23860099996A = { + CreatedOnToolsVersion = 16.2; TestTargetID = 961678FB2CFF100D00B2B6DF; }; }; @@ -393,6 +491,7 @@ AA8BC4CD2D00A4B700E1ABAA /* XCRemoteSwiftPackageReference "KeychainAccess" */, AAD720CE2D0816B200F6304D /* XCRemoteSwiftPackageReference "Alamofire" */, 961679512CFF207900B2B6DF /* XCRemoteSwiftPackageReference "swift-protobuf" */, + AA03806B2D2CD853002C2285 /* XCRemoteSwiftPackageReference "swifter" */, ); preferredProjectObjectVersion = 77; productRefGroup = 961678FD2CFF100D00B2B6DF /* Products */; @@ -403,7 +502,8 @@ 9616790E2CFF100E00B2B6DF /* Coder DesktopTests */, 961679182CFF100E00B2B6DF /* Coder DesktopUITests */, 9616792F2CFF117300B2B6DF /* VPN */, - 961679D82D030E1D00B2B6DF /* ProtoTests */, + AA3B3DA02D2D23860099996A /* VPNLib */, + AA3B3DA72D2D23860099996A /* VPNLibTests */, ); }; /* End PBXProject section */ @@ -437,7 +537,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 961679D72D030E1D00B2B6DF /* Resources */ = { + AA3B3D9F2D2D23860099996A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA3B3DA62D2D23860099996A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -475,7 +582,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 961679D52D030E1D00B2B6DF /* Sources */ = { + AA3B3D9D2D2D23860099996A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA3B3DA42D2D23860099996A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -500,10 +614,25 @@ target = 9616792F2CFF117300B2B6DF /* VPN */; targetProxy = 9616793B2CFF117300B2B6DF /* PBXContainerItemProxy */; }; - 961679DE2D030E1D00B2B6DF /* PBXTargetDependency */ = { + AA3B3DAB2D2D23860099996A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = AA3B3DA02D2D23860099996A /* VPNLib */; + targetProxy = AA3B3DAA2D2D23860099996A /* PBXContainerItemProxy */; + }; + AA3B3DAD2D2D23860099996A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 961678FB2CFF100D00B2B6DF /* Coder Desktop */; - targetProxy = 961679DD2D030E1D00B2B6DF /* PBXContainerItemProxy */; + targetProxy = AA3B3DAC2D2D23860099996A /* PBXContainerItemProxy */; + }; + AA3B3DB32D2D23860099996A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = AA3B3DA02D2D23860099996A /* VPNLib */; + targetProxy = AA3B3DB22D2D23860099996A /* PBXContainerItemProxy */; + }; + AA3B3DD02D2D249F0099996A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = AA3B3DA02D2D23860099996A /* VPNLib */; + targetProxy = AA3B3DCF2D2D249F0099996A /* PBXContainerItemProxy */; }; AA8BC33C2D0060E700E1ABAA /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -655,7 +784,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 14.0; + MACOSX_DEPLOYMENT_TARGET = 14.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -669,7 +798,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = "Coder Desktop/Coder_Desktop.entitlements"; + CODE_SIGN_ENTITLEMENTS = "Coder Desktop/Coder DesktopRelease.entitlements"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; @@ -685,7 +814,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 14.0; + MACOSX_DEPLOYMENT_TARGET = 14.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -703,7 +832,7 @@ DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 4399GN35BJ; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 14.0; + MACOSX_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-DesktopTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -722,7 +851,7 @@ DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 4399GN35BJ; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 14.0; + MACOSX_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-DesktopTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -740,7 +869,7 @@ DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 4399GN35BJ; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 14.6; + MACOSX_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-DesktopUITests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -758,7 +887,7 @@ DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 4399GN35BJ; GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 14.6; + MACOSX_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-DesktopUITests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -775,6 +904,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; + DEFINES_MODULE = NO; DEVELOPMENT_TEAM = 4399GN35BJ; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; @@ -786,11 +916,14 @@ "@executable_path/../Frameworks", "@executable_path/../../../../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 14.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop.VPN"; + PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)"; PRODUCT_NAME = "$(inherited)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "VPN/$(SWIFT_MODULE_NAME)-Bridging-Header.h"; SWIFT_VERSION = 6.0; }; name = Debug; @@ -802,6 +935,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; + DEFINES_MODULE = NO; DEVELOPMENT_TEAM = 4399GN35BJ; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; @@ -813,26 +947,100 @@ "@executable_path/../Frameworks", "@executable_path/../../../../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 14.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop.VPN"; + PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)"; PRODUCT_NAME = "$(inherited)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "VPN/$(SWIFT_MODULE_NAME)-Bridging-Header.h"; SWIFT_VERSION = 6.0; }; name = Release; }; - 961679E02D030E1D00B2B6DF /* Debug */ = { + AA3B3DB82D2D23860099996A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; - DEAD_CODE_STRIPPING = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 4399GN35BJ; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 14.6; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = coder.VPNLib; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 6.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + AA3B3DB92D2D23860099996A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 4399GN35BJ; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 14.6; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop.ProtoTests"; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = coder.VPNLib; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 6.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + AA3B3DBB2D2D23860099996A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 4399GN35BJ; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = coder.VPNLibTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 6.0; @@ -840,17 +1048,16 @@ }; name = Debug; }; - 961679E12D030E1D00B2B6DF /* Release */ = { + AA3B3DBC2D2D23860099996A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 4399GN35BJ; GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop.ProtoTests"; + PRODUCT_BUNDLE_IDENTIFIER = coder.VPNLibTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 6.0; @@ -906,11 +1113,20 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 961679DF2D030E1D00B2B6DF /* Build configuration list for PBXNativeTarget "ProtoTests" */ = { + AA3B3DB72D2D23860099996A /* Build configuration list for PBXNativeTarget "VPNLib" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AA3B3DB82D2D23860099996A /* Debug */, + AA3B3DB92D2D23860099996A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AA3B3DBA2D2D23860099996A /* Build configuration list for PBXNativeTarget "VPNLibTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - 961679E02D030E1D00B2B6DF /* Debug */, - 961679E12D030E1D00B2B6DF /* Release */, + AA3B3DBB2D2D23860099996A /* Debug */, + AA3B3DBC2D2D23860099996A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -926,6 +1142,14 @@ version = 1.28.2; }; }; + AA03806B2D2CD853002C2285 /* XCRemoteSwiftPackageReference "swifter" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/httpswift/swifter"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.5.0; + }; + }; AA8BC3372D00609700E1ABAA /* XCRemoteSwiftPackageReference "ViewInspector" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/nalexn/ViewInspector"; @@ -979,16 +1203,21 @@ package = 961679512CFF207900B2B6DF /* XCRemoteSwiftPackageReference "swift-protobuf" */; productName = SwiftProtobufPluginLibrary; }; - 961679E22D03144900B2B6DF /* SwiftProtobuf */ = { + AA3B3DBE2D2D23AB0099996A /* SwiftProtobuf */ = { isa = XCSwiftPackageProductDependency; package = 961679512CFF207900B2B6DF /* XCRemoteSwiftPackageReference "swift-protobuf" */; productName = SwiftProtobuf; }; - 961679E42D03144C00B2B6DF /* SwiftProtobufPluginLibrary */ = { + AA3B3DC02D2D23AB0099996A /* SwiftProtobufPluginLibrary */ = { isa = XCSwiftPackageProductDependency; package = 961679512CFF207900B2B6DF /* XCRemoteSwiftPackageReference "swift-protobuf" */; productName = SwiftProtobufPluginLibrary; }; + AA3B3DD12D2D26700099996A /* Swifter */ = { + isa = XCSwiftPackageProductDependency; + package = AA03806B2D2CD853002C2285 /* XCRemoteSwiftPackageReference "swifter" */; + productName = Swifter; + }; AA8BC3382D0060A900E1ABAA /* ViewInspector */ = { isa = XCSwiftPackageProductDependency; package = AA8BC3372D00609700E1ABAA /* XCRemoteSwiftPackageReference "ViewInspector" */; diff --git a/Coder Desktop/Coder Desktop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Coder Desktop/Coder Desktop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 065b859..9feec2b 100644 --- a/Coder Desktop/Coder Desktop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Coder Desktop/Coder Desktop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "aa8dd97dc6e28dedc4a5c45c435467a247486474bf3c1caf5e67085d52325132", + "originHash" : "0dee63153808d20a73f823faee354b1c4e55e5f9258a2831fa6074ed154cd0bc", "pins" : [ { "identity" : "alamofire", @@ -36,6 +36,15 @@ "version" : "1.28.2" } }, + { + "identity" : "swifter", + "kind" : "remoteSourceControl", + "location" : "https://github.com/httpswift/swifter", + "state" : { + "revision" : "9483a5d459b45c3ffd059f7b55f9638e268632fd", + "version" : "1.5.0" + } + }, { "identity" : "swiftlintplugins", "kind" : "remoteSourceControl", diff --git a/Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/Coder Desktop.xcscheme b/Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/Coder Desktop.xcscheme index 7439c84..e4f5432 100644 --- a/Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/Coder Desktop.xcscheme +++ b/Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/Coder Desktop.xcscheme @@ -62,9 +62,20 @@ parallelizable = "YES"> + + + + diff --git a/Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/ProtoTests.xcscheme b/Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/VPN.xcscheme similarity index 67% rename from Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/ProtoTests.xcscheme rename to Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/VPN.xcscheme index 556492f..17d9d7a 100644 --- a/Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/ProtoTests.xcscheme +++ b/Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/VPN.xcscheme @@ -6,6 +6,22 @@ parallelizeBuildables = "YES" buildImplicitDependencies = "YES" buildArchitectures = "Automatic"> + + + + + + - - - - - - - - - - diff --git a/Coder Desktop/Coder Desktop.xctestplan b/Coder Desktop/Coder Desktop.xctestplan index 444d6b9..0cef4af 100644 --- a/Coder Desktop/Coder Desktop.xctestplan +++ b/Coder Desktop/Coder Desktop.xctestplan @@ -19,15 +19,15 @@ { "target" : { "containerPath" : "container:Coder Desktop.xcodeproj", - "identifier" : "961679D82D030E1D00B2B6DF", - "name" : "ProtoTests" + "identifier" : "9616790E2CFF100E00B2B6DF", + "name" : "Coder DesktopTests" } }, { "target" : { "containerPath" : "container:Coder Desktop.xcodeproj", - "identifier" : "9616790E2CFF100E00B2B6DF", - "name" : "Coder DesktopTests" + "identifier" : "AA3B3DA72D2D23860099996A", + "name" : "VPNLibTests" } }, { diff --git a/Coder Desktop/Coder Desktop/Coder DesktopRelease.entitlements b/Coder Desktop/Coder Desktop/Coder DesktopRelease.entitlements new file mode 100644 index 0000000..441279c --- /dev/null +++ b/Coder Desktop/Coder Desktop/Coder DesktopRelease.entitlements @@ -0,0 +1,16 @@ + + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + com.apple.security.network.client + + + diff --git a/Coder Desktop/Coder Desktop/Coder_Desktop.entitlements b/Coder Desktop/Coder Desktop/Coder_Desktop.entitlements index 441279c..aa54c78 100644 --- a/Coder Desktop/Coder Desktop/Coder_Desktop.entitlements +++ b/Coder Desktop/Coder Desktop/Coder_Desktop.entitlements @@ -12,5 +12,7 @@ com.apple.security.network.client + com.apple.security.network.server + diff --git a/Coder Desktop/Coder Desktop/SDK/Client.swift b/Coder Desktop/Coder Desktop/SDK/Client.swift index 39a4023..30a2547 100644 --- a/Coder Desktop/Coder Desktop/SDK/Client.swift +++ b/Coder Desktop/Coder Desktop/SDK/Client.swift @@ -1,7 +1,7 @@ import Alamofire import Foundation -protocol Client { +protocol Client: Sendable { init(url: URL, token: String?) func user(_ ident: String) async throws(ClientError) -> User } @@ -114,10 +114,10 @@ struct APIError: Decodable { struct Response: Decodable { let message: String let detail: String? - let validations: [ValidationError]? + let validations: [FieldValidation]? } -struct ValidationError: Decodable { +struct FieldValidation: Decodable { let field: String let detail: String } diff --git a/Coder Desktop/VPN/Manager.swift b/Coder Desktop/VPN/Manager.swift new file mode 100644 index 0000000..2c65371 --- /dev/null +++ b/Coder Desktop/VPN/Manager.swift @@ -0,0 +1,20 @@ +import NetworkExtension +import os +import VPNLib + +actor Manager { + let ptp: PacketTunnelProvider + let downloader: Downloader + + var tunnelHandle: TunnelHandle? = nil + var speaker: Speaker? = nil + // TODO: XPC Speaker + + private let dest = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + .first!.appending(path: "coder-vpn.dylib") + + init(with: PacketTunnelProvider) { + ptp = with + self.downloader = Downloader() + } +} diff --git a/Coder Desktop/VPN/PacketTunnelProvider.swift b/Coder Desktop/VPN/PacketTunnelProvider.swift index d60e8f9..5322bad 100644 --- a/Coder Desktop/VPN/PacketTunnelProvider.swift +++ b/Coder Desktop/VPN/PacketTunnelProvider.swift @@ -1,12 +1,62 @@ import NetworkExtension +import os -class PacketTunnelProvider: NEPacketTunnelProvider { - override func startTunnel(options _: [String: NSObject]?, completionHandler _: @escaping (Error?) -> Void) { - // Add code here to start the process of connecting the tunnel. +/* From */ +let CTLIOCGINFO: UInt = 0xC064_4E03 + +class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { + private let logger = Logger(subsystem: "com.coder.Coder.CoderPacketTunnelProvider", category: "network-extension") + private var manager: Manager? + + private var tunnelFileDescriptor: Int32? { + var ctlInfo = ctl_info() + withUnsafeMutablePointer(to: &ctlInfo.ctl_name) { + $0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: $0.pointee)) { + _ = strcpy($0, "com.apple.net.utun_control") + } + } + for fd: Int32 in 0 ... 1024 { + var addr = sockaddr_ctl() + var ret: Int32 = -1 + var len = socklen_t(MemoryLayout.size(ofValue: addr)) + withUnsafeMutablePointer(to: &addr) { + $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { + ret = getpeername(fd, $0, &len) + } + } + if ret != 0 || addr.sc_family != AF_SYSTEM { + continue + } + if ctlInfo.ctl_id == 0 { + ret = ioctl(fd, CTLIOCGINFO, &ctlInfo) + if ret != 0 { + continue + } + } + if addr.sc_id == ctlInfo.ctl_id { + return fd + } + } + return nil + } + + override func startTunnel(options _: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { + guard manager == nil else { + logger.error("startTunnel called with non-nil Manager") + completionHandler(nil) + return + } + manager = Manager(with: self) + completionHandler(nil) } override func stopTunnel(with _: NEProviderStopReason, completionHandler: @escaping () -> Void) { - // Add code here to start the process of stopping the tunnel. + guard manager == nil else { + logger.error("stopTunnel called with nil Manager") + completionHandler() + return + } + manager = nil completionHandler() } diff --git a/Coder Desktop/VPN/TunnelHandle.swift b/Coder Desktop/VPN/TunnelHandle.swift new file mode 100644 index 0000000..4c9f558 --- /dev/null +++ b/Coder Desktop/VPN/TunnelHandle.swift @@ -0,0 +1,78 @@ +import Foundation +import os + +let startSymbol = "OpenTunnel" + +actor TunnelHandle { + private let logger = Logger(subsystem: "com.coder.Coder.CoderPacketTunnelProvider", category: "tunnel-handle") + + private var openTunnelFn: OpenTunnel! + private var tunnelPipe: Pipe! + private var dylibHandle: UnsafeMutableRawPointer! + + init(dylibPath: URL) throws(TunnelHandleError) { + dylibHandle = dlopen(dylibPath.path, RTLD_NOW | RTLD_LOCAL) + + guard dylibHandle != nil else { + var errStr = "UNKNOWN" + let e = dlerror() + if e != nil { + errStr = String(cString: e!) + } + throw TunnelHandleError.dylib(errStr) + } + + let startSym = dlsym(dylibHandle, startSymbol) + guard startSym != nil else { + var errStr = "UNKNOWN" + let e = dlerror() + if e != nil { + errStr = String(cString: e!) + } + throw TunnelHandleError.symbol(startSymbol, errStr) + } + openTunnelFn = unsafeBitCast(startSym, to: OpenTunnel.self) + tunnelPipe = Pipe() + let res = openTunnelFn(tunnelPipe.fileHandleForReading.fileDescriptor, + tunnelPipe.fileHandleForWriting.fileDescriptor) + guard res == 0 else { + throw TunnelHandleError.openTunnel(OpenTunnelError(rawValue: res) ?? .unknown) + } + } + + func close() throws { + dlclose(dylibHandle) + } +} + +enum TunnelHandleError: Error { + case dylib(String) + case symbol(String, String) + case openTunnel(OpenTunnelError) + + var description: String { + switch self { + case let .dylib(d): return d + case let .symbol(symbol, message): return "\(symbol): \(message)" + case let .openTunnel(error): return "OpenTunnel: \(error.message)" + } + } +} + +enum OpenTunnelError: Int32 { + case errDupReadFD = -2 + case errDupWriteFD = -3 + case errOpenPipe = -4 + case errNewTunnel = -5 + case unknown = -99 + + var message: String { + switch self { + case .errDupReadFD: return "Failed to duplicate read file descriptor" + case .errDupWriteFD: return "Failed to duplicate write file descriptor" + case .errOpenPipe: return "Failed to open the pipe" + case .errNewTunnel: return "Failed to create a new tunnel" + case .unknown: return "Unknown error code" + } + } +} diff --git a/Coder Desktop/VPN/com_coder_Coder_Desktop_VPN-Bridging-Header.h b/Coder Desktop/VPN/com_coder_Coder_Desktop_VPN-Bridging-Header.h new file mode 100644 index 0000000..6c8e5b4 --- /dev/null +++ b/Coder Desktop/VPN/com_coder_Coder_Desktop_VPN-Bridging-Header.h @@ -0,0 +1,7 @@ +#ifndef CoderPacketTunnelProvider_Bridging_Header_h +#define CoderPacketTunnelProvider_Bridging_Header_h + +// GoInt32 OpenTunnel(GoInt32 cReadFD, GoInt32 cWriteFD); +typedef int(*OpenTunnel)(int, int); + +#endif /* CoderPacketTunnelProvider_Bridging_Header_h */ diff --git a/Coder Desktop/VPNLib/Downloader.swift b/Coder Desktop/VPNLib/Downloader.swift new file mode 100644 index 0000000..6e29054 --- /dev/null +++ b/Coder Desktop/VPNLib/Downloader.swift @@ -0,0 +1,170 @@ +import CryptoKit +import Foundation + +public protocol Validator: Sendable { + func validate(path: URL) async throws +} + +public enum ValidationError: LocalizedError { + case fileNotFound + case unableToCreateStaticCode + case invalidSignature + case unableToRetrieveInfo + case invalidIdentifier(identifier: String?) + case invalidTeamIdentifier(identifier: String?) + case missingInfoPList + case invalidVersion(version: String?) + + public var errorDescription: String? { + switch self { + case .fileNotFound: + return "The file does not exist." + case .unableToCreateStaticCode: + return "Unable to create a static code object." + case .invalidSignature: + return "The file's signature is invalid." + case .unableToRetrieveInfo: + return "Unable to retrieve signing information." + case let .invalidIdentifier(identifier): + return "Invalid identifier: \(identifier ?? "unknown")." + case let .invalidVersion(version): + return "Invalid runtime version: \(version ?? "unknown")." + case let .invalidTeamIdentifier(identifier): + return "Invalid team identifier: \(identifier ?? "unknown")." + case .missingInfoPList: + return "Info.plist is not embedded within the dylib." + } + } +} + +public struct SignatureValidator: Validator { + private let expectedName = "CoderVPN" + private let expectedIdentifier = "com.coder.Coder-Desktop.VPN.dylib" + private let expectedTeamIdentifier = "4399GN35BJ" + private let minDylibVersion = "2.18.1" + + private let infoIdentifierKey = "CFBundleIdentifier" + private let infoNameKey = "CFBundleName" + private let infoShortVersionKey = "CFBundleShortVersionString" + + private let signInfoFlags: SecCSFlags = .init(rawValue: kSecCSSigningInformation) + + public init() {} + + public func validate(path: URL) throws { + guard FileManager.default.fileExists(atPath: path.path) else { + throw ValidationError.fileNotFound + } + + var staticCode: SecStaticCode? + let status = SecStaticCodeCreateWithPath(path as CFURL, SecCSFlags(), &staticCode) + guard status == errSecSuccess, let code = staticCode else { + throw ValidationError.unableToCreateStaticCode + } + + let validateStatus = SecStaticCodeCheckValidity(code, SecCSFlags(), nil) + guard validateStatus == errSecSuccess else { + throw ValidationError.invalidSignature + } + + var information: CFDictionary? + let infoStatus = SecCodeCopySigningInformation(code, signInfoFlags, &information) + guard infoStatus == errSecSuccess, let info = information as? [String: Any] else { + throw ValidationError.unableToRetrieveInfo + } + + guard let identifier = info[kSecCodeInfoIdentifier as String] as? String, + identifier == expectedIdentifier + else { + throw ValidationError.invalidIdentifier(identifier: info[kSecCodeInfoIdentifier as String] as? String) + } + + guard let teamIdentifier = info[kSecCodeInfoTeamIdentifier as String] as? String, + teamIdentifier == expectedTeamIdentifier + else { + throw ValidationError.invalidTeamIdentifier( + identifier: info[kSecCodeInfoTeamIdentifier as String] as? String + ) + } + + guard let infoPlist = info[kSecCodeInfoPList as String] as? [String: AnyObject] else { + throw ValidationError.missingInfoPList + } + + guard let plistIdent = infoPlist[infoIdentifierKey] as? String, plistIdent == expectedIdentifier else { + throw ValidationError.invalidIdentifier(identifier: infoPlist[infoIdentifierKey] as? String) + } + + guard let plistName = infoPlist[infoNameKey] as? String, plistName == expectedName else { + throw ValidationError.invalidIdentifier(identifier: infoPlist[infoNameKey] as? String) + } + + guard let dylibVersion = infoPlist[infoShortVersionKey] as? String, + minDylibVersion.compare(dylibVersion, options: .numeric) != .orderedDescending + else { + throw ValidationError.invalidVersion(version: infoPlist[infoShortVersionKey] as? String) + } + } +} + +public actor Downloader { + let validator: Validator + public init(validator: Validator = SignatureValidator()) { + self.validator = validator + } + + public func download(src: URL, dest: URL) async throws { + var req = URLRequest(url: src) + if FileManager.default.fileExists(atPath: dest.path) { + if let existingFileData = try? Data(contentsOf: dest) { + let sha1Hash = Insecure.SHA1.hash(data: existingFileData) + let etag = sha1Hash.map { String(format: "%02x", $0) }.joined() + // ETag header needs to be quoted + req.setValue("\"\(etag)\"", forHTTPHeaderField: "If-None-Match") + } + } + // TODO: Add Content-Length headers to coderd, add download progress delegate + let (tempURL, response) = try await URLSession.shared.download(for: req) + defer { + if FileManager.default.fileExists(atPath: dest.path) { + do { try FileManager.default.removeItem(at: tempURL) } catch {} + } + } + + guard let httpResponse = response as? HTTPURLResponse else { + throw DownloadError.invalidResponse + } + guard httpResponse.statusCode != 304 else { + return + } + guard (200 ..< 300).contains(httpResponse.statusCode) else { + throw DownloadError.unexpectedStatusCode(httpResponse.statusCode) + } + + if FileManager.default.fileExists(atPath: dest.path) { + try FileManager.default.removeItem(at: dest) + } + try FileManager.default.moveItem(at: tempURL, to: dest) + try await validator.validate(path: dest) + } +} + +func etag(data: Data) -> String { + let sha1Hash = Insecure.SHA1.hash(data: data) + let etag = sha1Hash.map { String(format: "%02x", $0) }.joined() + return "\"\(etag)\"" +} + +enum DownloadError: LocalizedError { + case unexpectedStatusCode(Int) + case invalidResponse + + var localizedDescription: String { + switch self { + case let .unexpectedStatusCode(code): + return "Unexpected status code: \(code)" + case .invalidResponse: + return "Received non-HTTP response" + } + } +} diff --git a/Coder Desktop/Proto/Receiver.swift b/Coder Desktop/VPNLib/Receiver.swift similarity index 100% rename from Coder Desktop/Proto/Receiver.swift rename to Coder Desktop/VPNLib/Receiver.swift diff --git a/Coder Desktop/Proto/Sender.swift b/Coder Desktop/VPNLib/Sender.swift similarity index 95% rename from Coder Desktop/Proto/Sender.swift rename to Coder Desktop/VPNLib/Sender.swift index 2a1013e..4ae258c 100644 --- a/Coder Desktop/Proto/Sender.swift +++ b/Coder Desktop/VPNLib/Sender.swift @@ -3,7 +3,7 @@ import SwiftProtobuf /// A actor that serializes and sends VPN protocol messages over a `FileHandle`, which is typically /// the write-side of a `Pipe`. -actor Sender { +public actor Sender { private let writeFD: FileHandle init(writeFD: FileHandle) { diff --git a/Coder Desktop/Proto/Speaker.swift b/Coder Desktop/VPNLib/Speaker.swift similarity index 96% rename from Coder Desktop/Proto/Speaker.swift rename to Coder Desktop/VPNLib/Speaker.swift index 6751aee..513b290 100644 --- a/Coder Desktop/Proto/Speaker.swift +++ b/Coder Desktop/VPNLib/Speaker.swift @@ -6,7 +6,7 @@ let newLine = 0x0A let headerPreamble = "codervpn" /// A message that has the `rpc` property for recording participation in a unary RPC. -protocol RPCMessage: Sendable { +public protocol RPCMessage: Sendable { var rpc: Vpn_RPC { get set } /// Returns true if `rpc` has been explicitly set. var hasRpc: Bool { get } @@ -50,7 +50,7 @@ struct ProtoVersion: CustomStringConvertible, Equatable, Codable { } /// An actor that communicates using the VPN protocol -actor Speaker { +public actor Speaker { private let logger = Logger(subsystem: "com.coder.Coder-Desktop", category: "proto") private let writeFD: FileHandle private let readFD: FileHandle @@ -130,20 +130,20 @@ actor Speaker { } } - enum IncomingMessage { + public enum IncomingMessage: Sendable { case message(RecvMsg) case RPC(RPCRequest) } } extension Speaker: AsyncSequence, AsyncIteratorProtocol { - typealias Element = IncomingMessage + public typealias Element = IncomingMessage public nonisolated func makeAsyncIterator() -> Speaker { self } - func next() async throws -> IncomingMessage? { + public func next() async throws -> IncomingMessage? { for try await msg in try await receiver.messages() { guard msg.hasRpc else { return .message(msg) @@ -277,7 +277,7 @@ enum HandshakeError: Error { case unsupportedVersion([ProtoVersion]) } -struct RPCRequest: Sendable { +public struct RPCRequest: Sendable { let msg: RecvMsg private let sender: Sender diff --git a/Coder Desktop/VPNLib/VPNLib.h b/Coder Desktop/VPNLib/VPNLib.h new file mode 100644 index 0000000..2ead51a --- /dev/null +++ b/Coder Desktop/VPNLib/VPNLib.h @@ -0,0 +1,11 @@ +#import + +//! Project version number for VPNLib. +FOUNDATION_EXPORT double VPNLibVersionNumber; + +//! Project version string for VPNLib. +FOUNDATION_EXPORT const unsigned char VPNLibVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/Coder Desktop/Proto/vpn.pb.swift b/Coder Desktop/VPNLib/vpn.pb.swift similarity index 77% rename from Coder Desktop/Proto/vpn.pb.swift rename to Coder Desktop/VPNLib/vpn.pb.swift index 184abfb..e3bdd3b 100644 --- a/Coder Desktop/Proto/vpn.pb.swift +++ b/Coder Desktop/VPNLib/vpn.pb.swift @@ -3,7 +3,7 @@ // swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: Coder Desktop/Proto/vpn.proto +// Source: Coder Desktop/VPNLib/vpn.proto // // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ @@ -24,38 +24,38 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// RPC allows a very simple unary request/response RPC mechanism. The requester generates a unique /// msg_id which it sets on the request, the responder sets response_to that msg_id on the response /// message -struct Vpn_RPC: Sendable { +public struct Vpn_RPC: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var msgID: UInt64 = 0 + public var msgID: UInt64 = 0 - var responseTo: UInt64 = 0 + public var responseTo: UInt64 = 0 - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - init() {} + public init() {} } /// ManagerMessage is a message from the manager (to the tunnel). -struct Vpn_ManagerMessage: Sendable { +public struct Vpn_ManagerMessage: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var rpc: Vpn_RPC { + public var rpc: Vpn_RPC { get {return _rpc ?? Vpn_RPC()} set {_rpc = newValue} } /// Returns true if `rpc` has been explicitly set. - var hasRpc: Bool {return self._rpc != nil} + public var hasRpc: Bool {return self._rpc != nil} /// Clears the value of `rpc`. Subsequent reads from it will return its default value. - mutating func clearRpc() {self._rpc = nil} + public mutating func clearRpc() {self._rpc = nil} - var msg: Vpn_ManagerMessage.OneOf_Msg? = nil + public var msg: Vpn_ManagerMessage.OneOf_Msg? = nil - var getPeerUpdate: Vpn_GetPeerUpdate { + public var getPeerUpdate: Vpn_GetPeerUpdate { get { if case .getPeerUpdate(let v)? = msg {return v} return Vpn_GetPeerUpdate() @@ -63,7 +63,7 @@ struct Vpn_ManagerMessage: Sendable { set {msg = .getPeerUpdate(newValue)} } - var networkSettings: Vpn_NetworkSettingsResponse { + public var networkSettings: Vpn_NetworkSettingsResponse { get { if case .networkSettings(let v)? = msg {return v} return Vpn_NetworkSettingsResponse() @@ -71,7 +71,7 @@ struct Vpn_ManagerMessage: Sendable { set {msg = .networkSettings(newValue)} } - var start: Vpn_StartRequest { + public var start: Vpn_StartRequest { get { if case .start(let v)? = msg {return v} return Vpn_StartRequest() @@ -79,7 +79,7 @@ struct Vpn_ManagerMessage: Sendable { set {msg = .start(newValue)} } - var stop: Vpn_StopRequest { + public var stop: Vpn_StopRequest { get { if case .stop(let v)? = msg {return v} return Vpn_StopRequest() @@ -87,9 +87,9 @@ struct Vpn_ManagerMessage: Sendable { set {msg = .stop(newValue)} } - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - enum OneOf_Msg: Equatable, Sendable { + public enum OneOf_Msg: Equatable, Sendable { case getPeerUpdate(Vpn_GetPeerUpdate) case networkSettings(Vpn_NetworkSettingsResponse) case start(Vpn_StartRequest) @@ -97,29 +97,29 @@ struct Vpn_ManagerMessage: Sendable { } - init() {} + public init() {} fileprivate var _rpc: Vpn_RPC? = nil } /// TunnelMessage is a message from the tunnel (to the manager). -struct Vpn_TunnelMessage: Sendable { +public struct Vpn_TunnelMessage: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var rpc: Vpn_RPC { + public var rpc: Vpn_RPC { get {return _rpc ?? Vpn_RPC()} set {_rpc = newValue} } /// Returns true if `rpc` has been explicitly set. - var hasRpc: Bool {return self._rpc != nil} + public var hasRpc: Bool {return self._rpc != nil} /// Clears the value of `rpc`. Subsequent reads from it will return its default value. - mutating func clearRpc() {self._rpc = nil} + public mutating func clearRpc() {self._rpc = nil} - var msg: Vpn_TunnelMessage.OneOf_Msg? = nil + public var msg: Vpn_TunnelMessage.OneOf_Msg? = nil - var log: Vpn_Log { + public var log: Vpn_Log { get { if case .log(let v)? = msg {return v} return Vpn_Log() @@ -127,7 +127,7 @@ struct Vpn_TunnelMessage: Sendable { set {msg = .log(newValue)} } - var peerUpdate: Vpn_PeerUpdate { + public var peerUpdate: Vpn_PeerUpdate { get { if case .peerUpdate(let v)? = msg {return v} return Vpn_PeerUpdate() @@ -135,7 +135,7 @@ struct Vpn_TunnelMessage: Sendable { set {msg = .peerUpdate(newValue)} } - var networkSettings: Vpn_NetworkSettingsRequest { + public var networkSettings: Vpn_NetworkSettingsRequest { get { if case .networkSettings(let v)? = msg {return v} return Vpn_NetworkSettingsRequest() @@ -143,7 +143,7 @@ struct Vpn_TunnelMessage: Sendable { set {msg = .networkSettings(newValue)} } - var start: Vpn_StartResponse { + public var start: Vpn_StartResponse { get { if case .start(let v)? = msg {return v} return Vpn_StartResponse() @@ -151,7 +151,7 @@ struct Vpn_TunnelMessage: Sendable { set {msg = .start(newValue)} } - var stop: Vpn_StopResponse { + public var stop: Vpn_StopResponse { get { if case .stop(let v)? = msg {return v} return Vpn_StopResponse() @@ -159,9 +159,9 @@ struct Vpn_TunnelMessage: Sendable { set {msg = .stop(newValue)} } - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - enum OneOf_Msg: Equatable, Sendable { + public enum OneOf_Msg: Equatable, Sendable { case log(Vpn_Log) case peerUpdate(Vpn_PeerUpdate) case networkSettings(Vpn_NetworkSettingsRequest) @@ -170,30 +170,30 @@ struct Vpn_TunnelMessage: Sendable { } - init() {} + public init() {} fileprivate var _rpc: Vpn_RPC? = nil } /// Log is a log message generated by the tunnel. The manager should log it to the system log. It is /// one-way tunnel -> manager with no response. -struct Vpn_Log: Sendable { +public struct Vpn_Log: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var level: Vpn_Log.Level = .debug + public var level: Vpn_Log.Level = .debug - var message: String = String() + public var message: String = String() - var loggerNames: [String] = [] + public var loggerNames: [String] = [] - var fields: [Vpn_Log.Field] = [] + public var fields: [Vpn_Log.Field] = [] - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - enum Level: SwiftProtobuf.Enum, Swift.CaseIterable { - typealias RawValue = Int + public enum Level: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int /// these are designed to match slog levels case debug // = 0 @@ -204,11 +204,11 @@ struct Vpn_Log: Sendable { case fatal // = 5 case UNRECOGNIZED(Int) - init() { + public init() { self = .debug } - init?(rawValue: Int) { + public init?(rawValue: Int) { switch rawValue { case 0: self = .debug case 1: self = .info @@ -220,7 +220,7 @@ struct Vpn_Log: Sendable { } } - var rawValue: Int { + public var rawValue: Int { switch self { case .debug: return 0 case .info: return 1 @@ -233,7 +233,7 @@ struct Vpn_Log: Sendable { } // The compiler won't synthesize support with the UNRECOGNIZED case. - static let allCases: [Vpn_Log.Level] = [ + public static let allCases: [Vpn_Log.Level] = [ .debug, .info, .warn, @@ -244,71 +244,71 @@ struct Vpn_Log: Sendable { } - struct Field: Sendable { + public struct Field: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var name: String = String() + public var name: String = String() - var value: String = String() + public var value: String = String() - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - init() {} + public init() {} } - init() {} + public init() {} } /// GetPeerUpdate asks for a PeerUpdate with a full set of data. -struct Vpn_GetPeerUpdate: Sendable { +public struct Vpn_GetPeerUpdate: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - init() {} + public init() {} } /// PeerUpdate is an update about workspaces and agents connected via the tunnel. It is generated in /// response to GetPeerUpdate (which dumps the full set). It is also generated on any changes (not in /// response to any request). -struct Vpn_PeerUpdate: Sendable { +public struct Vpn_PeerUpdate: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var upsertedWorkspaces: [Vpn_Workspace] = [] + public var upsertedWorkspaces: [Vpn_Workspace] = [] - var upsertedAgents: [Vpn_Agent] = [] + public var upsertedAgents: [Vpn_Agent] = [] - var deletedWorkspaces: [Vpn_Workspace] = [] + public var deletedWorkspaces: [Vpn_Workspace] = [] - var deletedAgents: [Vpn_Agent] = [] + public var deletedAgents: [Vpn_Agent] = [] - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - init() {} + public init() {} } -struct Vpn_Workspace: @unchecked Sendable { +public struct Vpn_Workspace: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. /// UUID - var id: Data = Data() + public var id: Data = Data() - var name: String = String() + public var name: String = String() - var status: Vpn_Workspace.Status = .unknown + public var status: Vpn_Workspace.Status = .unknown - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - enum Status: SwiftProtobuf.Enum, Swift.CaseIterable { - typealias RawValue = Int + public enum Status: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int case unknown // = 0 case pending // = 1 case starting // = 2 @@ -322,11 +322,11 @@ struct Vpn_Workspace: @unchecked Sendable { case deleted // = 10 case UNRECOGNIZED(Int) - init() { + public init() { self = .unknown } - init?(rawValue: Int) { + public init?(rawValue: Int) { switch rawValue { case 0: self = .unknown case 1: self = .pending @@ -343,7 +343,7 @@ struct Vpn_Workspace: @unchecked Sendable { } } - var rawValue: Int { + public var rawValue: Int { switch self { case .unknown: return 0 case .pending: return 1 @@ -361,7 +361,7 @@ struct Vpn_Workspace: @unchecked Sendable { } // The compiler won't synthesize support with the UNRECOGNIZED case. - static let allCases: [Vpn_Workspace.Status] = [ + public static let allCases: [Vpn_Workspace.Status] = [ .unknown, .pending, .starting, @@ -377,40 +377,40 @@ struct Vpn_Workspace: @unchecked Sendable { } - init() {} + public init() {} } -struct Vpn_Agent: @unchecked Sendable { +public struct Vpn_Agent: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. /// UUID - var id: Data = Data() + public var id: Data = Data() - var name: String = String() + public var name: String = String() /// UUID - var workspaceID: Data = Data() + public var workspaceID: Data = Data() - var fqdn: String = String() + public var fqdn: String = String() - var ipAddrs: [String] = [] + public var ipAddrs: [String] = [] /// last_handshake is the primary indicator of whether we are connected to a peer. Zero value or /// anything longer than 5 minutes ago means there is a problem. - var lastHandshake: SwiftProtobuf.Google_Protobuf_Timestamp { + public var lastHandshake: SwiftProtobuf.Google_Protobuf_Timestamp { get {return _lastHandshake ?? SwiftProtobuf.Google_Protobuf_Timestamp()} set {_lastHandshake = newValue} } /// Returns true if `lastHandshake` has been explicitly set. - var hasLastHandshake: Bool {return self._lastHandshake != nil} + public var hasLastHandshake: Bool {return self._lastHandshake != nil} /// Clears the value of `lastHandshake`. Subsequent reads from it will return its default value. - mutating func clearLastHandshake() {self._lastHandshake = nil} + public mutating func clearLastHandshake() {self._lastHandshake = nil} - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - init() {} + public init() {} fileprivate var _lastHandshake: SwiftProtobuf.Google_Protobuf_Timestamp? = nil } @@ -418,230 +418,230 @@ struct Vpn_Agent: @unchecked Sendable { /// NetworkSettingsRequest is based on /// https://developer.apple.com/documentation/networkextension/nepackettunnelnetworksettings for /// macOS. It is a request/response message with response NetworkSettingsResponse -struct Vpn_NetworkSettingsRequest: @unchecked Sendable { +public struct Vpn_NetworkSettingsRequest: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var tunnelOverheadBytes: UInt32 { + public var tunnelOverheadBytes: UInt32 { get {return _storage._tunnelOverheadBytes} set {_uniqueStorage()._tunnelOverheadBytes = newValue} } - var mtu: UInt32 { + public var mtu: UInt32 { get {return _storage._mtu} set {_uniqueStorage()._mtu = newValue} } - var dnsSettings: Vpn_NetworkSettingsRequest.DNSSettings { + public var dnsSettings: Vpn_NetworkSettingsRequest.DNSSettings { get {return _storage._dnsSettings ?? Vpn_NetworkSettingsRequest.DNSSettings()} set {_uniqueStorage()._dnsSettings = newValue} } /// Returns true if `dnsSettings` has been explicitly set. - var hasDnsSettings: Bool {return _storage._dnsSettings != nil} + public var hasDnsSettings: Bool {return _storage._dnsSettings != nil} /// Clears the value of `dnsSettings`. Subsequent reads from it will return its default value. - mutating func clearDnsSettings() {_uniqueStorage()._dnsSettings = nil} + public mutating func clearDnsSettings() {_uniqueStorage()._dnsSettings = nil} - var tunnelRemoteAddress: String { + public var tunnelRemoteAddress: String { get {return _storage._tunnelRemoteAddress} set {_uniqueStorage()._tunnelRemoteAddress = newValue} } - var ipv4Settings: Vpn_NetworkSettingsRequest.IPv4Settings { + public var ipv4Settings: Vpn_NetworkSettingsRequest.IPv4Settings { get {return _storage._ipv4Settings ?? Vpn_NetworkSettingsRequest.IPv4Settings()} set {_uniqueStorage()._ipv4Settings = newValue} } /// Returns true if `ipv4Settings` has been explicitly set. - var hasIpv4Settings: Bool {return _storage._ipv4Settings != nil} + public var hasIpv4Settings: Bool {return _storage._ipv4Settings != nil} /// Clears the value of `ipv4Settings`. Subsequent reads from it will return its default value. - mutating func clearIpv4Settings() {_uniqueStorage()._ipv4Settings = nil} + public mutating func clearIpv4Settings() {_uniqueStorage()._ipv4Settings = nil} - var ipv6Settings: Vpn_NetworkSettingsRequest.IPv6Settings { + public var ipv6Settings: Vpn_NetworkSettingsRequest.IPv6Settings { get {return _storage._ipv6Settings ?? Vpn_NetworkSettingsRequest.IPv6Settings()} set {_uniqueStorage()._ipv6Settings = newValue} } /// Returns true if `ipv6Settings` has been explicitly set. - var hasIpv6Settings: Bool {return _storage._ipv6Settings != nil} + public var hasIpv6Settings: Bool {return _storage._ipv6Settings != nil} /// Clears the value of `ipv6Settings`. Subsequent reads from it will return its default value. - mutating func clearIpv6Settings() {_uniqueStorage()._ipv6Settings = nil} + public mutating func clearIpv6Settings() {_uniqueStorage()._ipv6Settings = nil} - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - struct DNSSettings: Sendable { + public struct DNSSettings: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var servers: [String] = [] + public var servers: [String] = [] - var searchDomains: [String] = [] + public var searchDomains: [String] = [] /// domain_name is the primary domain name of the tunnel - var domainName: String = String() + public var domainName: String = String() - var matchDomains: [String] = [] + public var matchDomains: [String] = [] /// match_domains_no_search specifies if the domains in the matchDomains list should not be /// appended to the resolver’s list of search domains. - var matchDomainsNoSearch: Bool = false + public var matchDomainsNoSearch: Bool = false - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - init() {} + public init() {} } - struct IPv4Settings: Sendable { + public struct IPv4Settings: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var addrs: [String] = [] + public var addrs: [String] = [] - var subnetMasks: [String] = [] + public var subnetMasks: [String] = [] /// router is the next-hop router in dotted-decimal format - var router: String = String() + public var router: String = String() - var includedRoutes: [Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route] = [] + public var includedRoutes: [Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route] = [] - var excludedRoutes: [Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route] = [] + public var excludedRoutes: [Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route] = [] - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - struct IPv4Route: Sendable { + public struct IPv4Route: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var destination: String = String() + public var destination: String = String() - var mask: String = String() + public var mask: String = String() /// router is the next-hop router in dotted-decimal format - var router: String = String() + public var router: String = String() - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - init() {} + public init() {} } - init() {} + public init() {} } - struct IPv6Settings: Sendable { + public struct IPv6Settings: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var addrs: [String] = [] + public var addrs: [String] = [] - var prefixLengths: [UInt32] = [] + public var prefixLengths: [UInt32] = [] - var includedRoutes: [Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route] = [] + public var includedRoutes: [Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route] = [] - var excludedRoutes: [Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route] = [] + public var excludedRoutes: [Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route] = [] - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - struct IPv6Route: Sendable { + public struct IPv6Route: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var destination: String = String() + public var destination: String = String() - var prefixLength: UInt32 = 0 + public var prefixLength: UInt32 = 0 /// router is the address of the next-hop - var router: String = String() + public var router: String = String() - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - init() {} + public init() {} } - init() {} + public init() {} } - init() {} + public init() {} fileprivate var _storage = _StorageClass.defaultInstance } /// NetworkSettingsResponse is the response from the manager to the tunnel for a /// NetworkSettingsRequest -struct Vpn_NetworkSettingsResponse: Sendable { +public struct Vpn_NetworkSettingsResponse: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var success: Bool = false + public var success: Bool = false - var errorMessage: String = String() + public var errorMessage: String = String() - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - init() {} + public init() {} } /// StartRequest is a request from the manager to start the tunnel. The tunnel replies with a /// StartResponse. -struct Vpn_StartRequest: Sendable { +public struct Vpn_StartRequest: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var tunnelFileDescriptor: Int32 = 0 + public var tunnelFileDescriptor: Int32 = 0 - var coderURL: String = String() + public var coderURL: String = String() - var apiToken: String = String() + public var apiToken: String = String() - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - init() {} + public init() {} } -struct Vpn_StartResponse: Sendable { +public struct Vpn_StartResponse: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var success: Bool = false + public var success: Bool = false - var errorMessage: String = String() + public var errorMessage: String = String() - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - init() {} + public init() {} } /// StopRequest is a request from the manager to stop the tunnel. The tunnel replies with a /// StopResponse. -struct Vpn_StopRequest: Sendable { +public struct Vpn_StopRequest: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - init() {} + public init() {} } /// StopResponse is a response to stopping the tunnel. After sending this response, the tunnel closes /// its side of the bidirectional stream for writing. -struct Vpn_StopResponse: Sendable { +public struct Vpn_StopResponse: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var success: Bool = false + public var success: Bool = false - var errorMessage: String = String() + public var errorMessage: String = String() - var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - init() {} + public init() {} } // MARK: - Code below here is support for the SwiftProtobuf runtime. @@ -649,13 +649,13 @@ struct Vpn_StopResponse: Sendable { fileprivate let _protobuf_package = "vpn" extension Vpn_RPC: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".RPC" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = _protobuf_package + ".RPC" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "msg_id"), 2: .standard(proto: "response_to"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -668,7 +668,7 @@ extension Vpn_RPC: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { if self.msgID != 0 { try visitor.visitSingularUInt64Field(value: self.msgID, fieldNumber: 1) } @@ -678,7 +678,7 @@ extension Vpn_RPC: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_RPC, rhs: Vpn_RPC) -> Bool { + public static func ==(lhs: Vpn_RPC, rhs: Vpn_RPC) -> Bool { if lhs.msgID != rhs.msgID {return false} if lhs.responseTo != rhs.responseTo {return false} if lhs.unknownFields != rhs.unknownFields {return false} @@ -687,8 +687,8 @@ extension Vpn_RPC: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa } extension Vpn_ManagerMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ManagerMessage" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = _protobuf_package + ".ManagerMessage" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "rpc"), 2: .standard(proto: "get_peer_update"), 3: .standard(proto: "network_settings"), @@ -696,7 +696,7 @@ extension Vpn_ManagerMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImple 5: .same(proto: "stop"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -760,7 +760,7 @@ extension Vpn_ManagerMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImple } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every if/case branch local when no optimizations // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and @@ -790,7 +790,7 @@ extension Vpn_ManagerMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImple try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_ManagerMessage, rhs: Vpn_ManagerMessage) -> Bool { + public static func ==(lhs: Vpn_ManagerMessage, rhs: Vpn_ManagerMessage) -> Bool { if lhs._rpc != rhs._rpc {return false} if lhs.msg != rhs.msg {return false} if lhs.unknownFields != rhs.unknownFields {return false} @@ -799,8 +799,8 @@ extension Vpn_ManagerMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImple } extension Vpn_TunnelMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".TunnelMessage" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = _protobuf_package + ".TunnelMessage" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "rpc"), 2: .same(proto: "log"), 3: .standard(proto: "peer_update"), @@ -809,7 +809,7 @@ extension Vpn_TunnelMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem 6: .same(proto: "stop"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -886,7 +886,7 @@ extension Vpn_TunnelMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every if/case branch local when no optimizations // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and @@ -920,7 +920,7 @@ extension Vpn_TunnelMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_TunnelMessage, rhs: Vpn_TunnelMessage) -> Bool { + public static func ==(lhs: Vpn_TunnelMessage, rhs: Vpn_TunnelMessage) -> Bool { if lhs._rpc != rhs._rpc {return false} if lhs.msg != rhs.msg {return false} if lhs.unknownFields != rhs.unknownFields {return false} @@ -929,15 +929,15 @@ extension Vpn_TunnelMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem } extension Vpn_Log: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Log" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = _protobuf_package + ".Log" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "level"), 2: .same(proto: "message"), 3: .standard(proto: "logger_names"), 4: .same(proto: "fields"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -952,7 +952,7 @@ extension Vpn_Log: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { if self.level != .debug { try visitor.visitSingularEnumField(value: self.level, fieldNumber: 1) } @@ -968,7 +968,7 @@ extension Vpn_Log: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_Log, rhs: Vpn_Log) -> Bool { + public static func ==(lhs: Vpn_Log, rhs: Vpn_Log) -> Bool { if lhs.level != rhs.level {return false} if lhs.message != rhs.message {return false} if lhs.loggerNames != rhs.loggerNames {return false} @@ -979,7 +979,7 @@ extension Vpn_Log: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa } extension Vpn_Log.Level: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 0: .same(proto: "DEBUG"), 1: .same(proto: "INFO"), 2: .same(proto: "WARN"), @@ -990,13 +990,13 @@ extension Vpn_Log.Level: SwiftProtobuf._ProtoNameProviding { } extension Vpn_Log.Field: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Vpn_Log.protoMessageName + ".Field" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = Vpn_Log.protoMessageName + ".Field" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "name"), 2: .same(proto: "value"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -1009,7 +1009,7 @@ extension Vpn_Log.Field: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { if !self.name.isEmpty { try visitor.visitSingularStringField(value: self.name, fieldNumber: 1) } @@ -1019,7 +1019,7 @@ extension Vpn_Log.Field: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_Log.Field, rhs: Vpn_Log.Field) -> Bool { + public static func ==(lhs: Vpn_Log.Field, rhs: Vpn_Log.Field) -> Bool { if lhs.name != rhs.name {return false} if lhs.value != rhs.value {return false} if lhs.unknownFields != rhs.unknownFields {return false} @@ -1028,34 +1028,34 @@ extension Vpn_Log.Field: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa } extension Vpn_GetPeerUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".GetPeerUpdate" - static let _protobuf_nameMap = SwiftProtobuf._NameMap() + public static let protoMessageName: String = _protobuf_package + ".GetPeerUpdate" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap() - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { // Load everything into unknown fields while try decoder.nextFieldNumber() != nil {} } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_GetPeerUpdate, rhs: Vpn_GetPeerUpdate) -> Bool { + public static func ==(lhs: Vpn_GetPeerUpdate, rhs: Vpn_GetPeerUpdate) -> Bool { if lhs.unknownFields != rhs.unknownFields {return false} return true } } extension Vpn_PeerUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".PeerUpdate" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = _protobuf_package + ".PeerUpdate" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "upserted_workspaces"), 2: .standard(proto: "upserted_agents"), 3: .standard(proto: "deleted_workspaces"), 4: .standard(proto: "deleted_agents"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -1070,7 +1070,7 @@ extension Vpn_PeerUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { if !self.upsertedWorkspaces.isEmpty { try visitor.visitRepeatedMessageField(value: self.upsertedWorkspaces, fieldNumber: 1) } @@ -1086,7 +1086,7 @@ extension Vpn_PeerUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_PeerUpdate, rhs: Vpn_PeerUpdate) -> Bool { + public static func ==(lhs: Vpn_PeerUpdate, rhs: Vpn_PeerUpdate) -> Bool { if lhs.upsertedWorkspaces != rhs.upsertedWorkspaces {return false} if lhs.upsertedAgents != rhs.upsertedAgents {return false} if lhs.deletedWorkspaces != rhs.deletedWorkspaces {return false} @@ -1097,14 +1097,14 @@ extension Vpn_PeerUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement } extension Vpn_Workspace: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Workspace" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = _protobuf_package + ".Workspace" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "id"), 2: .same(proto: "name"), 3: .same(proto: "status"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -1118,7 +1118,7 @@ extension Vpn_Workspace: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { if !self.id.isEmpty { try visitor.visitSingularBytesField(value: self.id, fieldNumber: 1) } @@ -1131,7 +1131,7 @@ extension Vpn_Workspace: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_Workspace, rhs: Vpn_Workspace) -> Bool { + public static func ==(lhs: Vpn_Workspace, rhs: Vpn_Workspace) -> Bool { if lhs.id != rhs.id {return false} if lhs.name != rhs.name {return false} if lhs.status != rhs.status {return false} @@ -1141,7 +1141,7 @@ extension Vpn_Workspace: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa } extension Vpn_Workspace.Status: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 0: .same(proto: "UNKNOWN"), 1: .same(proto: "PENDING"), 2: .same(proto: "STARTING"), @@ -1157,8 +1157,8 @@ extension Vpn_Workspace.Status: SwiftProtobuf._ProtoNameProviding { } extension Vpn_Agent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Agent" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = _protobuf_package + ".Agent" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "id"), 2: .same(proto: "name"), 3: .standard(proto: "workspace_id"), @@ -1167,7 +1167,7 @@ extension Vpn_Agent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation 6: .standard(proto: "last_handshake"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -1184,7 +1184,7 @@ extension Vpn_Agent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every if/case branch local when no optimizations // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and @@ -1210,7 +1210,7 @@ extension Vpn_Agent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_Agent, rhs: Vpn_Agent) -> Bool { + public static func ==(lhs: Vpn_Agent, rhs: Vpn_Agent) -> Bool { if lhs.id != rhs.id {return false} if lhs.name != rhs.name {return false} if lhs.workspaceID != rhs.workspaceID {return false} @@ -1223,8 +1223,8 @@ extension Vpn_Agent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation } extension Vpn_NetworkSettingsRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".NetworkSettingsRequest" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = _protobuf_package + ".NetworkSettingsRequest" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "tunnel_overhead_bytes"), 2: .same(proto: "mtu"), 3: .standard(proto: "dns_settings"), @@ -1270,7 +1270,7 @@ extension Vpn_NetworkSettingsRequest: SwiftProtobuf.Message, SwiftProtobuf._Mess return _storage } - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { _ = _uniqueStorage() try withExtendedLifetime(_storage) { (_storage: _StorageClass) in while let fieldNumber = try decoder.nextFieldNumber() { @@ -1290,7 +1290,7 @@ extension Vpn_NetworkSettingsRequest: SwiftProtobuf.Message, SwiftProtobuf._Mess } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { try withExtendedLifetime(_storage) { (_storage: _StorageClass) in // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every if/case branch local when no optimizations @@ -1318,7 +1318,7 @@ extension Vpn_NetworkSettingsRequest: SwiftProtobuf.Message, SwiftProtobuf._Mess try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_NetworkSettingsRequest, rhs: Vpn_NetworkSettingsRequest) -> Bool { + public static func ==(lhs: Vpn_NetworkSettingsRequest, rhs: Vpn_NetworkSettingsRequest) -> Bool { if lhs._storage !== rhs._storage { let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in let _storage = _args.0 @@ -1339,8 +1339,8 @@ extension Vpn_NetworkSettingsRequest: SwiftProtobuf.Message, SwiftProtobuf._Mess } extension Vpn_NetworkSettingsRequest.DNSSettings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Vpn_NetworkSettingsRequest.protoMessageName + ".DNSSettings" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = Vpn_NetworkSettingsRequest.protoMessageName + ".DNSSettings" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "servers"), 2: .standard(proto: "search_domains"), 3: .standard(proto: "domain_name"), @@ -1348,7 +1348,7 @@ extension Vpn_NetworkSettingsRequest.DNSSettings: SwiftProtobuf.Message, SwiftPr 5: .standard(proto: "match_domains_no_search"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -1364,7 +1364,7 @@ extension Vpn_NetworkSettingsRequest.DNSSettings: SwiftProtobuf.Message, SwiftPr } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { if !self.servers.isEmpty { try visitor.visitRepeatedStringField(value: self.servers, fieldNumber: 1) } @@ -1383,7 +1383,7 @@ extension Vpn_NetworkSettingsRequest.DNSSettings: SwiftProtobuf.Message, SwiftPr try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_NetworkSettingsRequest.DNSSettings, rhs: Vpn_NetworkSettingsRequest.DNSSettings) -> Bool { + public static func ==(lhs: Vpn_NetworkSettingsRequest.DNSSettings, rhs: Vpn_NetworkSettingsRequest.DNSSettings) -> Bool { if lhs.servers != rhs.servers {return false} if lhs.searchDomains != rhs.searchDomains {return false} if lhs.domainName != rhs.domainName {return false} @@ -1395,8 +1395,8 @@ extension Vpn_NetworkSettingsRequest.DNSSettings: SwiftProtobuf.Message, SwiftPr } extension Vpn_NetworkSettingsRequest.IPv4Settings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Vpn_NetworkSettingsRequest.protoMessageName + ".IPv4Settings" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = Vpn_NetworkSettingsRequest.protoMessageName + ".IPv4Settings" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "addrs"), 2: .standard(proto: "subnet_masks"), 3: .same(proto: "router"), @@ -1404,7 +1404,7 @@ extension Vpn_NetworkSettingsRequest.IPv4Settings: SwiftProtobuf.Message, SwiftP 5: .standard(proto: "excluded_routes"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -1420,7 +1420,7 @@ extension Vpn_NetworkSettingsRequest.IPv4Settings: SwiftProtobuf.Message, SwiftP } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { if !self.addrs.isEmpty { try visitor.visitRepeatedStringField(value: self.addrs, fieldNumber: 1) } @@ -1439,7 +1439,7 @@ extension Vpn_NetworkSettingsRequest.IPv4Settings: SwiftProtobuf.Message, SwiftP try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_NetworkSettingsRequest.IPv4Settings, rhs: Vpn_NetworkSettingsRequest.IPv4Settings) -> Bool { + public static func ==(lhs: Vpn_NetworkSettingsRequest.IPv4Settings, rhs: Vpn_NetworkSettingsRequest.IPv4Settings) -> Bool { if lhs.addrs != rhs.addrs {return false} if lhs.subnetMasks != rhs.subnetMasks {return false} if lhs.router != rhs.router {return false} @@ -1451,14 +1451,14 @@ extension Vpn_NetworkSettingsRequest.IPv4Settings: SwiftProtobuf.Message, SwiftP } extension Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Vpn_NetworkSettingsRequest.IPv4Settings.protoMessageName + ".IPv4Route" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = Vpn_NetworkSettingsRequest.IPv4Settings.protoMessageName + ".IPv4Route" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "destination"), 2: .same(proto: "mask"), 3: .same(proto: "router"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -1472,7 +1472,7 @@ extension Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route: SwiftProtobuf.Messa } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { if !self.destination.isEmpty { try visitor.visitSingularStringField(value: self.destination, fieldNumber: 1) } @@ -1485,7 +1485,7 @@ extension Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route: SwiftProtobuf.Messa try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route, rhs: Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route) -> Bool { + public static func ==(lhs: Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route, rhs: Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route) -> Bool { if lhs.destination != rhs.destination {return false} if lhs.mask != rhs.mask {return false} if lhs.router != rhs.router {return false} @@ -1495,15 +1495,15 @@ extension Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route: SwiftProtobuf.Messa } extension Vpn_NetworkSettingsRequest.IPv6Settings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Vpn_NetworkSettingsRequest.protoMessageName + ".IPv6Settings" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = Vpn_NetworkSettingsRequest.protoMessageName + ".IPv6Settings" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "addrs"), 2: .standard(proto: "prefix_lengths"), 3: .standard(proto: "included_routes"), 4: .standard(proto: "excluded_routes"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -1518,7 +1518,7 @@ extension Vpn_NetworkSettingsRequest.IPv6Settings: SwiftProtobuf.Message, SwiftP } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { if !self.addrs.isEmpty { try visitor.visitRepeatedStringField(value: self.addrs, fieldNumber: 1) } @@ -1534,7 +1534,7 @@ extension Vpn_NetworkSettingsRequest.IPv6Settings: SwiftProtobuf.Message, SwiftP try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_NetworkSettingsRequest.IPv6Settings, rhs: Vpn_NetworkSettingsRequest.IPv6Settings) -> Bool { + public static func ==(lhs: Vpn_NetworkSettingsRequest.IPv6Settings, rhs: Vpn_NetworkSettingsRequest.IPv6Settings) -> Bool { if lhs.addrs != rhs.addrs {return false} if lhs.prefixLengths != rhs.prefixLengths {return false} if lhs.includedRoutes != rhs.includedRoutes {return false} @@ -1545,14 +1545,14 @@ extension Vpn_NetworkSettingsRequest.IPv6Settings: SwiftProtobuf.Message, SwiftP } extension Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Vpn_NetworkSettingsRequest.IPv6Settings.protoMessageName + ".IPv6Route" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = Vpn_NetworkSettingsRequest.IPv6Settings.protoMessageName + ".IPv6Route" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "destination"), 2: .standard(proto: "prefix_length"), 3: .same(proto: "router"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -1566,7 +1566,7 @@ extension Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route: SwiftProtobuf.Messa } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { if !self.destination.isEmpty { try visitor.visitSingularStringField(value: self.destination, fieldNumber: 1) } @@ -1579,7 +1579,7 @@ extension Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route: SwiftProtobuf.Messa try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route, rhs: Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route) -> Bool { + public static func ==(lhs: Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route, rhs: Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route) -> Bool { if lhs.destination != rhs.destination {return false} if lhs.prefixLength != rhs.prefixLength {return false} if lhs.router != rhs.router {return false} @@ -1589,13 +1589,13 @@ extension Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route: SwiftProtobuf.Messa } extension Vpn_NetworkSettingsResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".NetworkSettingsResponse" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = _protobuf_package + ".NetworkSettingsResponse" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "success"), 2: .standard(proto: "error_message"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -1608,7 +1608,7 @@ extension Vpn_NetworkSettingsResponse: SwiftProtobuf.Message, SwiftProtobuf._Mes } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { if self.success != false { try visitor.visitSingularBoolField(value: self.success, fieldNumber: 1) } @@ -1618,7 +1618,7 @@ extension Vpn_NetworkSettingsResponse: SwiftProtobuf.Message, SwiftProtobuf._Mes try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_NetworkSettingsResponse, rhs: Vpn_NetworkSettingsResponse) -> Bool { + public static func ==(lhs: Vpn_NetworkSettingsResponse, rhs: Vpn_NetworkSettingsResponse) -> Bool { if lhs.success != rhs.success {return false} if lhs.errorMessage != rhs.errorMessage {return false} if lhs.unknownFields != rhs.unknownFields {return false} @@ -1627,14 +1627,14 @@ extension Vpn_NetworkSettingsResponse: SwiftProtobuf.Message, SwiftProtobuf._Mes } extension Vpn_StartRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".StartRequest" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = _protobuf_package + ".StartRequest" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "tunnel_file_descriptor"), 2: .standard(proto: "coder_url"), 3: .standard(proto: "api_token"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -1648,7 +1648,7 @@ extension Vpn_StartRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { if self.tunnelFileDescriptor != 0 { try visitor.visitSingularInt32Field(value: self.tunnelFileDescriptor, fieldNumber: 1) } @@ -1661,7 +1661,7 @@ extension Vpn_StartRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_StartRequest, rhs: Vpn_StartRequest) -> Bool { + public static func ==(lhs: Vpn_StartRequest, rhs: Vpn_StartRequest) -> Bool { if lhs.tunnelFileDescriptor != rhs.tunnelFileDescriptor {return false} if lhs.coderURL != rhs.coderURL {return false} if lhs.apiToken != rhs.apiToken {return false} @@ -1671,13 +1671,13 @@ extension Vpn_StartRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme } extension Vpn_StartResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".StartResponse" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = _protobuf_package + ".StartResponse" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "success"), 2: .standard(proto: "error_message"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -1690,7 +1690,7 @@ extension Vpn_StartResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { if self.success != false { try visitor.visitSingularBoolField(value: self.success, fieldNumber: 1) } @@ -1700,7 +1700,7 @@ extension Vpn_StartResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_StartResponse, rhs: Vpn_StartResponse) -> Bool { + public static func ==(lhs: Vpn_StartResponse, rhs: Vpn_StartResponse) -> Bool { if lhs.success != rhs.success {return false} if lhs.errorMessage != rhs.errorMessage {return false} if lhs.unknownFields != rhs.unknownFields {return false} @@ -1709,32 +1709,32 @@ extension Vpn_StartResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem } extension Vpn_StopRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".StopRequest" - static let _protobuf_nameMap = SwiftProtobuf._NameMap() + public static let protoMessageName: String = _protobuf_package + ".StopRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap() - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { // Load everything into unknown fields while try decoder.nextFieldNumber() != nil {} } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_StopRequest, rhs: Vpn_StopRequest) -> Bool { + public static func ==(lhs: Vpn_StopRequest, rhs: Vpn_StopRequest) -> Bool { if lhs.unknownFields != rhs.unknownFields {return false} return true } } extension Vpn_StopResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".StopResponse" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = _protobuf_package + ".StopResponse" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "success"), 2: .standard(proto: "error_message"), ] - mutating func decodeMessage(decoder: inout D) throws { + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -1747,7 +1747,7 @@ extension Vpn_StopResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme } } - func traverse(visitor: inout V) throws { + public func traverse(visitor: inout V) throws { if self.success != false { try visitor.visitSingularBoolField(value: self.success, fieldNumber: 1) } @@ -1757,7 +1757,7 @@ extension Vpn_StopResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: Vpn_StopResponse, rhs: Vpn_StopResponse) -> Bool { + public static func ==(lhs: Vpn_StopResponse, rhs: Vpn_StopResponse) -> Bool { if lhs.success != rhs.success {return false} if lhs.errorMessage != rhs.errorMessage {return false} if lhs.unknownFields != rhs.unknownFields {return false} diff --git a/Coder Desktop/Proto/vpn.proto b/Coder Desktop/VPNLib/vpn.proto similarity index 100% rename from Coder Desktop/Proto/vpn.proto rename to Coder Desktop/VPNLib/vpn.proto diff --git a/Coder Desktop/VPNLibTests/DownloaderTests.swift b/Coder Desktop/VPNLibTests/DownloaderTests.swift new file mode 100644 index 0000000..3d4ddac --- /dev/null +++ b/Coder Desktop/VPNLibTests/DownloaderTests.swift @@ -0,0 +1,162 @@ +import Foundation +import Swifter +import Testing +@testable import VPNLib + +struct NoopValidator: Validator { + func validate(path _: URL) async throws {} +} + +@Suite +struct DownloaderTests { + let downloader = Downloader(validator: NoopValidator()) + + @Test + func downloadFile() async throws { + let destinationURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + + let server = HttpServer() + let testData = Data("foo".utf8) + + server["/test.txt"] = { _ in + HttpResponse.ok(.data(testData)) + } + + try server.start(freePort()) + defer { server.stop() } + + let fileURL = try URL(string: "http://localhost:\(server.port())/test.txt")! + try await downloader.download(src: fileURL, dest: destinationURL) + + try #require(FileManager.default.fileExists(atPath: destinationURL.path)) + defer { try? FileManager.default.removeItem(at: destinationURL) } + + let downloadedData = try Data(contentsOf: destinationURL) + #expect(downloadedData == testData) + } + + @Test + func fileNotModified() async throws { + let server = HttpServer() + let testData = Data("foo bar".utf8) + + let destinationURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + + var notModifiedSent = false + server["/test.txt"] = { req in + let etag = etag(data: testData) + if let ifNoneMatch = req.headers["if-none-match"], ifNoneMatch == etag { + notModifiedSent = true + return .raw(304, "Not Modified", nil, nil) + } else { + return .ok(.data(testData)) + } + } + + try server.start(freePort()) + defer { server.stop() } + + let fileURL = try URL(string: "http://localhost:\(server.port())/test.txt")! + try await downloader.download(src: fileURL, dest: destinationURL) + + try #require(FileManager.default.fileExists(atPath: destinationURL.path)) + defer { try? FileManager.default.removeItem(at: destinationURL) } + let downloadedData = try Data(contentsOf: destinationURL) + #expect(downloadedData == testData) + + try await downloader.download(src: fileURL, dest: destinationURL) + #expect(downloadedData == testData) + #expect(notModifiedSent) + } + + @Test + func fileUpdated() async throws { + let server = HttpServer() + let ogData = Data("foo bar".utf8) + let newData = Data("foo bar qux".utf8) + + let destinationURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + + server["/test.txt"] = { _ in + .ok(.data(ogData)) + } + + try server.start(freePort()) + defer { server.stop() } + + let fileURL = try URL(string: "http://localhost:\(server.port())/test.txt")! + + try await downloader.download(src: fileURL, dest: destinationURL) + try #require(FileManager.default.fileExists(atPath: destinationURL.path)) + defer { try? FileManager.default.removeItem(at: destinationURL) } + var downloadedData = try Data(contentsOf: destinationURL) + #expect(downloadedData == ogData) + + server["/test.txt"] = { _ in + .ok(.data(newData)) + } + try await downloader.download(src: fileURL, dest: destinationURL) + downloadedData = try Data(contentsOf: destinationURL) + #expect(downloadedData == newData) + } +} + +// From https://stackoverflow.com/questions/65670932/how-to-find-a-free-local-port-using-swift +func freePort() -> UInt16 { + var port: UInt16 = 8000 + + let socketFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) + if socketFD == -1 { + return port + } + + var hints = addrinfo( + ai_flags: AI_PASSIVE, + ai_family: AF_INET, + ai_socktype: SOCK_STREAM, + ai_protocol: 0, + ai_addrlen: 0, + ai_canonname: nil, + ai_addr: nil, + ai_next: nil + ) + + var addressInfo: UnsafeMutablePointer? + var result = getaddrinfo(nil, "0", &hints, &addressInfo) + if result != 0 { + close(socketFD) + return port + } + + result = Darwin.bind(socketFD, addressInfo!.pointee.ai_addr, socklen_t(addressInfo!.pointee.ai_addrlen)) + if result == -1 { + close(socketFD) + return port + } + + result = Darwin.listen(socketFD, 1) + if result == -1 { + close(socketFD) + return port + } + + var addr_in = sockaddr_in() + addr_in.sin_len = UInt8(MemoryLayout.size(ofValue: addr_in)) + addr_in.sin_family = sa_family_t(AF_INET) + + var len = socklen_t(addr_in.sin_len) + result = withUnsafeMutablePointer(to: &addr_in) { + $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { + Darwin.getsockname(socketFD, $0, &len) + } + } + + if result == 0 { + port = addr_in.sin_port + } + + Darwin.shutdown(socketFD, SHUT_RDWR) + close(socketFD) + + return port +} diff --git a/Coder Desktop/ProtoTests/ProtoTests.swift b/Coder Desktop/VPNLibTests/ProtoTests.swift similarity index 99% rename from Coder Desktop/ProtoTests/ProtoTests.swift rename to Coder Desktop/VPNLibTests/ProtoTests.swift index 5a71cae..6a1bbd9 100644 --- a/Coder Desktop/ProtoTests/ProtoTests.swift +++ b/Coder Desktop/VPNLibTests/ProtoTests.swift @@ -1,6 +1,6 @@ -@testable import Coder_Desktop import Foundation import Testing +@testable import VPNLib @Suite(.timeLimit(.minutes(1))) struct SenderReceiverTests { diff --git a/Coder Desktop/ProtoTests/SpeakerTests.swift b/Coder Desktop/VPNLibTests/SpeakerTests.swift similarity index 99% rename from Coder Desktop/ProtoTests/SpeakerTests.swift rename to Coder Desktop/VPNLibTests/SpeakerTests.swift index 3d90769..fd8ffb7 100644 --- a/Coder Desktop/ProtoTests/SpeakerTests.swift +++ b/Coder Desktop/VPNLibTests/SpeakerTests.swift @@ -1,6 +1,6 @@ -@testable import Coder_Desktop import Foundation import Testing +@testable import VPNLib @Suite(.timeLimit(.minutes(1))) struct SpeakerTests: Sendable { diff --git a/Makefile b/Makefile index e4f0cfb..5f56b6e 100644 --- a/Makefile +++ b/Makefile @@ -34,4 +34,4 @@ clean: -project $(PROJECT) proto: - protoc --swift_out=. 'Coder Desktop/Proto/vpn.proto' + protoc --swift_opt=Visibility=public --swift_out=. 'Coder Desktop/VPNLib/vpn.proto' From 48b35f06cc29874da52ce3b80d10212d11922ada Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 7 Jan 2025 21:40:47 +1100 Subject: [PATCH 02/17] fmt --- Coder Desktop/VPN/Manager.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Coder Desktop/VPN/Manager.swift b/Coder Desktop/VPN/Manager.swift index 2c65371..debd0dc 100644 --- a/Coder Desktop/VPN/Manager.swift +++ b/Coder Desktop/VPN/Manager.swift @@ -6,8 +6,8 @@ actor Manager { let ptp: PacketTunnelProvider let downloader: Downloader - var tunnelHandle: TunnelHandle? = nil - var speaker: Speaker? = nil + var tunnelHandle: TunnelHandle? + var speaker: Speaker? // TODO: XPC Speaker private let dest = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) @@ -15,6 +15,6 @@ actor Manager { init(with: PacketTunnelProvider) { ptp = with - self.downloader = Downloader() + downloader = Downloader() } } From f602fa1e40f1a89d372ee2a7c950afa36c74b03d Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 8 Jan 2025 12:56:58 +1100 Subject: [PATCH 03/17] http test server -> mocked requests --- .../Coder Desktop.xcodeproj/project.pbxproj | 20 +-- .../xcshareddata/swiftpm/Package.resolved | 18 +-- .../VPNLibTests/DownloaderTests.swift | 119 +++--------------- 3 files changed, 35 insertions(+), 122 deletions(-) diff --git a/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj b/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj index c114a79..d150fcc 100644 --- a/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj +++ b/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj @@ -18,7 +18,7 @@ AA3B3DC12D2D23AB0099996A /* SwiftProtobufPluginLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = AA3B3DC02D2D23AB0099996A /* SwiftProtobufPluginLibrary */; }; AA3B3DCD2D2D249F0099996A /* VPNLib.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA3B3DA12D2D23860099996A /* VPNLib.framework */; }; AA3B3DCE2D2D249F0099996A /* VPNLib.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AA3B3DA12D2D23860099996A /* VPNLib.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - AA3B3DD22D2D26700099996A /* Swifter in Frameworks */ = {isa = PBXBuildFile; productRef = AA3B3DD12D2D26700099996A /* Swifter */; }; + AA3B3E8E2D2E0FF40099996A /* Mocker in Frameworks */ = {isa = PBXBuildFile; productRef = AA3B3E8D2D2E0FF40099996A /* Mocker */; }; AA8BC3392D0060A900E1ABAA /* ViewInspector in Frameworks */ = {isa = PBXBuildFile; productRef = AA8BC3382D0060A900E1ABAA /* ViewInspector */; }; AA8BC33F2D0061F200E1ABAA /* FluidMenuBarExtra in Frameworks */ = {isa = PBXBuildFile; productRef = AA8BC33E2D0061F200E1ABAA /* FluidMenuBarExtra */; }; AA8BC4CF2D00A4B700E1ABAA /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = AA8BC4CE2D00A4B700E1ABAA /* KeychainAccess */; }; @@ -234,7 +234,7 @@ buildActionMask = 2147483647; files = ( AA3B3DA92D2D23860099996A /* VPNLib.framework in Frameworks */, - AA3B3DD22D2D26700099996A /* Swifter in Frameworks */, + AA3B3E8E2D2E0FF40099996A /* Mocker in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -436,7 +436,7 @@ ); name = VPNLibTests; packageProductDependencies = ( - AA3B3DD12D2D26700099996A /* Swifter */, + AA3B3E8D2D2E0FF40099996A /* Mocker */, ); productName = VPNLibTests; productReference = AA3B3DA82D2D23860099996A /* VPNLibTests.xctest */; @@ -491,7 +491,7 @@ AA8BC4CD2D00A4B700E1ABAA /* XCRemoteSwiftPackageReference "KeychainAccess" */, AAD720CE2D0816B200F6304D /* XCRemoteSwiftPackageReference "Alamofire" */, 961679512CFF207900B2B6DF /* XCRemoteSwiftPackageReference "swift-protobuf" */, - AA03806B2D2CD853002C2285 /* XCRemoteSwiftPackageReference "swifter" */, + AA3B3E8A2D2E0FE10099996A /* XCRemoteSwiftPackageReference "Mocker" */, ); preferredProjectObjectVersion = 77; productRefGroup = 961678FD2CFF100D00B2B6DF /* Products */; @@ -1142,12 +1142,12 @@ version = 1.28.2; }; }; - AA03806B2D2CD853002C2285 /* XCRemoteSwiftPackageReference "swifter" */ = { + AA3B3E8A2D2E0FE10099996A /* XCRemoteSwiftPackageReference "Mocker" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/httpswift/swifter"; + repositoryURL = "https://github.com/WeTransfer/Mocker"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.5.0; + minimumVersion = 3.0.2; }; }; AA8BC3372D00609700E1ABAA /* XCRemoteSwiftPackageReference "ViewInspector" */ = { @@ -1213,10 +1213,10 @@ package = 961679512CFF207900B2B6DF /* XCRemoteSwiftPackageReference "swift-protobuf" */; productName = SwiftProtobufPluginLibrary; }; - AA3B3DD12D2D26700099996A /* Swifter */ = { + AA3B3E8D2D2E0FF40099996A /* Mocker */ = { isa = XCSwiftPackageProductDependency; - package = AA03806B2D2CD853002C2285 /* XCRemoteSwiftPackageReference "swifter" */; - productName = Swifter; + package = AA3B3E8A2D2E0FE10099996A /* XCRemoteSwiftPackageReference "Mocker" */; + productName = Mocker; }; AA8BC3382D0060A900E1ABAA /* ViewInspector */ = { isa = XCSwiftPackageProductDependency; diff --git a/Coder Desktop/Coder Desktop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Coder Desktop/Coder Desktop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9feec2b..ebb3ead 100644 --- a/Coder Desktop/Coder Desktop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Coder Desktop/Coder Desktop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "0dee63153808d20a73f823faee354b1c4e55e5f9258a2831fa6074ed154cd0bc", + "originHash" : "1cd4f7368eeddbaa35ef829e13093bc7081a4e6d3da9492d22db0757464ad473", "pins" : [ { "identity" : "alamofire", @@ -28,21 +28,21 @@ } }, { - "identity" : "swift-protobuf", + "identity" : "mocker", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-protobuf.git", + "location" : "https://github.com/WeTransfer/Mocker", "state" : { - "revision" : "ebc7251dd5b37f627c93698e4374084d98409633", - "version" : "1.28.2" + "revision" : "95fa785c751f6bc40c49e112d433c3acf8417a97", + "version" : "3.0.2" } }, { - "identity" : "swifter", + "identity" : "swift-protobuf", "kind" : "remoteSourceControl", - "location" : "https://github.com/httpswift/swifter", + "location" : "https://github.com/apple/swift-protobuf.git", "state" : { - "revision" : "9483a5d459b45c3ffd059f7b55f9638e268632fd", - "version" : "1.5.0" + "revision" : "ebc7251dd5b37f627c93698e4374084d98409633", + "version" : "1.28.2" } }, { diff --git a/Coder Desktop/VPNLibTests/DownloaderTests.swift b/Coder Desktop/VPNLibTests/DownloaderTests.swift index 3d4ddac..49dd77b 100644 --- a/Coder Desktop/VPNLibTests/DownloaderTests.swift +++ b/Coder Desktop/VPNLibTests/DownloaderTests.swift @@ -1,5 +1,5 @@ import Foundation -import Swifter +import Mocker import Testing @testable import VPNLib @@ -14,18 +14,11 @@ struct DownloaderTests { @Test func downloadFile() async throws { let destinationURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) - - let server = HttpServer() let testData = Data("foo".utf8) - server["/test.txt"] = { _ in - HttpResponse.ok(.data(testData)) - } - - try server.start(freePort()) - defer { server.stop() } + let fileURL = URL(string: "http://example.com/test1.txt")! + Mock(url: fileURL, contentType: .html, statusCode: 200, data: [.get: testData]).register() - let fileURL = try URL(string: "http://localhost:\(server.port())/test.txt")! try await downloader.download(src: fileURL, dest: destinationURL) try #require(FileManager.default.fileExists(atPath: destinationURL.path)) @@ -37,126 +30,46 @@ struct DownloaderTests { @Test func fileNotModified() async throws { - let server = HttpServer() let testData = Data("foo bar".utf8) + let fileURL = URL(string: "http://example.com/test2.txt")! let destinationURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + defer { try? FileManager.default.removeItem(at: destinationURL) } - var notModifiedSent = false - server["/test.txt"] = { req in - let etag = etag(data: testData) - if let ifNoneMatch = req.headers["if-none-match"], ifNoneMatch == etag { - notModifiedSent = true - return .raw(304, "Not Modified", nil, nil) - } else { - return .ok(.data(testData)) - } - } - - try server.start(freePort()) - defer { server.stop() } - - let fileURL = try URL(string: "http://localhost:\(server.port())/test.txt")! - try await downloader.download(src: fileURL, dest: destinationURL) + Mock(url: fileURL, contentType: .html, statusCode: 200, data: [.get: testData]).register() + try await downloader.download(src: fileURL, dest: destinationURL) try #require(FileManager.default.fileExists(atPath: destinationURL.path)) - defer { try? FileManager.default.removeItem(at: destinationURL) } let downloadedData = try Data(contentsOf: destinationURL) #expect(downloadedData == testData) + Mock(url: fileURL, contentType: .html, statusCode: 304, data: [.get: Data()]).register() + try await downloader.download(src: fileURL, dest: destinationURL) - #expect(downloadedData == testData) - #expect(notModifiedSent) + let unchangedData = try Data(contentsOf: destinationURL) + #expect(unchangedData == testData) } @Test func fileUpdated() async throws { - let server = HttpServer() let ogData = Data("foo bar".utf8) let newData = Data("foo bar qux".utf8) + let fileURL = URL(string: "http://example.com/test3.txt")! let destinationURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + defer { try? FileManager.default.removeItem(at: destinationURL) } - server["/test.txt"] = { _ in - .ok(.data(ogData)) - } - - try server.start(freePort()) - defer { server.stop() } - - let fileURL = try URL(string: "http://localhost:\(server.port())/test.txt")! + Mock(url: fileURL, contentType: .html, statusCode: 200, data: [.get: ogData]).register() try await downloader.download(src: fileURL, dest: destinationURL) try #require(FileManager.default.fileExists(atPath: destinationURL.path)) - defer { try? FileManager.default.removeItem(at: destinationURL) } var downloadedData = try Data(contentsOf: destinationURL) #expect(downloadedData == ogData) - server["/test.txt"] = { _ in - .ok(.data(newData)) - } + Mock(url: fileURL, contentType: .html, statusCode: 200, data: [.get: newData]).register() + try await downloader.download(src: fileURL, dest: destinationURL) downloadedData = try Data(contentsOf: destinationURL) #expect(downloadedData == newData) } } - -// From https://stackoverflow.com/questions/65670932/how-to-find-a-free-local-port-using-swift -func freePort() -> UInt16 { - var port: UInt16 = 8000 - - let socketFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) - if socketFD == -1 { - return port - } - - var hints = addrinfo( - ai_flags: AI_PASSIVE, - ai_family: AF_INET, - ai_socktype: SOCK_STREAM, - ai_protocol: 0, - ai_addrlen: 0, - ai_canonname: nil, - ai_addr: nil, - ai_next: nil - ) - - var addressInfo: UnsafeMutablePointer? - var result = getaddrinfo(nil, "0", &hints, &addressInfo) - if result != 0 { - close(socketFD) - return port - } - - result = Darwin.bind(socketFD, addressInfo!.pointee.ai_addr, socklen_t(addressInfo!.pointee.ai_addrlen)) - if result == -1 { - close(socketFD) - return port - } - - result = Darwin.listen(socketFD, 1) - if result == -1 { - close(socketFD) - return port - } - - var addr_in = sockaddr_in() - addr_in.sin_len = UInt8(MemoryLayout.size(ofValue: addr_in)) - addr_in.sin_family = sa_family_t(AF_INET) - - var len = socklen_t(addr_in.sin_len) - result = withUnsafeMutablePointer(to: &addr_in) { - $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { - Darwin.getsockname(socketFD, $0, &len) - } - } - - if result == 0 { - port = addr_in.sin_port - } - - Darwin.shutdown(socketFD, SHUT_RDWR) - close(socketFD) - - return port -} From 8b6036852e163561ee179c8239618ca54caca0b7 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 8 Jan 2025 14:06:29 +1100 Subject: [PATCH 04/17] undo entitlements change --- .../Coder Desktop.xcodeproj/project.pbxproj | 2 +- .../Coder DesktopRelease.entitlements | 16 ---------------- .../Coder Desktop/Coder_Desktop.entitlements | 2 -- 3 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 Coder Desktop/Coder Desktop/Coder DesktopRelease.entitlements diff --git a/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj b/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj index d150fcc..e2cb98e 100644 --- a/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj +++ b/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj @@ -798,7 +798,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = "Coder Desktop/Coder DesktopRelease.entitlements"; + CODE_SIGN_ENTITLEMENTS = "Coder Desktop/Coder_Desktop.entitlements"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; diff --git a/Coder Desktop/Coder Desktop/Coder DesktopRelease.entitlements b/Coder Desktop/Coder Desktop/Coder DesktopRelease.entitlements deleted file mode 100644 index 441279c..0000000 --- a/Coder Desktop/Coder Desktop/Coder DesktopRelease.entitlements +++ /dev/null @@ -1,16 +0,0 @@ - - - - - com.apple.developer.networking.networkextension - - packet-tunnel-provider - - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-only - - com.apple.security.network.client - - - diff --git a/Coder Desktop/Coder Desktop/Coder_Desktop.entitlements b/Coder Desktop/Coder Desktop/Coder_Desktop.entitlements index aa54c78..441279c 100644 --- a/Coder Desktop/Coder Desktop/Coder_Desktop.entitlements +++ b/Coder Desktop/Coder Desktop/Coder_Desktop.entitlements @@ -12,7 +12,5 @@ com.apple.security.network.client - com.apple.security.network.server - From 6398b00928dd6830384066a9269fade4daf528ef Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 8 Jan 2025 14:28:09 +1100 Subject: [PATCH 05/17] fixup tunnel --- Coder Desktop/VPN/TunnelHandle.swift | 33 +++++++++++----------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/Coder Desktop/VPN/TunnelHandle.swift b/Coder Desktop/VPN/TunnelHandle.swift index 4c9f558..5b3275d 100644 --- a/Coder Desktop/VPN/TunnelHandle.swift +++ b/Coder Desktop/VPN/TunnelHandle.swift @@ -6,22 +6,16 @@ let startSymbol = "OpenTunnel" actor TunnelHandle { private let logger = Logger(subsystem: "com.coder.Coder.CoderPacketTunnelProvider", category: "tunnel-handle") - private var openTunnelFn: OpenTunnel! - private var tunnelPipe: Pipe! - private var dylibHandle: UnsafeMutableRawPointer! + private let tunnelWritePipe: Pipe + private let tunnelReadPipe: Pipe + private let dylibHandle: UnsafeMutableRawPointer + + var writeFD: Int32 { tunnelReadPipe.fileHandleForWriting.fileDescriptor } + var readFD: Int32 { tunnelWritePipe.fileHandleForReading.fileDescriptor } init(dylibPath: URL) throws(TunnelHandleError) { dylibHandle = dlopen(dylibPath.path, RTLD_NOW | RTLD_LOCAL) - guard dylibHandle != nil else { - var errStr = "UNKNOWN" - let e = dlerror() - if e != nil { - errStr = String(cString: e!) - } - throw TunnelHandleError.dylib(errStr) - } - let startSym = dlsym(dylibHandle, startSymbol) guard startSym != nil else { var errStr = "UNKNOWN" @@ -29,14 +23,15 @@ actor TunnelHandle { if e != nil { errStr = String(cString: e!) } - throw TunnelHandleError.symbol(startSymbol, errStr) + throw .symbol(startSymbol, errStr) } - openTunnelFn = unsafeBitCast(startSym, to: OpenTunnel.self) - tunnelPipe = Pipe() - let res = openTunnelFn(tunnelPipe.fileHandleForReading.fileDescriptor, - tunnelPipe.fileHandleForWriting.fileDescriptor) + let openTunnelFn = unsafeBitCast(startSym, to: OpenTunnel.self) + tunnelReadPipe = Pipe() + tunnelWritePipe = Pipe() + let res = openTunnelFn(tunnelReadPipe.fileHandleForReading.fileDescriptor, + tunnelWritePipe.fileHandleForWriting.fileDescriptor) guard res == 0 else { - throw TunnelHandleError.openTunnel(OpenTunnelError(rawValue: res) ?? .unknown) + throw .openTunnel(OpenTunnelError(rawValue: res) ?? .unknown) } } @@ -46,13 +41,11 @@ actor TunnelHandle { } enum TunnelHandleError: Error { - case dylib(String) case symbol(String, String) case openTunnel(OpenTunnelError) var description: String { switch self { - case let .dylib(d): return d case let .symbol(symbol, message): return "\(symbol): \(message)" case let .openTunnel(error): return "OpenTunnel: \(error.message)" } From fb36b594220902cb45476effaee5504571d33f9e Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 8 Jan 2025 14:33:35 +1100 Subject: [PATCH 06/17] fixup tunnel --- Coder Desktop/VPN/TunnelHandle.swift | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Coder Desktop/VPN/TunnelHandle.swift b/Coder Desktop/VPN/TunnelHandle.swift index 5b3275d..94852c5 100644 --- a/Coder Desktop/VPN/TunnelHandle.swift +++ b/Coder Desktop/VPN/TunnelHandle.swift @@ -14,10 +14,17 @@ actor TunnelHandle { var readFD: Int32 { tunnelWritePipe.fileHandleForReading.fileDescriptor } init(dylibPath: URL) throws(TunnelHandleError) { - dylibHandle = dlopen(dylibPath.path, RTLD_NOW | RTLD_LOCAL) + guard let dylibHandle = dlopen(dylibPath.path, RTLD_NOW | RTLD_LOCAL) else { + var errStr = "UNKNOWN" + let e = dlerror() + if e != nil { + errStr = String(cString: e!) + } + throw .dylib(errStr) + } + self.dylibHandle = dylibHandle - let startSym = dlsym(dylibHandle, startSymbol) - guard startSym != nil else { + guard let startSym = dlsym(dylibHandle, startSymbol) else { var errStr = "UNKNOWN" let e = dlerror() if e != nil { @@ -41,11 +48,13 @@ actor TunnelHandle { } enum TunnelHandleError: Error { + case dylib(String) case symbol(String, String) case openTunnel(OpenTunnelError) var description: String { switch self { + case let .dylib(d): return d case let .symbol(symbol, message): return "\(symbol): \(message)" case let .openTunnel(error): return "OpenTunnel: \(error.message)" } From 9e1a956c3a6bb54f8a9cd1897372acdd54931b12 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 8 Jan 2025 14:37:19 +1100 Subject: [PATCH 07/17] fixup tunnel --- Coder Desktop/VPN/TunnelHandle.swift | 4 ++-- Coder Desktop/VPNLib/Speaker.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Coder Desktop/VPN/TunnelHandle.swift b/Coder Desktop/VPN/TunnelHandle.swift index 94852c5..4be5d1d 100644 --- a/Coder Desktop/VPN/TunnelHandle.swift +++ b/Coder Desktop/VPN/TunnelHandle.swift @@ -10,8 +10,8 @@ actor TunnelHandle { private let tunnelReadPipe: Pipe private let dylibHandle: UnsafeMutableRawPointer - var writeFD: Int32 { tunnelReadPipe.fileHandleForWriting.fileDescriptor } - var readFD: Int32 { tunnelWritePipe.fileHandleForReading.fileDescriptor } + var writeHandle: FileHandle { tunnelReadPipe.fileHandleForWriting } + var readHandle: FileHandle { tunnelWritePipe.fileHandleForReading } init(dylibPath: URL) throws(TunnelHandleError) { guard let dylibHandle = dlopen(dylibPath.path, RTLD_NOW | RTLD_LOCAL) else { diff --git a/Coder Desktop/VPNLib/Speaker.swift b/Coder Desktop/VPNLib/Speaker.swift index 513b290..5bd9004 100644 --- a/Coder Desktop/VPNLib/Speaker.swift +++ b/Coder Desktop/VPNLib/Speaker.swift @@ -62,7 +62,7 @@ public actor Speaker Date: Wed, 8 Jan 2025 15:24:11 +1100 Subject: [PATCH 08/17] fixup downloader --- Coder Desktop/VPNLib/Downloader.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Coder Desktop/VPNLib/Downloader.swift b/Coder Desktop/VPNLib/Downloader.swift index 6e29054..4bbbf2f 100644 --- a/Coder Desktop/VPNLib/Downloader.swift +++ b/Coder Desktop/VPNLib/Downloader.swift @@ -117,10 +117,7 @@ public actor Downloader { var req = URLRequest(url: src) if FileManager.default.fileExists(atPath: dest.path) { if let existingFileData = try? Data(contentsOf: dest) { - let sha1Hash = Insecure.SHA1.hash(data: existingFileData) - let etag = sha1Hash.map { String(format: "%02x", $0) }.joined() - // ETag header needs to be quoted - req.setValue("\"\(etag)\"", forHTTPHeaderField: "If-None-Match") + req.setValue(etag(data: existingFileData), forHTTPHeaderField: "If-None-Match") } } // TODO: Add Content-Length headers to coderd, add download progress delegate From ce4f0da122de26d49feadc942e94a660887b0e9d Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 8 Jan 2025 15:44:31 +1100 Subject: [PATCH 09/17] flip recv and send --- Coder Desktop/VPN/Manager.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Coder Desktop/VPN/Manager.swift b/Coder Desktop/VPN/Manager.swift index debd0dc..f24549d 100644 --- a/Coder Desktop/VPN/Manager.swift +++ b/Coder Desktop/VPN/Manager.swift @@ -7,11 +7,12 @@ actor Manager { let downloader: Downloader var tunnelHandle: TunnelHandle? - var speaker: Speaker? + var speaker: Speaker? // TODO: XPC Speaker private let dest = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) .first!.appending(path: "coder-vpn.dylib") + private let logger = Logger(subsystem: "com.coder.Coder.CoderPacketTunnelProvider", category: "manager") init(with: PacketTunnelProvider) { ptp = with From 84531704a46980a927ae08a5d2888d3d5c09cfb1 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 8 Jan 2025 18:31:22 +1100 Subject: [PATCH 10/17] remove hardcoded bundle identifiers --- Coder Desktop/VPN/Manager.swift | 2 +- Coder Desktop/VPN/PacketTunnelProvider.swift | 2 +- Coder Desktop/VPN/TunnelHandle.swift | 2 +- Coder Desktop/VPNLib/Speaker.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Coder Desktop/VPN/Manager.swift b/Coder Desktop/VPN/Manager.swift index f24549d..f8a0745 100644 --- a/Coder Desktop/VPN/Manager.swift +++ b/Coder Desktop/VPN/Manager.swift @@ -12,7 +12,7 @@ actor Manager { private let dest = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) .first!.appending(path: "coder-vpn.dylib") - private let logger = Logger(subsystem: "com.coder.Coder.CoderPacketTunnelProvider", category: "manager") + private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "manager") init(with: PacketTunnelProvider) { ptp = with diff --git a/Coder Desktop/VPN/PacketTunnelProvider.swift b/Coder Desktop/VPN/PacketTunnelProvider.swift index 5322bad..c7a671b 100644 --- a/Coder Desktop/VPN/PacketTunnelProvider.swift +++ b/Coder Desktop/VPN/PacketTunnelProvider.swift @@ -5,7 +5,7 @@ import os let CTLIOCGINFO: UInt = 0xC064_4E03 class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { - private let logger = Logger(subsystem: "com.coder.Coder.CoderPacketTunnelProvider", category: "network-extension") + private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "network-extension") private var manager: Manager? private var tunnelFileDescriptor: Int32? { diff --git a/Coder Desktop/VPN/TunnelHandle.swift b/Coder Desktop/VPN/TunnelHandle.swift index 4be5d1d..743aff2 100644 --- a/Coder Desktop/VPN/TunnelHandle.swift +++ b/Coder Desktop/VPN/TunnelHandle.swift @@ -4,7 +4,7 @@ import os let startSymbol = "OpenTunnel" actor TunnelHandle { - private let logger = Logger(subsystem: "com.coder.Coder.CoderPacketTunnelProvider", category: "tunnel-handle") + private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "tunnel-handle") private let tunnelWritePipe: Pipe private let tunnelReadPipe: Pipe diff --git a/Coder Desktop/VPNLib/Speaker.swift b/Coder Desktop/VPNLib/Speaker.swift index 5bd9004..678bf7f 100644 --- a/Coder Desktop/VPNLib/Speaker.swift +++ b/Coder Desktop/VPNLib/Speaker.swift @@ -51,7 +51,7 @@ struct ProtoVersion: CustomStringConvertible, Equatable, Codable { /// An actor that communicates using the VPN protocol public actor Speaker { - private let logger = Logger(subsystem: "com.coder.Coder-Desktop", category: "proto") + private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "proto") private let writeFD: FileHandle private let readFD: FileHandle private let dispatch: DispatchIO From 5fb8cddb6623dc83132275f9a3b7f67c65082c93 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Thu, 9 Jan 2025 14:26:33 +1100 Subject: [PATCH 11/17] review --- .../Coder Desktop.xcodeproj/project.pbxproj | 8 ++++---- Coder Desktop/VPNLib/Downloader.swift | 7 ++++--- Coder Desktop/VPNLibTests/DownloaderTests.swift | 16 ++++++++++++++-- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj b/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj index e2cb98e..db65470 100644 --- a/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj +++ b/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj @@ -984,7 +984,7 @@ MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; - PRODUCT_BUNDLE_IDENTIFIER = coder.VPNLib; + PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).VPNLib"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1020,7 +1020,7 @@ MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; - PRODUCT_BUNDLE_IDENTIFIER = coder.VPNLib; + PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).VPNLib"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1040,7 +1040,7 @@ GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = coder.VPNLibTests; + PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop.VPNLibTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 6.0; @@ -1057,7 +1057,7 @@ GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = coder.VPNLibTests; + PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop.VPNLibTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 6.0; diff --git a/Coder Desktop/VPNLib/Downloader.swift b/Coder Desktop/VPNLib/Downloader.swift index 4bbbf2f..42fe5f6 100644 --- a/Coder Desktop/VPNLib/Downloader.swift +++ b/Coder Desktop/VPNLib/Downloader.swift @@ -107,7 +107,7 @@ public struct SignatureValidator: Validator { } } -public actor Downloader { +public struct Downloader { let validator: Validator public init(validator: Validator = SignatureValidator()) { self.validator = validator @@ -116,14 +116,14 @@ public actor Downloader { public func download(src: URL, dest: URL) async throws { var req = URLRequest(url: src) if FileManager.default.fileExists(atPath: dest.path) { - if let existingFileData = try? Data(contentsOf: dest) { + if let existingFileData = try? Data(contentsOf: dest, options: .mappedIfSafe) { req.setValue(etag(data: existingFileData), forHTTPHeaderField: "If-None-Match") } } // TODO: Add Content-Length headers to coderd, add download progress delegate let (tempURL, response) = try await URLSession.shared.download(for: req) defer { - if FileManager.default.fileExists(atPath: dest.path) { + if FileManager.default.fileExists(atPath: tempURL.path) { do { try FileManager.default.removeItem(at: tempURL) } catch {} } } @@ -132,6 +132,7 @@ public actor Downloader { throw DownloadError.invalidResponse } guard httpResponse.statusCode != 304 else { + // We already have the latest dylib downloaded on disk return } guard (200 ..< 300).contains(httpResponse.statusCode) else { diff --git a/Coder Desktop/VPNLibTests/DownloaderTests.swift b/Coder Desktop/VPNLibTests/DownloaderTests.swift index 49dd77b..0c2b009 100644 --- a/Coder Desktop/VPNLibTests/DownloaderTests.swift +++ b/Coder Desktop/VPNLibTests/DownloaderTests.swift @@ -43,11 +43,17 @@ struct DownloaderTests { let downloadedData = try Data(contentsOf: destinationURL) #expect(downloadedData == testData) - Mock(url: fileURL, contentType: .html, statusCode: 304, data: [.get: Data()]).register() + var mock = Mock(url: fileURL, contentType: .html, statusCode: 304, data: [.get: Data()]) + var etagIncluded = false + mock.onRequestHandler = OnRequestHandler { request in + etagIncluded = request.value(forHTTPHeaderField: "If-None-Match") == etag(data: testData) + } + mock.register() try await downloader.download(src: fileURL, dest: destinationURL) let unchangedData = try Data(contentsOf: destinationURL) #expect(unchangedData == testData) + #expect(etagIncluded) } @Test @@ -66,10 +72,16 @@ struct DownloaderTests { var downloadedData = try Data(contentsOf: destinationURL) #expect(downloadedData == ogData) - Mock(url: fileURL, contentType: .html, statusCode: 200, data: [.get: newData]).register() + var mock = Mock(url: fileURL, contentType: .html, statusCode: 200, data: [.get: newData]) + var etagIncluded = false + mock.onRequestHandler = OnRequestHandler { request in + etagIncluded = request.value(forHTTPHeaderField: "If-None-Match") == etag(data: ogData) + } + mock.register() try await downloader.download(src: fileURL, dest: destinationURL) downloadedData = try Data(contentsOf: destinationURL) #expect(downloadedData == newData) + #expect(etagIncluded) } } From bfb98f09a4dd636a7d6d0141f5063d1f11536ad9 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Thu, 9 Jan 2025 14:52:49 +1100 Subject: [PATCH 12/17] 200 resp only --- Coder Desktop/VPNLib/Downloader.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Coder Desktop/VPNLib/Downloader.swift b/Coder Desktop/VPNLib/Downloader.swift index 42fe5f6..0631096 100644 --- a/Coder Desktop/VPNLib/Downloader.swift +++ b/Coder Desktop/VPNLib/Downloader.swift @@ -135,7 +135,8 @@ public struct Downloader { // We already have the latest dylib downloaded on disk return } - guard (200 ..< 300).contains(httpResponse.statusCode) else { + + guard httpResponse.statusCode == 200 else { throw DownloadError.unexpectedStatusCode(httpResponse.statusCode) } From f48b106aa6795f6f82c9669f8c0860eb0b636039 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Thu, 9 Jan 2025 17:51:24 +1100 Subject: [PATCH 13/17] sendable downloader --- Coder Desktop/VPNLib/Downloader.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Coder Desktop/VPNLib/Downloader.swift b/Coder Desktop/VPNLib/Downloader.swift index 0631096..17fc777 100644 --- a/Coder Desktop/VPNLib/Downloader.swift +++ b/Coder Desktop/VPNLib/Downloader.swift @@ -107,7 +107,7 @@ public struct SignatureValidator: Validator { } } -public struct Downloader { +public struct Downloader: Sendable { let validator: Validator public init(validator: Validator = SignatureValidator()) { self.validator = validator From ae5b3e2ad537441bf3d6ee50a4a1ce6115210a5a Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Thu, 9 Jan 2025 17:54:21 +1100 Subject: [PATCH 14/17] receiver logger name --- Coder Desktop/VPNLib/Receiver.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Coder Desktop/VPNLib/Receiver.swift b/Coder Desktop/VPNLib/Receiver.swift index 2797bad..456114f 100644 --- a/Coder Desktop/VPNLib/Receiver.swift +++ b/Coder Desktop/VPNLib/Receiver.swift @@ -7,7 +7,7 @@ actor Receiver { private let dispatch: DispatchIO private let queue: DispatchQueue private var running = false - private let logger = Logger(subsystem: "com.coder.Coder-Desktop", category: "proto") + private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "proto") /// Creates an instance using the given `DispatchIO` channel and queue. init(dispatch: DispatchIO, queue: DispatchQueue) { From 4eac98bc63a7401683a121c8b82e72979a7c1bfa Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Thu, 9 Jan 2025 19:40:07 +1100 Subject: [PATCH 15/17] localizederror -> error --- Coder Desktop/VPNLib/Downloader.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Coder Desktop/VPNLib/Downloader.swift b/Coder Desktop/VPNLib/Downloader.swift index 17fc777..6708148 100644 --- a/Coder Desktop/VPNLib/Downloader.swift +++ b/Coder Desktop/VPNLib/Downloader.swift @@ -5,7 +5,7 @@ public protocol Validator: Sendable { func validate(path: URL) async throws } -public enum ValidationError: LocalizedError { +public enum ValidationError: Error { case fileNotFound case unableToCreateStaticCode case invalidSignature @@ -154,7 +154,7 @@ func etag(data: Data) -> String { return "\"\(etag)\"" } -enum DownloadError: LocalizedError { +enum DownloadError: Error { case unexpectedStatusCode(Int) case invalidResponse From 844df27d19decb1728cc52fb6a7c603a3c7c4f68 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Fri, 10 Jan 2025 14:53:27 +1100 Subject: [PATCH 16/17] download and validator to functions --- Coder Desktop/VPN/Manager.swift | 2 - .../{Downloader.swift => Download.swift} | 116 +++++++++--------- ...nloaderTests.swift => DownloadTests.swift} | 20 ++- 3 files changed, 66 insertions(+), 72 deletions(-) rename Coder Desktop/VPNLib/{Downloader.swift => Download.swift} (55%) rename Coder Desktop/VPNLibTests/{DownloaderTests.swift => DownloadTests.swift} (84%) diff --git a/Coder Desktop/VPN/Manager.swift b/Coder Desktop/VPN/Manager.swift index f8a0745..86a17af 100644 --- a/Coder Desktop/VPN/Manager.swift +++ b/Coder Desktop/VPN/Manager.swift @@ -4,7 +4,6 @@ import VPNLib actor Manager { let ptp: PacketTunnelProvider - let downloader: Downloader var tunnelHandle: TunnelHandle? var speaker: Speaker? @@ -16,6 +15,5 @@ actor Manager { init(with: PacketTunnelProvider) { ptp = with - downloader = Downloader() } } diff --git a/Coder Desktop/VPNLib/Downloader.swift b/Coder Desktop/VPNLib/Download.swift similarity index 55% rename from Coder Desktop/VPNLib/Downloader.swift rename to Coder Desktop/VPNLib/Download.swift index 6708148..729ba05 100644 --- a/Coder Desktop/VPNLib/Downloader.swift +++ b/Coder Desktop/VPNLib/Download.swift @@ -1,10 +1,6 @@ import CryptoKit import Foundation -public protocol Validator: Sendable { - func validate(path: URL) async throws -} - public enum ValidationError: Error { case fileNotFound case unableToCreateStaticCode @@ -37,114 +33,114 @@ public enum ValidationError: Error { } } -public struct SignatureValidator: Validator { - private let expectedName = "CoderVPN" - private let expectedIdentifier = "com.coder.Coder-Desktop.VPN.dylib" - private let expectedTeamIdentifier = "4399GN35BJ" - private let minDylibVersion = "2.18.1" +public class SignatureValidator { + private static let expectedName = "CoderVPN" + private static let expectedIdentifier = "com.coder.Coder-Desktop.VPN.dylib" + private static let expectedTeamIdentifier = "4399GN35BJ" + private static let minDylibVersion = "2.18.1" - private let infoIdentifierKey = "CFBundleIdentifier" - private let infoNameKey = "CFBundleName" - private let infoShortVersionKey = "CFBundleShortVersionString" + private static let infoIdentifierKey = "CFBundleIdentifier" + private static let infoNameKey = "CFBundleName" + private static let infoShortVersionKey = "CFBundleShortVersionString" - private let signInfoFlags: SecCSFlags = .init(rawValue: kSecCSSigningInformation) + private static let signInfoFlags: SecCSFlags = .init(rawValue: kSecCSSigningInformation) - public init() {} - - public func validate(path: URL) throws { + public static func validate(path: URL) throws(ValidationError) { guard FileManager.default.fileExists(atPath: path.path) else { - throw ValidationError.fileNotFound + throw .fileNotFound } var staticCode: SecStaticCode? let status = SecStaticCodeCreateWithPath(path as CFURL, SecCSFlags(), &staticCode) guard status == errSecSuccess, let code = staticCode else { - throw ValidationError.unableToCreateStaticCode + throw .unableToCreateStaticCode } let validateStatus = SecStaticCodeCheckValidity(code, SecCSFlags(), nil) guard validateStatus == errSecSuccess else { - throw ValidationError.invalidSignature + throw .invalidSignature } var information: CFDictionary? let infoStatus = SecCodeCopySigningInformation(code, signInfoFlags, &information) guard infoStatus == errSecSuccess, let info = information as? [String: Any] else { - throw ValidationError.unableToRetrieveInfo + throw .unableToRetrieveInfo } guard let identifier = info[kSecCodeInfoIdentifier as String] as? String, identifier == expectedIdentifier else { - throw ValidationError.invalidIdentifier(identifier: info[kSecCodeInfoIdentifier as String] as? String) + throw .invalidIdentifier(identifier: info[kSecCodeInfoIdentifier as String] as? String) } guard let teamIdentifier = info[kSecCodeInfoTeamIdentifier as String] as? String, teamIdentifier == expectedTeamIdentifier else { - throw ValidationError.invalidTeamIdentifier( + throw .invalidTeamIdentifier( identifier: info[kSecCodeInfoTeamIdentifier as String] as? String ) } guard let infoPlist = info[kSecCodeInfoPList as String] as? [String: AnyObject] else { - throw ValidationError.missingInfoPList + throw .missingInfoPList } guard let plistIdent = infoPlist[infoIdentifierKey] as? String, plistIdent == expectedIdentifier else { - throw ValidationError.invalidIdentifier(identifier: infoPlist[infoIdentifierKey] as? String) + throw .invalidIdentifier(identifier: infoPlist[infoIdentifierKey] as? String) } guard let plistName = infoPlist[infoNameKey] as? String, plistName == expectedName else { - throw ValidationError.invalidIdentifier(identifier: infoPlist[infoNameKey] as? String) + throw .invalidIdentifier(identifier: infoPlist[infoNameKey] as? String) } guard let dylibVersion = infoPlist[infoShortVersionKey] as? String, minDylibVersion.compare(dylibVersion, options: .numeric) != .orderedDescending else { - throw ValidationError.invalidVersion(version: infoPlist[infoShortVersionKey] as? String) + throw .invalidVersion(version: infoPlist[infoShortVersionKey] as? String) } } } -public struct Downloader: Sendable { - let validator: Validator - public init(validator: Validator = SignatureValidator()) { - self.validator = validator - } - - public func download(src: URL, dest: URL) async throws { - var req = URLRequest(url: src) - if FileManager.default.fileExists(atPath: dest.path) { - if let existingFileData = try? Data(contentsOf: dest, options: .mappedIfSafe) { - req.setValue(etag(data: existingFileData), forHTTPHeaderField: "If-None-Match") - } +public func download(src: URL, dest: URL) async throws(DownloadError) { + var req = URLRequest(url: src) + if FileManager.default.fileExists(atPath: dest.path) { + if let existingFileData = try? Data(contentsOf: dest, options: .mappedIfSafe) { + req.setValue(etag(data: existingFileData), forHTTPHeaderField: "If-None-Match") } - // TODO: Add Content-Length headers to coderd, add download progress delegate - let (tempURL, response) = try await URLSession.shared.download(for: req) - defer { - if FileManager.default.fileExists(atPath: tempURL.path) { - do { try FileManager.default.removeItem(at: tempURL) } catch {} - } + } + // TODO: Add Content-Length headers to coderd, add download progress delegate + let tempURL: URL + let response: URLResponse + do { + (tempURL, response) = try await URLSession.shared.download(for: req) + } catch { + throw .networkError(error) + } + defer { + if FileManager.default.fileExists(atPath: tempURL.path) { + try? FileManager.default.removeItem(at: tempURL) } + } - guard let httpResponse = response as? HTTPURLResponse else { - throw DownloadError.invalidResponse - } - guard httpResponse.statusCode != 304 else { - // We already have the latest dylib downloaded on disk - return - } + guard let httpResponse = response as? HTTPURLResponse else { + throw .invalidResponse + } + guard httpResponse.statusCode != 304 else { + // We already have the latest dylib downloaded on disk + return + } - guard httpResponse.statusCode == 200 else { - throw DownloadError.unexpectedStatusCode(httpResponse.statusCode) - } + guard httpResponse.statusCode == 200 else { + throw .unexpectedStatusCode(httpResponse.statusCode) + } + do { if FileManager.default.fileExists(atPath: dest.path) { try FileManager.default.removeItem(at: dest) } try FileManager.default.moveItem(at: tempURL, to: dest) - try await validator.validate(path: dest) + } catch { + throw .fileOpError(error) } } @@ -154,14 +150,20 @@ func etag(data: Data) -> String { return "\"\(etag)\"" } -enum DownloadError: Error { +public enum DownloadError: Error { case unexpectedStatusCode(Int) case invalidResponse + case networkError(any Error) + case fileOpError(any Error) var localizedDescription: String { switch self { case let .unexpectedStatusCode(code): - return "Unexpected status code: \(code)" + return "Unexpected HTTP status code: \(code)" + case let .networkError(error): + return "Network error: \(error.localizedDescription)" + case let .fileOpError(error): + return "File operation error: \(error.localizedDescription)" case .invalidResponse: return "Received non-HTTP response" } diff --git a/Coder Desktop/VPNLibTests/DownloaderTests.swift b/Coder Desktop/VPNLibTests/DownloadTests.swift similarity index 84% rename from Coder Desktop/VPNLibTests/DownloaderTests.swift rename to Coder Desktop/VPNLibTests/DownloadTests.swift index 0c2b009..357575b 100644 --- a/Coder Desktop/VPNLibTests/DownloaderTests.swift +++ b/Coder Desktop/VPNLibTests/DownloadTests.swift @@ -3,14 +3,8 @@ import Mocker import Testing @testable import VPNLib -struct NoopValidator: Validator { - func validate(path _: URL) async throws {} -} - -@Suite -struct DownloaderTests { - let downloader = Downloader(validator: NoopValidator()) - +@Suite(.timeLimit(.minutes(1))) +struct DownloadTests { @Test func downloadFile() async throws { let destinationURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) @@ -19,7 +13,7 @@ struct DownloaderTests { let fileURL = URL(string: "http://example.com/test1.txt")! Mock(url: fileURL, contentType: .html, statusCode: 200, data: [.get: testData]).register() - try await downloader.download(src: fileURL, dest: destinationURL) + try await download(src: fileURL, dest: destinationURL) try #require(FileManager.default.fileExists(atPath: destinationURL.path)) defer { try? FileManager.default.removeItem(at: destinationURL) } @@ -38,7 +32,7 @@ struct DownloaderTests { Mock(url: fileURL, contentType: .html, statusCode: 200, data: [.get: testData]).register() - try await downloader.download(src: fileURL, dest: destinationURL) + try await download(src: fileURL, dest: destinationURL) try #require(FileManager.default.fileExists(atPath: destinationURL.path)) let downloadedData = try Data(contentsOf: destinationURL) #expect(downloadedData == testData) @@ -50,7 +44,7 @@ struct DownloaderTests { } mock.register() - try await downloader.download(src: fileURL, dest: destinationURL) + try await download(src: fileURL, dest: destinationURL) let unchangedData = try Data(contentsOf: destinationURL) #expect(unchangedData == testData) #expect(etagIncluded) @@ -67,7 +61,7 @@ struct DownloaderTests { Mock(url: fileURL, contentType: .html, statusCode: 200, data: [.get: ogData]).register() - try await downloader.download(src: fileURL, dest: destinationURL) + try await download(src: fileURL, dest: destinationURL) try #require(FileManager.default.fileExists(atPath: destinationURL.path)) var downloadedData = try Data(contentsOf: destinationURL) #expect(downloadedData == ogData) @@ -79,7 +73,7 @@ struct DownloaderTests { } mock.register() - try await downloader.download(src: fileURL, dest: destinationURL) + try await download(src: fileURL, dest: destinationURL) downloadedData = try Data(contentsOf: destinationURL) #expect(downloadedData == newData) #expect(etagIncluded) From 4d0b3da727c0270262f202a5b603a41e3137dc0d Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Fri, 10 Jan 2025 15:54:12 +1100 Subject: [PATCH 17/17] improve tunnelhandle --- Coder Desktop/VPN/TunnelHandle.swift | 39 ++++++++++++++++++---------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/Coder Desktop/VPN/TunnelHandle.swift b/Coder Desktop/VPN/TunnelHandle.swift index 743aff2..4258e1b 100644 --- a/Coder Desktop/VPN/TunnelHandle.swift +++ b/Coder Desktop/VPN/TunnelHandle.swift @@ -15,22 +15,12 @@ actor TunnelHandle { init(dylibPath: URL) throws(TunnelHandleError) { guard let dylibHandle = dlopen(dylibPath.path, RTLD_NOW | RTLD_LOCAL) else { - var errStr = "UNKNOWN" - let e = dlerror() - if e != nil { - errStr = String(cString: e!) - } - throw .dylib(errStr) + throw .dylib(dlerror().flatMap { String(cString: $0) } ?? "UNKNOWN") } self.dylibHandle = dylibHandle guard let startSym = dlsym(dylibHandle, startSymbol) else { - var errStr = "UNKNOWN" - let e = dlerror() - if e != nil { - errStr = String(cString: e!) - } - throw .symbol(startSymbol, errStr) + throw .symbol(startSymbol, dlerror().flatMap { String(cString: $0) } ?? "UNKNOWN") } let openTunnelFn = unsafeBitCast(startSym, to: OpenTunnel.self) tunnelReadPipe = Pipe() @@ -42,8 +32,25 @@ actor TunnelHandle { } } - func close() throws { - dlclose(dylibHandle) + // This could be an isolated deinit in Swift 6.1 + func close() throws(TunnelHandleError) { + var errs: [Error] = [] + if dlclose(dylibHandle) == 0 { + errs.append(TunnelHandleError.dylib(dlerror().flatMap { String(cString: $0) } ?? "UNKNOWN")) + } + do { + try writeHandle.close() + } catch { + errs.append(error) + } + do { + try readHandle.close() + } catch { + errs.append(error) + } + if !errs.isEmpty { + throw .close(errs) + } } } @@ -51,12 +58,16 @@ enum TunnelHandleError: Error { case dylib(String) case symbol(String, String) case openTunnel(OpenTunnelError) + case pipe(any Error) + case close([any Error]) var description: String { switch self { + case let .pipe(err): return "pipe error: \(err)" case let .dylib(d): return d case let .symbol(symbol, message): return "\(symbol): \(message)" case let .openTunnel(error): return "OpenTunnel: \(error.message)" + case let .close(errs): return "close tunnel: \(errs.map(\.localizedDescription).joined(separator: ", "))" } } }