Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update data structure to support new obfuscation selection #7064

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 132 additions & 28 deletions ios/MullvadSettings/WireGuardObfuscationSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,158 @@

import Foundation

/// Whether UDP-over-TCP obfuscation is enabled
/// Whether obfuscation is enabled and which method is used
///
/// `.automatic` means an algorithm will decide whether to use it or not.
public enum WireGuardObfuscationState: Codable {
case automatic
@available(*, deprecated, renamed: "udpOverTcp")
case on

case automatic
case udpOverTcp
case shadowsocks
case off

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

var allKeys = ArraySlice(container.allKeys)
guard let key = allKeys.popFirst(), allKeys.isEmpty else {
throw DecodingError.typeMismatch(
WireGuardObfuscationState.self,
DecodingError.Context(
codingPath: container.codingPath,
debugDescription: "Invalid number of keys found, expected one.",
underlyingError: nil
)
)
}

switch key {
case .automatic:
self = .automatic
case .on, .udpOverTcp:
self = .udpOverTcp
case .shadowsocks:
self = .shadowsocks
case .off:
self = .off
}
}
}

/// The port to select when using UDP-over-TCP obfuscation
///
/// `.automatic` means an algorith will decide between using `port80` or `port5001`
public enum WireGuardObfuscationPort: UInt16, Codable, CaseIterable {
case automatic = 0
case port80 = 80
case port5001 = 5001
public enum WireGuardObfuscationUdpOverTcpPort: Codable, Equatable, CustomStringConvertible {
case automatic
case port80
case port5001

/// The `UInt16` representation of the port.
/// - Returns: `0` if `.automatic`, `80` or `5001` otherwise.
public var portValue: UInt16 {
self == .automatic ? 0 : rawValue
public var portValue: UInt16? {
switch self {
case .automatic:
nil
case .port80:
80
case .port5001:
5001
}
}

public init?(rawValue: UInt16) {
switch rawValue {
case 80:
self = .port80
case 5001:
self = .port5001
default: self = .automatic
public var description: String {
switch self {
case .automatic:
NSLocalizedString(
"WIREGUARD_OBFUSCATION_UDP_TCP_PORT_AUTOMATIC",
tableName: "VPNSettings",
value: "Automatic",
comment: ""
)
case .port80:
"80"
case .port5001:
"5001"
}
}
}

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let decodedValue = try? container.decode(UInt16.self)
public enum WireGuardObfuscationShadowsockPort: Codable, Equatable, CustomStringConvertible {
case automatic
case custom(UInt16)

let port = WireGuardObfuscationPort.allCases.first(where: { $0.rawValue == decodedValue })
self = port ?? .automatic
public var portValue: UInt16? {
switch self {
case .automatic:
nil
case let .custom(port):
port
}
}

public var description: String {
switch self {
case .automatic:
NSLocalizedString(
"WIREGUARD_OBFUSCATION_SHADOWSOCKS_PORT_AUTOMATIC",
tableName: "VPNSettings",
value: "Automatic",
comment: ""
)
case let .custom(port):
String(port)
}
}
}

// Can't deprecate the whole type since it'll yield a lint warning when decoding
// port in `WireGuardObfuscationSettings`.
private enum WireGuardObfuscationPort: UInt16, Codable {
@available(*, deprecated, message: "Use `udpOverTcpPort` instead")
case automatic = 0
@available(*, deprecated, message: "Use `udpOverTcpPort` instead")
case port80 = 80
@available(*, deprecated, message: "Use `udpOverTcpPort` instead")
case port5001 = 5001
}

public struct WireGuardObfuscationSettings: Codable, Equatable {
@available(*, deprecated, message: "Use `udpOverTcpPort` instead")
private var port: WireGuardObfuscationPort = .automatic

public let state: WireGuardObfuscationState
public let port: WireGuardObfuscationPort
public let udpOverTcpPort: WireGuardObfuscationUdpOverTcpPort
public let shadowsocksPort: WireGuardObfuscationShadowsockPort

public init(state: WireGuardObfuscationState = .automatic, port: WireGuardObfuscationPort = .automatic) {
public init(
state: WireGuardObfuscationState = .automatic,
udpOverTcpPort: WireGuardObfuscationUdpOverTcpPort = .automatic,
shadowsocksPort: WireGuardObfuscationShadowsockPort = .automatic
) {
self.state = state
self.port = port
self.udpOverTcpPort = udpOverTcpPort
self.shadowsocksPort = shadowsocksPort
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

state = try container.decode(WireGuardObfuscationState.self, forKey: .state)
shadowsocksPort = try container.decodeIfPresent(
WireGuardObfuscationShadowsockPort.self,
forKey: .shadowsocksPort
) ?? .automatic

if let port = try? container.decodeIfPresent(WireGuardObfuscationUdpOverTcpPort.self, forKey: .udpOverTcpPort) {
udpOverTcpPort = port
} else if let port = try? container.decodeIfPresent(WireGuardObfuscationPort.self, forKey: .port) {
switch port {
case .automatic:
udpOverTcpPort = .automatic
case .port80:
udpOverTcpPort = .port80
case .port5001:
udpOverTcpPort = .port5001
}
} else {
udpOverTcpPort = .automatic
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,16 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
value: "UDP-over-TCP",
comment: ""
)

#if DEBUG
cell.detailTitleLabel.text = String(format: NSLocalizedString(
"WIREGUARD_OBFUSCATION_UDP_TCP_PORT",
tableName: "VPNSettings",
value: "Port: %d",
value: "Port: %@",
comment: ""
), viewModel.obfuscationPort.portValue)
), viewModel.obfuscationUpdOverTcpPort.description)
#endif

cell.accessibilityIdentifier = item.accessibilityIdentifier
cell.applySubCellStyling()

Expand All @@ -169,14 +171,16 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
value: "Shadowsocks",
comment: ""
)

#if DEBUG
cell.detailTitleLabel.text = String(format: NSLocalizedString(
"WIREGUARD_OBFUSCATION_SHADOWSOCKS_PORT",
tableName: "VPNSettings",
value: "Port: %d",
value: "Port: %@",
comment: ""
), viewModel.obfuscationPort.portValue)
), viewModel.obfuscationShadowsocksPort.description)
#endif

cell.accessibilityIdentifier = item.accessibilityIdentifier
cell.applySubCellStyling()

Expand All @@ -199,7 +203,7 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
case let .wireGuardObfuscationPort(port):
guard let cell = cell as? SelectableSettingsCell else { return }

let portString = port == 0 ? "Automatic" : "\(port)"
let portString = port.description
cell.titleLabel.text = NSLocalizedString(
"WIREGUARD_OBFUSCATION_PORT_LABEL",
tableName: "VPNSettings",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case wireGuardObfuscationUdpOverTcp
case wireGuardObfuscationShadowsocks
case wireGuardObfuscationOff
case wireGuardObfuscationPort(_ port: UInt16)
case wireGuardObfuscationPort(_ port: WireGuardObfuscationUdpOverTcpPort)
case quantumResistanceAutomatic
case quantumResistanceOn
case quantumResistanceOff
Expand Down Expand Up @@ -107,7 +107,11 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
}

static var wireGuardObfuscationPort: [Item] {
[.wireGuardObfuscationPort(0), .wireGuardObfuscationPort(80), .wireGuardObfuscationPort(5001)]
[
.wireGuardObfuscationPort(.automatic),
.wireGuardObfuscationPort(.port80),
.wireGuardObfuscationPort(.port5001),
]
}

static var quantumResistance: [Item] {
Expand Down Expand Up @@ -178,7 +182,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
private var obfuscationSettings: WireGuardObfuscationSettings {
WireGuardObfuscationSettings(
state: viewModel.obfuscationState,
port: viewModel.obfuscationPort
udpOverTcpPort: viewModel.obfuscationUpdOverTcpPort,
shadowsocksPort: viewModel.obfuscationShadowsocksPort
)
}

Expand All @@ -192,7 +197,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
let obfuscationStateItem: Item = switch viewModel.obfuscationState {
case .automatic: .wireGuardObfuscationAutomatic
case .off: .wireGuardObfuscationOff
case .on: .wireGuardObfuscationUdpOverTcp
case .on, .udpOverTcp: .wireGuardObfuscationUdpOverTcp
case .shadowsocks: .wireGuardObfuscationShadowsocks
}

let quantumResistanceItem: Item = switch viewModel.quantumResistance {
Expand All @@ -201,7 +207,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case .on: .quantumResistanceOn
}

let obfuscationPortItem: Item = .wireGuardObfuscationPort(viewModel.obfuscationPort.portValue)
let obfuscationPortItem: Item = .wireGuardObfuscationPort(viewModel.obfuscationUpdOverTcpPort)

return [
wireGuardPortItem,
Expand Down Expand Up @@ -308,13 +314,13 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
selectObfuscationState(.automatic)
delegate?.didUpdateTunnelSettings(TunnelSettingsUpdate.obfuscation(obfuscationSettings))
case .wireGuardObfuscationUdpOverTcp:
selectObfuscationState(.on)
selectObfuscationState(.udpOverTcp)
delegate?.didUpdateTunnelSettings(TunnelSettingsUpdate.obfuscation(obfuscationSettings))
// TODO: When ready, add implementation for selected obfuscation.
// TODO: When ready, add implementation for selected obfuscation (navigate to new view etc).
case .wireGuardObfuscationShadowsocks:
selectObfuscationState(.on)
selectObfuscationState(.shadowsocks)
delegate?.didUpdateTunnelSettings(TunnelSettingsUpdate.obfuscation(obfuscationSettings))
// TODO: When ready, add implementation for selected obfuscation.
// TODO: When ready, add implementation for selected obfuscation (navigate to new view etc).
case .wireGuardObfuscationOff:
selectObfuscationState(.off)
delegate?.didUpdateTunnelSettings(TunnelSettingsUpdate.obfuscation(obfuscationSettings))
Expand Down Expand Up @@ -656,9 +662,8 @@ extension VPNSettingsDataSource: VPNSettingsCellEventHandler {
viewModel.setWireGuardObfuscationState(state)
}

func selectObfuscationPort(_ port: UInt16) {
let selectedPort = WireGuardObfuscationPort(rawValue: port)!
viewModel.setWireGuardObfuscationPort(selectedPort)
func selectObfuscationPort(_ port: WireGuardObfuscationUdpOverTcpPort) {
viewModel.setWireGuardObfuscationUdpOverTcpPort(port)
}

func selectQuantumResistance(_ state: TunnelQuantumResistance) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ struct VPNSettingsViewModel: Equatable {
var availableWireGuardPortRanges: [[UInt16]] = []

private(set) var obfuscationState: WireGuardObfuscationState
private(set) var obfuscationPort: WireGuardObfuscationPort
private(set) var obfuscationUpdOverTcpPort: WireGuardObfuscationUdpOverTcpPort
private(set) var obfuscationShadowsocksPort: WireGuardObfuscationShadowsockPort

private(set) var quantumResistance: TunnelQuantumResistance
private(set) var multihopState: MultihopState
Expand Down Expand Up @@ -178,8 +179,12 @@ struct VPNSettingsViewModel: Equatable {
obfuscationState = newState
}

mutating func setWireGuardObfuscationPort(_ newPort: WireGuardObfuscationPort) {
obfuscationPort = newPort
mutating func setWireGuardObfuscationShadowsockPort(_ newPort: WireGuardObfuscationShadowsockPort) {
obfuscationShadowsocksPort = newPort
}

mutating func setWireGuardObfuscationUdpOverTcpPort(_ newPort: WireGuardObfuscationUdpOverTcpPort) {
obfuscationUpdOverTcpPort = newPort
}

mutating func setQuantumResistance(_ newState: TunnelQuantumResistance) {
Expand Down Expand Up @@ -242,7 +247,8 @@ struct VPNSettingsViewModel: Equatable {
wireGuardPort = tunnelSettings.relayConstraints.port.value

obfuscationState = tunnelSettings.wireGuardObfuscation.state
obfuscationPort = tunnelSettings.wireGuardObfuscation.port
obfuscationUpdOverTcpPort = tunnelSettings.wireGuardObfuscation.udpOverTcpPort
obfuscationShadowsocksPort = tunnelSettings.wireGuardObfuscation.shadowsocksPort

quantumResistance = tunnelSettings.tunnelQuantumResistance
multihopState = tunnelSettings.tunnelMultihopState
Expand Down
15 changes: 12 additions & 3 deletions ios/MullvadVPNTests/MullvadSettings/MigrationManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,10 @@ final class MigrationManagerTests: XCTestCase {

settingsV5.relayConstraints = relayConstraints
settingsV5.tunnelQuantumResistance = .off
settingsV5.wireGuardObfuscation = WireGuardObfuscationSettings(state: .off, port: .automatic)
settingsV5.wireGuardObfuscation = WireGuardObfuscationSettings(
state: .off,
udpOverTcpPort: .automatic
)
settingsV5.tunnelMultihopState = .off

try migrateToLatest(settingsV5, version: .v5)
Expand All @@ -158,7 +161,10 @@ final class MigrationManagerTests: XCTestCase {

settingsV4.relayConstraints = relayConstraints
settingsV4.tunnelQuantumResistance = .off
settingsV4.wireGuardObfuscation = WireGuardObfuscationSettings(state: .off, port: .automatic)
settingsV4.wireGuardObfuscation = WireGuardObfuscationSettings(
state: .off,
udpOverTcpPort: .automatic
)

try migrateToLatest(settingsV4, version: .v4)

Expand All @@ -178,7 +184,10 @@ final class MigrationManagerTests: XCTestCase {

settingsV3.relayConstraints = relayConstraints
settingsV3.dnsSettings = DNSSettings()
settingsV3.wireGuardObfuscation = WireGuardObfuscationSettings(state: .on, port: .port80)
settingsV3.wireGuardObfuscation = WireGuardObfuscationSettings(
state: .udpOverTcp,
udpOverTcpPort: .port80
)

try migrateToLatest(settingsV3, version: .v3)

Expand Down
Loading
Loading