diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-09-01 10:42:49 +0200 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-09-03 15:52:43 +0200 |
| commit | e266d72875224a0522d50e55f0555a38deb45ff3 (patch) | |
| tree | d5d07d35aab1c90f094fba9d77b935ce23071b8b /android | |
| parent | f9693d2fe31c0c50027f69f5bd930d30dfa5c764 (diff) | |
| download | mullvadvpn-e266d72875224a0522d50e55f0555a38deb45ff3.tar.xz mullvadvpn-e266d72875224a0522d50e55f0555a38deb45ff3.zip | |
Add UI support for QUIC setting
Diffstat (limited to 'android')
59 files changed, 532 insertions, 229 deletions
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt index 27b5951cea..8e46a555c9 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt @@ -22,7 +22,7 @@ private val DUMMY_RELAY_1 = provider = ProviderId("PROVIDER RENTED"), ownership = Ownership.Rented, daita = false, - quic = false, + quic = null, ) private val DUMMY_RELAY_2 = RelayItem.Location.Relay( @@ -35,7 +35,7 @@ private val DUMMY_RELAY_2 = provider = ProviderId("PROVIDER OWNED"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) private val DUMMY_RELAY_CITY_1 = RelayItem.Location.City( diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterRow.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterRow.kt index 5ac7c765f0..710c4a5cac 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterRow.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterRow.kt @@ -65,6 +65,7 @@ fun FilterRow( is FilterChip.Daita -> DaitaFilterChip() is FilterChip.Entry -> EntryFilterChip() is FilterChip.Exit -> ExitFilterChip() + is FilterChip.Quic -> QuicFilterChip() } } } @@ -115,6 +116,15 @@ fun ExitFilterChip() { ) } +@Composable +fun QuicFilterChip() { + MullvadFilterChip( + text = stringResource(id = R.string.quic), + onRemoveClick = {}, + enabled = false, + ) +} + private fun Ownership.stringResources(): Int = when (this) { Ownership.MullvadOwned -> R.string.owned diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt index aa7416218a..bc0c52db83 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt @@ -122,6 +122,7 @@ private fun ObfuscationMode.toTitle() = ObfuscationMode.Off -> stringResource(id = R.string.off) ObfuscationMode.Udp2Tcp -> stringResource(id = R.string.upd_over_tcp) ObfuscationMode.Shadowsocks -> stringResource(id = R.string.shadowsocks) + ObfuscationMode.Quic -> stringResource(id = R.string.quic) } @Composable diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt index a485dbe9d9..98abee9589 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt @@ -130,7 +130,8 @@ private fun FeatureIndicator.text(): String { FeatureIndicator.QUANTUM_RESISTANCE -> R.string.feature_quantum_resistant FeatureIndicator.SPLIT_TUNNELING -> R.string.split_tunneling FeatureIndicator.SHADOWSOCKS, - FeatureIndicator.UDP_2_TCP -> R.string.feature_udp_2_tcp + FeatureIndicator.UDP_2_TCP, + FeatureIndicator.QUIC -> R.string.feature_obfuscation FeatureIndicator.LAN_SHARING -> R.string.local_network_sharing FeatureIndicator.DNS_CONTENT_BLOCKERS -> R.string.dns_content_blockers FeatureIndicator.CUSTOM_DNS -> R.string.feature_custom_dns diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt index 8f7775c613..8cb0bde801 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt @@ -791,6 +791,6 @@ private fun FeatureIndicator.destination() = FeatureIndicator.LAN_SHARING, FeatureIndicator.DNS_CONTENT_BLOCKERS, FeatureIndicator.CUSTOM_DNS, - FeatureIndicator.CUSTOM_MTU -> - VpnSettingsDestination(scrollToFeature = this, isModal = true) + FeatureIndicator.CUSTOM_MTU, + FeatureIndicator.QUIC -> VpnSettingsDestination(scrollToFeature = this, isModal = true) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt index ea45861c96..6aa1788ae9 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt @@ -475,7 +475,8 @@ fun VpnSettingsContent( val initialIndexFocus = when (initialScrollToFeature) { FeatureIndicator.UDP_2_TCP, - FeatureIndicator.SHADOWSOCKS -> VpnSettingItem.ObfuscationHeader::class + FeatureIndicator.SHADOWSOCKS, + FeatureIndicator.QUIC -> VpnSettingItem.ObfuscationHeader::class FeatureIndicator.LAN_SHARING -> VpnSettingItem.LocalNetworkSharingSetting::class FeatureIndicator.QUANTUM_RESISTANCE -> VpnSettingItem.QuantumResistanceHeader::class FeatureIndicator.DNS_CONTENT_BLOCKERS -> VpnSettingItem.DnsContentBlockersHeader::class @@ -852,6 +853,16 @@ fun VpnSettingsContent( ) } + is VpnSettingItem.ObfuscationItem.Quic -> + item(key = it::class.simpleName) { + SelectableCell( + title = stringResource(id = R.string.quic), + isSelected = it.selected, + modifier = Modifier.animateItem(), + onCellClicked = { onSelectObfuscationMode(ObfuscationMode.Quic) }, + ) + } + is VpnSettingItem.QuantumItem -> item(key = it::class.simpleName + it.quantumResistantState) { SelectableCell( diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingItem.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingItem.kt index 517959e1a2..3ce6637115 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingItem.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingItem.kt @@ -104,6 +104,8 @@ sealed interface VpnSettingItem { data class UdpOverTcp(override val selected: Boolean, val port: Constraint<Port>) : ObfuscationItem + data class Quic(override val selected: Boolean) : ObfuscationItem + data class Off(override val selected: Boolean) : ObfuscationItem } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt index bca87015b4..08fafc56c0 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt @@ -205,6 +205,10 @@ data class VpnSettingsUiState(val settings: List<VpnSettingItem>, val isModal: B ) ) add(VpnSettingItem.Divider) + add( + VpnSettingItem.ObfuscationItem.Quic(obfuscationMode == ObfuscationMode.Quic) + ) + add(VpnSettingItem.Divider) add(VpnSettingItem.ObfuscationItem.Off(obfuscationMode == ObfuscationMode.Off)) add(VpnSettingItem.Spacer) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt index f493aa97cb..c59b0124c3 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt @@ -2,8 +2,10 @@ package net.mullvad.mullvadvpn.relaylist import net.mullvad.mullvadvpn.lib.model.Constraint import net.mullvad.mullvadvpn.lib.model.GeoLocationId +import net.mullvad.mullvadvpn.lib.model.IpVersion import net.mullvad.mullvadvpn.lib.model.Ownership import net.mullvad.mullvadvpn.lib.model.Providers +import net.mullvad.mullvadvpn.lib.model.Quic import net.mullvad.mullvadvpn.lib.model.RelayItem fun RelayItem.children(): List<RelayItem> { @@ -57,13 +59,18 @@ fun RelayItem.CustomList.filter( ownership: Constraint<Ownership>, providers: Constraint<Providers>, daita: Boolean, + quic: Boolean, + ipVersion: Constraint<IpVersion>, ): RelayItem.CustomList { val newLocations = locations.mapNotNull { when (it) { - is RelayItem.Location.Country -> it.filter(ownership, providers, daita) - is RelayItem.Location.City -> it.filter(ownership, providers, daita) - is RelayItem.Location.Relay -> it.filter(ownership, providers, daita) + is RelayItem.Location.Country -> + it.filter(ownership, providers, daita, quic, ipVersion) + is RelayItem.Location.City -> + it.filter(ownership, providers, daita, quic, ipVersion) + is RelayItem.Location.Relay -> + it.filter(ownership, providers, daita, quic, ipVersion) } } return copy(locations = newLocations) @@ -73,8 +80,10 @@ fun RelayItem.Location.Country.filter( ownership: Constraint<Ownership>, providers: Constraint<Providers>, daita: Boolean, + quic: Boolean, + ipVersion: Constraint<IpVersion>, ): RelayItem.Location.Country? { - val cities = cities.mapNotNull { it.filter(ownership, providers, daita) } + val cities = cities.mapNotNull { it.filter(ownership, providers, daita, quic, ipVersion) } return if (cities.isNotEmpty()) { this.copy(cities = cities) } else { @@ -86,8 +95,10 @@ private fun RelayItem.Location.City.filter( ownership: Constraint<Ownership>, providers: Constraint<Providers>, daita: Boolean, + quic: Boolean, + ipVersion: Constraint<IpVersion>, ): RelayItem.Location.City? { - val relays = relays.mapNotNull { it.filter(ownership, providers, daita) } + val relays = relays.mapNotNull { it.filter(ownership, providers, daita, quic, ipVersion) } return if (relays.isNotEmpty()) { this.copy(relays = relays) } else { @@ -95,15 +106,38 @@ private fun RelayItem.Location.City.filter( } } -private fun RelayItem.Location.Relay.hasMatchingDaitaSetting(filterDaita: Boolean): Boolean = - if (filterDaita) daita else true +private fun RelayItem.Location.Relay.requiredFeatures( + requireDaita: Boolean, + requireQuic: Boolean, + ipVersion: Constraint<IpVersion>, +): Boolean = + when { + requireDaita && requireQuic -> daita && quic?.supports(ipVersion) == true + requireDaita -> daita + requireQuic -> quic?.supports(ipVersion) == true + else -> true + } + +private fun Quic.supports(ipVersion: Constraint<IpVersion>) = + when (ipVersion.getOrNull()) { + IpVersion.IPV4 -> supportsIpv4 + IpVersion.IPV6 -> supportsIpv6 + else -> inAddresses.isNotEmpty() + } private fun RelayItem.Location.Relay.filter( ownership: Constraint<Ownership>, providers: Constraint<Providers>, daita: Boolean, + quic: Boolean, + ipVersion: Constraint<IpVersion>, ): RelayItem.Location.Relay? = - if (hasMatchingDaitaSetting(daita) && hasOwnership(ownership) && hasProvider(providers)) this + if ( + requiredFeatures(daita, quic, ipVersion) && + hasOwnership(ownership) && + hasProvider(providers) + ) + this else null fun List<RelayItem.Location.Country>.findByGeoLocationId( diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt index 5e7a82d9ed..12ec3a296c 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt @@ -10,7 +10,10 @@ import net.mullvad.mullvadvpn.lib.model.Providers import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.repository.RelayListFilterRepository import net.mullvad.mullvadvpn.repository.SettingsRepository +import net.mullvad.mullvadvpn.util.isDaitaAndDirectOnly +import net.mullvad.mullvadvpn.util.isQuicEnabled import net.mullvad.mullvadvpn.util.shouldFilterByDaita +import net.mullvad.mullvadvpn.util.shouldFilterByQuic typealias ModelOwnership = Ownership @@ -30,7 +33,7 @@ class FilterChipUseCase( selectedOwnership = selectedOwnership, selectedConstraintProviders = selectedConstraintProviders, providerToOwnerships = providerOwnership, - daitaDirectOnly = settings?.daitaAndDirectOnly() == true, + settings = settings, relayListType = relayListType, ) } @@ -39,7 +42,7 @@ class FilterChipUseCase( selectedOwnership: Constraint<Ownership>, selectedConstraintProviders: Constraint<Providers>, providerToOwnerships: Map<ProviderId, Set<Ownership>>, - daitaDirectOnly: Boolean, + settings: Settings?, relayListType: RelayListType, ): List<FilterChip> { val ownershipFilter = selectedOwnership.getOrNull() @@ -70,18 +73,19 @@ class FilterChipUseCase( } if ( shouldFilterByDaita( - daitaDirectOnly = daitaDirectOnly, + daitaDirectOnly = settings?.isDaitaAndDirectOnly() == true, relayListType = relayListType, ) ) { add(FilterChip.Daita) } + if ( + shouldFilterByQuic(settings?.isQuicEnabled() == true, relayListType = relayListType) + ) { + add(FilterChip.Quic) + } } } - - private fun Settings.daitaAndDirectOnly() = - tunnelOptions.wireguard.daitaSettings.enabled && - tunnelOptions.wireguard.daitaSettings.directOnly } sealed interface FilterChip { @@ -94,4 +98,6 @@ sealed interface FilterChip { data object Entry : FilterChip data object Exit : FilterChip + + data object Quic : FilterChip } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt index 875d63c1ce..2681cfa3ad 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt @@ -3,16 +3,20 @@ package net.mullvad.mullvadvpn.usecase import kotlinx.coroutines.flow.combine import net.mullvad.mullvadvpn.compose.state.RelayListType import net.mullvad.mullvadvpn.lib.model.Constraint +import net.mullvad.mullvadvpn.lib.model.IpVersion import net.mullvad.mullvadvpn.lib.model.Ownership import net.mullvad.mullvadvpn.lib.model.Providers import net.mullvad.mullvadvpn.lib.model.RelayItem -import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.relaylist.filter import net.mullvad.mullvadvpn.repository.RelayListFilterRepository import net.mullvad.mullvadvpn.repository.RelayListRepository import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository +import net.mullvad.mullvadvpn.util.ipVersionConstraint +import net.mullvad.mullvadvpn.util.isDaitaAndDirectOnly +import net.mullvad.mullvadvpn.util.isQuicEnabled import net.mullvad.mullvadvpn.util.shouldFilterByDaita +import net.mullvad.mullvadvpn.util.shouldFilterByQuic class FilteredRelayListUseCase( private val relayListRepository: RelayListRepository, @@ -33,9 +37,15 @@ class FilteredRelayListUseCase( providers = selectedProviders, shouldFilterByDaita = shouldFilterByDaita( - daitaDirectOnly = settings?.daitaAndDirectOnly() == true, + daitaDirectOnly = settings?.isDaitaAndDirectOnly() == true, relayListType = relayListType, ), + shouldFilterByQuic = + shouldFilterByQuic( + settings?.isQuicEnabled() == true, + relayListType = relayListType, + ), + constraintIpVersion = settings?.ipVersionConstraint() ?: Constraint.Any, ) } @@ -43,9 +53,15 @@ class FilteredRelayListUseCase( ownership: Constraint<Ownership>, providers: Constraint<Providers>, shouldFilterByDaita: Boolean, - ) = mapNotNull { it.filter(ownership, providers, shouldFilterByDaita) } - - private fun Settings.daitaAndDirectOnly() = - tunnelOptions.wireguard.daitaSettings.enabled && - tunnelOptions.wireguard.daitaSettings.directOnly + shouldFilterByQuic: Boolean, + constraintIpVersion: Constraint<IpVersion>, + ) = mapNotNull { + it.filter( + ownership, + providers, + shouldFilterByDaita, + shouldFilterByQuic, + constraintIpVersion, + ) + } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/RecentsUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/RecentsUseCase.kt index 7357068a56..ae2cea104d 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/RecentsUseCase.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/RecentsUseCase.kt @@ -2,6 +2,7 @@ package net.mullvad.mullvadvpn.usecase import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.map import net.mullvad.mullvadvpn.compose.state.MultihopRelayListType import net.mullvad.mullvadvpn.compose.state.RelayListType @@ -22,31 +23,42 @@ class RecentsUseCase( private val settingsRepository: SettingsRepository, ) { - operator fun invoke(): Flow<List<Hop>?> = + operator fun invoke(isMultihop: Boolean): Flow<List<Hop>?> = + if (isMultihop) { + multiHopRecents() + } else { + singleHopRecents() + } + + private fun singleHopRecents(): Flow<List<Hop.Single<RelayItem>>?> = combine( - recents(), + recents().map { it?.filterIsInstance<Recent.Singlehop>() }, + filteredRelayListUseCase(RelayListType.Single), + customListsRelayItemUseCase(RelayListType.Single), + ) { recents, relayList, customList -> + recents?.mapNotNull { recent -> + val relayListItem = recent.location.findItem(customList, relayList) + + relayListItem?.let { Hop.Single(it) } + } + } + + private fun multiHopRecents(): Flow<List<Hop.Multi>?> = + combine( + recents().map { it?.filterIsInstance<Recent.Multihop>() }, filteredRelayListUseCase(RelayListType.Multihop(MultihopRelayListType.ENTRY)), customListsRelayItemUseCase(RelayListType.Multihop(MultihopRelayListType.ENTRY)), filteredRelayListUseCase(RelayListType.Multihop(MultihopRelayListType.EXIT)), customListsRelayItemUseCase(RelayListType.Multihop(MultihopRelayListType.EXIT)), ) { recents, entryRelayList, entryCustomLists, exitRelayList, exitCustomLists -> recents?.mapNotNull { recent -> - when (recent) { - is Recent.Multihop -> { - val entry = recent.entry.findItem(entryCustomLists, entryRelayList) - val exit = recent.exit.findItem(exitCustomLists, exitRelayList) - - if (entry != null && exit != null) { - Hop.Multi(entry, exit) - } else { - null - } - } - is Recent.Singlehop -> { - val relayListItem = recent.location.findItem(exitCustomLists, exitRelayList) + val entry = recent.entry.findItem(entryCustomLists, entryRelayList) + val exit = recent.exit.findItem(exitCustomLists, exitRelayList) - relayListItem?.let { Hop.Single(it) } - } + if (entry != null && exit != null) { + Hop.Multi(entry, exit) + } else { + null } } } @@ -66,7 +78,7 @@ class RecentsUseCase( relayList: List<RelayItem.Location.Country>, ): RelayItem? = when (this) { - is CustomListId -> customLists.firstOrNull { this == it.id } + is CustomListId -> customLists.firstOrNull { this == it.id && it.hasChildren } is GeoLocationId -> relayList.findByGeoLocationId(this) } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt index 243c0c643a..6604a76805 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt @@ -4,15 +4,19 @@ import kotlin.collections.mapNotNull import kotlinx.coroutines.flow.combine import net.mullvad.mullvadvpn.compose.state.RelayListType import net.mullvad.mullvadvpn.lib.model.Constraint +import net.mullvad.mullvadvpn.lib.model.IpVersion import net.mullvad.mullvadvpn.lib.model.Ownership import net.mullvad.mullvadvpn.lib.model.Providers import net.mullvad.mullvadvpn.lib.model.RelayItem -import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.relaylist.filter import net.mullvad.mullvadvpn.repository.RelayListFilterRepository import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository +import net.mullvad.mullvadvpn.util.ipVersionConstraint +import net.mullvad.mullvadvpn.util.isDaitaAndDirectOnly +import net.mullvad.mullvadvpn.util.isQuicEnabled import net.mullvad.mullvadvpn.util.shouldFilterByDaita +import net.mullvad.mullvadvpn.util.shouldFilterByQuic class FilterCustomListsRelayItemUseCase( private val customListsRelayItemUseCase: CustomListsRelayItemUseCase, @@ -34,9 +38,15 @@ class FilterCustomListsRelayItemUseCase( providers = selectedProviders, daita = shouldFilterByDaita( - daitaDirectOnly = settings?.daitaAndDirectOnly() == true, + daitaDirectOnly = settings?.isDaitaAndDirectOnly() == true, relayListType = relayListType, ), + quic = + shouldFilterByQuic( + settings?.isQuicEnabled() == true, + relayListType = relayListType, + ), + ipVersion = settings?.ipVersionConstraint() ?: Constraint.Any, ) } @@ -44,9 +54,9 @@ class FilterCustomListsRelayItemUseCase( ownership: Constraint<Ownership>, providers: Constraint<Providers>, daita: Boolean, - ) = mapNotNull { it.filter(ownership, providers, daita = daita) } - - private fun Settings.daitaAndDirectOnly() = - tunnelOptions.wireguard.daitaSettings.enabled && - tunnelOptions.wireguard.daitaSettings.directOnly + quic: Boolean, + ipVersion: Constraint<IpVersion>, + ) = mapNotNull { + it.filter(ownership, providers, daita = daita, quic = quic, ipVersion = ipVersion) + } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Daita.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Filter.kt index e4a0a9957a..4fa7ba231c 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Daita.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Filter.kt @@ -9,3 +9,10 @@ fun shouldFilterByDaita(daitaDirectOnly: Boolean, relayListType: RelayListType) is RelayListType.Multihop -> daitaDirectOnly && relayListType.multihopRelayListType == MultihopRelayListType.ENTRY } + +fun shouldFilterByQuic(isQuicEnabled: Boolean, relayListType: RelayListType) = + when (relayListType) { + RelayListType.Single -> isQuicEnabled + is RelayListType.Multihop -> + isQuicEnabled && relayListType.multihopRelayListType == MultihopRelayListType.ENTRY + } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Settings.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Settings.kt new file mode 100644 index 0000000000..4da463ab51 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Settings.kt @@ -0,0 +1,36 @@ +package net.mullvad.mullvadvpn.util + +import net.mullvad.mullvadvpn.lib.model.DnsState +import net.mullvad.mullvadvpn.lib.model.ObfuscationMode +import net.mullvad.mullvadvpn.lib.model.Settings + +fun Settings.quantumResistant() = tunnelOptions.wireguard.quantumResistant + +fun Settings.isCustomDnsEnabled() = tunnelOptions.dnsOptions.state == DnsState.Custom + +fun Settings.customDnsAddresses() = tunnelOptions.dnsOptions.customOptions.addresses + +fun Settings.contentBlockersSettings() = tunnelOptions.dnsOptions.defaultOptions + +fun Settings.selectedObfuscationMode() = obfuscationSettings.selectedObfuscationMode + +fun Settings.wireguardPort() = relaySettings.relayConstraints.wireguardConstraints.port + +fun Settings.deviceIpVersion() = relaySettings.relayConstraints.wireguardConstraints.ipVersion + +fun Settings.isDaitaAndDirectOnly() = isDaitaEnabled() && isDaitaDirectOnly() + +fun Settings.isQuicEnabled() = obfuscationSettings.selectedObfuscationMode == ObfuscationMode.Quic + +fun Settings.ipVersionConstraint() = relaySettings.relayConstraints.wireguardConstraints.ipVersion + +fun Settings.isDaitaEnabled() = daitaSettings().enabled + +fun Settings.isDaitaDirectOnly() = daitaSettings().directOnly + +fun Settings.shadowSocksPort() = obfuscationSettings.shadowsocks.port + +fun Settings.isMultihopEnabled() = + relaySettings.relayConstraints.wireguardConstraints.isMultihopEnabled + +private fun Settings.daitaSettings() = tunnelOptions.wireguard.daitaSettings diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DaitaViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DaitaViewModel.kt index f941b26455..fdaa9c7eb6 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DaitaViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DaitaViewModel.kt @@ -10,9 +10,10 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.compose.state.DaitaUiState -import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.util.Lc +import net.mullvad.mullvadvpn.util.isDaitaDirectOnly +import net.mullvad.mullvadvpn.util.isDaitaEnabled import net.mullvad.mullvadvpn.util.toLc class DaitaViewModel( @@ -27,8 +28,8 @@ class DaitaViewModel( .filterNotNull() .map { settings -> DaitaUiState( - daitaEnabled = settings.daitaSettings().enabled, - directOnly = settings.daitaSettings().directOnly, + daitaEnabled = settings.isDaitaEnabled(), + directOnly = settings.isDaitaDirectOnly(), navArgs.isModal, ) .toLc<Boolean, DaitaUiState>() @@ -46,6 +47,4 @@ class DaitaViewModel( fun setDirectOnly(enable: Boolean) { viewModelScope.launch { settingsRepository.setDaitaDirectOnly(enable) } } - - private fun Settings.daitaSettings() = tunnelOptions.wireguard.daitaSettings } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DnsDialogViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DnsDialogViewModel.kt index c79682039b..d5d733e8da 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DnsDialogViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DnsDialogViewModel.kt @@ -24,6 +24,7 @@ import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.usecase.DeleteCustomDnsUseCase +import net.mullvad.mullvadvpn.util.customDnsAddresses import org.apache.commons.validator.routines.InetAddressValidator sealed interface DnsDialogSideEffect { @@ -80,7 +81,9 @@ class DnsDialogViewModel( DnsDialogViewState( input = input, validationError = - input.validateDnsEntry(currentIndex, settings.addresses()).leftOrNull(), + input + .validateDnsEntry(currentIndex, settings.customDnsAddresses()) + .leftOrNull(), isAllowLanEnabled = settings.allowLan, isIpv6Enabled = settings.tunnelOptions.genericOptions.enableIpv6, index = currentIndex, @@ -169,8 +172,6 @@ class DnsDialogViewModel( } } - private fun Settings.addresses() = tunnelOptions.dnsOptions.customOptions.addresses - companion object { private const val EMPTY_STRING = "" } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ShadowsocksSettingsViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ShadowsocksSettingsViewModel.kt index fa0e886fee..c7c9e8d900 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ShadowsocksSettingsViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ShadowsocksSettingsViewModel.kt @@ -16,9 +16,9 @@ import net.mullvad.mullvadvpn.compose.state.ShadowsocksSettingsUiState import net.mullvad.mullvadvpn.constant.SHADOWSOCKS_PRESET_PORTS import net.mullvad.mullvadvpn.lib.model.Constraint import net.mullvad.mullvadvpn.lib.model.Port -import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.util.Lc +import net.mullvad.mullvadvpn.util.shadowSocksPort import net.mullvad.mullvadvpn.util.toLc class ShadowsocksSettingsViewModel(private val settingsRepository: SettingsRepository) : @@ -31,7 +31,7 @@ class ShadowsocksSettingsViewModel(private val settingsRepository: SettingsRepos settings, customPort -> ShadowsocksSettingsUiState( - port = settings.getShadowSocksPort(), + port = settings.shadowSocksPort(), customPort = customPort, ) .toLc<Unit, ShadowsocksSettingsUiState>() @@ -46,7 +46,7 @@ class ShadowsocksSettingsViewModel(private val settingsRepository: SettingsRepos viewModelScope.launch { val initialSettings = settingsRepository.settingsUpdates.filterNotNull().first() customPort.update { - val initialPort = initialSettings.getShadowSocksPort() + val initialPort = initialSettings.shadowSocksPort() if (initialPort.getOrNull() !in SHADOWSOCKS_PRESET_PORTS) { initialPort.getOrNull() } else { @@ -79,6 +79,4 @@ class ShadowsocksSettingsViewModel(private val settingsRepository: SettingsRepos } } } - - private fun Settings.getShadowSocksPort() = obfuscationSettings.shadowsocks.port } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt index 3f9a727b38..9ba3e00995 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt @@ -33,15 +33,21 @@ import net.mullvad.mullvadvpn.lib.model.IpVersion import net.mullvad.mullvadvpn.lib.model.ObfuscationMode import net.mullvad.mullvadvpn.lib.model.Port import net.mullvad.mullvadvpn.lib.model.QuantumResistantState -import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.repository.AutoStartAndConnectOnBootRepository import net.mullvad.mullvadvpn.repository.RelayListRepository import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository import net.mullvad.mullvadvpn.usecase.SystemVpnSettingsAvailableUseCase import net.mullvad.mullvadvpn.util.Lc +import net.mullvad.mullvadvpn.util.contentBlockersSettings +import net.mullvad.mullvadvpn.util.customDnsAddresses +import net.mullvad.mullvadvpn.util.deviceIpVersion +import net.mullvad.mullvadvpn.util.isCustomDnsEnabled import net.mullvad.mullvadvpn.util.onFirst +import net.mullvad.mullvadvpn.util.quantumResistant +import net.mullvad.mullvadvpn.util.selectedObfuscationMode import net.mullvad.mullvadvpn.util.toLc +import net.mullvad.mullvadvpn.util.wireguardPort sealed interface VpnSettingsSideEffect { sealed interface ShowToast : VpnSettingsSideEffect { @@ -75,7 +81,7 @@ class VpnSettingsViewModel( combine( settingsRepository.settingsUpdates.filterNotNull().onFirst { // Initialize wg port and content blockers state expand state - val initialPort = it.getWireguardPort().getOrNull() + val initialPort = it.wireguardPort().getOrNull() customPort.value = Some( if (initialPort !in WIREGUARD_PRESET_PORTS) { @@ -101,19 +107,19 @@ class VpnSettingsViewModel( mtu = settings.tunnelOptions.wireguard.mtu, isLocalNetworkSharingEnabled = settings.allowLan, isCustomDnsEnabled = settings.isCustomDnsEnabled(), - customDnsItems = settings.addresses().asStringAddressList(), + customDnsItems = settings.customDnsAddresses().asStringAddressList(), contentBlockersOptions = settings.contentBlockersSettings(), obfuscationMode = settings.selectedObfuscationMode(), selectedUdp2TcpObfuscationPort = settings.obfuscationSettings.udp2tcp.port, selectedShadowsocksObfuscationPort = settings.obfuscationSettings.shadowsocks.port, quantumResistant = settings.quantumResistant(), - selectedWireguardPort = settings.getWireguardPort(), + selectedWireguardPort = settings.wireguardPort(), customWireguardPort = customWgPort, availablePortRanges = portRanges, systemVpnSettingsAvailable = systemVpnSettingsUseCase(), autoStartAndConnectOnBoot = autoStartAndConnectOnBoot, - deviceIpVersion = settings.getDeviceIpVersion(), + deviceIpVersion = settings.deviceIpVersion(), isIpv6Enabled = settings.tunnelOptions.genericOptions.enableIpv6, isContentBlockersExpanded = isContentBlockersExpanded, isModal = navArgs.isModal, @@ -138,7 +144,7 @@ class VpnSettingsViewModel( return@launch } - val hasDnsEntries = settings.addresses().isNotEmpty() + val hasDnsEntries = settings.customDnsAddresses().isNotEmpty() if (hasDnsEntries) { settingsRepository @@ -258,22 +264,6 @@ class VpnSettingsViewModel( ) } - private fun Settings.quantumResistant() = tunnelOptions.wireguard.quantumResistant - - private fun Settings.isCustomDnsEnabled() = tunnelOptions.dnsOptions.state == DnsState.Custom - - private fun Settings.addresses() = tunnelOptions.dnsOptions.customOptions.addresses - - private fun Settings.contentBlockersSettings() = tunnelOptions.dnsOptions.defaultOptions - - private fun Settings.selectedObfuscationMode() = obfuscationSettings.selectedObfuscationMode - - private fun Settings.getWireguardPort() = - relaySettings.relayConstraints.wireguardConstraints.port - - private fun Settings.getDeviceIpVersion() = - relaySettings.relayConstraints.wireguardConstraints.ipVersion - private fun InetAddress.isLocalAddress(): Boolean = isLinkLocalAddress || isSiteLocalAddress fun showApplySettingChangesWarningToast() = diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/LocationUtil.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/LocationUtil.kt new file mode 100644 index 0000000000..2561f7f007 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/LocationUtil.kt @@ -0,0 +1,26 @@ +package net.mullvad.mullvadvpn.viewmodel.location + +import net.mullvad.mullvadvpn.compose.state.MultihopRelayListType +import net.mullvad.mullvadvpn.compose.state.RelayListType +import net.mullvad.mullvadvpn.lib.model.Settings +import net.mullvad.mullvadvpn.util.isDaitaDirectOnly +import net.mullvad.mullvadvpn.util.isDaitaEnabled +import net.mullvad.mullvadvpn.util.isMultihopEnabled + +// If Daita is enabled without direct only we should block selection, search and hide filters for +// the multihop enry list +internal fun RelayListType.isEntryAndBlocked(settings: Settings?): Boolean { + val isMultihopEntry = isMultihopEntry() + + if (!isMultihopEntry) { + return false + } + + return settings?.entryBlocked() == true +} + +private fun Settings.entryBlocked() = + isDaitaEnabled() && !isDaitaDirectOnly() && isMultihopEnabled() + +private fun RelayListType.isMultihopEntry() = + this is RelayListType.Multihop && multihopRelayListType == MultihopRelayListType.ENTRY diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/RelayItemListCreator.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/RelayItemListCreator.kt index 1d6691b755..d1c8a6ba61 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/RelayItemListCreator.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/RelayItemListCreator.kt @@ -121,17 +121,8 @@ private fun createRecentsSection( ): List<RelayListItem> = buildList { add(RelayListItem.RecentsListHeader) - val selectionIsSingle = itemSelection is RelayItemSelection.Single - val selectionIsMulti = itemSelection is RelayItemSelection.Multiple - val shown = recents - .filter { recent -> - when (recent) { - is Hop.Multi -> selectionIsMulti - is Hop.Single<*> -> selectionIsSingle - } - } .map { recent -> val isSelected = recent.matches(itemSelection, isEntryBlocked) if (isEntryBlocked) { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModel.kt index 30e45d05cf..ff67946361 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModel.kt @@ -13,7 +13,6 @@ import net.mullvad.mullvadvpn.compose.state.SelectLocationListUiState import net.mullvad.mullvadvpn.lib.model.CustomListId import net.mullvad.mullvadvpn.lib.model.GeoLocationId import net.mullvad.mullvadvpn.lib.model.RelayItemId -import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.repository.RelayListRepository import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository @@ -44,7 +43,7 @@ class SelectLocationListViewModel( customListsRelayItemUseCase(), settingsRepository.settingsUpdates, ) { relayListItems, customLists, settings -> - if (settings.isBlocked()) { + if (relayListType.isEntryAndBlocked(settings)) { Lce.Error(Unit) } else { Lce.Content( @@ -62,19 +61,11 @@ class SelectLocationListViewModel( _expandedItems.onToggleExpandSet(item, parent, expand) } - private fun Settings?.isBlocked(): Boolean = - when (relayListType) { - RelayListType.Single -> false - is RelayListType.Multihop -> - relayListType.multihopRelayListType == MultihopRelayListType.ENTRY && - this?.entryBlocked() == true - } - private fun relayListItems() = combine( filteredRelayListUseCase(relayListType = relayListType), filteredCustomListRelayItemsUseCase(relayListType = relayListType), - recentsUseCase(), + recentsUseCase(isMultihop = relayListType is RelayListType.Multihop), selectedLocationUseCase(), _expandedItems, ) { relayCountries, customLists, recents, selectedItem, expandedItems -> @@ -103,7 +94,7 @@ class SelectLocationListViewModel( selectedItem.selectedByOtherEntryExitList(relayListType, customLists), expandedItems = expandedItems, isEntryBlocked = - settingsRepository.settingsUpdates.value?.entryBlocked() == true, + relayListType.isEntryAndBlocked(settingsRepository.settingsUpdates.value), ) } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModel.kt index 768cf794eb..bf3abeaf91 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModel.kt @@ -21,6 +21,7 @@ import net.mullvad.mullvadvpn.lib.model.CustomListId import net.mullvad.mullvadvpn.lib.model.Hop import net.mullvad.mullvadvpn.lib.model.Recents import net.mullvad.mullvadvpn.lib.model.RelayItem +import net.mullvad.mullvadvpn.lib.model.Settings import net.mullvad.mullvadvpn.repository.CustomListsRepository import net.mullvad.mullvadvpn.repository.RelayListFilterRepository import net.mullvad.mullvadvpn.repository.RelayListRepository @@ -69,13 +70,20 @@ class SelectLocationViewModel( ) { filterChips, wireguardConstraints, relayListSelection, relayList, settings -> Lc.Content( SelectLocationUiState( - filterChips = filterChips, + filterChips = + // Hide filter chips when entry and blocked + if (relayListSelection.isEntryAndBlocked(settings)) { + emptyList() + } else { + filterChips + }, multihopEnabled = wireguardConstraints?.isMultihopEnabled == true, relayListType = relayListSelection, isSearchButtonEnabled = searchButtonEnabled( relayList = relayList, relayListSelection = relayListSelection, + settings = settings, ), isFilterButtonEnabled = relayList.isNotEmpty(), isRecentsEnabled = settings?.recents is Recents.Enabled, @@ -92,13 +100,11 @@ class SelectLocationViewModel( private fun searchButtonEnabled( relayList: List<RelayItem.Location.Country>, relayListSelection: RelayListType, + settings: Settings?, ): Boolean { val hasRelayListItems = relayList.isNotEmpty() - val isMultihopEntry = - relayListSelection is RelayListType.Multihop && - relayListSelection.multihopRelayListType == MultihopRelayListType.ENTRY - val isEntryBlocked = settingsRepository.settingsUpdates.value?.entryBlocked() == true - return hasRelayListItems && !(isMultihopEntry && isEntryBlocked) + val isEntryAndBlocked = relayListSelection.isEntryAndBlocked(settings = settings) + return hasRelayListItems && !isEntryAndBlocked } fun selectRelayList(multihopRelayListType: MultihopRelayListType) { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SettingsUtil.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SettingsUtil.kt deleted file mode 100644 index 05245f2503..0000000000 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SettingsUtil.kt +++ /dev/null @@ -1,10 +0,0 @@ -package net.mullvad.mullvadvpn.viewmodel.location - -import net.mullvad.mullvadvpn.lib.model.Settings - -// If Daita is enabled without direct only, it is not possible to manually select the entry -// location. -internal fun Settings.entryBlocked() = - tunnelOptions.wireguard.daitaSettings.enabled && - !tunnelOptions.wireguard.daitaSettings.directOnly && - relaySettings.relayConstraints.wireguardConstraints.isMultihopEnabled diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/RecentsUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/RecentsUseCaseTest.kt index 9623fffc7c..09759116a4 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/RecentsUseCaseTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/RecentsUseCaseTest.kt @@ -47,67 +47,65 @@ class RecentsUseCaseTest { @Test fun `given null settings when invoke then emit null`() = runTest { + // Arrange settingsFlow.value = null every { customListsRelayItemUseCase(any()) } returns flowOf(emptyList()) every { filteredRelayListUseCase(any()) } returns flowOf(emptyList()) - useCase().test { assertNull(awaitItem()) } + // Act, Assert + useCase(isMultihop = false).test { assertNull(awaitItem()) } } @Test fun `given recents disabled when invoke then emit null`() = runTest { + // Arrange settingsFlow.value = mockk<Settings> { every { recents } returns Recents.Disabled } every { customListsRelayItemUseCase(any()) } returns flowOf(emptyList()) every { filteredRelayListUseCase(any()) } returns flowOf(emptyList()) - useCase().test { assertNull(awaitItem()) } + // Act, Assert + useCase(isMultihop = false).test { assertNull(awaitItem()) } } @Test fun `given recents enabled but empty when invoke then emit empty list`() = runTest { + // Arrange settingsFlow.value = mockk<Settings> { every { recents } returns Recents.Enabled(emptyList()) } every { customListsRelayItemUseCase(any()) } returns flowOf(emptyList()) every { filteredRelayListUseCase(any()) } returns flowOf(emptyList()) - useCase().test { assertEquals(emptyList(), awaitItem()) } + // Act, Assert + useCase(isMultihop = false).test { assertEquals(emptyList(), awaitItem()) } } @Test - fun `given recents enabled when invoke then emit hops based on the relay item filters`() = - runTest { - val swedenId = GeoLocationId.Country("se") - val stockholmId = GeoLocationId.City(swedenId, "sto") - val sweden = - RelayItem.Location.Country( - id = swedenId, - name = "Sweden", - cities = - listOf( - RelayItem.Location.City( - id = stockholmId, - name = "Stockholm", - relays = emptyList(), - ) - ), - ) - - val norwayId = GeoLocationId.Country("no") - val norway = - RelayItem.Location.Country(id = norwayId, name = "Norway", cities = emptyList()) + fun `given recent custom list with no children should not emit that recent`() = runTest { + // Arrange + val id = CustomListId("id") + val customList = + RelayItem.CustomList( + customList = + CustomList( + id = id, + name = CustomListName.fromString("name"), + locations = emptyList(), + ), + locations = emptyList(), + ) + val recent = Recent.Singlehop(location = id) + settingsFlow.value = + mockk<Settings> { every { recents } returns Recents.Enabled(listOf(recent)) } + every { customListsRelayItemUseCase(any()) } returns flowOf(listOf(customList)) + every { filteredRelayListUseCase(any()) } returns flowOf(emptyList()) - val entryCustomListId = CustomListId("custom") - val customList = - CustomList( - id = entryCustomListId, - name = CustomListName.fromString("Custom"), - locations = listOf(swedenId, norwayId), - ) - val entryCustomList = - RelayItem.CustomList(customList = customList, locations = emptyList()) + useCase(isMultihop = false).test { assertEquals(emptyList(), awaitItem()) } + } - val singleHopRecent = Recent.Singlehop(stockholmId) - val multiHopRecent = Recent.Multihop(entry = entryCustomListId, exit = norwayId) + @Test + fun `given recents enabled when invoke then emit hops based on the relay item filters`() = + runTest { + val singleHopRecent = Recent.Singlehop(STOCKHOLM_ID) val filteredOutRecent = Recent.Singlehop( GeoLocationId.City(country = GeoLocationId.Country("xx"), code = "xx-xxx-xx") @@ -116,30 +114,72 @@ class RecentsUseCaseTest { settingsFlow.value = mockk<Settings> { every { recents } returns - Recents.Enabled(listOf(singleHopRecent, multiHopRecent, filteredOutRecent)) + Recents.Enabled(listOf(singleHopRecent, filteredOutRecent)) } - every { - customListsRelayItemUseCase(RelayListType.Multihop(MultihopRelayListType.ENTRY)) - } returns flowOf(listOf(entryCustomList)) - every { - customListsRelayItemUseCase(RelayListType.Multihop(MultihopRelayListType.EXIT)) - } returns flowOf(emptyList()) - every { - filteredRelayListUseCase(RelayListType.Multihop(MultihopRelayListType.ENTRY)) - } returns flowOf(listOf(sweden, norway)) - every { - filteredRelayListUseCase(RelayListType.Multihop(MultihopRelayListType.EXIT)) - } returns flowOf(listOf(sweden, norway)) + every { customListsRelayItemUseCase(RelayListType.Single) } returns flowOf(emptyList()) + every { filteredRelayListUseCase(RelayListType.Single) } returns + flowOf(listOf(SWEDEN, NORWAY)) - useCase().test { + useCase(isMultihop = false).test { val hops = awaitItem() - val stockholmCity = sweden.cities.first() - - val expectedHops = - listOf(Hop.Single(stockholmCity), Hop.Multi(entryCustomList, norway)) + val expectedHops = listOf(Hop.Single(STOCKHOLM)) assertEquals(expectedHops, hops) } } + + @Test + fun `given multihop true should filter out singlehop recents`() = runTest { + val singleHopRecent = Recent.Singlehop(STOCKHOLM_ID) + val multiHopRecent = Recent.Multihop(entry = CUSTOM_LIST_ID, exit = NORWAY_ID) + + settingsFlow.value = + mockk<Settings> { + every { recents } returns Recents.Enabled(listOf(singleHopRecent, multiHopRecent)) + } + + every { + customListsRelayItemUseCase(RelayListType.Multihop(MultihopRelayListType.ENTRY)) + } returns flowOf(listOf(CUSTOM_LIST_SWE_NO)) + every { + customListsRelayItemUseCase(RelayListType.Multihop(MultihopRelayListType.EXIT)) + } returns flowOf(emptyList()) + every { + filteredRelayListUseCase(RelayListType.Multihop(MultihopRelayListType.ENTRY)) + } returns flowOf(listOf(SWEDEN, NORWAY)) + every { + filteredRelayListUseCase(RelayListType.Multihop(MultihopRelayListType.EXIT)) + } returns flowOf(listOf(SWEDEN, NORWAY)) + + useCase(isMultihop = true).test { + val hops = awaitItem() + + val expectedHops = listOf(Hop.Multi(CUSTOM_LIST_SWE_NO, NORWAY)) + assertEquals(expectedHops, hops) + } + } + + companion object { + private val SWEDEN_ID = GeoLocationId.Country("se") + private val STOCKHOLM_ID = GeoLocationId.City(SWEDEN_ID, "sto") + private val STOCKHOLM = + RelayItem.Location.City(id = STOCKHOLM_ID, name = "Stockholm", relays = emptyList()) + private val SWEDEN = + RelayItem.Location.Country(id = SWEDEN_ID, name = "Sweden", cities = listOf(STOCKHOLM)) + private val NORWAY_ID = GeoLocationId.Country("no") + private val NORWAY = + RelayItem.Location.Country(id = NORWAY_ID, name = "Norway", cities = emptyList()) + private val CUSTOM_LIST_ID = CustomListId("custom") + private val CUSTOM_LIST_SWE_NO = + RelayItem.CustomList( + customList = + CustomList( + id = CUSTOM_LIST_ID, + name = CustomListName.fromString("Custom"), + locations = listOf(SWEDEN_ID, NORWAY_ID), + ), + locations = listOf(SWEDEN, NORWAY), + ) + } } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt index a3257f04d9..fc6dcf79c0 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt @@ -364,7 +364,7 @@ class CustomListLocationsViewModelTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) ), ) @@ -382,7 +382,7 @@ class CustomListLocationsViewModelTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) } } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModelTest.kt index 3f9bfe751a..8c01d592e5 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModelTest.kt @@ -67,7 +67,7 @@ class SelectLocationListViewModelTest { filteredCustomListRelayItems every { mockCustomListRelayItemsUseCase() } returns customListRelayItems every { mockSettingsRepository.settingsUpdates } returns settings - every { recentsUseCase() } returns recentsRelayItems + every { recentsUseCase(any()) } returns recentsRelayItems } @Test diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModelTest.kt index e50cfb48a2..b633e3402f 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationViewModelTest.kt @@ -21,6 +21,7 @@ import net.mullvad.mullvadvpn.compose.state.MultihopRelayListType import net.mullvad.mullvadvpn.compose.state.RelayListType import net.mullvad.mullvadvpn.compose.state.SelectLocationUiState import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule +import net.mullvad.mullvadvpn.lib.common.test.assertLists import net.mullvad.mullvadvpn.lib.model.Constraint import net.mullvad.mullvadvpn.lib.model.CustomList import net.mullvad.mullvadvpn.lib.model.CustomListId @@ -40,6 +41,7 @@ import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository import net.mullvad.mullvadvpn.usecase.FilterChip import net.mullvad.mullvadvpn.usecase.FilterChipUseCase +import net.mullvad.mullvadvpn.usecase.ModelOwnership import net.mullvad.mullvadvpn.usecase.ModifyMultihopUseCase import net.mullvad.mullvadvpn.usecase.MultihopChange import net.mullvad.mullvadvpn.usecase.SelectHopUseCase @@ -300,6 +302,51 @@ class SelectLocationViewModelTest { } } + @Test + fun `given entry blocked should not emit any filter if in entry list`() = runTest { + // Arrange + val mockSettings = mockk<Settings>(relaxed = true) + settings.value = mockSettings + every { mockSettings.tunnelOptions.wireguard.daitaSettings.enabled } returns true + every { mockSettings.tunnelOptions.wireguard.daitaSettings.directOnly } returns false + every { + mockSettings.relaySettings.relayConstraints.wireguardConstraints.isMultihopEnabled + } returns true + filterChips.value = listOf(FilterChip.Quic, FilterChip.Daita) + + // Act, Assert + viewModel.uiState.test { + awaitItem() // Initial state + viewModel.selectRelayList(MultihopRelayListType.ENTRY) + val state = awaitItem() + assertIs<Lc.Content<SelectLocationUiState>>(state) + assert(state.value.filterChips.isEmpty()) + } + } + + @Test + fun `given entry blocked should emit filters if in exit list`() = runTest { + // Arrange + val mockSettings = mockk<Settings>(relaxed = true) + val expectedFilters = listOf(FilterChip.Ownership(ModelOwnership.MullvadOwned)) + settings.value = mockSettings + filterChips.value = expectedFilters + every { mockSettings.tunnelOptions.wireguard.daitaSettings.enabled } returns true + every { mockSettings.tunnelOptions.wireguard.daitaSettings.directOnly } returns false + every { + mockSettings.relaySettings.relayConstraints.wireguardConstraints.isMultihopEnabled + } returns true + + // Act, Assert + viewModel.uiState.test { + awaitItem() // Initial state + viewModel.selectRelayList(MultihopRelayListType.EXIT) + val state = awaitItem() + assertIs<Lc.Content<SelectLocationUiState>>(state) + assertLists(expectedFilters, state.value.filterChips) + } + } + companion object { private const val RELAY_LIST_EXTENSIONS = "net.mullvad.mullvadvpn.relaylist.RelayListExtensionsKt" diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt index 113a5fc847..2424ff00a7 100644 --- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt +++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt @@ -92,6 +92,7 @@ internal fun ObfuscationMode.fromDomain(): ManagementInterface.ObfuscationSettings.SelectedObfuscation.UDP2TCP ObfuscationMode.Shadowsocks -> ManagementInterface.ObfuscationSettings.SelectedObfuscation.SHADOWSOCKS + ObfuscationMode.Quic -> ManagementInterface.ObfuscationSettings.SelectedObfuscation.QUIC ObfuscationMode.Auto -> ManagementInterface.ObfuscationSettings.SelectedObfuscation.AUTO ObfuscationMode.Off -> ManagementInterface.ObfuscationSettings.SelectedObfuscation.OFF } diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt index 140cf5aafb..9b4dd07056 100644 --- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt +++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt @@ -59,6 +59,7 @@ import net.mullvad.mullvadvpn.lib.model.PortRange import net.mullvad.mullvadvpn.lib.model.ProviderId import net.mullvad.mullvadvpn.lib.model.Providers import net.mullvad.mullvadvpn.lib.model.QuantumResistantState +import net.mullvad.mullvadvpn.lib.model.Quic import net.mullvad.mullvadvpn.lib.model.Recent import net.mullvad.mullvadvpn.lib.model.Recents import net.mullvad.mullvadvpn.lib.model.RedeemVoucherSuccess @@ -207,6 +208,8 @@ internal fun ManagementInterface.ObfuscationEndpoint.toDomain(): ObfuscationEndp obfuscationType = obfuscationType.toDomain(), ) +private fun String.toInetAddress(): InetAddress = InetAddress.getByName(this) + private fun String.toInetSocketAddress(): InetSocketAddress { val indexOfSeparator = indexOfLast { it == ':' } val ipPart = substring(0, indexOfSeparator).filter { it !in listOf('[', ']') } @@ -219,8 +222,7 @@ internal fun ManagementInterface.ObfuscationEndpoint.ObfuscationType.toDomain(): ManagementInterface.ObfuscationEndpoint.ObfuscationType.UDP2TCP -> ObfuscationType.Udp2Tcp ManagementInterface.ObfuscationEndpoint.ObfuscationType.SHADOWSOCKS -> ObfuscationType.Shadowsocks - ManagementInterface.ObfuscationEndpoint.ObfuscationType.QUIC -> - throw IllegalArgumentException("Unsupported obfuscation type") + ManagementInterface.ObfuscationEndpoint.ObfuscationType.QUIC -> ObfuscationType.Quic ManagementInterface.ObfuscationEndpoint.ObfuscationType.UNRECOGNIZED -> throw IllegalArgumentException("Unrecognized obfuscation type") } @@ -426,8 +428,7 @@ internal fun ManagementInterface.ObfuscationSettings.SelectedObfuscation.toDomai ObfuscationMode.Udp2Tcp ManagementInterface.ObfuscationSettings.SelectedObfuscation.SHADOWSOCKS -> ObfuscationMode.Shadowsocks - ManagementInterface.ObfuscationSettings.SelectedObfuscation.QUIC -> - throw IllegalArgumentException("Unsupported obfuscation type") + ManagementInterface.ObfuscationSettings.SelectedObfuscation.QUIC -> ObfuscationMode.Quic ManagementInterface.ObfuscationSettings.SelectedObfuscation.UNRECOGNIZED -> throw IllegalArgumentException("Unrecognized selected obfuscation") } @@ -592,9 +593,17 @@ internal fun ManagementInterface.Relay.toDomain( provider = ProviderId(provider), ownership = if (owned) Ownership.MullvadOwned else Ownership.Rented, daita = endpointData.wireguard.daita, - quic = endpointData.wireguard.hasQuic(), + quic = + if (endpointData.wireguard.hasQuic()) { + endpointData.wireguard.quic.toDomain() + } else { + null + }, ) +private fun ManagementInterface.Relay.RelayData.Wireguard.Quic.toDomain(): Quic = + Quic(inAddresses = addrInList.map { it.toInetAddress() }) + private fun Instant.atDefaultZone() = atZone(ZoneId.systemDefault()) internal fun ManagementInterface.Device.toDomain(): Device = @@ -695,6 +704,7 @@ internal fun ManagementInterface.FeatureIndicators.toDomain(): List<FeatureIndic internal fun ManagementInterface.TunnelOptions.GenericOptions.toDomain(): GenericOptions = GenericOptions(enableIpv6 = enableIpv6) +@Suppress("ComplexMethod") internal fun ManagementInterface.FeatureIndicator.toDomain() = when (this) { ManagementInterface.FeatureIndicator.QUANTUM_RESISTANCE -> @@ -712,7 +722,7 @@ internal fun ManagementInterface.FeatureIndicator.toDomain() = ManagementInterface.FeatureIndicator.SHADOWSOCKS -> FeatureIndicator.SHADOWSOCKS ManagementInterface.FeatureIndicator.MULTIHOP -> FeatureIndicator.MULTIHOP ManagementInterface.FeatureIndicator.DAITA_MULTIHOP -> FeatureIndicator.DAITA_MULTIHOP - ManagementInterface.FeatureIndicator.QUIC, + ManagementInterface.FeatureIndicator.QUIC -> FeatureIndicator.QUIC ManagementInterface.FeatureIndicator.LOCKDOWN_MODE, ManagementInterface.FeatureIndicator.BRIDGE_MODE, ManagementInterface.FeatureIndicator.CUSTOM_MSS_FIX, diff --git a/android/lib/daemon-grpc/src/test/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparatorTest.kt b/android/lib/daemon-grpc/src/test/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparatorTest.kt index e45c8087fd..310b2ab1fd 100644 --- a/android/lib/daemon-grpc/src/test/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparatorTest.kt +++ b/android/lib/daemon-grpc/src/test/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparatorTest.kt @@ -26,7 +26,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) val relay10 = RelayItem.Location.Relay( @@ -35,7 +35,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) relay9 assertOrderBothDirection relay10 @@ -50,7 +50,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) val relay9b = RelayItem.Location.Relay( @@ -59,7 +59,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) assertTrue(RelayNameComparator.compare(relay9a, relay9b) == 0) @@ -75,7 +75,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) val relay1 = RelayItem.Location.Relay( @@ -84,7 +84,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) val relay3 = RelayItem.Location.Relay( @@ -93,7 +93,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) val relay100 = RelayItem.Location.Relay( @@ -102,7 +102,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) relay001 assertOrderBothDirection relay1 @@ -120,7 +120,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) val relay9b = RelayItem.Location.Relay( @@ -129,7 +129,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) assertTrue(RelayNameComparator.compare(relay9a, relay9b) == 0) @@ -145,7 +145,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) val relay005 = RelayItem.Location.Relay( @@ -154,7 +154,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) relay001 assertOrderBothDirection relay005 @@ -169,7 +169,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) val relayAr8 = RelayItem.Location.Relay( @@ -178,7 +178,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) val relaySe5 = RelayItem.Location.Relay( @@ -187,7 +187,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) val relaySe10 = RelayItem.Location.Relay( @@ -196,7 +196,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) relayAr2 assertOrderBothDirection relayAr8 @@ -213,7 +213,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) val relay2w = RelayItem.Location.Relay( @@ -222,7 +222,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) relay2c assertOrderBothDirection relay2w @@ -237,7 +237,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) val relay22b = RelayItem.Location.Relay( @@ -246,7 +246,7 @@ class RelayNameComparatorTest { provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = false, - quic = false, + quic = null, ) relay22a assertOrderBothDirection relay22b diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/FeatureIndicator.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/FeatureIndicator.kt index 0213c06cef..6d7951749b 100644 --- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/FeatureIndicator.kt +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/FeatureIndicator.kt @@ -9,6 +9,7 @@ enum class FeatureIndicator { SPLIT_TUNNELING, UDP_2_TCP, SHADOWSOCKS, + QUIC, LAN_SHARING, DNS_CONTENT_BLOCKERS, CUSTOM_DNS, diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationMode.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationMode.kt index 7e4101e973..8926ded829 100644 --- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationMode.kt +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationMode.kt @@ -5,4 +5,5 @@ enum class ObfuscationMode { Off, Udp2Tcp, Shadowsocks, + Quic, } diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt index 80c2f70e13..5eb0ad5548 100644 --- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt @@ -3,4 +3,5 @@ package net.mullvad.mullvadvpn.lib.model enum class ObfuscationType { Udp2Tcp, Shadowsocks, + Quic, } diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Quic.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Quic.kt new file mode 100644 index 0000000000..01ebd96d3e --- /dev/null +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Quic.kt @@ -0,0 +1,8 @@ +package net.mullvad.mullvadvpn.lib.model + +import java.net.InetAddress + +data class Quic(val inAddresses: List<InetAddress>) { + val supportsIpv4 = inAddresses.any { it is java.net.Inet4Address } + val supportsIpv6 = inAddresses.any { it is java.net.Inet6Address } +} diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt index b1df67fea6..197e8e95ce 100644 --- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt @@ -85,7 +85,7 @@ sealed interface RelayItem { val ownership: Ownership, override val active: Boolean, val daita: Boolean, - val quic: Boolean, + val quic: Quic?, ) : Location { override val name: String = id.code override val hasChildren: Boolean = false diff --git a/android/lib/resource/src/main/res/values-da/strings.xml b/android/lib/resource/src/main/res/values-da/strings.xml index be26ab6e10..a395a1917d 100644 --- a/android/lib/resource/src/main/res/values-da/strings.xml +++ b/android/lib/resource/src/main/res/values-da/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">Brugerdefineret DNS</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">%1$s mere ...</string> + <string name="feature_obfuscation">Tilsløring</string> <string name="feature_quantum_resistant">Kvantemodstand</string> - <string name="feature_udp_2_tcp">Tilsløring</string> <string name="filter">Filter</string> <string name="filters">Filtre:</string> <string name="foreground_notification_channel_description">Viser den aktuelle VPN-tunnelstatus</string> diff --git a/android/lib/resource/src/main/res/values-de/strings.xml b/android/lib/resource/src/main/res/values-de/strings.xml index 12e43b5d9f..9070ee0abe 100644 --- a/android/lib/resource/src/main/res/values-de/strings.xml +++ b/android/lib/resource/src/main/res/values-de/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">Eigenes DNS</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">%1$s weitere …</string> + <string name="feature_obfuscation">Verschleierung</string> <string name="feature_quantum_resistant">Quantenresistenz</string> - <string name="feature_udp_2_tcp">Verschleierung</string> <string name="filter">Filter</string> <string name="filters">Filter:</string> <string name="foreground_notification_channel_description">Zeigt den aktuellen Status des VPN-Tunnels an</string> diff --git a/android/lib/resource/src/main/res/values-es/strings.xml b/android/lib/resource/src/main/res/values-es/strings.xml index b76ec33c1d..0a35370514 100644 --- a/android/lib/resource/src/main/res/values-es/strings.xml +++ b/android/lib/resource/src/main/res/values-es/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">DNS personalizado</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">%1$s más...</string> + <string name="feature_obfuscation">Ofuscación</string> <string name="feature_quantum_resistant">Resistencia cuántica</string> - <string name="feature_udp_2_tcp">Ofuscación</string> <string name="filter">Filtrar</string> <string name="filters">Filtros:</string> <string name="foreground_notification_channel_description">Muestra el estado actual del túnel VPN</string> diff --git a/android/lib/resource/src/main/res/values-fi/strings.xml b/android/lib/resource/src/main/res/values-fi/strings.xml index aca2052934..b90c8f021e 100644 --- a/android/lib/resource/src/main/res/values-fi/strings.xml +++ b/android/lib/resource/src/main/res/values-fi/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">Mukautettu DNS</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">%1$s lisää...</string> + <string name="feature_obfuscation">Hämäysteknologia</string> <string name="feature_quantum_resistant">Kvanttihyökkäysten esto</string> - <string name="feature_udp_2_tcp">Hämäysteknologia</string> <string name="filter">Suodatin</string> <string name="filters">Suodattimet:</string> <string name="foreground_notification_channel_description">Näyttää VPN-tunnelin nykyisen tilan</string> diff --git a/android/lib/resource/src/main/res/values-fr/strings.xml b/android/lib/resource/src/main/res/values-fr/strings.xml index e48f6b8bde..d03e810d5a 100644 --- a/android/lib/resource/src/main/res/values-fr/strings.xml +++ b/android/lib/resource/src/main/res/values-fr/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">DNS personnalisé</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">%1$s de plus…</string> + <string name="feature_obfuscation">Dissimulation</string> <string name="feature_quantum_resistant">Résistance quantique</string> - <string name="feature_udp_2_tcp">Dissimulation</string> <string name="filter">Filtrer</string> <string name="filters">Filtres :</string> <string name="foreground_notification_channel_description">Affiche l\'état actuel du tunnel VPN</string> diff --git a/android/lib/resource/src/main/res/values-it/strings.xml b/android/lib/resource/src/main/res/values-it/strings.xml index 3c42bff12a..323129f829 100644 --- a/android/lib/resource/src/main/res/values-it/strings.xml +++ b/android/lib/resource/src/main/res/values-it/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">DNS personalizzato</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">Altri %1$s...</string> + <string name="feature_obfuscation">Offuscamento</string> <string name="feature_quantum_resistant">Resistenza ad attacchi quantistici</string> - <string name="feature_udp_2_tcp">Offuscamento</string> <string name="filter">Filtra</string> <string name="filters">Filtri:</string> <string name="foreground_notification_channel_description">Mostra lo stato attuale del tunnel VPN</string> diff --git a/android/lib/resource/src/main/res/values-ja/strings.xml b/android/lib/resource/src/main/res/values-ja/strings.xml index 0019bd8cf6..782968ccd7 100644 --- a/android/lib/resource/src/main/res/values-ja/strings.xml +++ b/android/lib/resource/src/main/res/values-ja/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">カスタムDNS</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">その他%1$s件…</string> + <string name="feature_obfuscation">難読化</string> <string name="feature_quantum_resistant">耐量子</string> - <string name="feature_udp_2_tcp">難読化</string> <string name="filter">絞り込み</string> <string name="filters">絞り込み:</string> <string name="foreground_notification_channel_description">現在のVPNトンネルのステータスを表示します</string> diff --git a/android/lib/resource/src/main/res/values-ko/strings.xml b/android/lib/resource/src/main/res/values-ko/strings.xml index 4d5e18e0cf..f39d896349 100644 --- a/android/lib/resource/src/main/res/values-ko/strings.xml +++ b/android/lib/resource/src/main/res/values-ko/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">사용자 지정 DNS</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">%1$s개 더 보기...</string> + <string name="feature_obfuscation">난독 처리</string> <string name="feature_quantum_resistant">양자 저항</string> - <string name="feature_udp_2_tcp">난독 처리</string> <string name="filter">필터</string> <string name="filters">필터:</string> <string name="foreground_notification_channel_description">현재 VPN 터널 상태 표시</string> diff --git a/android/lib/resource/src/main/res/values-my/strings.xml b/android/lib/resource/src/main/res/values-my/strings.xml index 98b9982cc1..b8fe2478e8 100644 --- a/android/lib/resource/src/main/res/values-my/strings.xml +++ b/android/lib/resource/src/main/res/values-my/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">စိတ်ကြိုက် DNS</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">နောက်ထပ် %1$s ...</string> + <string name="feature_obfuscation">Obfuscation</string> <string name="feature_quantum_resistant">Quantum ခုခံမှု</string> - <string name="feature_udp_2_tcp">Obfuscation</string> <string name="filter">စစ်ထုတ်မှု</string> <string name="filters">စစ်ထုတ်မှုများ-</string> <string name="foreground_notification_channel_description">လက်ရှိ VPN Tunnel အခြေအနေကို ပြသပေးပါသည်</string> diff --git a/android/lib/resource/src/main/res/values-nb/strings.xml b/android/lib/resource/src/main/res/values-nb/strings.xml index c0d6622923..ea93ba5503 100644 --- a/android/lib/resource/src/main/res/values-nb/strings.xml +++ b/android/lib/resource/src/main/res/values-nb/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">Tilpasset DNS</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">%1$s til …</string> + <string name="feature_obfuscation">Tilsløring</string> <string name="feature_quantum_resistant">Kvantemotstand</string> - <string name="feature_udp_2_tcp">Tilsløring</string> <string name="filter">Filter</string> <string name="filters">Filtre:</string> <string name="foreground_notification_channel_description">Viser gjeldende VPN-tunnelstatus</string> diff --git a/android/lib/resource/src/main/res/values-nl/strings.xml b/android/lib/resource/src/main/res/values-nl/strings.xml index e1d1619d8e..77d03bc2ea 100644 --- a/android/lib/resource/src/main/res/values-nl/strings.xml +++ b/android/lib/resource/src/main/res/values-nl/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">Aangepaste DNS</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">Nog %1$s...</string> + <string name="feature_obfuscation">Obfuscatie</string> <string name="feature_quantum_resistant">Kwantumbestendigheid</string> - <string name="feature_udp_2_tcp">Obfuscatie</string> <string name="filter">Filter</string> <string name="filters">Filters:</string> <string name="foreground_notification_channel_description">Toont de huidige status van de VPN-tunnel</string> diff --git a/android/lib/resource/src/main/res/values-pl/strings.xml b/android/lib/resource/src/main/res/values-pl/strings.xml index e9f255c4e2..cd48028048 100644 --- a/android/lib/resource/src/main/res/values-pl/strings.xml +++ b/android/lib/resource/src/main/res/values-pl/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">Niestandardowy serwer DNS</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">Jeszcze %1$s...</string> + <string name="feature_obfuscation">Zaciemnianie</string> <string name="feature_quantum_resistant">Odporność na ataki z użyciem komputerów kwantowych</string> - <string name="feature_udp_2_tcp">Zaciemnianie</string> <string name="filter">Filtruj</string> <string name="filters">Filtry:</string> <string name="foreground_notification_channel_description">Pokazuje bieżący status tunelu VPN</string> diff --git a/android/lib/resource/src/main/res/values-pt/strings.xml b/android/lib/resource/src/main/res/values-pt/strings.xml index ee85151bc8..f5f6e733a4 100644 --- a/android/lib/resource/src/main/res/values-pt/strings.xml +++ b/android/lib/resource/src/main/res/values-pt/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">DNS personalizado</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">Mais %1$s...</string> + <string name="feature_obfuscation">Ofuscação</string> <string name="feature_quantum_resistant">Resistência quântica</string> - <string name="feature_udp_2_tcp">Ofuscação</string> <string name="filter">Filtrar</string> <string name="filters">Filtros:</string> <string name="foreground_notification_channel_description">Indica o estado atual do túnel VPN</string> diff --git a/android/lib/resource/src/main/res/values-ru/strings.xml b/android/lib/resource/src/main/res/values-ru/strings.xml index ce118dc517..2a0d879d57 100644 --- a/android/lib/resource/src/main/res/values-ru/strings.xml +++ b/android/lib/resource/src/main/res/values-ru/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">Пользовательский DNS</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">Еще %1$s...</string> + <string name="feature_obfuscation">Обфускация</string> <string name="feature_quantum_resistant">Квантовая устойчивость</string> - <string name="feature_udp_2_tcp">Обфускация</string> <string name="filter">Фильтр</string> <string name="filters">Фильтры:</string> <string name="foreground_notification_channel_description">Показывает текущее состояние VPN-туннеля</string> diff --git a/android/lib/resource/src/main/res/values-sv/strings.xml b/android/lib/resource/src/main/res/values-sv/strings.xml index 8656246738..265d7b6716 100644 --- a/android/lib/resource/src/main/res/values-sv/strings.xml +++ b/android/lib/resource/src/main/res/values-sv/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">Anpassad DNS</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">%1$s till ...</string> + <string name="feature_obfuscation">Obfuskering</string> <string name="feature_quantum_resistant">Kvantresistens</string> - <string name="feature_udp_2_tcp">Obfuskering</string> <string name="filter">Filtrera</string> <string name="filters">Filter:</string> <string name="foreground_notification_channel_description">Visar nuvarande status för VPN-tunnel</string> diff --git a/android/lib/resource/src/main/res/values-th/strings.xml b/android/lib/resource/src/main/res/values-th/strings.xml index ec273c5e66..13f27815d3 100644 --- a/android/lib/resource/src/main/res/values-th/strings.xml +++ b/android/lib/resource/src/main/res/values-th/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">DNS แบบกำหนดเอง</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">อีก %1$s...</string> + <string name="feature_obfuscation">การทำให้ข้อมูลยุ่งเหยิง</string> <string name="feature_quantum_resistant">การต่อต้านควอนตัม</string> - <string name="feature_udp_2_tcp">การทำให้ข้อมูลยุ่งเหยิง</string> <string name="filter">ตัวกรอง</string> <string name="filters">ตัวกรอง:</string> <string name="foreground_notification_channel_description">แสดงสถานะอุโมงค์ VPN ในปัจจุบัน</string> diff --git a/android/lib/resource/src/main/res/values-tr/strings.xml b/android/lib/resource/src/main/res/values-tr/strings.xml index 6a802fe7dc..bf1296c709 100644 --- a/android/lib/resource/src/main/res/values-tr/strings.xml +++ b/android/lib/resource/src/main/res/values-tr/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">Özel DNS</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">%1$s tane daha...</string> + <string name="feature_obfuscation">Gizleme</string> <string name="feature_quantum_resistant">Kuantum direnci</string> - <string name="feature_udp_2_tcp">Gizleme</string> <string name="filter">Filtrele</string> <string name="filters">Filtreler:</string> <string name="foreground_notification_channel_description">Mevcut VPN tünelinin durumunu gösterir</string> diff --git a/android/lib/resource/src/main/res/values-zh-rCN/strings.xml b/android/lib/resource/src/main/res/values-zh-rCN/strings.xml index fac8961cee..d2ed8c3aff 100644 --- a/android/lib/resource/src/main/res/values-zh-rCN/strings.xml +++ b/android/lib/resource/src/main/res/values-zh-rCN/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">自定义 DNS</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">其他 %1$s 个…</string> + <string name="feature_obfuscation">混淆</string> <string name="feature_quantum_resistant">量子阻力</string> - <string name="feature_udp_2_tcp">混淆</string> <string name="filter">筛选</string> <string name="filters">筛选器:</string> <string name="foreground_notification_channel_description">显示当前的 VPN 隧道状态</string> diff --git a/android/lib/resource/src/main/res/values-zh-rTW/strings.xml b/android/lib/resource/src/main/res/values-zh-rTW/strings.xml index d53584ca36..37acbc0a3f 100644 --- a/android/lib/resource/src/main/res/values-zh-rTW/strings.xml +++ b/android/lib/resource/src/main/res/values-zh-rTW/strings.xml @@ -191,8 +191,8 @@ <string name="feature_custom_dns">自訂 DNS</string> <string name="feature_custom_mtu">MTU</string> <string name="feature_indicators_show_more">其他 %1$s 個…</string> + <string name="feature_obfuscation">混淆</string> <string name="feature_quantum_resistant">抗量子</string> - <string name="feature_udp_2_tcp">混淆</string> <string name="filter">篩選</string> <string name="filters">篩選器:</string> <string name="foreground_notification_channel_description">顯示目前的 VPN 通道狀態</string> diff --git a/android/lib/resource/src/main/res/values/strings.xml b/android/lib/resource/src/main/res/values/strings.xml index 44b41aa99a..a6029aa1f2 100644 --- a/android/lib/resource/src/main/res/values/strings.xml +++ b/android/lib/resource/src/main/res/values/strings.xml @@ -351,7 +351,7 @@ <string name="connect_panel_active_features">Active features</string> <string name="connect_panel_connection_details">Connection details</string> <string name="feature_quantum_resistant">Quantum resistance</string> - <string name="feature_udp_2_tcp">Obfuscation</string> + <string name="feature_obfuscation">Obfuscation</string> <string name="feature_custom_dns">Custom DNS</string> <string name="feature_custom_mtu">MTU</string> <string name="dns_content_blockers">DNS content blockers</string> diff --git a/android/lib/resource/src/main/res/values/strings_non_translatable.xml b/android/lib/resource/src/main/res/values/strings_non_translatable.xml index 837740aa47..3f2c8b5c29 100644 --- a/android/lib/resource/src/main/res/values/strings_non_translatable.xml +++ b/android/lib/resource/src/main/res/values/strings_non_translatable.xml @@ -11,6 +11,7 @@ <string name="wireguard" translatable="false">WireGuard</string> <string name="socks5_remote">SOCKS5</string> <string name="shadowsocks">Shadowsocks</string> + <string name="quic">QUIC</string> <string name="local_network_sharing_ip_ranges"> <![CDATA[<ul><li>10.0.0.0/8</li><li>172.16.0.0/12</li><li>192.168.0.0/16</li><li>169.254.0.0/16</li><li>fe80::/10</li><li>fc00::/7</li></ul>]]> </string> diff --git a/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayItemPreviewData.kt b/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayItemPreviewData.kt index 724c567595..2e4e228439 100644 --- a/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayItemPreviewData.kt +++ b/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayItemPreviewData.kt @@ -51,7 +51,7 @@ private fun generateRelayItemRelay( provider = ProviderId("Provider"), ownership = Ownership.MullvadOwned, daita = daita, - quic = false, + quic = null, ) private fun String.generateCountryCode() = diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt index 2d7757ace1..e9c503e5b6 100644 --- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt +++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt @@ -241,6 +241,57 @@ class ConnectionTest : EndToEndTest() { @Test @HasDependencyOnLocalAPI @ClearFirewallRules + fun testQuic() = runTest { + app.launchAndLogIn(accountTestRule.validAccountNumber) + on<ConnectPage> { enableLocalNetworkSharingStory() } + + on<ConnectPage> { clickSelectLocation() } + + on<SelectLocationPage> { + val quicRelay = relayProvider.getQuicRelay() + clickLocationExpandButton(quicRelay.country) + clickLocationExpandButton(quicRelay.city) + scrollUntilCell(quicRelay.relay) + clickLocationCell(quicRelay.relay) + } + + device.acceptVpnPermissionDialog() + + var relayIpAddress: String? = null + + on<ConnectPage> { + waitForConnectedLabel() + relayIpAddress = extractInIpv4Address() + clickDisconnect() + } + + // Block UDP traffic to the relay + val firewallRule = DropRule.blockWireGuardTrafficRule(relayIpAddress!!) + firewallClient.createRule(firewallRule) + + // Enable QUIC + on<ConnectPage> { clickSettings() } + + on<SettingsPage> { clickVpnSettings() } + + on<VpnSettingsPage> { + scrollUntilWireGuardObfuscationQuicCell() + clickWireguardObfuscationQuicCell() + } + + device.pressBack() + device.pressBack() + + on<ConnectPage> { + clickConnect() + waitForConnectedLabel(timeout = EXTREMELY_LONG_TIMEOUT) + clickDisconnect() + } + } + + @Test + @HasDependencyOnLocalAPI + @ClearFirewallRules fun testShadowsocks() = runTest { app.launchAndLogIn(accountTestRule.validAccountNumber) on<ConnectPage> { enableLocalNetworkSharingStory() } |
