Skip to content

Commit

Permalink
Introduce shadowsocks request and decoding capabilities
Browse files Browse the repository at this point in the history
  • Loading branch information
kp-juan-docal committed Nov 6, 2023
1 parent 536a9d7 commit 4b9e63f
Show file tree
Hide file tree
Showing 19 changed files with 512 additions and 361 deletions.
2 changes: 1 addition & 1 deletion regions/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ plugins {
publishing {
repositories {
maven {
url = uri("https://maven.pkg.github.com/xvpn/cpz_pia-mobile_shared_regions/")
url = uri("https://maven.pkg.github.com/pia-foss/mobile-shared-regions")
credentials {
username = System.getenv("GITHUB_USERNAME")
password = System.getenv("GITHUB_TOKEN")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,13 @@ internal actual class PersistenceRegionsDataSource(
}
}

override fun storeJsonEntry(jsonEntry: String) = cache.edit()
.putString(RegionsDataSourceFactory.DEFAULT_CACHE_ENTRY_KEY, jsonEntry)
override fun storeJsonEntry(key: String, jsonEntry: String) = cache.edit()
.putString(key, jsonEntry)
.apply()

override fun retrieveJsonEntry(): String? = cache.getString(RegionsDataSourceFactory.DEFAULT_CACHE_ENTRY_KEY, null)
override fun retrieveJsonEntry(key: String): String? = cache.getString(key, null)

companion object {
private const val TAG: String = "PersistenceRegionsDataSource"
private const val DEFAULT_CACHE_ENTRY_KEY: String = "persist_region_entry"
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.privateinternetaccess.regions.internals

import com.privateinternetaccess.regions.PlatformInstancesProvider
import com.privateinternetaccess.regions.internals.RegionsDataSourceFactory.Companion.DEFAULT_CACHE_ENTRY_KEY

internal actual class RegionsDataSourceFactoryImpl actual constructor(
platformProvider: PlatformInstancesProvider?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import com.privateinternetaccess.regions.internals.Regions
import com.privateinternetaccess.regions.internals.RegionsCacheDataSource
import com.privateinternetaccess.regions.internals.RegionsDataSourceFactory
import com.privateinternetaccess.regions.internals.RegionsDataSourceFactoryImpl
import com.privateinternetaccess.regions.model.RegionsResponse
import com.privateinternetaccess.regions.model.ShadowsocksRegionsResponse
import com.privateinternetaccess.regions.model.VpnRegionsResponse


public enum class RegionsProtocol(val protocol: String) {
Expand All @@ -43,20 +44,37 @@ public const val REGIONS_PING_TIMEOUT: Int = 1500
public interface RegionsAPI {

/**
* Fetch all regions information for next-gen.
* Fetch all vpn regions information.
*
* @param locale `String`. Regions locale. If unknown defaults to en-us.
* @param callback `(response: RegionsResponse?, error: List<Error>)`. Invoked on the main thread.
* @param callback `(response: RegionsResponse?, error: Error?)`. Invoked on the main thread.
*/
fun fetchRegions(locale: String, callback: (response: RegionsResponse?, error: List<Error>) -> Unit)
fun fetchVpnRegions(
locale: String,
callback: (response: VpnRegionsResponse?, error: Error?) -> Unit
)

/**
* Fetch all shadowsocks regions information.
*
* @param locale `String`. Regions locale. If unknown defaults to en-us.
* @param callback `(response: List<ShadowsocksRegionsResponse>, error: Error?)`.
* Invoked on the main thread.
*/
fun fetchShadowsocksRegions(
locale: String,
callback: (response: List<ShadowsocksRegionsResponse>, error: Error?) -> Unit
)

/**
* Starts the process of ping requests and return the updated `ServerResponse` object as a
* callback parameter.
*
* @param callback `(response: RegionsResponse?, error: List<Error>)`. Invoked on the main thread.
* @param callback `(response: RegionsResponse?, error: Error?)`. Invoked on the main thread.
*/
fun pingRequests(callback: (response: List<RegionLowerLatencyInformation>, error: List<Error>) -> Unit)
fun pingRequests(
callback: (response: List<RegionLowerLatencyInformation>, error: Error?) -> Unit
)
}

/**
Expand Down Expand Up @@ -88,7 +106,8 @@ public class RegionsBuilder {
private var endpointsProvider: IRegionEndpointProvider? = null
private var certificate: String? = null
private var userAgent: String? = null
private var regionsListRequestPath: String? = null
private var vpnRegionsRequestPath: String? = null
private var shadowsocksRegionsRequestPath: String? = null
private var metadataRequestPath: String? = null
private var regionJsonFallback: RegionJsonFallback? = null
private var platformInstancesProvider: PlatformInstancesProvider? = null
Expand Down Expand Up @@ -123,12 +142,21 @@ public class RegionsBuilder {
}

/**
* It set the path to be used by the requests retrieving the regions list.
* It set the path to be used by the requests retrieving the vpn regions list.
*
* @param regionsListRequestPath `String`.
* @param vpnRegionsRequestPath `String`.
*/
fun setRegionsListRequestPath(regionsListRequestPath: String): RegionsBuilder = apply {
this.regionsListRequestPath = regionsListRequestPath
fun setVpnRegionsRequestPath(vpnRegionsRequestPath: String): RegionsBuilder = apply {
this.vpnRegionsRequestPath = vpnRegionsRequestPath
}

/**
* It set the path to be used by the requests retrieving the shadowsocks regions list.
*
* @param shadowsocksRegionsRequestPath `String`.
*/
fun setShadowsocksRegionsRequestPath(shadowsocksRegionsRequestPath: String): RegionsBuilder = apply {
this.shadowsocksRegionsRequestPath = shadowsocksRegionsRequestPath
}

/**
Expand Down Expand Up @@ -158,7 +186,8 @@ public class RegionsBuilder {
}

/**
* It defines the name to use by the persistence framework. Optional. If undefined, a default will be used instead.
* It defines the name to use by the persistence framework. Optional.
* If undefined, a default will be used instead.
*/
fun setPersistencePreferenceName(name: String?) = apply {
this.persistencePreferenceName = if (name.isNullOrBlank()) null else name.trim()
Expand All @@ -179,8 +208,10 @@ public class RegionsBuilder {
?: throw Exception("Endpoints provider missing.")
val userAgent = this.userAgent
?: throw Exception("User-Agent value missing.")
val regionsListRequestPath = this.regionsListRequestPath
?: throw Exception("Regions list request path missing.")
val vpnRegionsRequestPath = this.vpnRegionsRequestPath
?: throw Exception("Vpn regions request path missing.")
val shadowsocksRegionsRequestPath = this.shadowsocksRegionsRequestPath
?: throw Exception("Shadowsocks regions request path missing.")
val metadataRequestPath = this.metadataRequestPath
?: throw Exception("Metadata request path missing.")
val platformInstancesProvider = this.platformInstancesProvider
Expand All @@ -199,7 +230,8 @@ public class RegionsBuilder {

return Regions(
userAgent = userAgent,
regionsListRequestPath = regionsListRequestPath,
vpnRegionsRequestPath = vpnRegionsRequestPath,
shadowsocksRegionsRequestPath = shadowsocksRegionsRequestPath,
metadataRequestPath = metadataRequestPath,
regionJsonFallback = regionJsonFallback,
endpointsProvider = endpointsProvider,
Expand All @@ -213,11 +245,13 @@ public class RegionsBuilder {
/**
* Data class defining the required json information for a successful fallback response.
*
* @param regionsJson `String`.
* @param vpnRegionsJson `String`.
* @param shadowsocksRegionsJson `String`.
* @param metadataJson `String`.
*/
public data class RegionJsonFallback(
val regionsJson: String,
val vpnRegionsJson: String,
val shadowsocksRegionsJson: String,
val metadataJson: String
)

Expand All @@ -242,4 +276,4 @@ public data class RegionEndpoint(
val isProxy: Boolean,
val usePinnedCertificate: Boolean = false,
val certificateCommonName: String? = null
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ package com.privateinternetaccess.regions
* Internet Access Mobile Client. If not, see <https://www.gnu.org/licenses/>.
*/

import com.privateinternetaccess.regions.model.RegionsResponse
import com.privateinternetaccess.regions.model.VpnRegionsResponse
import kotlinx.serialization.json.Json


object RegionsUtils {

fun stringify(regionsResponse: RegionsResponse) =
Json { encodeDefaults = true }.encodeToString(RegionsResponse.serializer(), regionsResponse)
fun stringify(regionsResponse: VpnRegionsResponse) =
Json { encodeDefaults = true }.encodeToString(VpnRegionsResponse.serializer(), regionsResponse)

fun parse(regionsResponseString: String) =
Json { ignoreUnknownKeys = true }.decodeFromString(RegionsResponse.serializer(), regionsResponseString)
Json { ignoreUnknownKeys = true }.decodeFromString(VpnRegionsResponse.serializer(), regionsResponseString)

internal fun isErrorStatusCode(code: Int): Boolean {
when (code) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
package com.privateinternetaccess.regions.internals

import com.privateinternetaccess.regions.model.RegionsResponse
import kotlinx.serialization.decodeFromString
import com.privateinternetaccess.regions.model.ShadowsocksRegionsResponse
import com.privateinternetaccess.regions.model.VpnRegionsResponse
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

internal abstract class AbstractPersistenceRegionsDataSource : RegionsCacheDataSource {
final override fun saveRegion(locale: String, regionsResponse: RegionsResponse) {

companion object {
private const val VPN_REGIONS_ENTRY_KEY: String = "persist_region_entry"
private const val SHADOWSOCKS_REGIONS_ENTRY_KEY: String = "shadowsocks-regions-entry-key"
}

final override fun saveVpnRegions(locale: String, response: VpnRegionsResponse) {
val entry = CacheEntry(
locale = locale,
regionsResponse = regionsResponse
regionsResponse = response
)
val jsonEntry: String = try {
Json.encodeToString(entry)
} catch (t: Throwable) {
logError(t.stackTraceToString())
return
}
storeJsonEntry(jsonEntry = jsonEntry)
storeJsonEntry(key = VPN_REGIONS_ENTRY_KEY, jsonEntry = jsonEntry)
}

final override fun getRegion(locale: String?): Result<RegionsResponse> {
val jsonEntry: String? = retrieveJsonEntry()
final override fun getVpnRegions(locale: String?): Result<VpnRegionsResponse> {
val jsonEntry: String? = retrieveJsonEntry(key = VPN_REGIONS_ENTRY_KEY)
if (jsonEntry.isNullOrBlank()) {
return Result.failure(CacheError(locale))
}
Expand All @@ -38,9 +44,42 @@ internal abstract class AbstractPersistenceRegionsDataSource : RegionsCacheDataS
}
}

final override fun saveShadowsocksRegions(
locale: String,
response: List<ShadowsocksRegionsResponse>
) {
val jsonEntry: String = try {
Json.encodeToString(response)
} catch (t: Throwable) {
logError(t.stackTraceToString())
return
}
storeJsonEntry(key = SHADOWSOCKS_REGIONS_ENTRY_KEY, jsonEntry = jsonEntry)
}

final override fun getShadowsocksRegions(
locale: String?
): Result<List<ShadowsocksRegionsResponse>> {
val jsonEntry: String? = retrieveJsonEntry(key = SHADOWSOCKS_REGIONS_ENTRY_KEY)
if (jsonEntry.isNullOrBlank()) {
return Result.failure(Error("Unknown shadowsocks entry"))
}

val shadowsocksRegionsEntry: List<ShadowsocksRegionsResponse> = try {
Json.decodeFromString(jsonEntry)
} catch (t: Throwable) {
logError(t.stackTraceToString())
emptyList()
}
return when {
shadowsocksRegionsEntry.isNotEmpty() -> Result.success(shadowsocksRegionsEntry)
else -> Result.failure(Error("Shadowsocks entry is empty"))
}
}

protected abstract fun logError(error: String)

protected abstract fun storeJsonEntry(jsonEntry: String)
protected abstract fun storeJsonEntry(key: String, jsonEntry: String)

protected abstract fun retrieveJsonEntry(): String?
protected abstract fun retrieveJsonEntry(key: String): String?
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,41 @@
package com.privateinternetaccess.regions.internals

import com.privateinternetaccess.regions.model.RegionsResponse
import com.privateinternetaccess.regions.model.ShadowsocksRegionsResponse
import com.privateinternetaccess.regions.model.VpnRegionsResponse


internal class InMemoryRegionsCacheDataSource: RegionsCacheDataSource {
private var cacheEntry: CacheEntry? = null
private var vpnRegionsCacheEntry: CacheEntry? = null
private var shadowsocksRegionsEntry: List<ShadowsocksRegionsResponse> = emptyList()

override fun saveRegion(locale: String, regionsResponse: RegionsResponse) {
this.cacheEntry = CacheEntry(
override fun saveVpnRegions(locale: String, regionsResponse: VpnRegionsResponse) {
this.vpnRegionsCacheEntry = CacheEntry(
locale = locale,
regionsResponse = regionsResponse
)
}

override fun getRegion(locale: String?): Result<RegionsResponse> {
val cacheEntry: CacheEntry? = this.cacheEntry
override fun getVpnRegions(locale: String?): Result<VpnRegionsResponse> {
val cacheEntry: CacheEntry? = this.vpnRegionsCacheEntry
return when {
cacheEntry != null && (locale == null || cacheEntry.locale == locale) -> Result.success(cacheEntry.regionsResponse)
else -> Result.failure(CacheError(locale))
}
}
}

override fun saveShadowsocksRegions(
locale: String,
response: List<ShadowsocksRegionsResponse>
) {
this.shadowsocksRegionsEntry = response
}

override fun getShadowsocksRegions(
locale: String?
): Result<List<ShadowsocksRegionsResponse>> {
return when {
shadowsocksRegionsEntry.isNotEmpty() -> Result.success(shadowsocksRegionsEntry)
else -> Result.failure(Error("Shadowsocks entry is empty"))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.privateinternetaccess.regions.internals

internal expect object MessageVerificator {

/**
* @param message String. Message to verify.
* @param key String. Verification key.
*/
fun verifyMessage(message: String, key: String): Boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.privateinternetaccess.regions.internals

internal expect class PingPerformer() {

/**
* @param endpoints Map<String, List<String>>. Key: Region. List<String>: Endpoints within the
* region.
* @param callback Map<String, List<Pair<String, Long>>>. Key: Region.
* List<Pair<String, Long>>>: Endpoints and latencies within the region.
*/
fun pingEndpoints(
endpoints: Map<String, List<String>>,
callback: (result: Map<String, List<Pair<String, Long>>>) -> Unit
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.privateinternetaccess.regions.internals

import io.ktor.client.HttpClient


internal expect object RegionHttpClient {

/**
* @param certificate String?. Certificate required for pinning capabilities.
* @param pinnedEndpoint Pair<String, String>?. Contains endpoint as first,
* commonName as second.
*
* @return `Pair<HttpClient?, Exception?>`.
*/
fun client(
certificate: String? = null,
pinnedEndpoint: Pair<String, String>? = null
): Pair<HttpClient?, Exception?>
}
Loading

0 comments on commit 4b9e63f

Please sign in to comment.