session-android/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/IP2Country.kt

119 lines
4.3 KiB
Kotlin
Raw Normal View History

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
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import org.thoughtcrime.securesms.logging.Log
2020-06-02 07:00:37 +02:00
import com.opencsv.CSVReader
import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI
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
val countryNamesCache = mutableMapOf<String, String>()
2020-06-02 07:00:37 +02:00
private val ipv4Table by lazy {
loadFile("geolite2_country_blocks_ipv4.csv")
}
private val countryNamesTable by lazy {
loadFile("geolite2_country_locations_english.csv")
}
// 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 {
populateCacheIfNeeded()
2020-06-02 07:00:37 +02:00
pathsBuiltEventReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
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
}
private fun cacheCountryForIP(ip: String): String {
2020-06-02 07:00:37 +02:00
var truncatedIP = ip
fun getCountryInternal(): String {
val country = countryNamesCache[ip]
if (country != null) { return country }
val ipv4TableReader = CSVReader(FileReader(ipv4Table.absoluteFile))
2020-06-02 07:18:09 +02:00
val countryNamesTableReader = CSVReader(FileReader(countryNamesTable.absoluteFile))
2020-06-02 07:00:37 +02:00
var ipv4TableLine = ipv4TableReader.readNext()
while (ipv4TableLine != null) {
if (!ipv4TableLine[0].startsWith(truncatedIP)) {
ipv4TableLine = ipv4TableReader.readNext()
continue
}
val countryID = ipv4TableLine[1]
var countryNamesTableLine = countryNamesTableReader.readNext()
while (countryNamesTableLine != null) {
if (countryNamesTableLine[0] != countryID) {
countryNamesTableLine = countryNamesTableReader.readNext()
continue
}
@Suppress("NAME_SHADOWING") val country = countryNamesTableLine[5]
countryNamesCache[ip] = country
return country
}
}
if (truncatedIP.contains(".") && !truncatedIP.endsWith(".")) { // The fuzziest we want to go is xxx.x
2020-06-02 07:18:09 +02:00
truncatedIP = truncatedIP.dropLast(1)
if (truncatedIP.endsWith(".")) { truncatedIP = truncatedIP.dropLast(1) }
2020-06-02 07:00:37 +02:00
return getCountryInternal()
} else {
return "Unknown Country"
}
}
return getCountryInternal()
}
private fun populateCacheIfNeeded() {
2020-06-02 07:00:37 +02:00
Thread {
val path = OnionRequestAPI.paths.firstOrNull() ?: return@Thread
path.forEach { snode ->
cacheCountryForIP(snode.ip) // Preload if needed
2020-06-02 07:00:37 +02:00
}
Broadcaster(context).broadcast("onionRequestPathCountriesLoaded")
2020-06-02 07:00:37 +02:00
Log.d("Loki", "Finished preloading onion request path countries.")
}.start()
}
// endregion
}