2020-12-02 06:38:12 +01:00
|
|
|
@file:Suppress("NAME_SHADOWING")
|
|
|
|
|
|
|
|
package org.session.libsession.snode
|
|
|
|
|
2021-04-07 06:49:52 +02:00
|
|
|
import android.os.Build
|
2021-05-26 08:34:08 +02:00
|
|
|
import com.goterl.lazysodium.LazySodiumAndroid
|
|
|
|
import com.goterl.lazysodium.SodiumAndroid
|
|
|
|
import com.goterl.lazysodium.interfaces.PwHash
|
|
|
|
import com.goterl.lazysodium.interfaces.SecretBox
|
2020-12-10 05:32:38 +01:00
|
|
|
import nl.komponents.kovenant.*
|
2020-12-02 06:38:12 +01:00
|
|
|
import nl.komponents.kovenant.functional.bind
|
|
|
|
import nl.komponents.kovenant.functional.map
|
2021-04-27 06:36:03 +02:00
|
|
|
import org.session.libsession.messaging.utilities.MessageWrapper
|
2021-05-18 07:55:24 +02:00
|
|
|
import org.session.libsignal.crypto.getRandomElement
|
2021-05-18 01:44:06 +02:00
|
|
|
import org.session.libsignal.protos.SignalServiceProtos
|
2021-05-18 01:50:16 +02:00
|
|
|
import org.session.libsignal.utilities.Snode
|
2021-05-18 01:17:22 +02:00
|
|
|
import org.session.libsignal.utilities.HTTP
|
2021-05-18 01:50:16 +02:00
|
|
|
import org.session.libsignal.database.LokiAPIDatabaseProtocol
|
2021-05-18 01:36:20 +02:00
|
|
|
import org.session.libsignal.utilities.Broadcaster
|
2021-05-18 01:34:45 +02:00
|
|
|
import org.session.libsignal.utilities.prettifiedDescription
|
|
|
|
import org.session.libsignal.utilities.removing05PrefixIfNeeded
|
|
|
|
import org.session.libsignal.utilities.retryIfNeeded
|
2021-02-01 02:10:48 +01:00
|
|
|
import org.session.libsignal.utilities.*
|
2021-05-26 08:34:08 +02:00
|
|
|
import org.session.libsignal.utilities.Base64
|
2021-05-18 01:12:33 +02:00
|
|
|
import org.session.libsignal.utilities.Log
|
2020-12-02 06:38:12 +01:00
|
|
|
import java.security.SecureRandom
|
2021-05-26 08:34:08 +02:00
|
|
|
import java.util.*
|
2020-12-02 06:38:12 +01:00
|
|
|
|
|
|
|
object SnodeAPI {
|
2021-05-26 08:34:08 +02:00
|
|
|
private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) }
|
|
|
|
|
2021-04-26 02:26:31 +02:00
|
|
|
private val database: LokiAPIDatabaseProtocol
|
|
|
|
get() = SnodeModule.shared.storage
|
|
|
|
private val broadcaster: Broadcaster
|
|
|
|
get() = SnodeModule.shared.broadcaster
|
2020-12-02 06:38:12 +01:00
|
|
|
|
|
|
|
internal var snodeFailureCount: MutableMap<Snode, Int> = mutableMapOf()
|
|
|
|
internal var snodePool: Set<Snode>
|
|
|
|
get() = database.getSnodePool()
|
|
|
|
set(newValue) { database.setSnodePool(newValue) }
|
|
|
|
|
|
|
|
// Settings
|
|
|
|
private val maxRetryCount = 6
|
2021-05-06 07:46:22 +02:00
|
|
|
private val minimumSnodePoolCount = 12
|
2021-05-13 02:38:13 +02:00
|
|
|
private val minimumSwarmSnodeCount = 3
|
2021-04-26 02:26:31 +02:00
|
|
|
// Use port 4433 if the API level can handle the network security configuration and enforce pinned certificates
|
|
|
|
private val seedNodePort = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) 443 else 4433
|
2021-04-20 09:22:36 +02:00
|
|
|
private val seedNodePool by lazy {
|
|
|
|
if (useTestnet) {
|
|
|
|
setOf( "http://public.loki.foundation:38157" )
|
|
|
|
} else {
|
2021-04-28 09:41:30 +02:00
|
|
|
setOf( "https://storage.seed1.loki.network:$seedNodePort ", "https://storage.seed3.loki.network:$seedNodePort ", "https://public.loki.foundation:$seedNodePort" )
|
2021-04-20 09:22:36 +02:00
|
|
|
}
|
|
|
|
}
|
2021-05-13 02:38:13 +02:00
|
|
|
private val snodeFailureThreshold = 3
|
2020-12-02 06:38:12 +01:00
|
|
|
private val targetSwarmSnodeCount = 2
|
|
|
|
private val useOnionRequests = true
|
|
|
|
|
2021-05-04 01:06:59 +02:00
|
|
|
internal val useTestnet = false
|
2020-12-02 06:38:12 +01:00
|
|
|
|
|
|
|
// Error
|
2021-03-16 06:31:52 +01:00
|
|
|
internal sealed class Error(val description: String) : Exception(description) {
|
2020-12-02 06:38:12 +01:00
|
|
|
object Generic : Error("An error occurred.")
|
2021-04-26 02:26:31 +02:00
|
|
|
object ClockOutOfSync : Error("Your clock is out of sync with the Service Node network.")
|
2021-05-26 08:34:08 +02:00
|
|
|
// ONS
|
|
|
|
object DecryptionFailed : Error("Couldn't decrypt ONS name.")
|
|
|
|
object HashingFailed : Error("Couldn't compute ONS name hash.")
|
|
|
|
object ValidationFailed : Error("ONS name validation failed.")
|
2020-12-02 06:38:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Internal API
|
2021-05-26 08:34:08 +02:00
|
|
|
internal fun invoke(method: Snode.Method, snode: Snode, publicKey: String? = null, parameters: Map<String, Any>): RawResponsePromise {
|
2020-12-02 06:38:12 +01:00
|
|
|
val url = "${snode.address}:${snode.port}/storage_rpc/v1"
|
|
|
|
if (useOnionRequests) {
|
|
|
|
return OnionRequestAPI.sendOnionRequest(method, parameters, snode, publicKey)
|
|
|
|
} else {
|
|
|
|
val deferred = deferred<Map<*, *>, Exception>()
|
2021-01-27 00:54:25 +01:00
|
|
|
ThreadUtils.queue {
|
2020-12-02 06:38:12 +01:00
|
|
|
val payload = mapOf( "method" to method.rawValue, "params" to parameters )
|
|
|
|
try {
|
|
|
|
val json = HTTP.execute(HTTP.Verb.POST, url, payload)
|
|
|
|
deferred.resolve(json)
|
|
|
|
} catch (exception: Exception) {
|
|
|
|
val httpRequestFailedException = exception as? HTTP.HTTPRequestFailedException
|
|
|
|
if (httpRequestFailedException != null) {
|
|
|
|
val error = handleSnodeError(httpRequestFailedException.statusCode, httpRequestFailedException.json, snode, publicKey)
|
2021-01-27 00:54:25 +01:00
|
|
|
if (error != null) { return@queue deferred.reject(exception) }
|
2020-12-02 06:38:12 +01:00
|
|
|
}
|
|
|
|
Log.d("Loki", "Unhandled exception: $exception.")
|
|
|
|
deferred.reject(exception)
|
|
|
|
}
|
2021-01-27 00:54:25 +01:00
|
|
|
}
|
2020-12-02 06:38:12 +01:00
|
|
|
return deferred.promise
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal fun getRandomSnode(): Promise<Snode, Exception> {
|
|
|
|
val snodePool = this.snodePool
|
|
|
|
if (snodePool.count() < minimumSnodePoolCount) {
|
|
|
|
val target = seedNodePool.random()
|
|
|
|
val url = "$target/json_rpc"
|
|
|
|
Log.d("Loki", "Populating snode pool using: $target.")
|
|
|
|
val parameters = mapOf(
|
|
|
|
"method" to "get_n_service_nodes",
|
|
|
|
"params" to mapOf(
|
2021-04-26 02:26:31 +02:00
|
|
|
"active_only" to true,
|
2021-05-06 07:46:22 +02:00
|
|
|
"limit" to 256,
|
2021-04-26 02:26:31 +02:00
|
|
|
"fields" to mapOf( "public_ip" to true, "storage_port" to true, "pubkey_x25519" to true, "pubkey_ed25519" to true )
|
2020-12-02 06:38:12 +01:00
|
|
|
)
|
|
|
|
)
|
|
|
|
val deferred = deferred<Snode, Exception>()
|
2021-04-26 02:26:31 +02:00
|
|
|
deferred<Snode, Exception>()
|
2021-01-27 00:54:25 +01:00
|
|
|
ThreadUtils.queue {
|
2020-12-02 06:38:12 +01:00
|
|
|
try {
|
|
|
|
val json = HTTP.execute(HTTP.Verb.POST, url, parameters, useSeedNodeConnection = true)
|
|
|
|
val intermediate = json["result"] as? Map<*, *>
|
|
|
|
val rawSnodes = intermediate?.get("service_node_states") as? List<*>
|
|
|
|
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))
|
|
|
|
} else {
|
|
|
|
Log.d("Loki", "Failed to parse: ${rawSnode?.prettifiedDescription()}.")
|
|
|
|
null
|
|
|
|
}
|
|
|
|
}.toMutableSet()
|
|
|
|
Log.d("Loki", "Persisting snode pool to database.")
|
|
|
|
this.snodePool = snodePool
|
|
|
|
try {
|
|
|
|
deferred.resolve(snodePool.getRandomElement())
|
|
|
|
} catch (exception: Exception) {
|
|
|
|
Log.d("Loki", "Got an empty snode pool from: $target.")
|
|
|
|
deferred.reject(SnodeAPI.Error.Generic)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Log.d("Loki", "Failed to update snode pool from: ${(rawSnodes as List<*>?)?.prettifiedDescription()}.")
|
|
|
|
deferred.reject(SnodeAPI.Error.Generic)
|
|
|
|
}
|
|
|
|
} catch (exception: Exception) {
|
|
|
|
deferred.reject(exception)
|
|
|
|
}
|
2021-01-27 00:54:25 +01:00
|
|
|
}
|
2020-12-02 06:38:12 +01:00
|
|
|
return deferred.promise
|
|
|
|
} else {
|
|
|
|
return Promise.of(snodePool.getRandomElement())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal fun dropSnodeFromSwarmIfNeeded(snode: Snode, publicKey: String) {
|
|
|
|
val swarm = database.getSwarm(publicKey)?.toMutableSet()
|
|
|
|
if (swarm != null && swarm.contains(snode)) {
|
|
|
|
swarm.remove(snode)
|
|
|
|
database.setSwarm(publicKey, swarm)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal fun getSingleTargetSnode(publicKey: String): Promise<Snode, Exception> {
|
|
|
|
// SecureRandom() should be cryptographically secure
|
|
|
|
return getSwarm(publicKey).map { it.shuffled(SecureRandom()).random() }
|
|
|
|
}
|
|
|
|
|
|
|
|
// Public API
|
2021-05-26 08:34:08 +02:00
|
|
|
fun getSessionIDFor(onsName: String): Promise<String, Exception> {
|
|
|
|
val validationCount = 3
|
|
|
|
val sessionIDByteCount = 33
|
|
|
|
// Hash the ONS name using BLAKE2b
|
|
|
|
val name = onsName.toLowerCase(Locale.ENGLISH)
|
|
|
|
val nameHash = sodium.cryptoGenericHash(name)
|
|
|
|
val base64EncodedNameHash = nameHash
|
|
|
|
// Ask 3 different snodes for the Session ID associated with the given name hash
|
|
|
|
val parameters = mapOf(
|
|
|
|
"endpoint" to "ons_resolve",
|
|
|
|
"params" to mapOf( "type" to 0, "name_hash" to base64EncodedNameHash )
|
|
|
|
)
|
|
|
|
val promises = (0..validationCount).map {
|
|
|
|
getRandomSnode().bind { snode ->
|
|
|
|
invoke(Snode.Method.OxenDaemonRPCCall, snode, null, parameters)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
val deferred = deferred<String, Exception>()
|
|
|
|
val promise = deferred.promise
|
|
|
|
all(promises).success { results ->
|
|
|
|
val sessionIDs = mutableListOf<String>()
|
|
|
|
for (json in results) {
|
|
|
|
val intermediate = json["result"] as? Map<*, *>
|
|
|
|
val hexEncodedCiphertext = intermediate?.get("encrypted_value") as? String
|
|
|
|
if (hexEncodedCiphertext != null) {
|
|
|
|
val ciphertext = Hex.fromStringCondensed(hexEncodedCiphertext)
|
|
|
|
val isArgon2Based = (intermediate["nonce"] == null)
|
|
|
|
if (isArgon2Based) {
|
|
|
|
// Handle old Argon2-based encryption used before HF16
|
|
|
|
val salt = ByteArray(PwHash.SALTBYTES)
|
|
|
|
val key = sodium.cryptoPwHash(name, SecretBox.KEYBYTES, salt, PwHash.OPSLIMIT_MODERATE, PwHash.MEMLIMIT_MODERATE, PwHash.Alg.PWHASH_ALG_ARGON2ID13)
|
|
|
|
val nonce = ByteArray(SecretBox.NONCEBYTES)
|
|
|
|
val sessionID = sodium.cryptoSecretBoxOpenEasy(ciphertext, nonce, key)
|
|
|
|
} else {
|
|
|
|
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
deferred.reject(Error.Generic)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return promise
|
|
|
|
}
|
|
|
|
|
2020-12-02 06:38:12 +01:00
|
|
|
fun getTargetSnodes(publicKey: String): Promise<List<Snode>, Exception> {
|
|
|
|
// SecureRandom() should be cryptographically secure
|
|
|
|
return getSwarm(publicKey).map { it.shuffled(SecureRandom()).take(targetSwarmSnodeCount) }
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getSwarm(publicKey: String): Promise<Set<Snode>, Exception> {
|
|
|
|
val cachedSwarm = database.getSwarm(publicKey)
|
|
|
|
if (cachedSwarm != null && cachedSwarm.size >= minimumSwarmSnodeCount) {
|
|
|
|
val cachedSwarmCopy = mutableSetOf<Snode>() // Workaround for a Kotlin compiler issue
|
|
|
|
cachedSwarmCopy.addAll(cachedSwarm)
|
|
|
|
return task { cachedSwarmCopy }
|
|
|
|
} else {
|
2021-04-20 09:22:36 +02:00
|
|
|
val parameters = mapOf( "pubKey" to if (useTestnet) publicKey.removing05PrefixIfNeeded() else publicKey )
|
2020-12-02 06:38:12 +01:00
|
|
|
return getRandomSnode().bind {
|
|
|
|
invoke(Snode.Method.GetSwarm, it, publicKey, parameters)
|
2021-04-26 02:26:31 +02:00
|
|
|
}.map {
|
2020-12-02 06:38:12 +01:00
|
|
|
parseSnodes(it).toSet()
|
|
|
|
}.success {
|
|
|
|
database.setSwarm(publicKey, it)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getRawMessages(snode: Snode, publicKey: String): RawResponsePromise {
|
|
|
|
val lastHashValue = database.getLastMessageHashValue(snode, publicKey) ?: ""
|
2021-04-20 09:22:36 +02:00
|
|
|
val parameters = mapOf( "pubKey" to if (useTestnet) publicKey.removing05PrefixIfNeeded() else publicKey, "lastHash" to lastHashValue )
|
2020-12-02 06:38:12 +01:00
|
|
|
return invoke(Snode.Method.GetMessages, snode, publicKey, parameters)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getMessages(publicKey: String): MessageListPromise {
|
|
|
|
return retryIfNeeded(maxRetryCount) {
|
2021-04-26 02:26:31 +02:00
|
|
|
getSingleTargetSnode(publicKey).bind { snode ->
|
|
|
|
getRawMessages(snode, publicKey).map { parseRawMessagesResponse(it, snode, publicKey) }
|
2020-12-02 06:38:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun sendMessage(message: SnodeMessage): Promise<Set<RawResponsePromise>, Exception> {
|
2021-04-20 09:22:36 +02:00
|
|
|
val destination = if (useTestnet) message.recipient.removing05PrefixIfNeeded() else message.recipient
|
2020-12-02 06:38:12 +01:00
|
|
|
return retryIfNeeded(maxRetryCount) {
|
2021-03-02 02:24:09 +01:00
|
|
|
getTargetSnodes(destination).map { swarm ->
|
2020-12-02 06:38:12 +01:00
|
|
|
swarm.map { snode ->
|
|
|
|
val parameters = message.toJSON()
|
|
|
|
retryIfNeeded(maxRetryCount) {
|
2021-04-26 02:26:31 +02:00
|
|
|
invoke(Snode.Method.SendMessage, snode, destination, parameters)
|
2020-12-02 06:38:12 +01:00
|
|
|
}
|
|
|
|
}.toSet()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parsing
|
|
|
|
private fun parseSnodes(rawResponse: Any): List<Snode> {
|
|
|
|
val json = rawResponse as? Map<*, *>
|
|
|
|
val rawSnodes = json?.get("snodes") as? List<*>
|
|
|
|
if (rawSnodes != null) {
|
|
|
|
return rawSnodes.mapNotNull { rawSnode ->
|
|
|
|
val rawSnodeAsJSON = rawSnode as? Map<*, *>
|
|
|
|
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
|
|
|
|
if (address != null && port != null && ed25519Key != null && x25519Key != null && address != "0.0.0.0") {
|
|
|
|
Snode("https://$address", port, Snode.KeySet(ed25519Key, x25519Key))
|
|
|
|
} else {
|
|
|
|
Log.d("Loki", "Failed to parse snode from: ${rawSnode?.prettifiedDescription()}.")
|
|
|
|
null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Log.d("Loki", "Failed to parse snodes from: ${rawResponse.prettifiedDescription()}.")
|
|
|
|
return listOf()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 06:36:03 +02:00
|
|
|
fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String): List<SignalServiceProtos.Envelope> {
|
2020-12-02 06:38:12 +01:00
|
|
|
val messages = rawResponse["messages"] as? List<*>
|
|
|
|
return if (messages != null) {
|
|
|
|
updateLastMessageHashValueIfPossible(snode, publicKey, messages)
|
2021-04-27 06:36:03 +02:00
|
|
|
val newRawMessages = removeDuplicates(publicKey, messages)
|
|
|
|
return parseEnvelopes(newRawMessages);
|
2020-12-02 06:38:12 +01:00
|
|
|
} else {
|
2021-04-27 06:36:03 +02:00
|
|
|
listOf()
|
2020-12-02 06:38:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun updateLastMessageHashValueIfPossible(snode: Snode, publicKey: String, rawMessages: List<*>) {
|
|
|
|
val lastMessageAsJSON = rawMessages.lastOrNull() as? Map<*, *>
|
|
|
|
val hashValue = lastMessageAsJSON?.get("hash") as? String
|
|
|
|
if (hashValue != null) {
|
|
|
|
database.setLastMessageHashValue(snode, publicKey, hashValue)
|
|
|
|
} else if (rawMessages.isNotEmpty()) {
|
|
|
|
Log.d("Loki", "Failed to update last message hash value from: ${rawMessages.prettifiedDescription()}.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun removeDuplicates(publicKey: String, rawMessages: List<*>): List<*> {
|
|
|
|
val receivedMessageHashValues = database.getReceivedMessageHashValues(publicKey)?.toMutableSet() ?: mutableSetOf()
|
2021-05-13 02:38:13 +02:00
|
|
|
val result = rawMessages.filter { rawMessage ->
|
2020-12-02 06:38:12 +01:00
|
|
|
val rawMessageAsJSON = rawMessage as? Map<*, *>
|
|
|
|
val hashValue = rawMessageAsJSON?.get("hash") as? String
|
|
|
|
if (hashValue != null) {
|
|
|
|
val isDuplicate = receivedMessageHashValues.contains(hashValue)
|
|
|
|
receivedMessageHashValues.add(hashValue)
|
|
|
|
!isDuplicate
|
|
|
|
} else {
|
|
|
|
Log.d("Loki", "Missing hash value for message: ${rawMessage?.prettifiedDescription()}.")
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2021-05-13 02:38:13 +02:00
|
|
|
database.setReceivedMessageHashValues(publicKey, receivedMessageHashValues)
|
|
|
|
return result
|
2020-12-02 06:38:12 +01:00
|
|
|
}
|
|
|
|
|
2021-04-27 06:36:03 +02:00
|
|
|
private fun parseEnvelopes(rawMessages: List<*>): List<SignalServiceProtos.Envelope> {
|
|
|
|
return rawMessages.mapNotNull { rawMessage ->
|
|
|
|
val rawMessageAsJSON = rawMessage as? Map<*, *>
|
|
|
|
val base64EncodedData = rawMessageAsJSON?.get("data") as? String
|
|
|
|
val data = base64EncodedData?.let { Base64.decode(it) }
|
|
|
|
if (data != null) {
|
|
|
|
try {
|
|
|
|
MessageWrapper.unwrap(data)
|
|
|
|
} catch (e: Exception) {
|
|
|
|
Log.d("Loki", "Failed to unwrap data for message: ${rawMessage.prettifiedDescription()}.")
|
|
|
|
null
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Log.d("Loki", "Failed to decode data for message: ${rawMessage?.prettifiedDescription()}.")
|
|
|
|
null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// endregion
|
|
|
|
|
2020-12-02 06:38:12 +01:00
|
|
|
// Error Handling
|
|
|
|
internal fun handleSnodeError(statusCode: Int, json: Map<*, *>?, snode: Snode, publicKey: String? = null): Exception? {
|
|
|
|
fun handleBadSnode() {
|
|
|
|
val oldFailureCount = snodeFailureCount[snode] ?: 0
|
|
|
|
val newFailureCount = oldFailureCount + 1
|
|
|
|
snodeFailureCount[snode] = newFailureCount
|
|
|
|
Log.d("Loki", "Couldn't reach snode at $snode; setting failure count to $newFailureCount.")
|
|
|
|
if (newFailureCount >= snodeFailureThreshold) {
|
|
|
|
Log.d("Loki", "Failure threshold reached for: $snode; dropping it.")
|
|
|
|
if (publicKey != null) {
|
|
|
|
dropSnodeFromSwarmIfNeeded(snode, publicKey)
|
|
|
|
}
|
|
|
|
snodePool = snodePool.toMutableSet().minus(snode).toSet()
|
|
|
|
Log.d("Loki", "Snode pool count: ${snodePool.count()}.")
|
|
|
|
snodeFailureCount[snode] = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
when (statusCode) {
|
2021-05-13 02:38:13 +02:00
|
|
|
400, 500, 502, 503 -> { // Usually indicates that the snode isn't up to date
|
2020-12-02 06:38:12 +01:00
|
|
|
handleBadSnode()
|
|
|
|
}
|
|
|
|
406 -> {
|
|
|
|
Log.d("Loki", "The user's clock is out of sync with the service node network.")
|
|
|
|
broadcaster.broadcast("clockOutOfSync")
|
|
|
|
return Error.ClockOutOfSync
|
|
|
|
}
|
|
|
|
421 -> {
|
|
|
|
// The snode isn't associated with the given public key anymore
|
|
|
|
if (publicKey != null) {
|
2021-05-13 02:42:53 +02:00
|
|
|
fun invalidateSwarm() {
|
|
|
|
Log.d("Loki", "Invalidating swarm for: $publicKey.")
|
|
|
|
dropSnodeFromSwarmIfNeeded(snode, publicKey)
|
|
|
|
}
|
|
|
|
if (json != null) {
|
|
|
|
val snodes = parseSnodes(json)
|
|
|
|
if (snodes.isNotEmpty()) {
|
|
|
|
database.setSwarm(publicKey, snodes.toSet())
|
|
|
|
} else {
|
|
|
|
invalidateSwarm()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
invalidateSwarm()
|
|
|
|
}
|
2020-12-02 06:38:12 +01:00
|
|
|
} else {
|
|
|
|
Log.d("Loki", "Got a 421 without an associated public key.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else -> {
|
|
|
|
handleBadSnode()
|
|
|
|
Log.d("Loki", "Unhandled response code: ${statusCode}.")
|
|
|
|
return Error.Generic
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Type Aliases
|
|
|
|
typealias RawResponse = Map<*, *>
|
2021-04-27 06:36:03 +02:00
|
|
|
typealias MessageListPromise = Promise<List<SignalServiceProtos.Envelope>, Exception>
|
2020-12-02 06:38:12 +01:00
|
|
|
typealias RawResponsePromise = Promise<RawResponse, Exception>
|