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
This commit is contained in:
ThomasSession 2024-07-23 14:52:53 +10:00
parent 9957edd5ac
commit 6b55e37cda
5 changed files with 87 additions and 17 deletions

View File

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

View File

@ -10,6 +10,7 @@ import okhttp3.Request
import org.session.libsession.messaging.file_server.FileServerApi import org.session.libsession.messaging.file_server.FileServerApi
import org.session.libsession.utilities.AESGCM import org.session.libsession.utilities.AESGCM
import org.session.libsession.utilities.AESGCM.EncryptionResult import org.session.libsession.utilities.AESGCM.EncryptionResult
import org.session.libsession.utilities.Util
import org.session.libsession.utilities.getBodyForOnionRequest import org.session.libsession.utilities.getBodyForOnionRequest
import org.session.libsession.utilities.getHeadersForOnionRequest import org.session.libsession.utilities.getHeadersForOnionRequest
import org.session.libsignal.crypto.getRandomElement import org.session.libsignal.crypto.getRandomElement
@ -190,8 +191,19 @@ object OnionRequestAPI {
if (unusedSnodes.count() < pathSnodeCount) { throw InsufficientSnodesException() } if (unusedSnodes.count() < pathSnodeCount) { throw InsufficientSnodesException() }
// Don't test path snodes as this would reveal the user's IP to them // Don't test path snodes as this would reveal the user's IP to them
guardSnodes.minus(reusableGuardSnodes).map { guardSnode -> guardSnodes.minus(reusableGuardSnodes).map { guardSnode ->
val result = listOf( guardSnode ) + (0 until (pathSize - 1)).map { val result = listOf( guardSnode ) + (0 until (pathSize - 1)).mapIndexed() { index, _ ->
val pathSnode = unusedSnodes.getRandomElement() 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) unusedSnodes = unusedSnodes.minus(pathSnode)
pathSnode pathSnode
} }

View File

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

View File

@ -365,6 +365,34 @@ object Util {
val digitGroups = (Math.log10(sizeBytes.toDouble()) / Math.log10(1024.0)).toInt() val digitGroups = (Math.log10(sizeBytes.toDouble()) / Math.log10(1024.0)).toInt()
return DecimalFormat("#,##0.#").format(sizeBytes / Math.pow(1024.0, digitGroups.toDouble())) + " " + units[digitGroups] 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 fun <T, R> T.runIf(condition: Boolean, block: T.() -> R): R where T: R = if (condition) block() else this

View File

@ -1,6 +1,6 @@
package org.session.libsignal.utilities 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://") val ip: String get() = address.removePrefix("https://")
public enum class Method(val rawValue: String) { public enum class Method(val rawValue: String) {