Skip to content

Commit

Permalink
Merge branch 'adjust-relay-selector-to-support-custom-lists-ios-461'
Browse files Browse the repository at this point in the history
  • Loading branch information
buggmagnet committed Feb 26, 2024
2 parents 118fcfe + c39298e commit 4c0797d
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 38 deletions.
32 changes: 18 additions & 14 deletions ios/MullvadREST/Relay/RelaySelector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,24 +150,28 @@ public enum RelaySelector {
}
}

switch constraints.location {
switch constraints.locations {
case .any:
return true
case let .only(relayConstraint):
switch relayConstraint {
case let .country(countryCode):
return relayWithLocation.serverLocation.countryCode == countryCode &&
relayWithLocation.relay.includeInCountry

case let .city(countryCode, cityCode):
return relayWithLocation.serverLocation.countryCode == countryCode &&
relayWithLocation.serverLocation.cityCode == cityCode

case let .hostname(countryCode, cityCode, hostname):
return relayWithLocation.serverLocation.countryCode == countryCode &&
relayWithLocation.serverLocation.cityCode == cityCode &&
relayWithLocation.relay.hostname == hostname
for location in relayConstraint.locations {
switch location {
case let .country(countryCode):
return relayWithLocation.serverLocation.countryCode == countryCode &&
relayWithLocation.relay.includeInCountry

case let .city(countryCode, cityCode):
return relayWithLocation.serverLocation.countryCode == countryCode &&
relayWithLocation.serverLocation.cityCode == cityCode

case let .hostname(countryCode, cityCode, hostname):
return relayWithLocation.serverLocation.countryCode == countryCode &&
relayWithLocation.serverLocation.cityCode == cityCode &&
relayWithLocation.relay.hostname == hostname
}
}

return false
}
}.filter { relayWithLocation -> Bool in
relayWithLocation.relay.active
Expand Down
37 changes: 32 additions & 5 deletions ios/MullvadTypes/RelayConstraints.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,59 @@ public class RelayConstraintsUpdater: ConstraintsPropagation {
}

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

// Added in 2023.3
public var port: RelayConstraint<UInt16>
public var filter: RelayConstraint<RelayFilter>

// Added in 2024.1
public var locations: RelayConstraint<RelayLocations>

public var debugDescription: String {
"RelayConstraints { location: \(location), port: \(port) }"
"RelayConstraints { locations: \(locations), port: \(port) }"
}

public init(
location: RelayConstraint<RelayLocation> = .only(.country("se")),
locations: RelayConstraint<RelayLocations> = .only(RelayLocations(locations: [.country("se")])),
port: RelayConstraint<UInt16> = .any,
filter: RelayConstraint<RelayFilter> = .any
) {
self.location = location
self.locations = locations
self.port = port
self.filter = filter
}

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

// Added in 2023.3
port = try container.decodeIfPresent(RelayConstraint<UInt16>.self, forKey: .port) ?? .any
filter = try container.decodeIfPresent(RelayConstraint<RelayFilter>.self, forKey: .filter) ?? .any

// Added in 2024.1
locations = try container.decodeIfPresent(RelayConstraint<RelayLocations>.self, forKey: .locations)
?? Self.migrateLocations(decoder: decoder)
?? .only(RelayLocations(locations: [.country("se")]))
}
}

extension RelayConstraints {
private static func migrateLocations(decoder: Decoder) -> RelayConstraint<RelayLocations>? {
let container = try? decoder.container(keyedBy: CodingKeys.self)

guard
let location = try? container?.decodeIfPresent(RelayConstraint<RelayLocation>.self, forKey: .location)
else {
return nil
}

switch location {
case .any:
return .any
case let .only(location):
return .only(RelayLocations(locations: [location]))
}
}
}
10 changes: 10 additions & 0 deletions ios/MullvadTypes/RelayLocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,13 @@ public enum RelayLocation: Codable, Hashable, CustomDebugStringConvertible {
}
}
}

public struct RelayLocations: Codable, Equatable {
public let locations: [RelayLocation]
public let customListId: UUID?

public init(locations: [RelayLocation], customListId: UUID? = nil) {
self.locations = locations
self.customListId = customListId
}
}
8 changes: 6 additions & 2 deletions ios/MullvadVPN/Coordinators/SelectLocationCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ class SelectLocationCoordinator: Coordinator, Presentable, Presenting, RelayCach
guard let self else { return }

var relayConstraints = tunnelManager.settings.relayConstraints
relayConstraints.location = .only(relay)
relayConstraints.locations = .only(RelayLocations(
locations: [relay],
customListId: nil
))

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

selectLocationViewController.relayLocation = tunnelManager.settings.relayConstraints.location.value
selectLocationViewController.relayLocation =
tunnelManager.settings.relayConstraints.locations.value?.locations.first

