Skip to content

Commit 4c0797d

Browse files
committed
Merge branch 'adjust-relay-selector-to-support-custom-lists-ios-461'
2 parents 118fcfe + c39298e commit 4c0797d

File tree

8 files changed

+109
-38
lines changed

8 files changed

+109
-38
lines changed

ios/MullvadREST/Relay/RelaySelector.swift

+18-14
Original file line numberDiff line numberDiff line change
@@ -150,24 +150,28 @@ public enum RelaySelector {
150150
}
151151
}
152152

153-
switch constraints.location {
153+
switch constraints.locations {
154154
case .any:
155155
return true
156156
case let .only(relayConstraint):
157-
switch relayConstraint {
158-
case let .country(countryCode):
159-
return relayWithLocation.serverLocation.countryCode == countryCode &&
160-
relayWithLocation.relay.includeInCountry
161-
162-
case let .city(countryCode, cityCode):
163-
return relayWithLocation.serverLocation.countryCode == countryCode &&
164-
relayWithLocation.serverLocation.cityCode == cityCode
165-
166-
case let .hostname(countryCode, cityCode, hostname):
167-
return relayWithLocation.serverLocation.countryCode == countryCode &&
168-
relayWithLocation.serverLocation.cityCode == cityCode &&
169-
relayWithLocation.relay.hostname == hostname
157+
for location in relayConstraint.locations {
158+
switch location {
159+
case let .country(countryCode):
160+
return relayWithLocation.serverLocation.countryCode == countryCode &&
161+
relayWithLocation.relay.includeInCountry
162+
163+
case let .city(countryCode, cityCode):
164+
return relayWithLocation.serverLocation.countryCode == countryCode &&
165+
relayWithLocation.serverLocation.cityCode == cityCode
166+
167+
case let .hostname(countryCode, cityCode, hostname):
168+
return relayWithLocation.serverLocation.countryCode == countryCode &&
169+
relayWithLocation.serverLocation.cityCode == cityCode &&
170+
relayWithLocation.relay.hostname == hostname
171+
}
170172
}
173+
174+
return false
171175
}
172176
}.filter { relayWithLocation -> Bool in
173177
relayWithLocation.relay.active

ios/MullvadTypes/RelayConstraints.swift

+32-5
Original file line numberDiff line numberDiff line change
@@ -21,32 +21,59 @@ public class RelayConstraintsUpdater: ConstraintsPropagation {
2121
}
2222

2323
public struct RelayConstraints: Codable, Equatable, CustomDebugStringConvertible {
24-
public var location: RelayConstraint<RelayLocation>
24+
@available(*, deprecated, renamed: "locations")
25+
private var location: RelayConstraint<RelayLocation> = .only(.country("se"))
2526

2627
// Added in 2023.3
2728
public var port: RelayConstraint<UInt16>
2829
public var filter: RelayConstraint<RelayFilter>
2930

31+
// Added in 2024.1
32+
public var locations: RelayConstraint<RelayLocations>
33+
3034
public var debugDescription: String {
31-
"RelayConstraints { location: \(location), port: \(port) }"
35+
"RelayConstraints { locations: \(locations), port: \(port) }"
3236
}
3337

3438
public init(
35-
location: RelayConstraint<RelayLocation> = .only(.country("se")),
39+
locations: RelayConstraint<RelayLocations> = .only(RelayLocations(locations: [.country("se")])),
3640
port: RelayConstraint<UInt16> = .any,
3741
filter: RelayConstraint<RelayFilter> = .any
3842
) {
39-
self.location = location
43+
self.locations = locations
4044
self.port = port
4145
self.filter = filter
4246
}
4347

4448
public init(from decoder: Decoder) throws {
4549
let container = try decoder.container(keyedBy: CodingKeys.self)
46-
location = try container.decode(RelayConstraint<RelayLocation>.self, forKey: .location)
4750

4851
// Added in 2023.3
4952
port = try container.decodeIfPresent(RelayConstraint<UInt16>.self, forKey: .port) ?? .any
5053
filter = try container.decodeIfPresent(RelayConstraint<RelayFilter>.self, forKey: .filter) ?? .any
54+
55+
// Added in 2024.1
56+
locations = try container.decodeIfPresent(RelayConstraint<RelayLocations>.self, forKey: .locations)
57+
?? Self.migrateLocations(decoder: decoder)
58+
?? .only(RelayLocations(locations: [.country("se")]))
59+
}
60+
}
61+
62+
extension RelayConstraints {
63+
private static func migrateLocations(decoder: Decoder) -> RelayConstraint<RelayLocations>? {
64+
let container = try? decoder.container(keyedBy: CodingKeys.self)
65+
66+
guard
67+
let location = try? container?.decodeIfPresent(RelayConstraint<RelayLocation>.self, forKey: .location)
68+
else {
69+
return nil
70+
}
71+
72+
switch location {
73+
case .any:
74+
return .any
75+
case let .only(location):
76+
return .only(RelayLocations(locations: [location]))
77+
}
5178
}
5279
}

ios/MullvadTypes/RelayLocation.swift

+10
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,13 @@ public enum RelayLocation: Codable, Hashable, CustomDebugStringConvertible {
106106
}
107107
}
108108
}
109+
110+
public struct RelayLocations: Codable, Equatable {
111+
public let locations: [RelayLocation]
112+
public let customListId: UUID?
113+
114+
public init(locations: [RelayLocation], customListId: UUID? = nil) {
115+
self.locations = locations
116+
self.customListId = customListId
117+
}
118+
}

