Skip to content

Commit

Permalink
Merge branch 'IOS-881-shadowsocks-obfuscation-settings'
Browse files Browse the repository at this point in the history
  • Loading branch information
buggmagnet committed Nov 28, 2024
2 parents b1b697f + 85b91d0 commit c0667d4
Show file tree
Hide file tree
Showing 10 changed files with 492 additions and 57 deletions.
24 changes: 16 additions & 8 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@
06799AFC28F98EE300ACD94E /* AddressCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AC114128F8413A0037AF9A /* AddressCache.swift */; };
0697D6E728F01513007A9E99 /* TransportMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0697D6E628F01513007A9E99 /* TransportMonitor.swift */; };
06AC116228F94C450037AF9A /* ApplicationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */; };
44075DFB2CDA4F7400F61139 /* UDPTCPObfuscationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44075DFA2CDA4F7400F61139 /* UDPTCPObfuscationSettingsViewModel.swift */; };
44075DFB2CDA4F7400F61139 /* UDPOverTCPObfuscationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44075DFA2CDA4F7400F61139 /* UDPOverTCPObfuscationSettingsViewModel.swift */; };
440E5AB02CDBD67D00B09614 /* StatefulPreviewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 440E5AAF2CDBD67D00B09614 /* StatefulPreviewWrapper.swift */; };
440E5AB42CDCF24500B09614 /* TunnelObfuscationSettingsWatchingObservableObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 440E5AB32CDCF24500B09614 /* TunnelObfuscationSettingsWatchingObservableObject.swift */; };
4422C0712CCFF6790001A385 /* UDPTCPObfuscationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4422C0702CCFF6790001A385 /* UDPTCPObfuscationSettingsView.swift */; };
4422C0712CCFF6790001A385 /* UDPOverTCPObfuscationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4422C0702CCFF6790001A385 /* UDPOverTCPObfuscationSettingsView.swift */; };
4424CDD32CDBD4A6009D8C9F /* SingleChoiceList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4424CDD22CDBD4A6009D8C9F /* SingleChoiceList.swift */; };
447F3D8A2CDE1853006E3462 /* ShadowsocksObfuscationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 447F3D882CDE1852006E3462 /* ShadowsocksObfuscationSettingsViewModel.swift */; };
447F3D8B2CDE1853006E3462 /* ShadowsocksObfuscationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 447F3D892CDE1853006E3462 /* ShadowsocksObfuscationSettingsView.swift */; };
449275422C3570CA000526DE /* ICMP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449275412C3570CA000526DE /* ICMP.swift */; };
449872E12B7BBC5400094DDC /* TunnelSettingsUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449872E02B7BBC5400094DDC /* TunnelSettingsUpdate.swift */; };
449872E42B7CB96300094DDC /* TunnelSettingsUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449872E32B7CB96300094DDC /* TunnelSettingsUpdateTests.swift */; };
Expand Down Expand Up @@ -1392,11 +1394,13 @@
06FAE67A28F83CA50033DD93 /* RESTDevicesProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTDevicesProxy.swift; sourceTree = "<group>"; };
06FAE67B28F83CA50033DD93 /* REST.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = REST.swift; sourceTree = "<group>"; };
06FAE67D28F83CA50033DD93 /* RESTTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTTransport.swift; sourceTree = "<group>"; };
44075DFA2CDA4F7400F61139 /* UDPTCPObfuscationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPTCPObfuscationSettingsViewModel.swift; sourceTree = "<group>"; };
44075DFA2CDA4F7400F61139 /* UDPOverTCPObfuscationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPOverTCPObfuscationSettingsViewModel.swift; sourceTree = "<group>"; };
440E5AAF2CDBD67D00B09614 /* StatefulPreviewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatefulPreviewWrapper.swift; sourceTree = "<group>"; };
440E5AB32CDCF24500B09614 /* TunnelObfuscationSettingsWatchingObservableObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelObfuscationSettingsWatchingObservableObject.swift; sourceTree = "<group>"; };
4422C0702CCFF6790001A385 /* UDPTCPObfuscationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPTCPObfuscationSettingsView.swift; sourceTree = "<group>"; };
4422C0702CCFF6790001A385 /* UDPOverTCPObfuscationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPOverTCPObfuscationSettingsView.swift; sourceTree = "<group>"; };
4424CDD22CDBD4A6009D8C9F /* SingleChoiceList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleChoiceList.swift; sourceTree = "<group>"; };
447F3D882CDE1852006E3462 /* ShadowsocksObfuscationSettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowsocksObfuscationSettingsViewModel.swift; sourceTree = "<group>"; };
447F3D892CDE1853006E3462 /* ShadowsocksObfuscationSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowsocksObfuscationSettingsView.swift; sourceTree = "<group>"; };
449275412C3570CA000526DE /* ICMP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ICMP.swift; sourceTree = "<group>"; };
449275432C3C3029000526DE /* TunnelPinger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelPinger.swift; sourceTree = "<group>"; };
449872E02B7BBC5400094DDC /* TunnelSettingsUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsUpdate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2602,9 +2606,11 @@
4422C06F2CCFF6520001A385 /* Obfuscation */ = {
isa = PBXGroup;
children = (
447F3D892CDE1853006E3462 /* ShadowsocksObfuscationSettingsView.swift */,
447F3D882CDE1852006E3462 /* ShadowsocksObfuscationSettingsViewModel.swift */,
440E5AB32CDCF24500B09614 /* TunnelObfuscationSettingsWatchingObservableObject.swift */,
4422C0702CCFF6790001A385 /* UDPTCPObfuscationSettingsView.swift */,
44075DFA2CDA4F7400F61139 /* UDPTCPObfuscationSettingsViewModel.swift */,
4422C0702CCFF6790001A385 /* UDPOverTCPObfuscationSettingsView.swift */,
44075DFA2CDA4F7400F61139 /* UDPOverTCPObfuscationSettingsViewModel.swift */,
);
path = Obfuscation;
sourceTree = "<group>";
Expand Down Expand Up @@ -5662,9 +5668,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
44075DFB2CDA4F7400F61139 /* UDPTCPObfuscationSettingsViewModel.swift in Sources */,
44075DFB2CDA4F7400F61139 /* UDPOverTCPObfuscationSettingsViewModel.swift in Sources */,
7A6389DC2B7E3BD6008E77E1 /* CustomListViewModel.swift in Sources */,
4422C0712CCFF6790001A385 /* UDPTCPObfuscationSettingsView.swift in Sources */,
4422C0712CCFF6790001A385 /* UDPOverTCPObfuscationSettingsView.swift in Sources */,
7A9CCCC42A96302800DD6A34 /* TunnelCoordinator.swift in Sources */,
5827B0A42B0F38FD00CCBBA1 /* EditAccessMethodInteractorProtocol.swift in Sources */,
586C0D852B03D31E00E7CDD7 /* SocksSectionHandler.swift in Sources */,
Expand Down Expand Up @@ -5747,6 +5753,7 @@
58B26E22294351EA00D5980C /* InAppNotificationProvider.swift in Sources */,
5893716A28817A45004EE76C /* DeviceManagementViewController.swift in Sources */,
7A9CCCB82A96302800DD6A34 /* SetupAccountCompletedCoordinator.swift in Sources */,
447F3D8B2CDE1853006E3462 /* ShadowsocksObfuscationSettingsView.swift in Sources */,
58BFA5C622A7C97F00A6173D /* RelayCacheTracker.swift in Sources */,
7A0B311E2B303A0D004B12E0 /* AccessbilityIdentifier.swift in Sources */,
E158B360285381C60002F069 /* String+AccountFormatting.swift in Sources */,
Expand Down Expand Up @@ -5889,6 +5896,7 @@
F02F41A22B9723AF00625A4F /* AddLocationsCoordinator.swift in Sources */,
7A27E3CD2CB814EF0088BCFF /* DAITAInfoView.swift in Sources */,
F028A56A2A34D4E700C0CAA3 /* RedeemVoucherViewController.swift in Sources */,
447F3D8A2CDE1853006E3462 /* ShadowsocksObfuscationSettingsViewModel.swift in Sources */,
7A5869C52B5A899C00640D27 /* MethodSettingsCellConfiguration.swift in Sources */,
58E11188292FA11F009FCA84 /* SettingsMigrationUIHandler.swift in Sources */,
58CAFA002983FF0200BE19F7 /* LoginInteractor.swift in Sources */,
Expand Down
9 changes: 6 additions & 3 deletions ios/MullvadVPN/UI appearance/UIColor+Palette.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,21 @@ extension UIColor {

enum TextField {
static let placeholderTextColor = UIColor(red: 0.16, green: 0.30, blue: 0.45, alpha: 0.40)
static let inactivePlaceholderTextColor = UIColor(white: 1.0, alpha: 0.4)
static let textColor = UIColor(red: 0.16, green: 0.30, blue: 0.45, alpha: 1.0)
static let inactiveTextColor = UIColor.white
static let backgroundColor = UIColor.white
static let inactiveBackgroundColor = UIColor(white: 1.0, alpha: 0.1)
static let invalidInputTextColor = UIColor.dangerColor
}

enum SearchTextField {
static let placeholderTextColor = TextField.placeholderTextColor
static let inactivePlaceholderTextColor = UIColor(white: 1.0, alpha: 0.4)
static let inactivePlaceholderTextColor = TextField.inactivePlaceholderTextColor
static let textColor = TextField.textColor
static let inactiveTextColor = UIColor.white
static let inactiveTextColor = TextField.inactiveTextColor
static let backgroundColor = TextField.backgroundColor
static let inactiveBackgroundColor = UIColor(white: 1.0, alpha: 0.1)
static let inactiveBackgroundColor = TextField.inactiveBackgroundColor
static let leftViewTintColor = UIColor.primaryColor
static let inactiveLeftViewTintColor = UIColor.white
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// ShadowsocksObfuscationSettingsView.swift
// MullvadVPN
//
// Created by Andrew Bulhak on 2024-11-07.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import MullvadSettings
import SwiftUI

struct ShadowsocksObfuscationSettingsView<VM>: View where VM: ShadowsocksObfuscationSettingsViewModel {
@StateObject var viewModel: VM

var body: some View {
let portString = NSLocalizedString(
"SHADOWSOCKS_PORT_LABEL",
tableName: "Shadowsocks",
value: "Port",
comment: ""
)

SingleChoiceList(
title: portString,
options: [WireGuardObfuscationShadowsockPort.automatic],
value: $viewModel.value,
itemDescription: { item in NSLocalizedString(
"SHADOWSOCKS_PORT_VALUE_\(item)",
tableName: "Shadowsocks",
value: "\(item)",
comment: ""
) },
parseCustomValue: { UInt16($0).flatMap { $0 > 0 ? WireGuardObfuscationShadowsockPort.custom($0) : nil }
},
formatCustomValue: {
if case let .custom(port) = $0 {
"\(port)"
} else {
nil
}
},
customLabel: NSLocalizedString(
"SHADOWSOCKS_PORT_VALUE_CUSTOM",
tableName: "Shadowsocks",
value: "Custom",
comment: ""
),
customPrompt: NSLocalizedString(
"SHADOWSOCKS_PORT_VALUE_PORT_PROMPT",
tableName: "Shadowsocks",
value: "Port",
comment: ""
),
customLegend: NSLocalizedString(
"SHADOWSOCKS_PORT_VALUE_PORT_LEGEND",
tableName: "Shadowsocks",
value: "Valid range: 1 - 65535",
comment: ""
),
customInputMinWidth: 100,
customInputMaxLength: 5,
customFieldMode: .numericText
).onDisappear {
viewModel.commit()
}
}
}

#Preview {
let model = MockShadowsocksObfuscationSettingsViewModel(shadowsocksPort: .automatic)
return ShadowsocksObfuscationSettingsView(viewModel: model)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// ShadowsocksObfuscationSettingsViewModel.swift
// MullvadVPN
//
// Created by Andrew Bulhak on 2024-11-07.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadSettings

protocol ShadowsocksObfuscationSettingsViewModel: ObservableObject {
var value: WireGuardObfuscationShadowsockPort { get set }

func commit()
}

/** A simple mock view model for use in Previews and similar */
class MockShadowsocksObfuscationSettingsViewModel: ShadowsocksObfuscationSettingsViewModel {
@Published var value: WireGuardObfuscationShadowsockPort

init(shadowsocksPort: WireGuardObfuscationShadowsockPort = .automatic) {
self.value = shadowsocksPort
}

func commit() {}
}

/// ** The live view model which interfaces with the TunnelManager */
class TunnelShadowsocksObfuscationSettingsViewModel: TunnelObfuscationSettingsWatchingObservableObject<
WireGuardObfuscationShadowsockPort
>,
ShadowsocksObfuscationSettingsViewModel {
init(tunnelManager: TunnelManager) {
super.init(
tunnelManager: tunnelManager,
keyPath: \.shadowsocksPort
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,12 @@ class TunnelObfuscationSettingsWatchingObservableObject<T: Equatable>: Observabl
let keyPath: WritableKeyPath<WireGuardObfuscationSettings, T>
private var tunnelObserver: TunnelObserver?

// this is essentially @Published from scratch
var value: T {
willSet(newValue) {
guard newValue != self.value else { return }
objectWillChange.send()
var obfuscationSettings = tunnelManager.settings.wireGuardObfuscation
obfuscationSettings[keyPath: keyPath] = newValue
tunnelManager.updateSettings([.obfuscation(obfuscationSettings)])
}
}
@Published var value: T

init(tunnelManager: TunnelManager, keyPath: WritableKeyPath<WireGuardObfuscationSettings, T>, _ initialValue: T) {
init(tunnelManager: TunnelManager, keyPath: WritableKeyPath<WireGuardObfuscationSettings, T>) {
self.tunnelManager = tunnelManager
self.keyPath = keyPath
self.value = initialValue
self.value = tunnelManager.settings.wireGuardObfuscation[keyPath: keyPath]
tunnelObserver =
TunnelBlockObserver(didUpdateTunnelSettings: { [weak self] _, newSettings in
guard let self else { return }
Expand All @@ -45,4 +36,11 @@ class TunnelObfuscationSettingsWatchingObservableObject<T: Equatable>: Observabl
value = newValue
}
}

// Commit the temporarily stored value upstream
func commit() {
var obfuscationSettings = tunnelManager.settings.wireGuardObfuscation
obfuscationSettings[keyPath: keyPath] = value
tunnelManager.updateSettings([.obfuscation(obfuscationSettings)])
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// UDPTCPObfuscationSettingsView.swift
// UDPOverTCPObfuscationSettingsView.swift
// MullvadVPN
//
// Created by Andrew Bulhak on 2024-10-28.
Expand All @@ -9,7 +9,7 @@
import MullvadSettings
import SwiftUI

struct UDPTCPObfuscationSettingsView<VM>: View where VM: UDPTCPObfuscationSettingsViewModel {
struct UDPOverTCPObfuscationSettingsView<VM>: View where VM: UDPOverTCPObfuscationSettingsViewModel {
@StateObject var viewModel: VM

var body: some View {
Expand All @@ -29,11 +29,13 @@ struct UDPTCPObfuscationSettingsView<VM>: View where VM: UDPTCPObfuscationSettin
value: "\(item)",
comment: ""
) }
)
).onDisappear {
viewModel.commit()
}
}
}

#Preview {
let model = MockUDPTCPObfuscationSettingsViewModel(udpTcpPort: .port5001)
return UDPTCPObfuscationSettingsView(viewModel: model)
let model = MockUDPOverTCPObfuscationSettingsViewModel(udpTcpPort: .port5001)
return UDPOverTCPObfuscationSettingsView(viewModel: model)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// UDPTCPObfuscationSettingsViewModel.swift
// UDPOverTCPObfuscationSettingsViewModel.swift
// MullvadVPN
//
// Created by Andrew Bulhak on 2024-11-05.
Expand All @@ -9,29 +9,32 @@
import Foundation
import MullvadSettings

protocol UDPTCPObfuscationSettingsViewModel: ObservableObject {
protocol UDPOverTCPObfuscationSettingsViewModel: ObservableObject {
var value: WireGuardObfuscationUdpOverTcpPort { get set }

func commit()
}

/** A simple mock view model for use in Previews and similar */
class MockUDPTCPObfuscationSettingsViewModel: UDPTCPObfuscationSettingsViewModel {
class MockUDPOverTCPObfuscationSettingsViewModel: UDPOverTCPObfuscationSettingsViewModel {
@Published var value: WireGuardObfuscationUdpOverTcpPort

init(udpTcpPort: WireGuardObfuscationUdpOverTcpPort = .automatic) {
self.value = udpTcpPort
}

func commit() {}
}

/** The live view model which interfaces with the TunnelManager */
class TunnelUDPTCPObfuscationSettingsViewModel: TunnelObfuscationSettingsWatchingObservableObject<
class TunnelUDPOverTCPObfuscationSettingsViewModel: TunnelObfuscationSettingsWatchingObservableObject<
WireGuardObfuscationUdpOverTcpPort
>,
UDPTCPObfuscationSettingsViewModel {
UDPOverTCPObfuscationSettingsViewModel {
init(tunnelManager: TunnelManager) {
super.init(
tunnelManager: tunnelManager,
keyPath: \.udpOverTcpPort,
tunnelManager.settings.wireGuardObfuscation.udpOverTcpPort
keyPath: \.udpOverTcpPort
)
}
}
Loading

0 comments on commit c0667d4

Please sign in to comment.