Skip to content

Commit 5311870

Browse files
Jon Peterssonpinkisemils
Jon Petersson
authored andcommitted
Prevent user from selecting same entry and exit relay for multihop
1 parent c64351f commit 5311870

File tree

6 files changed

+81
-50
lines changed

6 files changed

+81
-50
lines changed

ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift

-4
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,6 @@ extension AddLocationsDataSource {
161161
func nodeShouldBeSelected(_ node: LocationNode) -> Bool {
162162
customListLocationNode.children.contains(node)
163163
}
164-
165-
func excludedRelayTitle(_ node: LocationNode) -> String? {
166-
nil // N/A
167-
}
168164
}
169165

170166
// MARK: - Toggle selection in table view

ios/MullvadVPN/View controllers/SelectLocation/LocationCell.swift

+5-6
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,6 @@ class LocationCell: UITableViewCell {
209209

210210
private func updateDisabled(_ isDisabled: Bool) {
211211
locationLabel.alpha = isDisabled ? 0.2 : 1
212-
collapseButton.alpha = isDisabled ? 0.2 : 1
213212

214213
if isDisabled {
215214
accessibilityTraits.insert(.notEnabled)
@@ -339,14 +338,14 @@ extension LocationCell {
339338
}
340339
}
341340

342-
setExcludedRelayTitle(item.excludedRelayTitle)
343341
setBehavior(behavior)
344342
}
345343

346-
private func setExcludedRelayTitle(_ title: String?) {
347-
if let title {
348-
locationLabel.text! += " (\(title))"
349-
updateDisabled(true)
344+
func setExcluded(relayTitle: String? = nil) {
345+
updateDisabled(true)
346+
347+
if let relayTitle {
348+
locationLabel.text! += " (\(relayTitle))"
350349
}
351350
}
352351

ios/MullvadVPN/View controllers/SelectLocation/LocationCellViewModel.swift

+52-7
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,7 @@ struct LocationCellViewModel: Hashable {
3131
}
3232

3333
extension [LocationCellViewModel] {
34-
mutating func addSubNodes(
35-
from item: LocationCellViewModel,
36-
at indexPath: IndexPath,
37-
excludedRelayTitleCallback: ((LocationNode) -> String?)?
38-
) {
34+
mutating func addSubNodes(from item: LocationCellViewModel, at indexPath: IndexPath) {
3935
let section = LocationSection.allCases[indexPath.section]
4036
let row = indexPath.row + 1
4137

@@ -44,8 +40,7 @@ extension [LocationCellViewModel] {
4440
section: section,
4541
node: $0,
4642
indentationLevel: item.indentationLevel + 1,
47-
isSelected: false,
48-
excludedRelayTitle: excludedRelayTitleCallback?($0)
43+
isSelected: false
4944
)
5045
}
5146

@@ -65,3 +60,53 @@ extension [LocationCellViewModel] {
6560
}
6661
}
6762
}
63+
64+
extension LocationCellViewModel {
65+
/* Exclusion of other locations in the same node tree as the currently excluded location
66+
happens when there are no more hosts in that tree that can be selected.
67+
We check this by doing the following, in order:
68+
69+
1. Count host names in the tree. More than one means that there are other locations than
70+
the excluded one for the relay selector to choose from. No exlusion.
71+
72+
2. Count host names in the excluded node. More than one means that there are multiple
73+
locations for the relay selector to choose from. No exlusion.
74+
75+
3. Check existance of a location in the tree that match the currently excluded location.
76+
No match means no exclusion.
77+
*/
78+
func shouldExcludeLocation(_ excludedLocation: LocationCellViewModel?) -> Bool {
79+
guard let excludedLocation else {
80+
return false
81+
}
82+
83+
var proxyNode = RootLocationNode(children: [node])
84+
let allLocations = Set(proxyNode.flattened.flatMap { $0.locations })
85+
let hostCount = allLocations.filter { location in
86+
if case .hostname = location { true } else { false }
87+
}.count
88+
89+
// If the there are more than one selectable relay in the current node we don't need
90+
// show this in the location tree and can return early.
91+
guard hostCount == 1 else { return false }
92+
93+
let proxyExcludedNode = RootLocationNode(children: [excludedLocation.node])
94+
let allExcludedLocations = Set(proxyExcludedNode.flattened.flatMap { $0.locations })
95+
let excludedHostCount = allExcludedLocations.filter { location in
96+
if case .hostname = location { true } else { false }
97+
}.count
98+
99+
// If the there are more than one selectable relay in the excluded node we don't need
100+
// show this in the location tree and can return early.
101+
guard excludedHostCount == 1 else { return false }
102+
103+
var containsExcludedLocation = false
104+
if allLocations.contains(where: { allExcludedLocations.contains($0) }) {
105+
containsExcludedLocation = true
106+
}
107+
108+
// If the tree doesn't contain the excluded node we do nothing, otherwise the
109+
// required conditions have now all been met.
110+
return containsExcludedLocation
111+
}
112+
}

ios/MullvadVPN/View controllers/SelectLocation/LocationDataSource.swift

+21-18
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,8 @@ final class LocationDataSource:
142142
func setSelectedRelays(_ selectedRelays: RelaySelection) {
143143
selectedLocation = mapSelection(from: selectedRelays.selected)
144144

145-
if selectedRelays.hasExcludedRelay {
146-
excludedLocation = mapSelection(from: selectedRelays.excluded)
147-
excludedLocation?.excludedRelayTitle = selectedRelays.excludedTitle
148-
}
145+
excludedLocation = mapSelection(from: selectedRelays.excluded)
146+
excludedLocation?.excludedRelayTitle = selectedRelays.excludedTitle
149147

150148
tableView.reloadData()
151149
}
@@ -246,9 +244,24 @@ final class LocationDataSource:
246244
}
247245

248246
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
249-
// swiftlint:disable:next force_cast
250-
let cell = super.tableView(tableView, cellForRowAt: indexPath) as! LocationCell
247+
let cell = super.tableView(tableView, cellForRowAt: indexPath)
248+
guard let cell = cell as? LocationCell, let item = itemIdentifier(for: indexPath) else {
249+
return cell
250+
}
251+
251252
cell.delegate = self
253+
254+
if item.shouldExcludeLocation(excludedLocation) {
255+
// Only host locations should have an excluded title. Since custom list nodes contain
256+
// all locations of all child nodes, its first location could possibly be a host.
257+
// Therefore we need to check for that as well.
258+
if case .hostname = item.node.locations.first, !(item.node is CustomListLocationNode) {
259+
cell.setExcluded(relayTitle: excludedLocation?.excludedRelayTitle)
260+
} else {
261+
cell.setExcluded()
262+
}
263+
}
264+
252265
return cell
253266
}
254267
}
@@ -263,14 +276,6 @@ extension LocationDataSource {
263276
func nodeShouldBeSelected(_ node: LocationNode) -> Bool {
264277
false // N/A
265278
}
266-
267-
func excludedRelayTitle(_ node: LocationNode) -> String? {
268-
if nodeMatchesExcludedLocation(node) {
269-
excludedLocation?.excludedRelayTitle
270-
} else {
271-
nil
272-
}
273-
}
274279
}
275280

