Skip to content

Commit

Permalink
Merge branch 'refactor-AnyRelay-location-type'
Browse files Browse the repository at this point in the history
  • Loading branch information
buggmagnet committed Jan 28, 2025
2 parents ad60808 + 4fb7eef commit 7adc8ac
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 25 deletions.
4 changes: 2 additions & 2 deletions ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ extension REST {
public let hostname: String
public let active: Bool
public let owned: Bool
public let location: String
public let location: LocationIdentifier
public let provider: String
public let ipv4AddrIn: IPv4Address
public let weight: UInt64
Expand All @@ -54,7 +54,7 @@ extension REST {
public let hostname: String
public let active: Bool
public let owned: Bool
public let location: String
public let location: LocationIdentifier
public let provider: String
public let weight: UInt64
public let ipv4AddrIn: IPv4Address
Expand Down
2 changes: 1 addition & 1 deletion ios/MullvadREST/Relay/AnyRelay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Network
public protocol AnyRelay {
var hostname: String { get }
var owned: Bool { get }
var location: String { get }
var location: REST.LocationIdentifier { get }
var provider: String { get }
var weight: UInt64 { get }
var active: Bool { get }
Expand Down
73 changes: 73 additions & 0 deletions ios/MullvadREST/Relay/LocationIdentifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// LocationIdentifier.swift
// MullvadVPN
//
// Created by Andrew Bulhak on 2025-01-24.
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

extension REST {
// locations are currently always "aa-bbb" for some country code aa and city code bbb. Should this change, this type can be extended.
public struct LocationIdentifier: Sendable {
public let country: Substring
public let city: Substring

fileprivate static func parse(_ input: String) -> (Substring, Substring)? {
let components = input.split(separator: "-")
guard components.count == 2 else { return nil }
return (components[0], components[1])
}
}
}

extension REST.LocationIdentifier: RawRepresentable {
public var rawValue: String { country.base }

public init?(rawValue: String) {
guard let parsed = Self.parse(rawValue) else { return nil }
(country, city) = parsed
}
}

extension REST.LocationIdentifier: ExpressibleByStringLiteral {
public init(stringLiteral value: StringLiteralType) {
guard let parsed = Self.parse(value) else {
// this is ugly, but it will only ever be called for
// code initialised from a literal in code, and
// never from real-world input, so it'll have to do.
fatalError("Invalid LocationIdentifier: \(value)")
}
(country, city) = parsed
}
}

// Allow LocationIdentifier to code to/from JSON Strings
extension REST.LocationIdentifier: Codable {
enum ParsingError: Error {
case malformed
}

public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
guard let parsed = Self.parse(try container.decode(String.self)) else {
throw ParsingError.malformed
}
(country, city) = parsed
}

public func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(rawValue)
}
}

// As the location's values are Substrings of the same String, to which they maintain references, we use the base String for holistic operations such as equality and hashing
extension REST.LocationIdentifier: Hashable {
public static func == (lhs: REST.LocationIdentifier, rhs: REST.LocationIdentifier) -> Bool {
lhs.rawValue == rhs.rawValue
}

public func hash(into hasher: inout Hasher) {
hasher.combine(rawValue)
}
}
7 changes: 2 additions & 5 deletions ios/MullvadREST/Relay/RelaySelector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,11 @@ public enum RelaySelector {
_ serverLocation: REST.ServerLocation,
relay: T
) -> RelayWithLocation<T>? {
let locationComponents = relay.location.split(separator: "-")
guard locationComponents.count > 1 else { return nil }

let location = Location(
country: serverLocation.country,
countryCode: String(locationComponents[0]),
countryCode: String(relay.location.country),
city: serverLocation.city,
cityCode: String(locationComponents[1]),
cityCode: String(relay.location.city),
latitude: serverLocation.latitude,
longitude: serverLocation.longitude
)
Expand Down
8 changes: 3 additions & 5 deletions ios/MullvadREST/Relay/RelayWithLocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,16 @@ public struct RelayWithLocation<T: AnyRelay> {
}

init?(_ relay: T, locations: [String: REST.ServerLocation]) {
let locationComponents = relay.location.split(separator: "-")
guard
locationComponents.count > 1,
let serverLocation = locations[relay.location]
let serverLocation = locations[relay.location.rawValue]
else { return nil }

self.relay = relay
self.serverLocation = Location(
country: serverLocation.country,
countryCode: String(locationComponents[0]),
countryCode: String(relay.location.country),
city: serverLocation.city,
cityCode: String(locationComponents[1]),
cityCode: String(relay.location.city),
latitude: serverLocation.latitude,
longitude: serverLocation.longitude
)
Expand Down
5 changes: 4 additions & 1 deletion ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
44BB5F972BE527F4002520EB /* TunnelState+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44BB5F962BE527F4002520EB /* TunnelState+UI.swift */; };
44BB5F982BE527F4002520EB /* TunnelState+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44BB5F962BE527F4002520EB /* TunnelState+UI.swift */; };
44C18DE32C74DF93009BE3E1 /* TunnelPinger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449275432C3C3029000526DE /* TunnelPinger.swift */; };
44CAEAA12D442F5E004A8E65 /* LocationIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44CAEAA02D442F56004A8E65 /* LocationIdentifier.swift */; };
44DD7D242B6CFFD70005F67F /* StartTunnelOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */; };
44DD7D272B6D18FB0005F67F /* MockTunnelInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */; };
44DD7D292B7113CA0005F67F /* MockTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D282B7113CA0005F67F /* MockTunnel.swift */; };
Expand Down Expand Up @@ -1481,6 +1482,7 @@
44B3C43C2C00CBBC0079782C /* PacketTunnelActorReducerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacketTunnelActorReducerTests.swift; sourceTree = "<group>"; };
44BB5F962BE527F4002520EB /* TunnelState+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelState+UI.swift"; sourceTree = "<group>"; };
44BB5F992BE529FE002520EB /* TunnelStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelStateTests.swift; sourceTree = "<group>"; };
44CAEAA02D442F56004A8E65 /* LocationIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationIdentifier.swift; sourceTree = "<group>"; };
44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartTunnelOperationTests.swift; sourceTree = "<group>"; };
44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnelInteractor.swift; sourceTree = "<group>"; };
44DD7D282B7113CA0005F67F /* MockTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4468,6 +4470,7 @@
585DA87626B024A600B8C587 /* CachedRelays.swift */,
F0DDE4272B220A15006B57A7 /* Haversine.swift */,
7A516C392B7111A700BBD33D /* IPOverrideWrapper.swift */,
44CAEAA02D442F56004A8E65 /* LocationIdentifier.swift */,
F0DDE4292B220A15006B57A7 /* Midpoint.swift */,
7ACE19102C1C349200260BB6 /* MultihopDecisionFlow.swift */,
F0F3161A2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift */,
Expand Down Expand Up @@ -5569,6 +5572,7 @@
A90763C32B2858630045ADF0 /* Socks5Configuration.swift in Sources */,
06799AE428F98E4800ACD94E /* RESTAccountsProxy.swift in Sources */,
5897F1742913EAF800AF5695 /* ExponentialBackoff.swift in Sources */,
44CAEAA12D442F5E004A8E65 /* LocationIdentifier.swift in Sources */,
06799AE328F98E4800ACD94E /* RESTNetworkOperation.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -6106,7 +6110,6 @@
5868585524054096000B8131 /* CustomButton.swift in Sources */,
58E25F812837BBBB002CFB2C /* SceneDelegate.swift in Sources */,
7A1A26492A29D48A00B978AA /* RelayFilterCellFactory.swift in Sources */,
5867771629097C5B006F721F /* ProductState.swift in Sources */,
F0D5591E2D38051C0072B63F /* LatestChangesNotificationProvider.swift in Sources */,
7A28826A2BA8336600FD9F20 /* VPNSettingsCoordinator.swift in Sources */,
7A6389DE2B7E3BD6008E77E1 /* CustomListItemIdentifier.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ class AllLocationDataSource: LocationDataSourceProtocol {
let expandedRelays = nodes.flatMap { [$0] + $0.flattened }.filter { $0.showsChildren }.map { $0.code }

for relay in relays.relays {
guard case
let .city(countryCode, cityCode) = RelayLocation(dashSeparatedString: relay.location),
let serverLocation = relays.locations[relay.location]
guard
let serverLocation = relays.locations[relay.location.rawValue]
else { continue }

let relayLocation = RelayLocation.hostname(countryCode, cityCode, relay.hostname)
let relayLocation = RelayLocation.hostname(
String(relay.location.country),
String(relay.location.city),
relay.hostname
)

for ancestorOrSelf in relayLocation.ancestors + [relayLocation] {
addLocation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,15 @@ class RelaySelectorTests: XCTestCase {
)

let relayWithLocations = sampleRelays.wireguard.relays.map {
let location = sampleRelays.locations[$0.location]!
let locationComponents = $0.location.split(separator: "-")
let location = sampleRelays.locations[$0.location.rawValue]!

return RelayWithLocation(
relay: $0,
serverLocation: Location(
country: location.country,
countryCode: String(locationComponents[0]),
countryCode: String($0.location.country),
city: location.city,
cityCode: String(locationComponents[1]),
cityCode: String($0.location.city),
latitude: location.latitude,
longitude: location.longitude
)
Expand Down Expand Up @@ -138,7 +137,7 @@ class RelaySelectorTests: XCTestCase {

func testClosestRelay() throws {
let relayWithLocations = try sampleRelays.wireguard.relays.map {
let serverLocation = try XCTUnwrap(sampleRelays.locations[$0.location])
let serverLocation = try XCTUnwrap(sampleRelays.locations[$0.location.rawValue])
let location = Location(
country: serverLocation.country,
countryCode: serverLocation.country,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ extension IPOverrideWrapperTests {
hostname: "",
active: true,
owned: true,
location: "",
location: "xx-yyy",
provider: "",
weight: 0,
ipv4AddrIn: .any,
Expand All @@ -94,7 +94,7 @@ extension IPOverrideWrapperTests {
hostname: "",
active: true,
owned: true,
location: "",
location: "xx-yyy",
provider: "",
ipv4AddrIn: .any,
weight: 0,
Expand Down

0 comments on commit 7adc8ac

Please sign in to comment.