Snode version number

Getting the version number from the API and checking the last node in the onion routing, making sure its version is at least 2.8.0
feature/snode-version
ThomasSession 10 months ago
parent 9957edd5ac
commit 6b55e37cda

@ -179,7 +179,8 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
val port = components.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null
val ed25519Key = components.getOrNull(2) ?: return@mapNotNull null
val x25519Key = components.getOrNull(3) ?: return@mapNotNull null
Snode(address, port, Snode.KeySet(ed25519Key, x25519Key))
val version = components.getOrNull(4) ?: "0.0.0"
Snode(address, port, Snode.KeySet(ed25519Key, x25519Key), version)
}
}?.toSet() ?: setOf()
}
@ -192,6 +193,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
if (keySet != null) {
string += "-${keySet.ed25519Key}-${keySet.x25519Key}"
}
string += "-${snode.version}"
string
}
val row = wrap(mapOf( Companion.dummyKey to "dummy_key", snodePool to snodePoolAsString ))
@ -207,6 +209,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
if (keySet != null) {
snodeAsString += "-${keySet.ed25519Key}-${keySet.x25519Key}"
}
snodeAsString += "-${snode.version}"
val row = wrap(mapOf( Companion.indexPath to indexPath, Companion.snode to snodeAsString ))
database.insertOrUpdate(onionRequestPathTable, row, "${Companion.indexPath} = ?", wrap(indexPath))
}
@ -232,8 +235,9 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
val port = components.getOrNull(1)?.toIntOrNull()
val ed25519Key = components.getOrNull(2)
val x25519Key = components.getOrNull(3)
val version = components.getOrNull(4) ?: "0.0.0"
if (port != null && ed25519Key != null && x25519Key != null) {
Snode(address, port, Snode.KeySet(ed25519Key, x25519Key))
Snode(address, port, Snode.KeySet(ed25519Key, x25519Key), version)
} else {
null
}
@ -271,7 +275,8 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
val port = components.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null
val ed25519Key = components.getOrNull(2) ?: return@mapNotNull null
val x25519Key = components.getOrNull(3) ?: return@mapNotNull null
Snode(address, port, Snode.KeySet(ed25519Key, x25519Key))
val version = components.getOrNull(4) ?: "0.0.0"
Snode(address, port, Snode.KeySet(ed25519Key, x25519Key), version)
}
}?.toSet()
}