276281
extension LocationDataSource: UITableViewDelegate {
@@ -307,7 +312,7 @@ extension LocationDataSource: UITableViewDelegate {
307312

308313
func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
309314
guard let item = itemIdentifier(for: indexPath) else { return false }
310-
return !nodeMatchesExcludedLocation(item.node) && item.node.isActive
315+
return !item.shouldExcludeLocation(excludedLocation) && item.node.isActive
311316
}
312317

313318
func tableView(_ tableView: UITableView, indentationLevelForRowAt indexPath: IndexPath) -> Int {
@@ -360,9 +365,7 @@ extension LocationDataSource: LocationCellDelegate {
360365
guard let indexPath = tableView.indexPath(for: cell),
361366
let item = itemIdentifier(for: indexPath) else { return }
362367

363-
let items = toggledItems(for: cell, excludedRelayTitleCallback: { node in
364-
self.excludedRelayTitle(node)
365-
})
368+
let items = toggledItems(for: cell)
366369

367370
updateDataSnapshot(with: items, reloadExisting: true, completion: {
368371
self.scroll(to: item, animated: true)

ios/MullvadVPN/View controllers/SelectLocation/LocationDiffableDataSourceProtocol.swift

+3-8
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ protocol LocationDiffableDataSourceProtocol: UITableViewDiffableDataSource<Locat
1414
var sections: [LocationSection] { get }
1515
func nodeShowsChildren(_ node: LocationNode) -> Bool
1616
func nodeShouldBeSelected(_ node: LocationNode) -> Bool
17-
func excludedRelayTitle(_ node: LocationNode) -> String?
1817
}
1918

2019
extension LocationDiffableDataSourceProtocol {
@@ -40,10 +39,7 @@ extension LocationDiffableDataSourceProtocol {
4039
}
4140
}
4241

43-
func toggledItems(
44-
for cell: LocationCell,
45-
excludedRelayTitleCallback: ((LocationNode) -> String?)? = nil
46-
) -> [[LocationCellViewModel]] {
42+
func toggledItems(for cell: LocationCell) -> [[LocationCellViewModel]] {
4743
guard let indexPath = tableView.indexPath(for: cell),
4844
let item = itemIdentifier(for: indexPath) else { return [[]] }
4945

@@ -54,7 +50,7 @@ extension LocationDiffableDataSourceProtocol {
5450
item.node.showsChildren = !isExpanded
5551

5652
if !isExpanded {
57-
locationList.addSubNodes(from: item, at: indexPath, excludedRelayTitleCallback: excludedRelayTitleCallback)
53+
locationList.addSubNodes(from: item, at: indexPath)
5854
} else {
5955
locationList.removeSubNodes(from: item.node)
6056
}
@@ -103,8 +99,7 @@ extension LocationDiffableDataSourceProtocol {
10399
section: section,
104100
node: childNode,
105101
indentationLevel: indentationLevel,
106-
isSelected: nodeShouldBeSelected(childNode),
107-
excludedRelayTitle: excludedRelayTitle(childNode)
102+
isSelected: nodeShouldBeSelected(childNode)
108103
)
109104
)
110105

ios/MullvadVPN/View controllers/SelectLocation/RelaySelection.swift

-7
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,4 @@ struct RelaySelection {
1212
var selected: UserSelectedRelays?
1313
var excluded: UserSelectedRelays?
1414
var excludedTitle: String?
15-
16-
var hasExcludedRelay: Bool {
17-
if excluded?.locations.count == 1, case .hostname = excluded?.locations.first {
18-
return true
19-
}
20-
return false
21-
}
2215
}

0 commit comments

Comments
 (0)