ios/MullvadVPN/Coordinators/SelectLocationCoordinator.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ class SelectLocationCoordinator: Coordinator, Presentable, Presenting, RelayCach
5858
guard let self else { return }
5959

6060
var relayConstraints = tunnelManager.settings.relayConstraints
61-
relayConstraints.location = .only(relay)
61+
relayConstraints.locations = .only(RelayLocations(
62+
locations: [relay],
63+
customListId: nil
64+
))
6265

6366
tunnelManager.updateSettings([.relayConstraints(relayConstraints)]) {
6467
self.tunnelManager.startTunnel()
@@ -98,7 +101,8 @@ class SelectLocationCoordinator: Coordinator, Presentable, Presenting, RelayCach
98101
selectLocationViewController.setCachedRelays(cachedRelays, filter: relayFilter)
99102
}
100103

101-
selectLocationViewController.relayLocation = tunnelManager.settings.relayConstraints.location.value
104+
selectLocationViewController.relayLocation =
105+
tunnelManager.settings.relayConstraints.locations.value?.locations.first
102106

103107
navigationController.pushViewController(selectLocationViewController, animated: false)
104108
}

ios/MullvadVPNTests/MigrationManagerTests.swift

+8-2
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,10 @@ final class MigrationManagerTests: XCTestCase {
121121

122122
func testSuccessfulMigrationFromV2ToLatest() throws {
123123
var settingsV2 = TunnelSettingsV2()
124-
let osakaRelayConstraints: RelayConstraints = .init(location: .only(.city("jp", "osa")))
124+
let osakaRelayConstraints = RelayConstraints(
125+
locations: .only(RelayLocations(locations: [.city("jp", "osa")]))
126+
)
127+
125128
settingsV2.relayConstraints = osakaRelayConstraints
126129

127130
try migrateToLatest(settingsV2, version: .v2)
@@ -132,7 +135,10 @@ final class MigrationManagerTests: XCTestCase {
132135

133136
func testSuccessfulMigrationFromV1ToLatest() throws {
134137
var settingsV1 = TunnelSettingsV1()
135-
let osakaRelayConstraints: RelayConstraints = .init(location: .only(.city("jp", "osa")))
138+
let osakaRelayConstraints = RelayConstraints(
139+
locations: .only(RelayLocations(locations: [.city("jp", "osa")]))
140+
)
141+
136142
settingsV1.relayConstraints = osakaRelayConstraints
137143

138144
try migrateToLatest(settingsV1, version: .v1)

ios/MullvadVPNTests/MullvadSettings/TunnelSettingsUpdateTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ final class TunnelSettingsUpdateTests: XCTestCase {
4848

4949
// When:
5050
let relayConstraints = RelayConstraints(
51-
location: .only(.country("zz")),
51+
locations: .only(RelayLocations(locations: [.country("zz")])),
5252
port: .only(9999),
5353
filter: .only(.init(ownership: .rented, providers: .only(["foo", "bar"])))
5454
)

ios/MullvadVPNTests/RelaySelectorTests.swift

+31-13
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ class RelaySelectorTests: XCTestCase {
1818
let sampleRelays = ServerRelaysResponseStubs.sampleRelays
1919

2020
func testCountryConstraint() throws {
21-
let constraints = RelayConstraints(location: .only(.country("es")))
21+
let constraints = RelayConstraints(
22+
locations: .only(RelayLocations(locations: [.country("es")]))
23+
)
2224

2325
let result = try RelaySelector.evaluate(
2426
relays: sampleRelays,
@@ -30,7 +32,10 @@ class RelaySelectorTests: XCTestCase {
3032
}
3133

3234
func testCityConstraint() throws {
33-
let constraints = RelayConstraints(location: .only(.city("se", "got")))
35+
let constraints = RelayConstraints(
36+
locations: .only(RelayLocations(locations: [.city("se", "got")]))
37+
)
38+
3439
let result = try RelaySelector.evaluate(
3540
relays: sampleRelays,
3641
constraints: constraints,
@@ -41,7 +46,9 @@ class RelaySelectorTests: XCTestCase {
4146
}
4247

4348
func testHostnameConstraint() throws {
44-
let constraints = RelayConstraints(location: .only(.hostname("se", "sto", "se6-wireguard")))
49+
let constraints = RelayConstraints(
50+
locations: .only(RelayLocations(locations: [.hostname("se", "sto", "se6-wireguard")]))
51+
)
4552

4653
let result = try RelaySelector.evaluate(
4754
relays: sampleRelays,
@@ -53,7 +60,10 @@ class RelaySelectorTests: XCTestCase {
5360
}
5461

5562
func testSpecificPortConstraint() throws {
56-
let constraints = RelayConstraints(location: .only(.hostname("se", "sto", "se6-wireguard")), port: .only(1))
63+
let constraints = RelayConstraints(
64+
locations: .only(RelayLocations(locations: [.hostname("se", "sto", "se6-wireguard")])),
65+
port: .only(1)
66+
)
5767

5868
let result = try RelaySelector.evaluate(
5969
relays: sampleRelays,
@@ -65,7 +75,9 @@ class RelaySelectorTests: XCTestCase {
6575
}
6676

6777
func testRandomPortSelectionWithFailedAttempts() throws {
68-
let constraints = RelayConstraints(location: .only(.hostname("se", "sto", "se6-wireguard")))
78+
let constraints = RelayConstraints(
79+
locations: .only(RelayLocations(locations: [.hostname("se", "sto", "se6-wireguard")]))
80+
)
6981
let allPorts = portRanges.flatMap { $0 }
7082

7183
var result = try RelaySelector.evaluate(
@@ -89,15 +101,19 @@ class RelaySelectorTests: XCTestCase {
89101
}
90102

91103
func testClosestShadowsocksRelay() throws {
92-
let constraints = RelayConstraints(location: .only(.city("se", "sto")))
104+
let constraints = RelayConstraints(
105+
locations: .only(RelayLocations(locations: [.city("se", "sto")]))
106+
)
93107

94108
let selectedRelay = RelaySelector.closestShadowsocksRelayConstrained(by: constraints, in: sampleRelays)
95109

96110
XCTAssertEqual(selectedRelay?.hostname, "se-sto-br-001")
97111
}
98112

99113
func testClosestShadowsocksRelayIsRandomWhenNoContraintsAreSatisfied() throws {
100-
let constraints = RelayConstraints(location: .only(.country("INVALID COUNTRY")))
114+
let constraints = RelayConstraints(
115+
locations: .only(RelayLocations(locations: [.country("INVALID COUNTRY")]))
116+
)
101117

102118
let selectedRelay = try XCTUnwrap(RelaySelector.closestShadowsocksRelayConstrained(
103119
by: constraints,
@@ -109,8 +125,9 @@ class RelaySelectorTests: XCTestCase {
109125

110126
func testRelayFilterConstraintWithOwnedOwnership() throws {
111127
let filter = RelayFilter(ownership: .owned, providers: .any)
128+
112129
let constraints = RelayConstraints(
113-
location: .only(.hostname("se", "sto", "se6-wireguard")),
130+
locations: .only(RelayLocations(locations: [.hostname("se", "sto", "se6-wireguard")])),
114131
filter: .only(filter)
115132
)
116133

@@ -125,8 +142,9 @@ class RelaySelectorTests: XCTestCase {
125142

126143
func testRelayFilterConstraintWithRentedOwnership() throws {
127144
let filter = RelayFilter(ownership: .rented, providers: .any)
145+
128146
let constraints = RelayConstraints(
129-
location: .only(.hostname("se", "sto", "se6-wireguard")),
147+
locations: .only(RelayLocations(locations: [.hostname("se", "sto", "se6-wireguard")])),
130148
filter: .only(filter)
131149
)
132150

@@ -141,10 +159,10 @@ class RelaySelectorTests: XCTestCase {
141159

142160
func testRelayFilterConstraintWithCorrectProvider() throws {
143161
let provider = "31173"
144-
145162
let filter = RelayFilter(ownership: .any, providers: .only([provider]))
163+
146164
let constraints = RelayConstraints(
147-
location: .only(.hostname("se", "sto", "se6-wireguard")),
165+
locations: .only(RelayLocations(locations: [.hostname("se", "sto", "se6-wireguard")])),
148166
filter: .only(filter)
149167
)
150168

@@ -159,10 +177,10 @@ class RelaySelectorTests: XCTestCase {
159177

160178
func testRelayFilterConstraintWithIncorrectProvider() throws {
161179
let provider = "DataPacket"
162-
163180
let filter = RelayFilter(ownership: .any, providers: .only([provider]))
181+
164182
let constraints = RelayConstraints(
165-
location: .only(.hostname("se", "sto", "se6-wireguard")),
183+
locations: .only(RelayLocations(locations: [.hostname("se", "sto", "se6-wireguard")])),
166184
filter: .only(filter)
167185
)
168186

ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ final class AppMessageHandlerTests: XCTestCase {
7777
let actor = PacketTunnelActorStub(reconnectExpectation: reconnectExpectation)
7878
let appMessageHandler = createAppMessageHandler(actor: actor)
7979

80-
let relayConstraints = RelayConstraints(location: .only(.hostname("se", "sto", "se6-wireguard")))
80+
let relayConstraints = RelayConstraints(
81+
locations: .only(RelayLocations(locations: [.hostname("se", "sto", "se6-wireguard")]))
82+
)
8183
let selectorResult = try XCTUnwrap(try? RelaySelector.evaluate(
8284
relays: ServerRelaysResponseStubs.sampleRelays,
8385
constraints: relayConstraints,

0 commit comments

Comments
 (0)