navigationController.pushViewController(selectLocationViewController, animated: false)
}
Expand Down
10 changes: 8 additions & 2 deletions ios/MullvadVPNTests/MigrationManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,10 @@ final class MigrationManagerTests: XCTestCase {

func testSuccessfulMigrationFromV2ToLatest() throws {
var settingsV2 = TunnelSettingsV2()
let osakaRelayConstraints: RelayConstraints = .init(location: .only(.city("jp", "osa")))
let osakaRelayConstraints = RelayConstraints(
locations: .only(RelayLocations(locations: [.city("jp", "osa")]))
)

settingsV2.relayConstraints = osakaRelayConstraints

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

func testSuccessfulMigrationFromV1ToLatest() throws {
var settingsV1 = TunnelSettingsV1()
let osakaRelayConstraints: RelayConstraints = .init(location: .only(.city("jp", "osa")))
let osakaRelayConstraints = RelayConstraints(
locations: .only(RelayLocations(locations: [.city("jp", "osa")]))
)

settingsV1.relayConstraints = osakaRelayConstraints

try migrateToLatest(settingsV1, version: .v1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ final class TunnelSettingsUpdateTests: XCTestCase {

// When:
let relayConstraints = RelayConstraints(
location: .only(.country("zz")),
locations: .only(RelayLocations(locations: [.country("zz")])),
port: .only(9999),
filter: .only(.init(ownership: .rented, providers: .only(["foo", "bar"])))
)
Expand Down
44 changes: 31 additions & 13 deletions ios/MullvadVPNTests/RelaySelectorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ class RelaySelectorTests: XCTestCase {
let sampleRelays = ServerRelaysResponseStubs.sampleRelays

func testCountryConstraint() throws {
let constraints = RelayConstraints(location: .only(.country("es")))
let constraints = RelayConstraints(
locations: .only(RelayLocations(locations: [.country("es")]))
)

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

func testCityConstraint() throws {
let constraints = RelayConstraints(location: .only(.city("se", "got")))
let constraints = RelayConstraints(
locations: .only(RelayLocations(locations: [.city("se", "got")]))
)

let result = try RelaySelector.evaluate(
relays: sampleRelays,
constraints: constraints,
Expand All @@ -41,7 +46,9 @@ class RelaySelectorTests: XCTestCase {
}

func testHostnameConstraint() throws {
let constraints = RelayConstraints(location: .only(.hostname("se", "sto", "se6-wireguard")))
let constraints = RelayConstraints(
locations: .only(RelayLocations(locations: [.hostname("se", "sto", "se6-wireguard")]))
)

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

func testSpecificPortConstraint() throws {
let constraints = RelayConstraints(location: .only(.hostname("se", "sto", "se6-wireguard")), port: .only(1))
let constraints = RelayConstraints(
locations: .only(RelayLocations(locations: [.hostname("se", "sto", "se6-wireguard")])),
port: .only(1)
)

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

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

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

func testClosestShadowsocksRelay() throws {
let constraints = RelayConstraints(location: .only(.city("se", "sto")))
let constraints = RelayConstraints(
locations: .only(RelayLocations(locations: [.city("se", "sto")]))
)

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

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

func testClosestShadowsocksRelayIsRandomWhenNoContraintsAreSatisfied() throws {
let constraints = RelayConstraints(location: .only(.country("INVALID COUNTRY")))
let constraints = RelayConstraints(
locations: .only(RelayLocations(locations: [.country("INVALID COUNTRY")]))
)

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

func testRelayFilterConstraintWithOwnedOwnership() throws {
let filter = RelayFilter(ownership: .owned, providers: .any)

let constraints = RelayConstraints(
location: .only(.hostname("se", "sto", "se6-wireguard")),
locations: .only(RelayLocations(locations: [.hostname("se", "sto", "se6-wireguard")])),
filter: .only(filter)
)

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

func testRelayFilterConstraintWithRentedOwnership() throws {
let filter = RelayFilter(ownership: .rented, providers: .any)

let constraints = RelayConstraints(
location: .only(.hostname("se", "sto", "se6-wireguard")),
locations: .only(RelayLocations(locations: [.hostname("se", "sto", "se6-wireguard")])),
filter: .only(filter)
)

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

func testRelayFilterConstraintWithCorrectProvider() throws {
let provider = "31173"

let filter = RelayFilter(ownership: .any, providers: .only([provider]))

let constraints = RelayConstraints(
location: .only(.hostname("se", "sto", "se6-wireguard")),
locations: .only(RelayLocations(locations: [.hostname("se", "sto", "se6-wireguard")])),
filter: .only(filter)
)

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

func testRelayFilterConstraintWithIncorrectProvider() throws {
let provider = "DataPacket"

let filter = RelayFilter(ownership: .any, providers: .only([provider]))

let constraints = RelayConstraints(
location: .only(.hostname("se", "sto", "se6-wireguard")),
locations: .only(RelayLocations(locations: [.hostname("se", "sto", "se6-wireguard")])),
filter: .only(filter)
)

Expand Down
4 changes: 3 additions & 1 deletion ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ final class AppMessageHandlerTests: XCTestCase {
let actor = PacketTunnelActorStub(reconnectExpectation: reconnectExpectation)
let appMessageHandler = createAppMessageHandler(actor: actor)

let relayConstraints = RelayConstraints(location: .only(.hostname("se", "sto", "se6-wireguard")))
let relayConstraints = RelayConstraints(
locations: .only(RelayLocations(locations: [.hostname("se", "sto", "se6-wireguard")]))
)
let selectorResult = try XCTUnwrap(try? RelaySelector.evaluate(
relays: ServerRelaysResponseStubs.sampleRelays,
constraints: relayConstraints,
Expand Down

0 comments on commit 4c0797d

Please sign in to comment.