@ -10,6 +10,7 @@ import okhttp3.Request
import org.session.libsession.messaging.file_server.FileServerApi
import org.session.libsession.utilities.AESGCM
import org.session.libsession.utilities.AESGCM.EncryptionResult
import org.session.libsession.utilities.Util
import org.session.libsession.utilities.getBodyForOnionRequest
import org.session.libsession.utilities.getHeadersForOnionRequest
import org.session.libsignal.crypto.getRandomElement
@ -190,8 +191,19 @@ object OnionRequestAPI {
if (unusedSnodes.count() < pathSnodeCount) { throw InsufficientSnodesException() }
// Don't test path snodes as this would reveal the user's IP to them
guardSnodes.minus(reusableGuardSnodes).map { guardSnode ->
val result = listOf( guardSnode ) + (0 until (pathSize - 1)).map {
val pathSnode = unusedSnodes.getRandomElement()
val result = listOf( guardSnode ) + (0 until (pathSize - 1)).mapIndexed() { index, _ ->
var pathSnode = unusedSnodes.getRandomElement()
// we want to make sure the last node in the path is above version 2.8.0
// to help with an issue that will disappear once the nodes are all updated
if(index == pathSize - 2) {
// because we are now grabbing the whole node pool there should always
// be a node that is above version 2.8.0
while(Util.compareVersions(pathSnode.version, "2.8.0") < 0) {
pathSnode = unusedSnodes.getRandomElement()
}
}
unusedSnodes = unusedSnodes.minus(pathSnode)
pathSnode
}

@ -88,6 +88,12 @@ object SnodeAPI {
const val useTestnet = false
const val KEY_IP = "public_ip"
const val KEY_PORT = "storage_port"
const val KEY_X25519 = "pubkey_x25519"
const val KEY_ED25519 = "pubkey_ed25519"
const val KEY_VERSION = "storage_server_version"
// Error
internal sealed class Error(val description: String) : Exception(description) {
object Generic : Error("An error occurred.")
@ -146,6 +152,7 @@ object SnodeAPI {
internal fun getRandomSnode(): Promise<Snode, Exception> {
val snodePool = this.snodePool
if (snodePool.count() < minimumSnodePoolCount) {
val target = seedNodePool.random()
val url = "$target/json_rpc"
@ -154,8 +161,11 @@ object SnodeAPI {
"method" to "get_n_service_nodes",
"params" to mapOf(
"active_only" to true,
"limit" to 256,
"fields" to mapOf("public_ip" to true, "storage_port" to true, "pubkey_x25519" to true, "pubkey_ed25519" to true)
"fields" to mapOf(
KEY_IP to true, KEY_PORT to true,
KEY_X25519 to true, KEY_ED25519 to true,
KEY_VERSION to true
)
)
)
val deferred = deferred<Snode, Exception>()
@ -173,12 +183,22 @@ object SnodeAPI {
if (rawSnodes != null) {
val snodePool = rawSnodes.mapNotNull { rawSnode ->
val rawSnodeAsJSON = rawSnode as? Map<*, *>
val address = rawSnodeAsJSON?.get("public_ip") as? String
val port = rawSnodeAsJSON?.get("storage_port") as? Int
val ed25519Key = rawSnodeAsJSON?.get("pubkey_ed25519") as? String
val x25519Key = rawSnodeAsJSON?.get("pubkey_x25519") as? String
if (address != null && port != null && ed25519Key != null && x25519Key != null && address != "0.0.0.0") {
Snode("https://$address", port, Snode.KeySet(ed25519Key, x25519Key))
val address = rawSnodeAsJSON?.get(KEY_IP) as? String
val port = rawSnodeAsJSON?.get(KEY_PORT) as? Int
val ed25519Key = rawSnodeAsJSON?.get(KEY_ED25519) as? String
val x25519Key = rawSnodeAsJSON?.get(KEY_X25519) as? String
val version = (rawSnodeAsJSON?.get(KEY_VERSION) as? ArrayList<*>)
?.filterIsInstance<Int>() // get the array as Integers
?.joinToString(separator = ".") // turn it int a version string
if (address != null && port != null && ed25519Key != null && x25519Key != null
&& address != "0.0.0.0" && version != null) {
Snode(
address = "https://$address",
port = port,
publicKeySet = Snode.KeySet(ed25519Key, x25519Key),
version = version
)
} else {
Log.d("Loki", "Failed to parse: ${rawSnode?.prettifiedDescription()}.")
null
@ -206,6 +226,10 @@ object SnodeAPI {
}
}
private fun extractVersionString(jsonVersion: String): String{
return jsonVersion.removeSurrounding("[", "]").split(", ").joinToString(separator = ".")
}
internal fun dropSnodeFromSwarmIfNeeded(snode: Snode, publicKey: String) {
val swarm = database.getSwarm(publicKey)?.toMutableSet()
if (swarm != null && swarm.contains(snode)) {
@ -716,10 +740,11 @@ object SnodeAPI {
val address = rawSnodeAsJSON?.get("ip") as? String
val portAsString = rawSnodeAsJSON?.get("port") as? String
val port = portAsString?.toInt()
val ed25519Key = rawSnodeAsJSON?.get("pubkey_ed25519") as? String
val x25519Key = rawSnodeAsJSON?.get("pubkey_x25519") as? String
val ed25519Key = rawSnodeAsJSON?.get(KEY_ED25519) as? String
val x25519Key = rawSnodeAsJSON?.get(KEY_X25519) as? String
if (address != null && port != null && ed25519Key != null && x25519Key != null && address != "0.0.0.0") {
Snode("https://$address", port, Snode.KeySet(ed25519Key, x25519Key))
Snode("https://$address", port, Snode.KeySet(ed25519Key, x25519Key), "0.0.0")
} else {
Log.d("Loki", "Failed to parse snode from: ${rawSnode?.prettifiedDescription()}.")
null

@ -365,6 +365,34 @@ object Util {
val digitGroups = (Math.log10(sizeBytes.toDouble()) / Math.log10(1024.0)).toInt()
return DecimalFormat("#,##0.#").format(sizeBytes / Math.pow(1024.0, digitGroups.toDouble())) + " " + units[digitGroups]
}
/**
* Compares two version strings (for example "1.8.0")
*
* @param version1 the first version string to compare.
* @param version2 the second version string to compare.
* @return an integer indicating the result of the comparison:
* - 0 if the versions are equal
* - a positive number if version1 is greater than version2
* - a negative number if version1 is less than version2
*/
@JvmStatic
fun compareVersions(version1: String, version2: String): Int {
val parts1 = version1.split(".").map { it.toIntOrNull() ?: 0 }
val parts2 = version2.split(".").map { it.toIntOrNull() ?: 0 }
val maxLength = maxOf(parts1.size, parts2.size)
val paddedParts1 = parts1 + List(maxLength - parts1.size) { 0 }
val paddedParts2 = parts2 + List(maxLength - parts2.size) { 0 }
for (i in 0 until maxLength) {
val compare = paddedParts1[i].compareTo(paddedParts2[i])
if (compare != 0) {
return compare
}
}
return 0
}
}
fun <T, R> T.runIf(condition: Boolean, block: T.() -> R): R where T: R = if (condition) block() else this

@ -1,6 +1,6 @@
package org.session.libsignal.utilities
class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) {
class Snode(val address: String, val port: Int, val publicKeySet: KeySet?, val version: String) {
val ip: String get() = address.removePrefix("https://")
public enum class Method(val rawValue: String) {

Loading…
Cancel
Save