diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0a391e9..08bee9b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -210,6 +210,7 @@ dependencies { implementation(libs.lifecycle.runtime.compose) implementation(libs.kotlinx.serialization) implementation(libs.androidx.window) + implementation(libs.androidx.lifecycle.service) // logging implementation(libs.timber) diff --git a/app/src/main/java/net/nymtech/nymvpn/data/GatewayRepository.kt b/app/src/main/java/net/nymtech/nymvpn/data/GatewayRepository.kt index 06f31a1..c068d3d 100644 --- a/app/src/main/java/net/nymtech/nymvpn/data/GatewayRepository.kt +++ b/app/src/main/java/net/nymtech/nymvpn/data/GatewayRepository.kt @@ -2,24 +2,23 @@ package net.nymtech.nymvpn.data import kotlinx.coroutines.flow.Flow import net.nymtech.nymvpn.data.model.Gateways -import net.nymtech.vpn.model.Hop -import net.nymtech.vpn.model.HopCountries +import net.nymtech.vpn.model.Country interface GatewayRepository { - suspend fun getFirstHopCountry() : Hop.Country - suspend fun setFirstHopCountry(country: Hop.Country) + suspend fun getFirstHopCountry() : Country + suspend fun setFirstHopCountry(country: Country) - suspend fun getLowLatencyCountry() : Hop.Country - suspend fun setLowLatencyCountry(country: Hop.Country) + suspend fun getLowLatencyCountry() : Country + suspend fun setLowLatencyCountry(country: Country) - suspend fun getLastHopCountry() : Hop.Country - suspend fun setLastHopCountry(country: Hop.Country) + suspend fun getLastHopCountry() : Country + suspend fun setLastHopCountry(country: Country) - suspend fun setEntryCountries(countries: HopCountries) - suspend fun getEntryCountries() : HopCountries + suspend fun setEntryCountries(countries: Set) + suspend fun getEntryCountries() : Set - suspend fun setExitCountries(countries: HopCountries) - suspend fun getExitCountries() : HopCountries + suspend fun setExitCountries(countries: Set) + suspend fun getExitCountries() : Set val gatewayFlow : Flow } \ No newline at end of file diff --git a/app/src/main/java/net/nymtech/nymvpn/data/datastore/DataStoreGatewayRepository.kt b/app/src/main/java/net/nymtech/nymvpn/data/datastore/DataStoreGatewayRepository.kt index d490fad..4d2c0bc 100644 --- a/app/src/main/java/net/nymtech/nymvpn/data/datastore/DataStoreGatewayRepository.kt +++ b/app/src/main/java/net/nymtech/nymvpn/data/datastore/DataStoreGatewayRepository.kt @@ -4,65 +4,64 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import net.nymtech.nymvpn.data.GatewayRepository import net.nymtech.nymvpn.data.model.Gateways -import net.nymtech.vpn.model.Hop -import net.nymtech.vpn.model.HopCountries +import net.nymtech.vpn.model.Country import timber.log.Timber class DataStoreGatewayRepository(private val dataStoreManager: DataStoreManager) : GatewayRepository { - override suspend fun getFirstHopCountry(): Hop.Country { + override suspend fun getFirstHopCountry(): Country { val country = dataStoreManager.getFromStore(DataStoreManager.FIRST_HOP_COUNTRY) - return Hop.Country.from(country) + return Country.from(country) } - override suspend fun setFirstHopCountry(country: Hop.Country) { + override suspend fun setFirstHopCountry(country: Country) { dataStoreManager.saveToDataStore(DataStoreManager.FIRST_HOP_COUNTRY, country.toString()) } - override suspend fun getLowLatencyCountry(): Hop.Country { + override suspend fun getLowLatencyCountry(): Country { val country = dataStoreManager.getFromStore(DataStoreManager.LOW_LATENCY_COUNTRY) - return Hop.Country.from(country) + return Country.from(country) } - override suspend fun setLowLatencyCountry(country: Hop.Country) { + override suspend fun setLowLatencyCountry(country: Country) { dataStoreManager.saveToDataStore(DataStoreManager.LOW_LATENCY_COUNTRY, country.toString()) } - override suspend fun getLastHopCountry(): Hop.Country { + override suspend fun getLastHopCountry(): Country { val country = dataStoreManager.getFromStore(DataStoreManager.LAST_HOP_COUNTRY) - return Hop.Country.from(country) + return Country.from(country) } - override suspend fun setLastHopCountry(country: Hop.Country) { + override suspend fun setLastHopCountry(country: Country) { dataStoreManager.saveToDataStore(DataStoreManager.LAST_HOP_COUNTRY, country.toString()) } - override suspend fun setEntryCountries(countries: HopCountries) { + override suspend fun setEntryCountries(countries: Set) { dataStoreManager.saveToDataStore(DataStoreManager.ENTRY_COUNTRIES, countries.toString()) } - override suspend fun getEntryCountries(): HopCountries { + override suspend fun getEntryCountries(): Set { val countries = dataStoreManager.getFromStore(DataStoreManager.ENTRY_COUNTRIES) - return Hop.Country.fromCollectionString(countries) + return Country.fromCollectionString(countries) } - override suspend fun setExitCountries(countries: HopCountries) { + override suspend fun setExitCountries(countries: Set) { dataStoreManager.saveToDataStore(DataStoreManager.EXIT_COUNTRIES, countries.toString()) } - override suspend fun getExitCountries(): HopCountries { + override suspend fun getExitCountries(): Set { val countries = dataStoreManager.getFromStore(DataStoreManager.EXIT_COUNTRIES) - return Hop.Country.fromCollectionString(countries) + return Country.fromCollectionString(countries) } override val gatewayFlow: Flow = dataStoreManager.preferencesFlow.map { prefs -> prefs?.let { pref -> try{ Gateways( - firstHopCountry = Hop.Country.from(pref[DataStoreManager.FIRST_HOP_COUNTRY]), - lastHopCountry = Hop.Country.from(pref[DataStoreManager.LAST_HOP_COUNTRY]), - lowLatencyCountry = Hop.Country.from(pref[DataStoreManager.LOW_LATENCY_COUNTRY]), - exitCountries = Hop.Country.fromCollectionString(pref[DataStoreManager.EXIT_COUNTRIES]), - entryCountries = Hop.Country.fromCollectionString(pref[DataStoreManager.ENTRY_COUNTRIES]) + firstHopCountry = Country.from(pref[DataStoreManager.FIRST_HOP_COUNTRY]), + lastHopCountry = Country.from(pref[DataStoreManager.LAST_HOP_COUNTRY]), + lowLatencyCountry = Country.from(pref[DataStoreManager.LOW_LATENCY_COUNTRY]), + exitCountries = Country.fromCollectionString(pref[DataStoreManager.EXIT_COUNTRIES]), + entryCountries = Country.fromCollectionString(pref[DataStoreManager.ENTRY_COUNTRIES]) ) } catch (e : IllegalArgumentException) { Timber.e(e) diff --git a/app/src/main/java/net/nymtech/nymvpn/data/datastore/DataStoreManager.kt b/app/src/main/java/net/nymtech/nymvpn/data/datastore/DataStoreManager.kt index 4a658b3..561ae56 100644 --- a/app/src/main/java/net/nymtech/nymvpn/data/datastore/DataStoreManager.kt +++ b/app/src/main/java/net/nymtech/nymvpn/data/datastore/DataStoreManager.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.runBlocking -import net.nymtech.vpn.model.Hop +import net.nymtech.vpn.model.Country import timber.log.Timber import java.io.IOException @@ -40,8 +40,8 @@ class DataStoreManager(private val context: Context) { suspend fun init() { context.dataStore.edit { - if(it[FIRST_HOP_COUNTRY] == null) it[FIRST_HOP_COUNTRY] = Hop.Country(isDefault = true).toString() - if(it[LAST_HOP_COUNTRY] == null) it[LAST_HOP_COUNTRY] = Hop.Country(isDefault = true).toString() + if(it[FIRST_HOP_COUNTRY] == null) it[FIRST_HOP_COUNTRY] = Country(isDefault = true).toString() + if(it[LAST_HOP_COUNTRY] == null) it[LAST_HOP_COUNTRY] = Country(isDefault = true).toString() } } diff --git a/app/src/main/java/net/nymtech/nymvpn/data/model/Gateways.kt b/app/src/main/java/net/nymtech/nymvpn/data/model/Gateways.kt index adbc466..eafa4ba 100644 --- a/app/src/main/java/net/nymtech/nymvpn/data/model/Gateways.kt +++ b/app/src/main/java/net/nymtech/nymvpn/data/model/Gateways.kt @@ -1,12 +1,10 @@ package net.nymtech.nymvpn.data.model -import net.nymtech.vpn.model.Hop -import net.nymtech.vpn.model.HopCountries - +import net.nymtech.vpn.model.Country data class Gateways( - val firstHopCountry: Hop.Country = Hop.Country(), - val lastHopCountry: Hop.Country = Hop.Country(), - val lowLatencyCountry: Hop.Country = Hop.Country(), - val entryCountries: HopCountries = emptySet(), - val exitCountries: HopCountries = emptySet() + val firstHopCountry: Country = Country(), + val lastHopCountry: Country = Country(), + val lowLatencyCountry: Country = Country(), + val entryCountries: Set = emptySet(), + val exitCountries: Set = emptySet() ) \ No newline at end of file diff --git a/app/src/main/java/net/nymtech/nymvpn/receiver/BootReceiver.kt b/app/src/main/java/net/nymtech/nymvpn/receiver/BootReceiver.kt index 3f743c2..0c468fb 100644 --- a/app/src/main/java/net/nymtech/nymvpn/receiver/BootReceiver.kt +++ b/app/src/main/java/net/nymtech/nymvpn/receiver/BootReceiver.kt @@ -5,28 +5,30 @@ import android.content.Context import android.content.Intent import dagger.hilt.android.AndroidEntryPoint import net.nymtech.nymvpn.NymVpn -import net.nymtech.nymvpn.data.datastore.DataStoreManager +import net.nymtech.nymvpn.data.GatewayRepository +import net.nymtech.nymvpn.data.SettingsRepository import net.nymtech.nymvpn.util.goAsync import net.nymtech.vpn.NymVpnClient -import net.nymtech.vpn.model.Hop -import net.nymtech.vpn.model.VpnMode import javax.inject.Inject @AndroidEntryPoint class BootReceiver : BroadcastReceiver() { - @Inject lateinit var dataStoreManager: DataStoreManager + @Inject lateinit var gatewayRepository: GatewayRepository + + @Inject lateinit var settingsRepository: SettingsRepository override fun onReceive(context: Context?, intent: Intent?) = goAsync { if (Intent.ACTION_BOOT_COMPLETED != intent?.action) return@goAsync - val autoStart = dataStoreManager.getFromStore(DataStoreManager.AUTO_START) - if (autoStart == true) { - val firstHopCountry = dataStoreManager.getFromStore(DataStoreManager.FIRST_HOP_COUNTRY) - val lastHopCountry = dataStoreManager.getFromStore(DataStoreManager.LAST_HOP_COUNTRY) - val mode = dataStoreManager.getFromStore(DataStoreManager.VPN_MODE) + if (settingsRepository.isAutoStartEnabled()) { + val entryCountry = gatewayRepository.getFirstHopCountry() + val exitCountry = gatewayRepository.getLastHopCountry() + val mode = settingsRepository.getVpnMode() context?.let { context -> - NymVpnClient.connectForeground(context, Hop.Country.from(firstHopCountry), Hop.Country.from(lastHopCountry), - VpnMode.from(mode)) + val entry = entryCountry.toEntryPoint() + val exit = exitCountry.toExitPoint() + NymVpnClient.configure(entry,exit,mode) + NymVpnClient.start(context) NymVpn.requestTileServiceStateUpdate(context) } } diff --git a/app/src/main/java/net/nymtech/nymvpn/service/AlwaysOnVpnService.kt b/app/src/main/java/net/nymtech/nymvpn/service/AlwaysOnVpnService.kt index 1e2bd46..e89d433 100644 --- a/app/src/main/java/net/nymtech/nymvpn/service/AlwaysOnVpnService.kt +++ b/app/src/main/java/net/nymtech/nymvpn/service/AlwaysOnVpnService.kt @@ -1,35 +1,44 @@ package net.nymtech.nymvpn.service -import android.app.Service import android.content.Intent import android.os.IBinder +import androidx.lifecycle.LifecycleService +import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch import net.nymtech.nymvpn.NymVpn -import net.nymtech.nymvpn.data.datastore.DataStoreManager +import net.nymtech.nymvpn.data.GatewayRepository +import net.nymtech.nymvpn.data.SettingsRepository import net.nymtech.vpn.NymVpnClient -import net.nymtech.vpn.model.Hop -import net.nymtech.vpn.model.VpnMode import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint -class AlwaysOnVpnService : Service() { +class AlwaysOnVpnService : LifecycleService() { - @Inject - lateinit var dataStoreManager: DataStoreManager - override fun onBind(intent: Intent?): IBinder? { + @Inject lateinit var gatewayRepository: GatewayRepository + + @Inject lateinit var settingsRepository: SettingsRepository + + override fun onBind(intent: Intent): IBinder? { + super.onBind(intent) + // We don't provide binding, so return null return null } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if (intent == null || intent.component == null || intent.component!!.packageName != packageName) { Timber.i("Always-on VPN requested start") - val firstHopCountry = dataStoreManager.getFromStoreBlocking(DataStoreManager.FIRST_HOP_COUNTRY) - val lastHopCountry = dataStoreManager.getFromStoreBlocking(DataStoreManager.LAST_HOP_COUNTRY) - val mode = dataStoreManager.getFromStoreBlocking(DataStoreManager.VPN_MODE) - NymVpn.requestTileServiceStateUpdate(this) - NymVpnClient.connectForeground(this, Hop.Country.from(firstHopCountry), Hop.Country.from(lastHopCountry), - VpnMode.from(mode)) + lifecycleScope.launch { + val entryCountry = gatewayRepository.getFirstHopCountry() + val exitCountry = gatewayRepository.getLastHopCountry() + val mode = settingsRepository.getVpnMode() + val entry = entryCountry.toEntryPoint() + val exit = exitCountry.toExitPoint() + NymVpnClient.configure(entry,exit,mode) + NymVpnClient.start(this@AlwaysOnVpnService) + NymVpn.requestTileServiceStateUpdate(this@AlwaysOnVpnService) + } START_STICKY } else { START_NOT_STICKY diff --git a/app/src/main/java/net/nymtech/nymvpn/service/tile/VpnQuickTile.kt b/app/src/main/java/net/nymtech/nymvpn/service/tile/VpnQuickTile.kt index 5583bbd..462bb48 100644 --- a/app/src/main/java/net/nymtech/nymvpn/service/tile/VpnQuickTile.kt +++ b/app/src/main/java/net/nymtech/nymvpn/service/tile/VpnQuickTile.kt @@ -8,10 +8,11 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel import kotlinx.coroutines.launch +import net.nymtech.nymvpn.NymVpn import net.nymtech.nymvpn.R -import net.nymtech.nymvpn.data.datastore.DataStoreManager +import net.nymtech.nymvpn.data.GatewayRepository +import net.nymtech.nymvpn.data.SettingsRepository import net.nymtech.vpn.NymVpnClient -import net.nymtech.vpn.model.Hop import net.nymtech.vpn.model.VpnMode import net.nymtech.vpn.model.VpnState import timber.log.Timber @@ -21,7 +22,9 @@ import javax.inject.Inject class VpnQuickTile : TileService() { @Inject - lateinit var dataStoreManager: DataStoreManager + lateinit var gatewayRepository: GatewayRepository + + lateinit var settingsRepository: SettingsRepository private val scope = CoroutineScope(Dispatchers.IO) @@ -75,12 +78,16 @@ class VpnQuickTile : TileService() { VpnState.Up -> NymVpnClient.disconnect(this) VpnState.Down -> { scope.launch { - val firstHopCountry = dataStoreManager.getFromStore(DataStoreManager.FIRST_HOP_COUNTRY) - val lastHopCountry = dataStoreManager.getFromStore(DataStoreManager.LAST_HOP_COUNTRY) - val mode = dataStoreManager.getFromStore(DataStoreManager.VPN_MODE) - NymVpnClient.connectForeground(this@VpnQuickTile, - Hop.Country.from(firstHopCountry), Hop.Country.from(lastHopCountry), VpnMode.from(mode)) + val entryCountry = gatewayRepository.getFirstHopCountry() + val exitCountry = gatewayRepository.getLastHopCountry() + val mode = settingsRepository.getVpnMode() + val entry = entryCountry.toEntryPoint() + val exit = exitCountry.toExitPoint() + NymVpnClient.configure(entry,exit,mode) + NymVpnClient.start(this@VpnQuickTile) + NymVpn.requestTileServiceStateUpdate(this@VpnQuickTile) } + } else -> Unit } @@ -88,14 +95,14 @@ class VpnQuickTile : TileService() { } private fun setTileText() = scope.launch { - val firstHopCountry = dataStoreManager.getFromStore(DataStoreManager.FIRST_HOP_COUNTRY) - val lastHopCountry = dataStoreManager.getFromStore(DataStoreManager.LAST_HOP_COUNTRY) - val mode = dataStoreManager.getFromStore(DataStoreManager.VPN_MODE) - val isTwoHop = VpnMode.from(mode) == VpnMode.TWO_HOP_MIXNET + val firstHopCountry = gatewayRepository.getFirstHopCountry() + val lastHopCountry = gatewayRepository.getLastHopCountry() + val mode = settingsRepository.getVpnMode() + val isTwoHop = mode == VpnMode.TWO_HOP_MIXNET setTitle("${this@VpnQuickTile.getString(R.string.mode)}: ${if(isTwoHop) this@VpnQuickTile.getString( R.string.two_hop) else this@VpnQuickTile.getString(R.string.five_hop)}") setTileDescription( - "${Hop.Country.from(firstHopCountry).isoCode} -> ${Hop.Country.from(lastHopCountry).isoCode}") + "${firstHopCountry.isoCode} -> ${lastHopCountry.isoCode}") } private fun setActive() { diff --git a/app/src/main/java/net/nymtech/nymvpn/ui/AppViewModel.kt b/app/src/main/java/net/nymtech/nymvpn/ui/AppViewModel.kt index 6f2e184..aa30b79 100644 --- a/app/src/main/java/net/nymtech/nymvpn/ui/AppViewModel.kt +++ b/app/src/main/java/net/nymtech/nymvpn/ui/AppViewModel.kt @@ -27,7 +27,7 @@ import net.nymtech.nymvpn.util.Constants import net.nymtech.nymvpn.util.FileUtils import net.nymtech.nymvpn.util.log.NymLibException import net.nymtech.vpn.NymVpnClient -import net.nymtech.vpn.model.Hop +import net.nymtech.vpn.model.Country import timber.log.Timber import java.time.Instant import java.util.Locale @@ -103,38 +103,29 @@ class AppViewModel @Inject constructor( fun onEntryLocationSelected(selected : Boolean) = viewModelScope.launch { settingsRepository.setFirstHopSelection(selected) - gatewayRepository.setFirstHopCountry(Hop.Country(isDefault = true)) + gatewayRepository.setFirstHopCountry(Country(isDefault = true)) updateFirstHopDefaultCountry() } private suspend fun updateFirstHopDefaultCountry() { val firstHop = gatewayRepository.getFirstHopCountry() - if(firstHop.isDefault || firstHop.isFastest) { + if(firstHop.isDefault || firstHop.isLowLatency) { setFirstHopToLowLatency() } } private suspend fun updateEntryCountriesCache() { - val entryGateways = NymVpnClient.gateways(false) - val entryCountries = entryGateways.map { - val countryIso = it - Hop.Country(countryIso, Locale(countryIso.lowercase(), countryIso).displayCountry) - }.toSet() + val entryCountries = NymVpnClient.gateways(false) gatewayRepository.setEntryCountries(entryCountries) } private suspend fun updateExitCountriesCache() { - val exitGateways = NymVpnClient.gateways(true) - val exitCountries = exitGateways.map { - val countryIso = it - Hop.Country(countryIso, Locale(countryIso.lowercase(), countryIso).displayCountry) - }.toSet() + val exitCountries = NymVpnClient.gateways(true) gatewayRepository.setExitCountries(exitCountries) } private suspend fun setFirstHopToLowLatency() { - val lowLatencyCountryIso = NymVpnClient.getLowLatencyEntryCountryCode() - Timber.i("Setting low latency entry country: $lowLatencyCountryIso") - gatewayRepository.setFirstHopCountry(Hop.Country(isoCode = lowLatencyCountryIso, isFastest = true)) + val lowLatencyCountry = NymVpnClient.getLowLatencyEntryCountryCode() + gatewayRepository.setFirstHopCountry(lowLatencyCountry) } fun openWebPage(url: String) { diff --git a/app/src/main/java/net/nymtech/nymvpn/ui/common/functions/countryIcon.kt b/app/src/main/java/net/nymtech/nymvpn/ui/common/functions/countryIcon.kt index 8eb3e69..4bebab9 100644 --- a/app/src/main/java/net/nymtech/nymvpn/ui/common/functions/countryIcon.kt +++ b/app/src/main/java/net/nymtech/nymvpn/ui/common/functions/countryIcon.kt @@ -16,13 +16,13 @@ import net.nymtech.nymvpn.ui.theme.iconSize import net.nymtech.nymvpn.util.StringUtils import net.nymtech.nymvpn.util.scaledHeight import net.nymtech.nymvpn.util.scaledWidth -import net.nymtech.vpn.model.Hop +import net.nymtech.vpn.model.Country @Composable -fun countryIcon(country: Hop.Country): @Composable () -> Unit { +fun countryIcon(country: Country): @Composable () -> Unit { val context = LocalContext.current val image = - if (country.isFastest) ImageVector.vectorResource(R.drawable.bolt) + if (country.isLowLatency) ImageVector.vectorResource(R.drawable.bolt) else ImageVector.vectorResource(StringUtils.getFlagImageVectorByName(context, country.isoCode.lowercase())) return { Image( @@ -34,7 +34,7 @@ fun countryIcon(country: Hop.Country): @Composable () -> Unit { iconSize ), colorFilter = - if (country.isFastest) ColorFilter.tint(MaterialTheme.colorScheme.onSurface) + if (country.isLowLatency) ColorFilter.tint(MaterialTheme.colorScheme.onSurface) else null) } } \ No newline at end of file diff --git a/app/src/main/java/net/nymtech/nymvpn/ui/screens/hop/HopScreen.kt b/app/src/main/java/net/nymtech/nymvpn/ui/screens/hop/HopScreen.kt index 60b2ece..122acb5 100644 --- a/app/src/main/java/net/nymtech/nymvpn/ui/screens/hop/HopScreen.kt +++ b/app/src/main/java/net/nymtech/nymvpn/ui/screens/hop/HopScreen.kt @@ -71,7 +71,7 @@ fun HopScreen( } item { if (uiState.countries.isNotEmpty()) { - val fastest = uiState.countries.firstOrNull { it.isFastest } + val fastest = uiState.countries.firstOrNull { it.isLowLatency } if (fastest != null) { val name = StringUtils.buildCountryNameString(fastest, context) val icon = ImageVector.vectorResource(R.drawable.bolt) @@ -98,7 +98,7 @@ fun HopScreen( } } items(uiState.queriedCountries.toList()) { - if (it.isFastest) return@items + if (it.isLowLatency) return@items val icon = ImageVector.vectorResource(StringUtils.getFlagImageVectorByName(context, it.isoCode.lowercase())) SelectionItemButton( diff --git a/app/src/main/java/net/nymtech/nymvpn/ui/screens/hop/HopUiState.kt b/app/src/main/java/net/nymtech/nymvpn/ui/screens/hop/HopUiState.kt index 8db2d92..5a4bf5f 100644 --- a/app/src/main/java/net/nymtech/nymvpn/ui/screens/hop/HopUiState.kt +++ b/app/src/main/java/net/nymtech/nymvpn/ui/screens/hop/HopUiState.kt @@ -1,13 +1,12 @@ package net.nymtech.nymvpn.ui.screens.hop import net.nymtech.nymvpn.ui.HopType -import net.nymtech.vpn.model.Hop -import net.nymtech.vpn.model.HopCountries +import net.nymtech.vpn.model.Country data class HopUiState( - val countries: HopCountries = emptySet(), + val countries: Set = emptySet(), val hopType: HopType = HopType.FIRST, - val queriedCountries: HopCountries = emptySet(), - val selected: Hop.Country? = null, + val queriedCountries: Set = emptySet(), + val selected: Country? = null, val query: String = "" ) \ No newline at end of file diff --git a/app/src/main/java/net/nymtech/nymvpn/ui/screens/hop/HopViewModel.kt b/app/src/main/java/net/nymtech/nymvpn/ui/screens/hop/HopViewModel.kt index 3989f1a..d528315 100644 --- a/app/src/main/java/net/nymtech/nymvpn/ui/screens/hop/HopViewModel.kt +++ b/app/src/main/java/net/nymtech/nymvpn/ui/screens/hop/HopViewModel.kt @@ -12,7 +12,7 @@ import net.nymtech.nymvpn.NymVpn import net.nymtech.nymvpn.data.GatewayRepository import net.nymtech.nymvpn.ui.HopType import net.nymtech.nymvpn.util.Constants -import net.nymtech.vpn.model.Hop +import net.nymtech.vpn.model.Country import javax.inject.Inject @HiltViewModel @@ -67,7 +67,7 @@ class HopViewModel @Inject constructor( ) } - fun onSelected(country: Hop.Country) = viewModelScope.launch { + fun onSelected(country: Country) = viewModelScope.launch { when (_uiState.value.hopType) { HopType.FIRST -> gatewayRepository.setFirstHopCountry(country) HopType.LAST -> gatewayRepository.setLastHopCountry(country) diff --git a/app/src/main/java/net/nymtech/nymvpn/ui/screens/main/MainUiState.kt b/app/src/main/java/net/nymtech/nymvpn/ui/screens/main/MainUiState.kt index afeef5c..cbb0dd5 100644 --- a/app/src/main/java/net/nymtech/nymvpn/ui/screens/main/MainUiState.kt +++ b/app/src/main/java/net/nymtech/nymvpn/ui/screens/main/MainUiState.kt @@ -3,7 +3,7 @@ package net.nymtech.nymvpn.ui.screens.main import net.nymtech.nymvpn.ui.model.ConnectionState import net.nymtech.nymvpn.ui.model.StateMessage import net.nymtech.nymvpn.util.StringValue -import net.nymtech.vpn.model.Hop +import net.nymtech.vpn.model.Country import net.nymtech.vpn.model.VpnMode data class MainUiState( @@ -14,6 +14,6 @@ data class MainUiState( val connectionTime: String = "", val networkMode: VpnMode = VpnMode.TWO_HOP_MIXNET, val firstHopEnabled: Boolean = false, - val firstHopCounty: Hop.Country = Hop.Country(), - val lastHopCountry: Hop.Country = Hop.Country() + val firstHopCounty: Country = Country(), + val lastHopCountry: Country = Country() ) diff --git a/app/src/main/java/net/nymtech/nymvpn/ui/screens/main/MainViewModel.kt b/app/src/main/java/net/nymtech/nymvpn/ui/screens/main/MainViewModel.kt index 97d0e7a..88d714f 100644 --- a/app/src/main/java/net/nymtech/nymvpn/ui/screens/main/MainViewModel.kt +++ b/app/src/main/java/net/nymtech/nymvpn/ui/screens/main/MainViewModel.kt @@ -75,9 +75,13 @@ constructor( fun onConnect() = viewModelScope.launch(Dispatchers.IO) { - NymVpnClient.connect(application,gatewayRepository.getFirstHopCountry(), - gatewayRepository.getLastHopCountry(), - mode = uiState.value.networkMode) + val entryCountry = gatewayRepository.getFirstHopCountry() + val exitCountry = gatewayRepository.getLastHopCountry() + val mode = settingsRepository.getVpnMode() + val entry = entryCountry.toEntryPoint() + val exit = exitCountry.toExitPoint() + NymVpnClient.configure(entry,exit,mode) + NymVpnClient.start(application) NymVpn.requestTileServiceStateUpdate(application) } diff --git a/app/src/main/java/net/nymtech/nymvpn/util/StringUtils.kt b/app/src/main/java/net/nymtech/nymvpn/util/StringUtils.kt index 450031f..104dbb5 100644 --- a/app/src/main/java/net/nymtech/nymvpn/util/StringUtils.kt +++ b/app/src/main/java/net/nymtech/nymvpn/util/StringUtils.kt @@ -4,12 +4,13 @@ import android.annotation.SuppressLint import android.content.Context import androidx.compose.ui.text.buildAnnotatedString import net.nymtech.nymvpn.R -import net.nymtech.vpn.model.Hop +import net.nymtech.vpn.model.Country +import timber.log.Timber object StringUtils { - fun buildCountryNameString(country : Hop.Country, context : Context) : String { + fun buildCountryNameString(country : Country, context : Context) : String { return buildAnnotatedString { - if(country.isFastest) { + if(country.isLowLatency) { append(context.getString(R.string.fastest)) append(" (") append(country.name) @@ -20,6 +21,11 @@ object StringUtils { @SuppressLint("DiscouragedApi") fun getFlagImageVectorByName(context: Context, name: String): Int { val flagAssetName = "flag_%S".format(name).lowercase() - return context.resources.getIdentifier(flagAssetName, "drawable", context.packageName) + val resourceId = context.resources.getIdentifier(flagAssetName, "drawable", context.packageName) + return if(resourceId == 0) { + //TODO add a unknown icon flag + Timber.e("Cannot find flag for countryIso: $name") + 0 + } else resourceId } } \ No newline at end of file diff --git a/app/src/main/res/drawable/flag_vn.xml b/app/src/main/res/drawable/flag_vn.xml new file mode 100644 index 0000000..ae51abe --- /dev/null +++ b/app/src/main/res/drawable/flag_vn.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/flag_za.xml b/app/src/main/res/drawable/flag_za.xml new file mode 100644 index 0000000..26e520b --- /dev/null +++ b/app/src/main/res/drawable/flag_za.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + diff --git a/buildSrc/src/main/kotlin/Constants.kt b/buildSrc/src/main/kotlin/Constants.kt index a1bdb57..d47ad6b 100644 --- a/buildSrc/src/main/kotlin/Constants.kt +++ b/buildSrc/src/main/kotlin/Constants.kt @@ -31,6 +31,9 @@ object Constants { const val SANDBOX_API_URL = "https://sandbox-nym-api1.nymtech.net/api" const val SANDBOX_EXPLORER_URL = "https://sandbox-explorer.nymtech.net/api" + const val MAINNET_API_URL = "https://validator.nymtech.net/api/" + const val MAINNET_EXPLORER_URL = "https://explorer.nymtech.net/api/" + //licensee val allowedLicenses = listOf("MIT", "Apache-2.0", "BSD-3-Clause") const val ANDROID_TERMS_URL = "https://developer.android.com/studio/terms.html" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3db32c7..c6b87e6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -48,11 +48,14 @@ converter-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.r detekt-rules-compose = { module = "ru.kode:detekt-rules-compose", version.ref = "detektRulesCompose" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } + jna = { module = "net.java.dev.jna:jna", version.ref = "jna" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } +androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle-runtime-compose" } +androidx-lifecycle-service = { module = "androidx.lifecycle:lifecycle-service", version.ref = "lifecycle-runtime-compose" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } androidx-ui = { group = "androidx.compose.ui", name = "ui" } diff --git a/nym_vpn_client/build.gradle.kts b/nym_vpn_client/build.gradle.kts index d1fea13..bbf120d 100644 --- a/nym_vpn_client/build.gradle.kts +++ b/nym_vpn_client/build.gradle.kts @@ -54,6 +54,7 @@ android { } } compileOptions { + isCoreLibraryDesugaringEnabled = true sourceCompatibility = Constants.JAVA_VERSION targetCompatibility = Constants.JAVA_VERSION } @@ -70,14 +71,21 @@ android { } dependencies { - implementation(project(":logcat_helper")) + coreLibraryDesugaring(libs.com.android.tools.desugar) + implementation(libs.androidx.core.ktx) implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.serialization) implementation(libs.timber) implementation(libs.jna) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) } diff --git a/nym_vpn_client/src/androidTest/java/net/nymtech/vpn/ExampleInstrumentedTest.kt b/nym_vpn_client/src/androidTest/java/net/nymtech/vpn/ExampleInstrumentedTest.kt deleted file mode 100644 index 993fd8c..0000000 --- a/nym_vpn_client/src/androidTest/java/net/nymtech/vpn/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package net.nymtech.vpn - -//import androidx.test.ext.junit.runners.AndroidJUnit4 -//import androidx.test.platform.app.InstrumentationRegistry -//import org.junit.Assert.* -//import org.junit.Test -//import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -//@RunWith(AndroidJUnit4::class) -//class ExampleInstrumentedTest { -// @Test -// fun useAppContext() { -// // Context of the app under test. -// val appContext = InstrumentationRegistry.getInstrumentation().targetContext -// assertEquals("net.nymtech.vpn_client.test", appContext.packageName) -// } -//} \ No newline at end of file diff --git a/nym_vpn_client/src/androidTest/java/net/nymtech/vpn/NymVpnClientTest.kt b/nym_vpn_client/src/androidTest/java/net/nymtech/vpn/NymVpnClientTest.kt new file mode 100644 index 0000000..c57e7d3 --- /dev/null +++ b/nym_vpn_client/src/androidTest/java/net/nymtech/vpn/NymVpnClientTest.kt @@ -0,0 +1,18 @@ +package net.nymtech.vpn + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class NymVpnClientTest { + @Test + fun useAppContext() { + //TODO write tests + // Context of the app under test. + val context = InstrumentationRegistry.getInstrumentation().targetContext + //assertEquals("net.nymtech.vpn_client.test", appContext.packageName) + //NymVpnClient.connect(context, EntryPoint.Location("DE"), ) + } +} \ No newline at end of file diff --git a/nym_vpn_client/src/main/java/net/nymtech/vpn/NymVpnClient.kt b/nym_vpn_client/src/main/java/net/nymtech/vpn/NymVpnClient.kt index e52fea7..e12ee82 100644 --- a/nym_vpn_client/src/main/java/net/nymtech/vpn/NymVpnClient.kt +++ b/nym_vpn_client/src/main/java/net/nymtech/vpn/NymVpnClient.kt @@ -18,27 +18,28 @@ import kotlinx.coroutines.withContext import net.nymtech.logcat_helper.LogcatHelper import net.nymtech.logcat_helper.model.LogLevel import net.nymtech.vpn.model.ClientState -import net.nymtech.vpn.model.EntryPoint +import net.nymtech.vpn.model.Country import net.nymtech.vpn.model.ErrorState -import net.nymtech.vpn.model.ExitPoint import net.nymtech.vpn.model.VpnMode import net.nymtech.vpn.model.VpnState -import net.nymtech.vpn.util.Constants import net.nymtech.vpn.util.ServiceManager import net.nymtech.vpn.util.safeCollect import net.nymtech.vpn_client.BuildConfig +import nym_vpn_lib.EntryPoint +import nym_vpn_lib.ExitPoint +import nym_vpn_lib.VpnConfig +import nym_vpn_lib.getGatewayCountries +import nym_vpn_lib.getLowLatencyEntryCountry +import nym_vpn_lib.runVpn import timber.log.Timber -import uniffi.nym_vpn_lib.Country -import uniffi.nym_vpn_lib.getGatewayCountries -import uniffi.nym_vpn_lib.getLowLatencyEntryCountry +import java.net.URL +//TODO change to builder pattern? object NymVpnClient : VpnClient { - init { - Constants.setupEnvironment() - System.loadLibrary(Constants.NYM_VPN_LIB) - Timber.i( "Loaded native library in client") - } + private val apiUrl = URL(BuildConfig.API_URL) + private val explorerUrl = URL(BuildConfig.EXPLORER_URL) + private val scope = CoroutineScope(Dispatchers.IO) private val _state = MutableStateFlow(ClientState()) override val stateFlow: Flow = _state.asStateFlow() @@ -46,59 +47,55 @@ object NymVpnClient : VpnClient { return _state.value } - override suspend fun gateways(exitOnly: Boolean) : List { + override suspend fun gateways(exitOnly: Boolean) : Set { return withContext(CoroutineScope(Dispatchers.IO).coroutineContext) { - val gateways = getGatewayCountries(BuildConfig.API_URL,BuildConfig.EXPLORER_URL,exitOnly) - gateways.map { - (it as Country.Code).value - } + getGatewayCountries(apiUrl,explorerUrl,exitOnly).map { + Country(isoCode = it.twoLetterIsoCountryCode) + }.toSet() } } - override suspend fun getLowLatencyEntryCountryCode(): String { + override suspend fun getLowLatencyEntryCountryCode(): Country { return withContext(CoroutineScope(Dispatchers.IO).coroutineContext) { - getLowLatencyEntryCountry(BuildConfig.API_URL, BuildConfig.EXPLORER_URL).let { - (it as Country.Code).value - } + Country(isoCode = getLowLatencyEntryCountry(apiUrl, explorerUrl).twoLetterIsoCountryCode, isLowLatency = true) } } - - private val scope = CoroutineScope(Dispatchers.IO) - private var statusJob: Job? = null + override fun configure(entryPoint: EntryPoint, exitPoint: ExitPoint, mode: VpnMode) { + _state.value = _state.value.copy( + entryPoint = entryPoint, + exitPoint = exitPoint, + mode = mode + ) + } + override fun prepare(context : Context): Intent? { return VpnService.prepare(context) } - override fun connect(context: Context, entryPoint: EntryPoint, exitPoint: ExitPoint, mode: VpnMode) { + override fun start(context: Context) { clearErrorStatus() - setMode(mode) - val extras = mapOf( - ENTRY_POINT_EXTRA_KEY to entryPoint.toLibString(), - EXIT_POINT_EXTRA_KEY to exitPoint.toLibString(), - TWO_HOP_EXTRA_KEY to isTwoHop(mode).toString() - ) - //TODO fix logic for more modes later statusJob = collectLogStatus(context) - ServiceManager.startVpnService(context, extras) + ServiceManager.startVpnService(context) } - override fun connectForeground( - context: Context, - entryPoint: EntryPoint, - exitPoint: ExitPoint, - mode: VpnMode - ) { + override fun startForeground(context: Context) { clearErrorStatus() - setMode(mode) - val extras = mapOf( - ENTRY_POINT_EXTRA_KEY to entryPoint.toLibString(), - EXIT_POINT_EXTRA_KEY to exitPoint.toLibString(), - TWO_HOP_EXTRA_KEY to isTwoHop(mode).toString() - ) statusJob = collectLogStatus(context) - ServiceManager.startVpnServiceForeground(context, extras) + ServiceManager.startVpnServiceForeground(context) + } + internal fun connect() { + //TODO refactor + if(_state.value.exitPoint != null && _state.value.entryPoint != null) { + try { + runVpn(VpnConfig( + apiUrl, explorerUrl, + _state.value.entryPoint!!, _state.value.exitPoint!!, isTwoHop(_state.value.mode))) + } catch (e : Exception) { + Timber.e(e) + } + } } private fun isTwoHop(mode : VpnMode) : Boolean = when(mode) { @@ -161,13 +158,6 @@ object NymVpnClient : VpnClient { errorState = ErrorState.None ) } - - private fun setMode(mode : VpnMode) { - _state.value = _state.value.copy( - mode = mode - ) - } - private fun setErrorState(message : String) { _state.value = _state.value.copy( errorState = ErrorState.LibraryError(message) diff --git a/nym_vpn_client/src/main/java/net/nymtech/vpn/NymVpnService.kt b/nym_vpn_client/src/main/java/net/nymtech/vpn/NymVpnService.kt index 0e53498..6fc637e 100644 --- a/nym_vpn_client/src/main/java/net/nymtech/vpn/NymVpnService.kt +++ b/nym_vpn_client/src/main/java/net/nymtech/vpn/NymVpnService.kt @@ -13,18 +13,15 @@ import androidx.core.app.NotificationCompat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.cancel import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import net.nymtech.vpn.model.VpnState import net.nymtech.vpn.tun_provider.TunConfig import net.nymtech.vpn.util.Action import net.nymtech.vpn.util.Constants -import net.nymtech.vpn_client.BuildConfig import net.nymtech.vpn_client.R +import nym_vpn_lib.stopVpn import timber.log.Timber -import uniffi.nym_vpn_lib.runVpn -import uniffi.nym_vpn_lib.stopVpn import java.net.Inet4Address import java.net.Inet6Address import java.net.InetAddress @@ -35,11 +32,12 @@ class NymVpnService : VpnService() { init { Constants.setupEnvironment() System.loadLibrary(Constants.NYM_VPN_LIB) - Timber.i( "Loaded native library in service") + Timber.i("Loaded native library in service") } - } + val scope = CoroutineScope(Dispatchers.IO) + private var activeTunStatus by observable(null) { _, oldTunStatus, _ -> val oldTunFd = when (oldTunStatus) { is CreateTunResult.Success -> oldTunStatus.tunFd @@ -62,21 +60,31 @@ class NymVpnService : VpnService() { val connectivityListener = ConnectivityListener() + @OptIn(DelicateCoroutinesApi::class) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { return when (intent?.action) { Action.START.name, Action.START_FOREGROUND.name -> { NymVpnClient.setVpnState(VpnState.Connecting.InitializingClient) currentTunConfig = defaultTunConfig() Timber.i("VPN start") - startVpn(intent) + if(prepare(this) == null) { + scope.launch { + initVPN(this) + NymVpnClient.connect() + } + } START_STICKY } Action.STOP.name -> { Timber.d("VPN stop") NymVpnClient.setVpnState(VpnState.Disconnecting) - runBlocking { - stopVpn() - } + scope.launch(Dispatchers.IO) { + try { + stopVpn() + } catch (e : Exception) { + Timber.e(e) + } + } stopSelf() START_NOT_STICKY } @@ -84,27 +92,6 @@ class NymVpnService : VpnService() { } } - private fun startVpn(intent : Intent) { - try { - if(prepare(this) == null) { - val isTwoHop = intent.extras?.getString(NymVpnClient.TWO_HOP_EXTRA_KEY).toBoolean() - val entry = intent.extras?.getString(NymVpnClient.ENTRY_POINT_EXTRA_KEY) - val exit = intent.extras?.getString(NymVpnClient.EXIT_POINT_EXTRA_KEY) - Timber.i("$entry $exit $isTwoHop") - if(!entry.isNullOrBlank() && !exit.isNullOrBlank()) { - initVPN(isTwoHop, BuildConfig.API_URL, BuildConfig.EXPLORER_URL, entry, exit,this) - CoroutineScope(Dispatchers.IO).launch { - launch { - runVpn() - } - } - } - } - } catch (e : Exception) { - Timber.e(e) - } - } - private fun createNotificationChannel(): String{ val channelId = "my_service" val channelName = "My Background Service" @@ -143,14 +130,11 @@ class NymVpnService : VpnService() { startForeground(123, notification) } - @OptIn(DelicateCoroutinesApi::class) override fun onDestroy() { Timber.i("VpnService destroyed") NymVpnClient.setVpnState(VpnState.Down) connectivityListener.unregister() - GlobalScope.launch { - stopVpn() - } + scope.cancel() stopSelf() } @@ -257,11 +241,6 @@ class NymVpnService : VpnService() { } private external fun initVPN( - enable_two_hop: Boolean, - api_url: String, - explorer_url: String, - entry_gateway: String, - exit_router: String, vpn_service: Any ) diff --git a/nym_vpn_client/src/main/java/net/nymtech/vpn/VpnClient.kt b/nym_vpn_client/src/main/java/net/nymtech/vpn/VpnClient.kt index e1ca225..fcb2562 100644 --- a/nym_vpn_client/src/main/java/net/nymtech/vpn/VpnClient.kt +++ b/nym_vpn_client/src/main/java/net/nymtech/vpn/VpnClient.kt @@ -4,19 +4,21 @@ import android.content.Context import android.content.Intent import kotlinx.coroutines.flow.Flow import net.nymtech.vpn.model.ClientState -import net.nymtech.vpn.model.EntryPoint -import net.nymtech.vpn.model.ExitPoint +import net.nymtech.vpn.model.Country import net.nymtech.vpn.model.VpnMode -import uniffi.nym_vpn_lib.Country +import nym_vpn_lib.EntryPoint +import nym_vpn_lib.ExitPoint interface VpnClient { + + fun configure(entryPoint: EntryPoint, exitPoint: ExitPoint, mode: VpnMode = VpnMode.TWO_HOP_MIXNET) fun prepare(context : Context) : Intent? - fun connect(context: Context, entryPoint: EntryPoint, exitPoint: ExitPoint, mode: VpnMode) - fun connectForeground(context: Context, entryPoint: EntryPoint, exitPoint: ExitPoint, mode: VpnMode) + fun start(context: Context) + fun startForeground(context: Context) fun disconnect(context: Context) val stateFlow : Flow fun getState() : ClientState - suspend fun gateways(exitOnly: Boolean = false) : List + suspend fun gateways(exitOnly: Boolean = false) : Set - suspend fun getLowLatencyEntryCountryCode() : String + suspend fun getLowLatencyEntryCountryCode() : Country } \ No newline at end of file diff --git a/nym_vpn_client/src/main/java/net/nymtech/vpn/model/ClientState.kt b/nym_vpn_client/src/main/java/net/nymtech/vpn/model/ClientState.kt index 1b9e49d..962696e 100644 --- a/nym_vpn_client/src/main/java/net/nymtech/vpn/model/ClientState.kt +++ b/nym_vpn_client/src/main/java/net/nymtech/vpn/model/ClientState.kt @@ -1,8 +1,13 @@ package net.nymtech.vpn.model +import nym_vpn_lib.EntryPoint +import nym_vpn_lib.ExitPoint + data class ClientState( val vpnState: VpnState = VpnState.Down, val statistics: VpnStatistics = VpnStatistics(), val errorState: ErrorState = ErrorState.None, - val mode: VpnMode = VpnMode.TWO_HOP_MIXNET + val mode: VpnMode = VpnMode.TWO_HOP_MIXNET, + val entryPoint: EntryPoint? = null, + val exitPoint: ExitPoint? = null, ) \ No newline at end of file diff --git a/nym_vpn_client/src/main/java/net/nymtech/vpn/model/Country.kt b/nym_vpn_client/src/main/java/net/nymtech/vpn/model/Country.kt new file mode 100644 index 0000000..45d534d --- /dev/null +++ b/nym_vpn_client/src/main/java/net/nymtech/vpn/model/Country.kt @@ -0,0 +1,46 @@ +package net.nymtech.vpn.model + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import net.nymtech.vpn.util.Constants +import nym_vpn_lib.EntryPoint +import nym_vpn_lib.ExitPoint +import java.util.Locale + +@Serializable +data class Country( + val isoCode: String = Constants.DEFAULT_COUNTRY_ISO, + val name: String = Locale(isoCode.lowercase(), isoCode).displayCountry, + val isLowLatency: Boolean = false, + val isDefault: Boolean = false +) { + + init { + if(isoCode.length > 2) { + throw IllegalArgumentException("isoCode must be two characters") + } + } + + override fun toString(): String { + return Json.encodeToString(serializer(), this) + } + + fun toEntryPoint() : EntryPoint { + return EntryPoint.Location(isoCode) + } + + fun toExitPoint() : ExitPoint { + return ExitPoint.Location(isoCode) + } + + companion object { + fun from(string: String?): Country { + return string?.let { Json.decodeFromString(string)} ?: Country() + } + fun fromCollectionString(string: String?) : Set { + return string?.let { + Json.decodeFromString>(it) + } ?: emptySet() + } + } +} diff --git a/nym_vpn_client/src/main/java/net/nymtech/vpn/model/EntryPoint.kt b/nym_vpn_client/src/main/java/net/nymtech/vpn/model/EntryPoint.kt deleted file mode 100644 index a83a099..0000000 --- a/nym_vpn_client/src/main/java/net/nymtech/vpn/model/EntryPoint.kt +++ /dev/null @@ -1,3 +0,0 @@ -package net.nymtech.vpn.model - -interface EntryPoint : Point \ No newline at end of file diff --git a/nym_vpn_client/src/main/java/net/nymtech/vpn/model/ExitPoint.kt b/nym_vpn_client/src/main/java/net/nymtech/vpn/model/ExitPoint.kt deleted file mode 100644 index 07dea3d..0000000 --- a/nym_vpn_client/src/main/java/net/nymtech/vpn/model/ExitPoint.kt +++ /dev/null @@ -1,3 +0,0 @@ -package net.nymtech.vpn.model - -interface ExitPoint : Point \ No newline at end of file diff --git a/nym_vpn_client/src/main/java/net/nymtech/vpn/model/Hop.kt b/nym_vpn_client/src/main/java/net/nymtech/vpn/model/Hop.kt deleted file mode 100644 index ec0e6c4..0000000 --- a/nym_vpn_client/src/main/java/net/nymtech/vpn/model/Hop.kt +++ /dev/null @@ -1,53 +0,0 @@ -package net.nymtech.vpn.model - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -import net.nymtech.vpn.util.Constants -import java.util.Locale - -typealias HopCountries = Set -sealed class Hop { - @Serializable - data class Country( - val isoCode: String = Constants.DEFAULT_COUNTRY_ISO, - val name: String = Locale(isoCode.lowercase(), isoCode).displayCountry, - val isFastest: Boolean = false, - val isDefault: Boolean = false - ) : ExitPoint, EntryPoint, Hop() { - - init { - if(isoCode.length > 2) { - throw IllegalArgumentException("isoCode must be two characters") - } - } - - override fun toLibString(): String { - return Location(Location.CountryISO(this.isoCode)).toString() - } - - override fun toString(): String { - return Json.encodeToString(serializer(), this) - } - - companion object { - //TODO handle errors - fun from(string: String?): Country { - return string?.let { Json.decodeFromString(string)} ?: Country() - } - fun fromCollectionString(string: String?) : HopCountries { - return string?.let { - Json.decodeFromString(it) - } ?: emptySet() - } - } - } - - @Serializable - @SerialName("Gateway") - data class Gateway(@SerialName("identity") val identity : String) : ExitPoint, EntryPoint, Hop() { - override fun toLibString(): String { - return this.toString() - } - } -} \ No newline at end of file diff --git a/nym_vpn_client/src/main/java/net/nymtech/vpn/model/Location.kt b/nym_vpn_client/src/main/java/net/nymtech/vpn/model/Location.kt deleted file mode 100644 index 4a9ee22..0000000 --- a/nym_vpn_client/src/main/java/net/nymtech/vpn/model/Location.kt +++ /dev/null @@ -1,14 +0,0 @@ -package net.nymtech.vpn.model - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json - -@Serializable -internal data class Location(@SerialName("Location") val countryISO: CountryISO) { - @Serializable - internal class CountryISO(@SerialName("location") val iso : String) - override fun toString(): String { - return Json.encodeToString(serializer(), this) - } -} \ No newline at end of file diff --git a/nym_vpn_client/src/main/java/net/nymtech/vpn/model/Point.kt b/nym_vpn_client/src/main/java/net/nymtech/vpn/model/Point.kt deleted file mode 100644 index 1a077c8..0000000 --- a/nym_vpn_client/src/main/java/net/nymtech/vpn/model/Point.kt +++ /dev/null @@ -1,5 +0,0 @@ -package net.nymtech.vpn.model - -interface Point { - fun toLibString() : String -} \ No newline at end of file diff --git a/nym_vpn_client/src/main/java/net/nymtech/vpn/uniffi/nym_vpn_lib/nym_vpn_lib.kt b/nym_vpn_client/src/main/java/net/nymtech/vpn/nym_vpn_lib/nym_vpn_lib.kt similarity index 82% rename from nym_vpn_client/src/main/java/net/nymtech/vpn/uniffi/nym_vpn_lib/nym_vpn_lib.kt rename to nym_vpn_client/src/main/java/net/nymtech/vpn/nym_vpn_lib/nym_vpn_lib.kt index e4931de..b010991 100644 --- a/nym_vpn_client/src/main/java/net/nymtech/vpn/uniffi/nym_vpn_lib/nym_vpn_lib.kt +++ b/nym_vpn_client/src/main/java/net/nymtech/vpn/nym_vpn_lib/nym_vpn_lib.kt @@ -3,7 +3,7 @@ @file:Suppress("NAME_SHADOWING") -package uniffi.nym_vpn_lib; +package nym_vpn_lib; // Common helper code. // @@ -29,6 +29,8 @@ import java.nio.ByteOrder import java.nio.CharBuffer import java.nio.charset.CodingErrorAction import java.util.concurrent.ConcurrentHashMap +import java.net.URI +import java.net.URL // This is a helper for safely working with byte buffers returned from the Rust code. // A rust-owned buffer is represented by its capacity, its current length, and a @@ -386,7 +388,7 @@ internal interface UniffiLib : Library { ): RustBuffer.ByValue fun uniffi_nym_vpn_lib_fn_func_getlowlatencyentrycountry(`apiUrl`: RustBuffer.ByValue,`explorerUrl`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue - fun uniffi_nym_vpn_lib_fn_func_runvpn(uniffi_out_err: UniffiRustCallStatus, + fun uniffi_nym_vpn_lib_fn_func_runvpn(`config`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): Unit fun uniffi_nym_vpn_lib_fn_func_stopvpn(uniffi_out_err: UniffiRustCallStatus, ): Unit @@ -527,16 +529,16 @@ private fun uniffiCheckContractApiVersion(lib: UniffiLib) { @Suppress("UNUSED_PARAMETER") private fun uniffiCheckApiChecksums(lib: UniffiLib) { - if (lib.uniffi_nym_vpn_lib_checksum_func_getgatewaycountries() != 14840.toShort()) { + if (lib.uniffi_nym_vpn_lib_checksum_func_getgatewaycountries() != 45821.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_nym_vpn_lib_checksum_func_getlowlatencyentrycountry() != 27191.toShort()) { + if (lib.uniffi_nym_vpn_lib_checksum_func_getlowlatencyentrycountry() != 48712.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_nym_vpn_lib_checksum_func_runvpn() != 15640.toShort()) { + if (lib.uniffi_nym_vpn_lib_checksum_func_runvpn() != 53939.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_nym_vpn_lib_checksum_func_stopvpn() != 4440.toShort()) { + if (lib.uniffi_nym_vpn_lib_checksum_func_stopvpn() != 23819.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } } @@ -576,6 +578,26 @@ inline fun T.use(block: (T) -> R) = } } +public object FfiConverterDouble: FfiConverter { + override fun lift(value: Double): Double { + return value + } + + override fun read(buf: ByteBuffer): Double { + return buf.getDouble() + } + + override fun lower(value: Double): Double { + return value + } + + override fun allocationSize(value: Double) = 8 + + override fun write(value: Double, buf: ByteBuffer) { + buf.putDouble(value) + } +} + public object FfiConverterBoolean: FfiConverter { override fun lift(value: Byte): Boolean { return value.toInt() != 0 @@ -652,103 +674,88 @@ public object FfiConverterString: FfiConverter { -enum class ClientState { +data class Location ( + var `twoLetterIsoCountryCode`: String, + var `threeLetterIsoCountryCode`: String, + var `countryName`: String, + var `latitude`: Double?, + var `longitude`: Double? +) { - UNINITIALISED, - CONNECTED, - DISCONNECTED; companion object } -public object FfiConverterTypeClientState: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer) = try { - ClientState.values()[buf.getInt() - 1] - } catch (e: IndexOutOfBoundsException) { - throw RuntimeException("invalid enum value, something is very wrong!!", e) - } - - override fun allocationSize(value: ClientState) = 4 - - override fun write(value: ClientState, buf: ByteBuffer) { - buf.putInt(value.ordinal + 1) +public object FfiConverterTypeLocation: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Location { + return Location( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterString.read(buf), + FfiConverterOptionalDouble.read(buf), + FfiConverterOptionalDouble.read(buf), + ) + } + + override fun allocationSize(value: Location) = ( + FfiConverterString.allocationSize(value.`twoLetterIsoCountryCode`) + + FfiConverterString.allocationSize(value.`threeLetterIsoCountryCode`) + + FfiConverterString.allocationSize(value.`countryName`) + + FfiConverterOptionalDouble.allocationSize(value.`latitude`) + + FfiConverterOptionalDouble.allocationSize(value.`longitude`) + ) + + override fun write(value: Location, buf: ByteBuffer) { + FfiConverterString.write(value.`twoLetterIsoCountryCode`, buf) + FfiConverterString.write(value.`threeLetterIsoCountryCode`, buf) + FfiConverterString.write(value.`countryName`, buf) + FfiConverterOptionalDouble.write(value.`latitude`, buf) + FfiConverterOptionalDouble.write(value.`longitude`, buf) } } - - -sealed class Country { - - data class Code( - - val `value`: String - ) : Country() { - companion object - } - - data class Name( - - val `value`: String - ) : Country() { - companion object - } - - +data class VpnConfig ( + var `apiUrl`: Url, + var `explorerUrl`: Url, + var `entryGateway`: EntryPoint, + var `exitRouter`: ExitPoint, + var `enableTwoHop`: Boolean +) { companion object } -public object FfiConverterTypeCountry : FfiConverterRustBuffer{ - override fun read(buf: ByteBuffer): Country { - return when(buf.getInt()) { - 1 -> Country.Code( - FfiConverterString.read(buf), - ) - 2 -> Country.Name( - FfiConverterString.read(buf), - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } - } - - override fun allocationSize(value: Country) = when(value) { - is Country.Code -> { - // Add the size for the Int that specifies the variant plus the size needed for all fields - ( - 4 - + FfiConverterString.allocationSize(value.`value`) - ) - } - is Country.Name -> { - // Add the size for the Int that specifies the variant plus the size needed for all fields - ( - 4 - + FfiConverterString.allocationSize(value.`value`) - ) - } - } - - override fun write(value: Country, buf: ByteBuffer) { - when(value) { - is Country.Code -> { - buf.putInt(1) - FfiConverterString.write(value.`value`, buf) - Unit - } - is Country.Name -> { - buf.putInt(2) - FfiConverterString.write(value.`value`, buf) - Unit - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } +public object FfiConverterTypeVPNConfig: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): VpnConfig { + return VpnConfig( + FfiConverterTypeUrl.read(buf), + FfiConverterTypeUrl.read(buf), + FfiConverterTypeEntryPoint.read(buf), + FfiConverterTypeExitPoint.read(buf), + FfiConverterBoolean.read(buf), + ) + } + + override fun allocationSize(value: VpnConfig) = ( + FfiConverterTypeUrl.allocationSize(value.`apiUrl`) + + FfiConverterTypeUrl.allocationSize(value.`explorerUrl`) + + FfiConverterTypeEntryPoint.allocationSize(value.`entryGateway`) + + FfiConverterTypeExitPoint.allocationSize(value.`exitRouter`) + + FfiConverterBoolean.allocationSize(value.`enableTwoHop`) + ) + + override fun write(value: VpnConfig, buf: ByteBuffer) { + FfiConverterTypeUrl.write(value.`apiUrl`, buf) + FfiConverterTypeUrl.write(value.`explorerUrl`, buf) + FfiConverterTypeEntryPoint.write(value.`entryGateway`, buf) + FfiConverterTypeExitPoint.write(value.`exitRouter`, buf) + FfiConverterBoolean.write(value.`enableTwoHop`, buf) } } - - sealed class EntryPoint { data class Gateway( @@ -957,17 +964,25 @@ sealed class FfiException: Exception() { get() = "" } - class IncorrectState( - - val `current`: ClientState, - - val `expected`: ClientState + class VpnNotStopped( + ) : FfiException() { + override val message + get() = "" + } + + class VpnNotStarted( ) : FfiException() { override val message - get() = "current=${ `current` }, expected=${ `expected` }" + get() = "" } - class UrlParse( + class NoContext( + ) : FfiException() { + override val message + get() = "" + } + + class LibException( val `inner`: String ) : FfiException() { @@ -975,7 +990,7 @@ sealed class FfiException: Exception() { get() = "inner=${ `inner` }" } - class LibException( + class GatewayDirectoryException( val `inner`: String ) : FfiException() { @@ -998,14 +1013,13 @@ public object FfiConverterTypeFFIError : FfiConverterRustBuffer { return when(buf.getInt()) { 1 -> FfiException.InvalidValueUniffi() 2 -> FfiException.FdNotFound() - 3 -> FfiException.IncorrectState( - FfiConverterTypeClientState.read(buf), - FfiConverterTypeClientState.read(buf), - ) - 4 -> FfiException.UrlParse( + 3 -> FfiException.VpnNotStopped() + 4 -> FfiException.VpnNotStarted() + 5 -> FfiException.NoContext() + 6 -> FfiException.LibException( FfiConverterString.read(buf), ) - 5 -> FfiException.LibException( + 7 -> FfiException.GatewayDirectoryException( FfiConverterString.read(buf), ) else -> throw RuntimeException("invalid error enum value, something is very wrong!!") @@ -1022,22 +1036,28 @@ public object FfiConverterTypeFFIError : FfiConverterRustBuffer { // Add the size for the Int that specifies the variant plus the size needed for all fields 4 ) - is FfiException.IncorrectState -> ( + is FfiException.VpnNotStopped -> ( // Add the size for the Int that specifies the variant plus the size needed for all fields 4 - + FfiConverterTypeClientState.allocationSize(value.`current`) - + FfiConverterTypeClientState.allocationSize(value.`expected`) ) - is FfiException.UrlParse -> ( + is FfiException.VpnNotStarted -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4 + ) + is FfiException.NoContext -> ( // Add the size for the Int that specifies the variant plus the size needed for all fields 4 - + FfiConverterString.allocationSize(value.`inner`) ) is FfiException.LibException -> ( // Add the size for the Int that specifies the variant plus the size needed for all fields 4 + FfiConverterString.allocationSize(value.`inner`) ) + is FfiException.GatewayDirectoryException -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4 + + FfiConverterString.allocationSize(value.`inner`) + ) } } @@ -1051,19 +1071,25 @@ public object FfiConverterTypeFFIError : FfiConverterRustBuffer { buf.putInt(2) Unit } - is FfiException.IncorrectState -> { + is FfiException.VpnNotStopped -> { buf.putInt(3) - FfiConverterTypeClientState.write(value.`current`, buf) - FfiConverterTypeClientState.write(value.`expected`, buf) Unit } - is FfiException.UrlParse -> { + is FfiException.VpnNotStarted -> { buf.putInt(4) - FfiConverterString.write(value.`inner`, buf) Unit } - is FfiException.LibException -> { + is FfiException.NoContext -> { buf.putInt(5) + Unit + } + is FfiException.LibException -> { + buf.putInt(6) + FfiConverterString.write(value.`inner`, buf) + Unit + } + is FfiException.GatewayDirectoryException -> { + buf.putInt(7) FfiConverterString.write(value.`inner`, buf) Unit } @@ -1075,24 +1101,53 @@ public object FfiConverterTypeFFIError : FfiConverterRustBuffer { -public object FfiConverterSequenceTypeCountry: FfiConverterRustBuffer> { - override fun read(buf: ByteBuffer): List { +public object FfiConverterOptionalDouble: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Double? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterDouble.read(buf) + } + + override fun allocationSize(value: Double?): Int { + if (value == null) { + return 1 + } else { + return 1 + FfiConverterDouble.allocationSize(value) + } + } + + override fun write(value: Double?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterDouble.write(value, buf) + } + } +} + + + + +public object FfiConverterSequenceTypeLocation: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { val len = buf.getInt() - return List(len) { - FfiConverterTypeCountry.read(buf) + return List(len) { + FfiConverterTypeLocation.read(buf) } } - override fun allocationSize(value: List): Int { + override fun allocationSize(value: List): Int { val sizeForLength = 4 - val sizeForItems = value.map { FfiConverterTypeCountry.allocationSize(it) }.sum() + val sizeForItems = value.map { FfiConverterTypeLocation.allocationSize(it) }.sum() return sizeForLength + sizeForItems } - override fun write(value: List, buf: ByteBuffer) { + override fun write(value: List, buf: ByteBuffer) { buf.putInt(value.size) value.forEach { - FfiConverterTypeCountry.write(it, buf) + FfiConverterTypeLocation.write(it, buf) } } } @@ -1116,36 +1171,78 @@ public typealias FfiConverterTypeNodeIdentity = FfiConverterString */ public typealias Recipient = String public typealias FfiConverterTypeRecipient = FfiConverterString + + + + + +/** + * Typealias from the type name used in the UDL file to the custom type. This + * is needed because the UDL type name is used in function/method signatures. + * It's also what we have an external type that references a custom type. + */ +public typealias Url = URL + + + +public object FfiConverterTypeUrl: FfiConverter { + override fun lift(value: RustBuffer.ByValue): Url { + val builtinValue = FfiConverterString.lift(value) + return URI(builtinValue).toURL() + } + + override fun lower(value: Url): RustBuffer.ByValue { + val builtinValue = value.toString() + return FfiConverterString.lower(builtinValue) + } + + override fun read(buf: ByteBuffer): Url { + val builtinValue = FfiConverterString.read(buf) + return URI(builtinValue).toURL() + } + + override fun allocationSize(value: Url): Int { + val builtinValue = value.toString() + return FfiConverterString.allocationSize(builtinValue) + } + + override fun write(value: Url, buf: ByteBuffer) { + val builtinValue = value.toString() + FfiConverterString.write(builtinValue, buf) + } +} @Throws(FfiException::class) -fun `getGatewayCountries`(`apiUrl`: String, `explorerUrl`: String, `exitOnly`: Boolean): List { - return FfiConverterSequenceTypeCountry.lift( +fun `getGatewayCountries`(`apiUrl`: Url, `explorerUrl`: Url, `exitOnly`: Boolean): List { + return FfiConverterSequenceTypeLocation.lift( uniffiRustCallWithError(FfiException) { _status -> - UniffiLib.INSTANCE.uniffi_nym_vpn_lib_fn_func_getgatewaycountries(FfiConverterString.lower(`apiUrl`),FfiConverterString.lower(`explorerUrl`),FfiConverterBoolean.lower(`exitOnly`),_status) + UniffiLib.INSTANCE.uniffi_nym_vpn_lib_fn_func_getgatewaycountries(FfiConverterTypeUrl.lower(`apiUrl`),FfiConverterTypeUrl.lower(`explorerUrl`),FfiConverterBoolean.lower(`exitOnly`),_status) }) } @Throws(FfiException::class) -fun `getLowLatencyEntryCountry`(`apiUrl`: String, `explorerUrl`: String): Country { - return FfiConverterTypeCountry.lift( +fun `getLowLatencyEntryCountry`(`apiUrl`: Url, `explorerUrl`: Url): Location { + return FfiConverterTypeLocation.lift( uniffiRustCallWithError(FfiException) { _status -> - UniffiLib.INSTANCE.uniffi_nym_vpn_lib_fn_func_getlowlatencyentrycountry(FfiConverterString.lower(`apiUrl`),FfiConverterString.lower(`explorerUrl`),_status) + UniffiLib.INSTANCE.uniffi_nym_vpn_lib_fn_func_getlowlatencyentrycountry(FfiConverterTypeUrl.lower(`apiUrl`),FfiConverterTypeUrl.lower(`explorerUrl`),_status) }) } +@Throws(FfiException::class) -fun `runVpn`() = +fun `runVpn`(`config`: VpnConfig) = - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_nym_vpn_lib_fn_func_runvpn(_status) + uniffiRustCallWithError(FfiException) { _status -> + UniffiLib.INSTANCE.uniffi_nym_vpn_lib_fn_func_runvpn(FfiConverterTypeVPNConfig.lower(`config`),_status) } +@Throws(FfiException::class) fun `stopVpn`() = - uniffiRustCall() { _status -> + uniffiRustCallWithError(FfiException) { _status -> UniffiLib.INSTANCE.uniffi_nym_vpn_lib_fn_func_stopvpn(_status) } diff --git a/nym_vpn_client/src/main/java/net/nymtech/vpn/util/ServiceManager.kt b/nym_vpn_client/src/main/java/net/nymtech/vpn/util/ServiceManager.kt index e06aebc..e5e3a61 100644 --- a/nym_vpn_client/src/main/java/net/nymtech/vpn/util/ServiceManager.kt +++ b/nym_vpn_client/src/main/java/net/nymtech/vpn/util/ServiceManager.kt @@ -40,7 +40,7 @@ object ServiceManager { } } - fun startVpnService(context: Context, extras : Map?) { + fun startVpnService(context: Context, extras : Map? = null) { Timber.d("Called start vpn service") actionOnService( Action.START, @@ -50,7 +50,7 @@ object ServiceManager { ) } - fun startVpnServiceForeground(context: Context, extras : Map?) { + fun startVpnServiceForeground(context: Context, extras : Map? = null) { Timber.d("Called start vpn service foreground") actionOnService( Action.START_FOREGROUND, diff --git a/nym_vpn_client/src/main/scripts/build-libs.sh b/nym_vpn_client/src/main/scripts/build-libs.sh index e3c11c6..1b73f3b 100644 --- a/nym_vpn_client/src/main/scripts/build-libs.sh +++ b/nym_vpn_client/src/main/scripts/build-libs.sh @@ -21,6 +21,8 @@ case "$(uname -s)" in esac (cd $PWD/src/tools/nym-vpn-client; cargo run --bin uniffi-bindgen generate --library ./target/aarch64-linux-android/debug/libnym_vpn_lib.so --language kotlin --out-dir ../../main/java/net/nymtech/vpn -n) +#fix package name +sed -i 's/package nym-vpn-lib;/package nym_vpn_lib;/g' $PWD/src/main/java/net/nymtech/vpn/nym-vpn-lib/nym_vpn_lib.kt mv $PWD/src/main/jniLibs/arm64-v8a/libnym_vpn_lib.so $PWD/src/main/jniLibs/arm64-v8a/libnym_vpn_lib.so mv $PWD/src/main/jniLibs/armeabi-v7a/libnym_vpn_lib.so $PWD/src/main/jniLibs/armeabi-v7a/libnym_vpn_lib.so diff --git a/nym_vpn_client/src/tools/nym-vpn-client b/nym_vpn_client/src/tools/nym-vpn-client index e222395..ca8cb10 160000 --- a/nym_vpn_client/src/tools/nym-vpn-client +++ b/nym_vpn_client/src/tools/nym-vpn-client @@ -1 +1 @@ -Subproject commit e222395476790d7b73852f4aba898b2900f22830 +Subproject commit ca8cb10e574dd4219a2535f47eef362e9a0f473a