1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
//
// RelaySelector+Shadowsocks.swift
// MullvadREST
//
// Created by Mojgan on 2024-05-17.
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
import Foundation
import MullvadTypes
extension RelaySelector {
public enum Shadowsocks {
/**
Returns random shadowsocks TCP bridge, otherwise `nil` if there are no shadowdsocks bridges.
*/
public static func tcpBridge(from relays: REST.ServerRelaysResponse) -> REST.ServerShadowsocks? {
relays.bridge.shadowsocks.filter { $0.protocol == "tcp" }.randomElement()
}
/// Return a random Shadowsocks bridge relay, or `nil` if no relay were found.
///
/// Non `active` relays are filtered out.
/// - Parameter relays: The list of relays to randomly select from.
/// - Returns: A Shadowsocks relay or `nil` if no active relay were found.
public static func relay(from relaysResponse: REST.ServerRelaysResponse) -> REST.BridgeRelay? {
relaysResponse.bridge.relays.filter { $0.active }.randomElement()
}
/// Returns the closest Shadowsocks relay using the given `location`, or a random relay if `constraints` were
/// unsatisfiable.
///
/// - Parameters:
/// - location: The user selected `location`
/// - port: The user selected port
/// - filter: The user filtered criteria
/// - relays: The list of relays to randomly select from.
/// - Returns: A Shadowsocks relay or `nil` if no active relay were found.
public static func closestRelay(
location: RelayConstraint<UserSelectedRelays>,
port: RelayConstraint<UInt16>,
filter: RelayConstraint<RelayFilter>,
in relaysResponse: REST.ServerRelaysResponse
) -> REST.BridgeRelay? {
let mappedBridges = RelayWithLocation.locateRelays(
relays: relaysResponse.bridge.relays,
locations: relaysResponse.locations
)
let filteredRelays =
(try? applyConstraints(
location,
filterConstraint: filter,
daitaEnabled: false,
relays: mappedBridges
)) ?? []
guard filteredRelays.isEmpty == false else { return relay(from: relaysResponse) }
// Compute the midpoint location from all the filtered relays
// Take *either* the first five relays, OR the relays below maximum bridge distance
// sort all of them by Haversine distance from the computed midpoint location
// then use the roulette selection to pick a bridge
let midpointDistance = Midpoint.location(in: filteredRelays.map { $0.serverLocation.geoCoordinate })
let maximumBridgeDistance = 1500.0
let relaysWithDistance = filteredRelays.map {
RelayWithDistance(
relay: $0.relay,
distance: Haversine.distance(
midpointDistance.latitude,
midpointDistance.longitude,
$0.serverLocation.latitude,
$0.serverLocation.longitude
)
)
}.sorted {
$0.distance < $1.distance
}.filter {
$0.distance <= maximumBridgeDistance
}.prefix(5)
var greatestDistance = 0.0
relaysWithDistance.forEach {
if $0.distance > greatestDistance {
greatestDistance = $0.distance
}
}
let randomRelay = rouletteSelection(
relays: Array(relaysWithDistance),
weightFunction: { relay in
UInt64(1 + greatestDistance - relay.distance)
})
return randomRelay?.relay ?? filteredRelays.randomElement()?.relay
}
}
}
|