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

Show number of available relays in filter view #7646

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions ios/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ Line wrap the file at 100 chars. Th
### Added
- Make account number copyable on welcome screen.

### Changed
- Improve the filter view to display the number of available servers based on selected criteria.

## 2025.2 - 2025-02-08
### Added
- Add different themes for app icons
Expand Down
28 changes: 28 additions & 0 deletions ios/MullvadMockData/MullvadREST/MockRelayCache.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// MockRelayCache.swift
// MullvadVPN
//
// Created by Mojgan on 2025-03-10.
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
import Foundation
@testable import MullvadREST

public struct MockRelayCache: RelayCacheProtocol {
public init() {}

public func read() throws -> MullvadREST.StoredRelays {
try .init(
cachedRelays: CachedRelays(
relays: ServerRelaysResponseStubs.sampleRelays,
updatedAt: Date()
)
)
}

public func readPrebundledRelays() throws -> MullvadREST.StoredRelays {
try self.read()
}

public func write(record: MullvadREST.StoredRelays) throws {}
}
27 changes: 22 additions & 5 deletions ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,19 @@ import WireGuardKitTypes

/// Relay selector stub that accepts a block that can be used to provide custom implementation.
public final class RelaySelectorStub: RelaySelectorProtocol {
public let relayCache: any RelayCacheProtocol

var selectedRelaysResult: (UInt) throws -> SelectedRelays
var candidatesResult: (() throws -> RelayCandidates)?

init(selectedRelaysResult: @escaping (UInt) throws -> SelectedRelays) {
init(
relayCache: RelayCacheProtocol = MockRelayCache(),
selectedRelaysResult: @escaping (UInt) throws -> SelectedRelays,
candidatesResult: (() throws -> RelayCandidates)? = nil
) {
self.relayCache = relayCache
self.selectedRelaysResult = selectedRelaysResult
self.candidatesResult = candidatesResult
}

public func selectRelays(
Expand All @@ -25,14 +34,20 @@ public final class RelaySelectorStub: RelaySelectorProtocol {
) throws -> SelectedRelays {
return try selectedRelaysResult(connectionAttemptCount)
}

public func findCandidates(
tunnelSettings: LatestTunnelSettings
) throws -> RelayCandidates {
return try candidatesResult?() ?? RelayCandidates(entryRelays: [], exitRelays: [])
}
}

extension RelaySelectorStub {
/// Returns a relay selector that never fails.
public static func nonFallible() -> RelaySelectorStub {
let publicKey = PrivateKey().publicKey.rawValue

return RelaySelectorStub { _ in
return RelaySelectorStub(selectedRelaysResult: { _ in
let cityRelay = SelectedRelay(
endpoint: MullvadEndpoint(
ipv4Relay: IPv4Endpoint(ip: .loopback, port: 1300),
Expand All @@ -56,13 +71,15 @@ extension RelaySelectorStub {
exit: cityRelay,
retryAttempt: 0
)
}
}, candidatesResult: nil)
}

/// Returns a relay selector that cannot satisfy constraints .
public static func unsatisfied() -> RelaySelectorStub {
return RelaySelectorStub { _ in
return RelaySelectorStub(selectedRelaysResult: { _ in
throw NoRelaysSatisfyingConstraintsError(.relayConstraintNotMatching)
}, candidatesResult: {
throw NoRelaysSatisfyingConstraintsError(.relayConstraintNotMatching)
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import Foundation
@testable import MullvadREST
import WireGuardKitTypes

enum ServerRelaysResponseStubs {
static let wireguardPortRanges: [[UInt16]] = [[4000, 4001], [5000, 5001]]
static let shadowsocksPortRanges: [[UInt16]] = [[51900, 51949]]
public enum ServerRelaysResponseStubs {
public static let wireguardPortRanges: [[UInt16]] = [[4000, 4001], [5000, 5001]]
public static let shadowsocksPortRanges: [[UInt16]] = [[51900, 51949]]

static let sampleRelays = REST.ServerRelaysResponse(
public static let sampleRelays = REST.ServerRelaysResponse(
locations: [
"es-mad": REST.ServerLocation(
country: "Spain",
Expand Down Expand Up @@ -79,9 +79,9 @@ enum ServerRelaysResponseStubs {
REST.ServerRelay(
hostname: "es1-wireguard",
active: true,
owned: true,
owned: false,
location: "es-mad",
provider: "",
provider: "100TB",
weight: 500,
ipv4AddrIn: .loopback,
ipv6AddrIn: .loopback,
Expand All @@ -95,7 +95,7 @@ enum ServerRelaysResponseStubs {
active: true,
owned: true,
location: "se-got",
provider: "",
provider: "Blix",
weight: 1000,
ipv4AddrIn: .loopback,
ipv6AddrIn: .loopback,
Expand All @@ -109,7 +109,7 @@ enum ServerRelaysResponseStubs {
active: true,
owned: true,
location: "se-sto",
provider: "",
provider: "DataPacket",
weight: 50,
ipv4AddrIn: .loopback,
ipv6AddrIn: .loopback,
Expand Down Expand Up @@ -137,7 +137,7 @@ enum ServerRelaysResponseStubs {
active: true,
owned: true,
location: "us-dal",
provider: "",
provider: "M247",
weight: 100,
ipv4AddrIn: .loopback,
ipv6AddrIn: .loopback,
Expand All @@ -149,9 +149,9 @@ enum ServerRelaysResponseStubs {
REST.ServerRelay(
hostname: "us-nyc-wg-301",
active: true,
owned: true,
owned: false,
location: "us-nyc",
provider: "",
provider: "xtom",
weight: 100,
ipv4AddrIn: .loopback,
ipv6AddrIn: .loopback,
Expand All @@ -165,7 +165,7 @@ enum ServerRelaysResponseStubs {
active: false,
owned: true,
location: "us-nyc",
provider: "",
provider: "Qnax",
weight: 100,
ipv4AddrIn: .loopback,
ipv6AddrIn: .loopback,
Expand Down
19 changes: 19 additions & 0 deletions ios/MullvadREST/Relay/RelayCandidates.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// RelayCandidates.swift
// MullvadVPN
//
// Created by Mojgan on 2025-03-03.
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

public struct RelayCandidates: Equatable, Sendable {
public let entryRelays: [RelayWithLocation<REST.ServerRelay>]?
public let exitRelays: [RelayWithLocation<REST.ServerRelay>]
public init(
entryRelays: [RelayWithLocation<REST.ServerRelay>]?,
exitRelays: [RelayWithLocation<REST.ServerRelay>]
) {
self.entryRelays = entryRelays
self.exitRelays = exitRelays
}
}
5 changes: 5 additions & 0 deletions ios/MullvadREST/Relay/RelaySelectorProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ import MullvadTypes

/// Protocol describing a type that can select a relay.
public protocol RelaySelectorProtocol {
var relayCache: RelayCacheProtocol { get }
func selectRelays(
tunnelSettings: LatestTunnelSettings,
connectionAttemptCount: UInt
) throws -> SelectedRelays

func findCandidates(
tunnelSettings: LatestTunnelSettings
) throws -> RelayCandidates
}

/// Struct describing the selected relay.
Expand Down
46 changes: 39 additions & 7 deletions ios/MullvadREST/Relay/RelaySelectorWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import MullvadSettings
import MullvadTypes

public final class RelaySelectorWrapper: RelaySelectorProtocol, Sendable {
let relayCache: RelayCacheProtocol
public let relayCache: RelayCacheProtocol

public init(relayCache: RelayCacheProtocol) {
self.relayCache = relayCache
Expand All @@ -20,12 +20,7 @@ public final class RelaySelectorWrapper: RelaySelectorProtocol, Sendable {
tunnelSettings: LatestTunnelSettings,
connectionAttemptCount: UInt
) throws -> SelectedRelays {
let obfuscation = try ObfuscatorPortSelector(
relays: try relayCache.read().relays
).obfuscate(
tunnelSettings: tunnelSettings,
connectionAttemptCount: connectionAttemptCount
)
let obfuscation = try prepareObfuscation(for: tunnelSettings, connectionAttemptCount: connectionAttemptCount)

return switch tunnelSettings.tunnelMultihopState {
case .off:
Expand All @@ -44,4 +39,41 @@ public final class RelaySelectorWrapper: RelaySelectorProtocol, Sendable {
).pick()
}
}

public func findCandidates(tunnelSettings: LatestTunnelSettings) throws -> RelayCandidates {
let obfuscation = try prepareObfuscation(for: tunnelSettings, connectionAttemptCount: 0)

let findCandidates: (REST.ServerRelaysResponse, Bool) throws
-> [RelayWithLocation<REST.ServerRelay>] = { relays, daitaEnabled in
try RelaySelector.WireGuard.findCandidates(
by: .any,
in: relays,
filterConstraint: tunnelSettings.relayConstraints.filter,
daitaEnabled: daitaEnabled
)
}

if tunnelSettings.daita.isAutomaticRouting || tunnelSettings.tunnelMultihopState.isEnabled {
let entryCandidates = try findCandidates(
tunnelSettings.tunnelMultihopState.isEnabled ? obfuscation.entryRelays : obfuscation.exitRelays,
tunnelSettings.daita.daitaState.isEnabled
)
let exitCandidates = try findCandidates(obfuscation.exitRelays, false)
return RelayCandidates(entryRelays: entryCandidates, exitRelays: exitCandidates)
} else {
let exitCandidates = try findCandidates(obfuscation.exitRelays, tunnelSettings.daita.daitaState.isEnabled)
return RelayCandidates(entryRelays: nil, exitRelays: exitCandidates)
}
}

private func prepareObfuscation(
for tunnelSettings: LatestTunnelSettings,
connectionAttemptCount: UInt
) throws -> ObfuscatorPortSelection {
let relays = try relayCache.read().relays
return try ObfuscatorPortSelector(relays: relays).obfuscate(
tunnelSettings: tunnelSettings,
connectionAttemptCount: connectionAttemptCount
)
}
}
10 changes: 7 additions & 3 deletions ios/MullvadREST/Relay/RelayWithLocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import Foundation
import MullvadTypes

public struct RelayWithLocation<T: AnyRelay> {
let relay: T
public struct RelayWithLocation<T: AnyRelay & Sendable>: Sendable {
public let relay: T
public let serverLocation: Location

public func matches(location: RelayLocation) -> Bool {
Expand Down Expand Up @@ -60,8 +60,12 @@ public struct RelayWithLocation<T: AnyRelay> {
}
}

extension RelayWithLocation: Equatable {
extension RelayWithLocation: Hashable {
public static func == (lhs: RelayWithLocation<T>, rhs: RelayWithLocation<T>) -> Bool {
lhs.relay.hostname == rhs.relay.hostname
}

public func hash(into hasher: inout Hasher) {
hasher.combine(relay.hostname)
}
}
7 changes: 3 additions & 4 deletions ios/MullvadTypes/RelayConstraint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import Foundation

private let anyConstraint = "any"

public enum RelayConstraint<T>: Codable, Equatable,
CustomDebugStringConvertible where T: Codable & Equatable {
public enum RelayConstraint<T>: Codable, Equatable, CustomDebugStringConvertible, Sendable
where T: Codable & Equatable & Sendable {
case any
case only(T)

Expand All @@ -34,7 +34,7 @@ public enum RelayConstraint<T>: Codable, Equatable,
return output
}

private struct OnlyRepr: Codable {
private struct OnlyRepr: Codable, Sendable {
var only: T
}

Expand All @@ -46,7 +46,6 @@ public enum RelayConstraint<T>: Codable, Equatable,
self = .any
} else {
let onlyVariant = try container.decode(OnlyRepr.self)

self = .only(onlyVariant.only)
}
}
Expand Down
4 changes: 2 additions & 2 deletions ios/MullvadTypes/RelayFilter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

import Foundation

public struct RelayFilter: Codable, Equatable {
public enum Ownership: Codable {
public struct RelayFilter: Codable, Equatable, Sendable {
public enum Ownership: Codable, Sendable {
case any
case owned
case rented
Expand Down
Loading
Loading