2020-06-02 07:00:37 +02:00
|
|
|
package org.thoughtcrime.securesms.loki.utilities
|
|
|
|
|
|
|
|
import android.content.BroadcastReceiver
|
|
|
|
import android.content.Context
|
|
|
|
import android.content.Intent
|
|
|
|
import android.content.IntentFilter
|
2020-08-19 02:06:26 +02:00
|
|
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
2020-12-03 07:52:41 +01:00
|
|
|
import org.thoughtcrime.securesms.logging.Log
|
2020-06-02 07:00:37 +02:00
|
|
|
import com.opencsv.CSVReader
|
2020-11-26 00:07:45 +01:00
|
|
|
import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI
|
2021-02-01 02:10:48 +01:00
|
|
|
import org.session.libsignal.utilities.ThreadUtils
|
2020-06-02 07:00:37 +02:00
|
|
|
import java.io.File
|
|
|
|
import java.io.FileOutputStream
|
|
|
|
import java.io.FileReader
|
|
|
|
|
|
|
|
class IP2Country private constructor(private val context: Context) {
|
|
|
|
private val pathsBuiltEventReceiver: BroadcastReceiver
|
2020-06-02 07:59:25 +02:00
|
|
|
val countryNamesCache = mutableMapOf<String, String>()
|
2020-06-02 07:00:37 +02:00
|
|
|
|
2021-01-31 23:39:14 +01:00
|
|
|
private fun Ipv4Int(ip:String) = ip.takeWhile { it != '/' }.split('.').foldIndexed(0L) { i, acc, s ->
|
|
|
|
val asInt = s.toLong()
|
|
|
|
acc + (asInt shl (8 * (3-i)))
|
2020-06-02 07:00:37 +02:00
|
|
|
}
|
|
|
|
|
2021-01-31 23:39:14 +01:00
|
|
|
private val ipv4ToCountry by lazy {
|
|
|
|
val file = loadFile("geolite2_country_blocks_ipv4.csv")
|
|
|
|
val csv = CSVReader(FileReader(file.absoluteFile)).apply {
|
|
|
|
skip(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
csv.readAll()
|
|
|
|
.associate { cols ->
|
|
|
|
Ipv4Int(cols[0]) to cols[1].toIntOrNull()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private val countryToNames by lazy {
|
|
|
|
val file = loadFile("geolite2_country_locations_english.csv")
|
|
|
|
val csv = CSVReader(FileReader(file.absoluteFile)).apply {
|
|
|
|
skip(1)
|
|
|
|
}
|
|
|
|
csv.readAll()
|
|
|
|
.filter { cols -> !cols[0].isNullOrEmpty() && !cols[1].isNullOrEmpty() }
|
|
|
|
.associate { cols ->
|
|
|
|
cols[0].toInt() to cols[5]
|
|
|
|
}
|
2020-06-02 07:00:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// region Initialization
|
|
|
|
companion object {
|
|
|
|
|
|
|
|
public lateinit var shared: IP2Country
|
|
|
|
|
2020-08-19 04:15:02 +02:00
|
|
|
public val isInitialized: Boolean get() = ::shared.isInitialized
|
|
|
|
|
2020-06-02 07:00:37 +02:00
|
|
|
public fun configureIfNeeded(context: Context) {
|
2020-08-19 04:15:02 +02:00
|
|
|
if (isInitialized) { return; }
|
2020-06-02 07:00:37 +02:00
|
|
|
shared = IP2Country(context)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
init {
|
2020-06-02 07:59:25 +02:00
|
|
|
populateCacheIfNeeded()
|
2020-06-02 07:00:37 +02:00
|
|
|
pathsBuiltEventReceiver = object : BroadcastReceiver() {
|
|
|
|
override fun onReceive(context: Context, intent: Intent) {
|
2020-06-02 07:59:25 +02:00
|
|
|
populateCacheIfNeeded()
|
2020-06-02 07:00:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
LocalBroadcastManager.getInstance(context).registerReceiver(pathsBuiltEventReceiver, IntentFilter("pathsBuilt"))
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Deinit?
|
|
|
|
// endregion
|
|
|
|
|
|
|
|
// region Implementation
|
|
|
|
private fun loadFile(fileName: String): File {
|
|
|
|
val directory = File(context.applicationInfo.dataDir)
|
|
|
|
val file = File(directory, fileName)
|
|
|
|
if (directory.list().contains(fileName)) { return file }
|
|
|
|
val inputStream = context.assets.open("csv/$fileName")
|
|
|
|
val outputStream = FileOutputStream(file)
|
|
|
|
val buffer = ByteArray(1024)
|
|
|
|
while (true) {
|
|
|
|
val count = inputStream.read(buffer)
|
|
|
|
if (count < 0) { break }
|
|
|
|
outputStream.write(buffer, 0, count)
|
|
|
|
}
|
|
|
|
inputStream.close()
|
|
|
|
outputStream.close()
|
|
|
|
return file
|
|
|
|
}
|
|
|
|
|
2021-01-31 23:39:14 +01:00
|
|
|
private fun cacheCountryForIP(ip: String): String? {
|
|
|
|
|
|
|
|
// return early if cached
|
|
|
|
countryNamesCache[ip]?.let { return it }
|
|
|
|
|
|
|
|
val comps = ipv4ToCountry.asSequence()
|
|
|
|
|
|
|
|
val bestMatchCountry = comps.lastOrNull { it.key <= Ipv4Int(ip) }?.let { (_, code) ->
|
|
|
|
if (code != null) {
|
|
|
|
countryToNames[code]
|
2020-06-02 07:00:37 +02:00
|
|
|
} else {
|
2021-01-31 23:39:14 +01:00
|
|
|
null
|
2020-06-02 07:00:37 +02:00
|
|
|
}
|
|
|
|
}
|
2021-01-31 23:39:14 +01:00
|
|
|
|
|
|
|
if (bestMatchCountry != null) {
|
|
|
|
countryNamesCache[ip] = bestMatchCountry
|
|
|
|
return bestMatchCountry
|
|
|
|
} else {
|
|
|
|
Log.d("Loki","Country name for $ip couldn't be found")
|
|
|
|
}
|
|
|
|
return null
|
2020-06-02 07:00:37 +02:00
|
|
|
}
|
|
|
|
|
2020-06-02 07:59:25 +02:00
|
|
|
private fun populateCacheIfNeeded() {
|
2021-01-31 23:39:14 +01:00
|
|
|
ThreadUtils.queue {
|
|
|
|
OnionRequestAPI.paths.forEach { path ->
|
|
|
|
path.forEach { snode ->
|
|
|
|
cacheCountryForIP(snode.ip) // Preload if needed
|
|
|
|
}
|
2020-06-02 07:00:37 +02:00
|
|
|
}
|
2020-06-02 07:59:25 +02:00
|
|
|
Broadcaster(context).broadcast("onionRequestPathCountriesLoaded")
|
2020-06-02 07:00:37 +02:00
|
|
|
Log.d("Loki", "Finished preloading onion request path countries.")
|
2021-01-31 23:39:14 +01:00
|
|
|
}
|
2020-06-02 07:00:37 +02:00
|
|
|
}
|
|
|
|
// endregion
|
|
|
|
}
|