pull/503/head
Niels Andriesse 3 years ago
parent 7415c728eb
commit bc66c45bca

@ -32,16 +32,13 @@ import androidx.multidex.MultiDexApplication;
import org.conscrypt.Conscrypt; import org.conscrypt.Conscrypt;
import org.session.libsession.messaging.MessagingConfiguration; import org.session.libsession.messaging.MessagingConfiguration;
import org.session.libsession.messaging.avatars.AvatarHelper; import org.session.libsession.messaging.avatars.AvatarHelper;
import org.session.libsession.messaging.fileserver.FileServerAPI;
import org.session.libsession.messaging.jobs.JobQueue; import org.session.libsession.messaging.jobs.JobQueue;
import org.session.libsession.messaging.opengroups.OpenGroupAPI; import org.session.libsession.messaging.opengroups.OpenGroupAPI;
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI;
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPoller; import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPoller;
import org.session.libsession.messaging.sending_receiving.pollers.Poller; import org.session.libsession.messaging.sending_receiving.pollers.Poller;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.session.libsession.snode.SnodeAPI; import org.session.libsession.snode.SnodeModule;
import org.session.libsession.snode.SnodeConfiguration;
import org.session.libsession.utilities.IdentityKeyUtil; import org.session.libsession.utilities.IdentityKeyUtil;
import org.session.libsession.utilities.SSKEnvironment; import org.session.libsession.utilities.SSKEnvironment;
import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.TextSecurePreferences;
@ -96,7 +93,6 @@ import org.webrtc.voiceengine.WebRtcAudioUtils;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.security.SecureRandom;
import java.security.Security; import java.security.Security;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
@ -177,7 +173,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
DatabaseFactory.getStorage(this), DatabaseFactory.getStorage(this),
DatabaseFactory.getAttachmentProvider(this), DatabaseFactory.getAttachmentProvider(this),
new SessionProtocolImpl(this)); new SessionProtocolImpl(this));
SnodeConfiguration.Companion.configure(apiDB, broadcaster); SnodeModule.Companion.configure(apiDB, broadcaster);
if (userPublicKey != null) { if (userPublicKey != null) {
MentionsManager.Companion.configureIfNeeded(userPublicKey, threadDB, userDB); MentionsManager.Companion.configureIfNeeded(userPublicKey, threadDB, userDB);
} }

@ -18,7 +18,7 @@ import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.messaging.utilities.MessageWrapper
import org.session.libsession.snode.RawResponsePromise import org.session.libsession.snode.RawResponsePromise
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.SnodeConfiguration import org.session.libsession.snode.SnodeModule
import org.session.libsession.snode.SnodeMessage import org.session.libsession.snode.SnodeMessage
import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.SSKEnvironment
import org.session.libsignal.service.internal.push.PushTransportDetails import org.session.libsignal.service.internal.push.PushTransportDetails
@ -82,7 +82,7 @@ object MessageSender {
fun handleFailure(error: Exception) { fun handleFailure(error: Exception) {
handleFailedMessageSend(message, error) handleFailedMessageSend(message, error)
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) { if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
SnodeConfiguration.shared.broadcaster.broadcast("messageFailed", message.sentTimestamp!!) SnodeModule.shared.broadcaster.broadcast("messageFailed", message.sentTimestamp!!)
} }
deferred.reject(error) deferred.reject(error)
} }
@ -147,12 +147,12 @@ object MessageSender {
val wrappedMessage = MessageWrapper.wrap(kind, message.sentTimestamp!!, senderPublicKey, ciphertext) val wrappedMessage = MessageWrapper.wrap(kind, message.sentTimestamp!!, senderPublicKey, ciphertext)
// Send the result // Send the result
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) { if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
SnodeConfiguration.shared.broadcaster.broadcast("calculatingPoW", message.sentTimestamp!!) SnodeModule.shared.broadcaster.broadcast("calculatingPoW", message.sentTimestamp!!)
} }
val base64EncodedData = Base64.encodeBytes(wrappedMessage) val base64EncodedData = Base64.encodeBytes(wrappedMessage)
val snodeMessage = SnodeMessage(message.recipient!!, base64EncodedData, message.ttl, message.sentTimestamp!!) val snodeMessage = SnodeMessage(message.recipient!!, base64EncodedData, message.ttl, message.sentTimestamp!!)
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) { if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
SnodeConfiguration.shared.broadcaster.broadcast("sendingMessage", message.sentTimestamp!!) SnodeModule.shared.broadcaster.broadcast("sendingMessage", message.sentTimestamp!!)
} }
SnodeAPI.sendMessage(snodeMessage).success { promises: Set<RawResponsePromise> -> SnodeAPI.sendMessage(snodeMessage).success { promises: Set<RawResponsePromise> ->
var isSuccess = false var isSuccess = false
@ -163,7 +163,7 @@ object MessageSender {
if (isSuccess) { return@success } // Succeed as soon as the first promise succeeds if (isSuccess) { return@success } // Succeed as soon as the first promise succeeds
isSuccess = true isSuccess = true
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) { if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
SnodeConfiguration.shared.broadcaster.broadcast("messageSent", message.sentTimestamp!!) SnodeModule.shared.broadcaster.broadcast("messageSent", message.sentTimestamp!!)
} }
handleSuccessfulMessageSend(message, destination, isSyncMessage) handleSuccessfulMessageSend(message, destination, isSyncMessage)
var shouldNotify = (message is VisibleMessage && !isSyncMessage) var shouldNotify = (message is VisibleMessage && !isSyncMessage)

@ -7,7 +7,7 @@ import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.jobs.MessageReceiveJob import org.session.libsession.messaging.jobs.MessageReceiveJob
import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.messaging.utilities.MessageWrapper
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.SnodeConfiguration import org.session.libsession.snode.SnodeModule
import org.session.libsignal.service.loki.api.Snode import org.session.libsignal.service.loki.api.Snode
import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
@ -47,9 +47,9 @@ class Poller {
private fun setUpPolling() { private fun setUpPolling() {
if (!hasStarted) { return; } if (!hasStarted) { return; }
val thread = Thread.currentThread() val thread = Thread.currentThread()
SnodeAPI.getSwarm(userPublicKey).bind(SnodeAPI.messagePollingContext) { SnodeAPI.getSwarm(userPublicKey).bind {
usedSnodes.clear() usedSnodes.clear()
val deferred = deferred<Unit, Exception>(SnodeAPI.messagePollingContext) val deferred = deferred<Unit, Exception>()
pollNextSnode(deferred) pollNextSnode(deferred)
deferred.promise deferred.promise
}.always { }.always {
@ -63,7 +63,7 @@ class Poller {
} }
private fun pollNextSnode(deferred: Deferred<Unit, Exception>) { private fun pollNextSnode(deferred: Deferred<Unit, Exception>) {
val swarm = SnodeConfiguration.shared.storage.getSwarm(userPublicKey) ?: setOf() val swarm = SnodeModule.shared.storage.getSwarm(userPublicKey) ?: setOf()
val unusedSnodes = swarm.subtract(usedSnodes) val unusedSnodes = swarm.subtract(usedSnodes)
if (unusedSnodes.isNotEmpty()) { if (unusedSnodes.isNotEmpty()) {
val index = SecureRandom().nextInt(unusedSnodes.size) val index = SecureRandom().nextInt(unusedSnodes.size)
@ -87,7 +87,7 @@ class Poller {
private fun poll(snode: Snode, deferred: Deferred<Unit, Exception>): Promise<Unit, Exception> { private fun poll(snode: Snode, deferred: Deferred<Unit, Exception>): Promise<Unit, Exception> {
if (!hasStarted) { return Promise.ofFail(PromiseCanceledException()) } if (!hasStarted) { return Promise.ofFail(PromiseCanceledException()) }
return SnodeAPI.getRawMessages(snode, userPublicKey).bind(SnodeAPI.messagePollingContext) { rawResponse -> return SnodeAPI.getRawMessages(snode, userPublicKey).bind { rawResponse ->
isCaughtUp = true isCaughtUp = true
if (deferred.promise.isDone()) { if (deferred.promise.isDone()) {
task { Unit } // The long polling connection has been canceled; don't recurse task { Unit } // The long polling connection has been canceled; don't recurse

@ -83,7 +83,7 @@ open class DotNetAPI {
Log.d("Loki", "Requesting auth token for server: $server.") Log.d("Loki", "Requesting auth token for server: $server.")
val userKeyPair = MessagingConfiguration.shared.storage.getUserKeyPair() ?: throw Error.Generic val userKeyPair = MessagingConfiguration.shared.storage.getUserKeyPair() ?: throw Error.Generic
val parameters: Map<String, Any> = mapOf( "pubKey" to userKeyPair.first ) val parameters: Map<String, Any> = mapOf( "pubKey" to userKeyPair.first )
return execute(HTTPVerb.GET, server, "loki/v1/get_challenge", false, parameters).map(SnodeAPI.sharedContext) { json -> return execute(HTTPVerb.GET, server, "loki/v1/get_challenge", false, parameters).map { json ->
try { try {
val base64EncodedChallenge = json["cipherText64"] as String val base64EncodedChallenge = json["cipherText64"] as String
val challenge = Base64.decode(base64EncodedChallenge) val challenge = Base64.decode(base64EncodedChallenge)

@ -17,6 +17,7 @@ import org.session.libsession.utilities.AESGCM.EncryptionResult
import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.ThreadUtils
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.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.utilities.* import org.session.libsignal.service.loki.utilities.*
private typealias Path = List<Snode> private typealias Path = List<Snode>
@ -25,16 +26,21 @@ private typealias Path = List<Snode>
* See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information. * See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information.
*/ */
object OnionRequestAPI { object OnionRequestAPI {
private val database: LokiAPIDatabaseProtocol
get() = SnodeModule.shared.storage
private val broadcaster: Broadcaster
get() = SnodeModule.shared.broadcaster
private val pathFailureCount = mutableMapOf<Path, Int>() private val pathFailureCount = mutableMapOf<Path, Int>()
private val snodeFailureCount = mutableMapOf<Snode, Int>() private val snodeFailureCount = mutableMapOf<Snode, Int>()
var guardSnodes = setOf<Snode>() var guardSnodes = setOf<Snode>()
var paths: List<Path> // Not a set to ensure we consistently show the same path to the user var paths: List<Path> // Not a set to ensure we consistently show the same path to the user
get() = SnodeAPI.database.getOnionRequestPaths() get() = database.getOnionRequestPaths()
set(newValue) { set(newValue) {
if (newValue.isEmpty()) { if (newValue.isEmpty()) {
SnodeAPI.database.clearOnionRequestPaths() database.clearOnionRequestPaths()
} else { } else {
SnodeAPI.database.setOnionRequestPaths(newValue) database.setOnionRequestPaths(newValue)
} }
} }
@ -51,16 +57,15 @@ object OnionRequestAPI {
* The number of times a snode can fail before it's replaced. * The number of times a snode can fail before it's replaced.
*/ */
private const val snodeFailureThreshold = 1 private const val snodeFailureThreshold = 1
/**
* The number of paths to maintain.
*/
const val targetPathCount = 2 // A main path and a backup path for the case where the target snode is in the main path
/** /**
* The number of guard snodes required to maintain `targetPathCount` paths. * The number of guard snodes required to maintain `targetPathCount` paths.
*/ */
private val targetGuardSnodeCount private val targetGuardSnodeCount
get() = targetPathCount // One per path get() = targetPathCount // One per path
/**
* The number of paths to maintain.
*/
const val targetPathCount = 2 // A main path and a backup path for the case where the target snode is in the main path
// endregion // endregion
class HTTPRequestFailedAtDestinationException(val statusCode: Int, val json: Map<*, *>) class HTTPRequestFailedAtDestinationException(val statusCode: Int, val json: Map<*, *>)
@ -113,7 +118,7 @@ object OnionRequestAPI {
return Promise.of(guardSnodes) return Promise.of(guardSnodes)
} else { } else {
Log.d("Loki", "Populating guard snode cache.") Log.d("Loki", "Populating guard snode cache.")
return SnodeAPI.getRandomSnode().bind(SnodeAPI.sharedContext) { // Just used to populate the snode pool return SnodeAPI.getRandomSnode().bind { // Just used to populate the snode pool
var unusedSnodes = SnodeAPI.snodePool.minus(reusableGuardSnodes) var unusedSnodes = SnodeAPI.snodePool.minus(reusableGuardSnodes)
val reusableGuardSnodeCount = reusableGuardSnodes.count() val reusableGuardSnodeCount = reusableGuardSnodes.count()
if (unusedSnodes.count() < (targetGuardSnodeCount - reusableGuardSnodeCount)) { throw InsufficientSnodesException() } if (unusedSnodes.count() < (targetGuardSnodeCount - reusableGuardSnodeCount)) { throw InsufficientSnodesException() }
@ -138,7 +143,7 @@ object OnionRequestAPI {
return deferred.promise return deferred.promise
} }
val promises = (0 until (targetGuardSnodeCount - reusableGuardSnodeCount)).map { getGuardSnode() } val promises = (0 until (targetGuardSnodeCount - reusableGuardSnodeCount)).map { getGuardSnode() }
all(promises).map(SnodeAPI.sharedContext) { guardSnodes -> all(promises).map { guardSnodes ->
val guardSnodesAsSet = (guardSnodes + reusableGuardSnodes).toSet() val guardSnodesAsSet = (guardSnodes + reusableGuardSnodes).toSet()
OnionRequestAPI.guardSnodes = guardSnodesAsSet OnionRequestAPI.guardSnodes = guardSnodesAsSet
guardSnodesAsSet guardSnodesAsSet
@ -153,10 +158,10 @@ object OnionRequestAPI {
*/ */
private fun buildPaths(reusablePaths: List<Path>): Promise<List<Path>, Exception> { private fun buildPaths(reusablePaths: List<Path>): Promise<List<Path>, Exception> {
Log.d("Loki", "Building onion request paths.") Log.d("Loki", "Building onion request paths.")
SnodeAPI.broadcaster.broadcast("buildingPaths") broadcaster.broadcast("buildingPaths")
return SnodeAPI.getRandomSnode().bind(SnodeAPI.sharedContext) { // Just used to populate the snode pool return SnodeAPI.getRandomSnode().bind { // Just used to populate the snode pool
val reusableGuardSnodes = reusablePaths.map { it[0] } val reusableGuardSnodes = reusablePaths.map { it[0] }
getGuardSnodes(reusableGuardSnodes).map(SnodeAPI.sharedContext) { guardSnodes -> getGuardSnodes(reusableGuardSnodes).map { guardSnodes ->
var unusedSnodes = SnodeAPI.snodePool.minus(guardSnodes).minus(reusablePaths.flatten()) var unusedSnodes = SnodeAPI.snodePool.minus(guardSnodes).minus(reusablePaths.flatten())
val reusableGuardSnodeCount = reusableGuardSnodes.count() val reusableGuardSnodeCount = reusableGuardSnodes.count()
val pathSnodeCount = (targetGuardSnodeCount - reusableGuardSnodeCount) * pathSize - (targetGuardSnodeCount - reusableGuardSnodeCount) val pathSnodeCount = (targetGuardSnodeCount - reusableGuardSnodeCount) * pathSize - (targetGuardSnodeCount - reusableGuardSnodeCount)
@ -173,7 +178,7 @@ object OnionRequestAPI {
} }
}.map { paths -> }.map { paths ->
OnionRequestAPI.paths = paths + reusablePaths OnionRequestAPI.paths = paths + reusablePaths
SnodeAPI.broadcaster.broadcast("pathsBuilt") broadcaster.broadcast("pathsBuilt")
paths paths
} }
} }
@ -207,12 +212,12 @@ object OnionRequestAPI {
buildPaths(paths) // Re-build paths in the background buildPaths(paths) // Re-build paths in the background
return Promise.of(getPath(paths)) return Promise.of(getPath(paths))
} else { } else {
return buildPaths(paths).map(SnodeAPI.sharedContext) { newPaths -> return buildPaths(paths).map { newPaths ->
getPath(newPaths) getPath(newPaths)
} }
} }
} else { } else {
return buildPaths(listOf()).map(SnodeAPI.sharedContext) { newPaths -> return buildPaths(listOf()).map { newPaths ->
getPath(newPaths) getPath(newPaths)
} }
} }
@ -263,10 +268,10 @@ object OnionRequestAPI {
is Destination.Snode -> destination.snode is Destination.Snode -> destination.snode
is Destination.Server -> null is Destination.Server -> null
} }
return getPath(snodeToExclude).bind(SnodeAPI.sharedContext) { path -> return getPath(snodeToExclude).bind { path ->
guardSnode = path.first() guardSnode = path.first()
// Encrypt in reverse order, i.e. the destination first // Encrypt in reverse order, i.e. the destination first
OnionRequestEncryption.encryptPayloadForDestination(payload, destination).bind(SnodeAPI.sharedContext) { r -> OnionRequestEncryption.encryptPayloadForDestination(payload, destination).bind { r ->
destinationSymmetricKey = r.symmetricKey destinationSymmetricKey = r.symmetricKey
// Recursively encrypt the layers of the onion (again in reverse order) // Recursively encrypt the layers of the onion (again in reverse order)
encryptionResult = r encryptionResult = r
@ -278,7 +283,7 @@ object OnionRequestAPI {
} else { } else {
val lhs = Destination.Snode(path.last()) val lhs = Destination.Snode(path.last())
path = path.dropLast(1) path = path.dropLast(1)
return OnionRequestEncryption.encryptHop(lhs, rhs, encryptionResult).bind(SnodeAPI.sharedContext) { r -> return OnionRequestEncryption.encryptHop(lhs, rhs, encryptionResult).bind { r ->
encryptionResult = r encryptionResult = r
rhs = lhs rhs = lhs
addLayer() addLayer()
@ -287,7 +292,7 @@ object OnionRequestAPI {
} }
addLayer() addLayer()
} }
}.map(SnodeAPI.sharedContext) { OnionBuildingResult(guardSnode, encryptionResult, destinationSymmetricKey) } }.map { OnionBuildingResult(guardSnode, encryptionResult, destinationSymmetricKey) }
} }
/** /**

@ -1,17 +1,10 @@
package org.session.libsession.snode package org.session.libsession.snode
class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) { class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) {
val ip: String get() = address.removePrefix("https://") val ip: String get() = address.removePrefix("https://")
internal enum class Method(val rawValue: String) { internal enum class Method(val rawValue: String) {
/**
* Only supported by snode targets.
*/
GetSwarm("get_snodes_for_pubkey"), GetSwarm("get_snodes_for_pubkey"),
/**
* Only supported by snode targets.
*/
GetMessages("retrieve"), GetMessages("retrieve"),
SendMessage("store") SendMessage("store")
} }

@ -19,12 +19,10 @@ import org.session.libsignal.utilities.logging.Log
import java.security.SecureRandom import java.security.SecureRandom
object SnodeAPI { object SnodeAPI {
val database: LokiAPIDatabaseProtocol private val database: LokiAPIDatabaseProtocol
get() = SnodeConfiguration.shared.storage get() = SnodeModule.shared.storage
val broadcaster: Broadcaster private val broadcaster: Broadcaster
get() = SnodeConfiguration.shared.broadcaster get() = SnodeModule.shared.broadcaster
val sharedContext = Kovenant.createContext()
val messagePollingContext = Kovenant.createContext()
internal var snodeFailureCount: MutableMap<Snode, Int> = mutableMapOf() internal var snodeFailureCount: MutableMap<Snode, Int> = mutableMapOf()
internal var snodePool: Set<Snode> internal var snodePool: Set<Snode>
@ -33,30 +31,27 @@ object SnodeAPI {
// Settings // Settings
private val maxRetryCount = 6 private val maxRetryCount = 6
private val minimumSnodePoolCount = 64 private val minimumSnodePoolCount = 24
private val minimumSwarmSnodeCount = 2 private val minimumSwarmSnodeCount = 2
// Use port 4433 if the API level can handle the network security configuration and enforce pinned certificates
// use port 4433 if API level can handle network security config and enforce pinned certificates private val seedNodePort = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) 443 else 4433
private val seedPort = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) 443 else 4433
private val seedNodePool by lazy { private val seedNodePool by lazy {
if (useTestnet) { if (useTestnet) {
setOf( "http://public.loki.foundation:38157" ) setOf( "http://public.loki.foundation:38157" )
} else { } else {
setOf( "https://storage.seed1.loki.network:$seedPort", "https://storage.seed3.loki.network:$seedPort", "https://public.loki.foundation:$seedPort" ) setOf( "https://storage.seed1.loki.network:$seedNodePort", "https://storage.seed3.loki.network:$seedNodePort", "https://public.loki.foundation:$seedNodePort" )
} }
} }
private val snodeFailureThreshold = 4 private val snodeFailureThreshold = 4
private val targetSwarmSnodeCount = 2 private val targetSwarmSnodeCount = 2
private val useOnionRequests = true private val useOnionRequests = true
internal val useTestnet = false internal val useTestnet = true
internal var powDifficulty = 1
// 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.")
object ClockOutOfSync : Error("The user's clock is out of sync with the service node network.") object ClockOutOfSync : Error("Your clock is out of sync with the Service Node network.")
object RandomSnodePoolUpdatingFailed : Error("Failed to update random service node pool.")
} }
// Internal API // Internal API
@ -94,12 +89,12 @@ object SnodeAPI {
val parameters = mapOf( val parameters = mapOf(
"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,
"fields" to mapOf( "public_ip" to true, "storage_port" to true, "pubkey_x25519" to true, "pubkey_ed25519" to true ) "fields" to mapOf( "public_ip" to true, "storage_port" to true, "pubkey_x25519" to true, "pubkey_ed25519" to true )
) )
) )
val deferred = deferred<Snode, Exception>() val deferred = deferred<Snode, Exception>()
deferred<org.session.libsignal.service.loki.api.Snode, Exception>(SnodeAPI.sharedContext) deferred<Snode, Exception>()
ThreadUtils.queue { ThreadUtils.queue {
try { try {
val json = HTTP.execute(HTTP.Verb.POST, url, parameters, useSeedNodeConnection = true) val json = HTTP.execute(HTTP.Verb.POST, url, parameters, useSeedNodeConnection = true)
@ -170,7 +165,7 @@ object SnodeAPI {
val parameters = mapOf( "pubKey" to if (useTestnet) publicKey.removing05PrefixIfNeeded() else publicKey ) val parameters = mapOf( "pubKey" to if (useTestnet) publicKey.removing05PrefixIfNeeded() else publicKey )
return getRandomSnode().bind { return getRandomSnode().bind {
invoke(Snode.Method.GetSwarm, it, publicKey, parameters) invoke(Snode.Method.GetSwarm, it, publicKey, parameters)
}.map(sharedContext) { }.map {
parseSnodes(it).toSet() parseSnodes(it).toSet()
}.success { }.success {
database.setSwarm(publicKey, it) database.setSwarm(publicKey, it)
@ -186,8 +181,8 @@ object SnodeAPI {
fun getMessages(publicKey: String): MessageListPromise { fun getMessages(publicKey: String): MessageListPromise {
return retryIfNeeded(maxRetryCount) { return retryIfNeeded(maxRetryCount) {
getSingleTargetSnode(publicKey).bind(messagePollingContext) { snode -> getSingleTargetSnode(publicKey).bind { snode ->
getRawMessages(snode, publicKey).map(messagePollingContext) { parseRawMessagesResponse(it, snode, publicKey) } getRawMessages(snode, publicKey).map { parseRawMessagesResponse(it, snode, publicKey) }
} }
} }
} }
@ -199,19 +194,7 @@ object SnodeAPI {
swarm.map { snode -> swarm.map { snode ->
val parameters = message.toJSON() val parameters = message.toJSON()
retryIfNeeded(maxRetryCount) { retryIfNeeded(maxRetryCount) {
invoke(Snode.Method.SendMessage, snode, destination, parameters).map { rawResponse -> invoke(Snode.Method.SendMessage, snode, destination, parameters)
val json = rawResponse as? Map<*, *>
val powDifficulty = json?.get("difficulty") as? Int
if (powDifficulty != null) {
if (powDifficulty != SnodeAPI.powDifficulty && powDifficulty < 100) {
Log.d("Loki", "Setting proof of work difficulty to $powDifficulty (snode: $snode).")
SnodeAPI.powDifficulty = powDifficulty
}
} else {
Log.d("Loki", "Failed to update proof of work difficulty from: ${rawResponse.prettifiedDescription()}.")
}
rawResponse
}
} }
}.toSet() }.toSet()
} }
@ -256,7 +239,6 @@ object SnodeAPI {
private fun updateLastMessageHashValueIfPossible(snode: Snode, publicKey: String, rawMessages: List<*>) { private fun updateLastMessageHashValueIfPossible(snode: Snode, publicKey: String, rawMessages: List<*>) {
val lastMessageAsJSON = rawMessages.lastOrNull() as? Map<*, *> val lastMessageAsJSON = rawMessages.lastOrNull() as? Map<*, *>
val hashValue = lastMessageAsJSON?.get("hash") as? String val hashValue = lastMessageAsJSON?.get("hash") as? String
val expiration = lastMessageAsJSON?.get("expiration") as? Int
if (hashValue != null) { if (hashValue != null) {
database.setLastMessageHashValue(snode, publicKey, hashValue) database.setLastMessageHashValue(snode, publicKey, hashValue)
} else if (rawMessages.isNotEmpty()) { } else if (rawMessages.isNotEmpty()) {
@ -316,20 +298,6 @@ object SnodeAPI {
Log.d("Loki", "Got a 421 without an associated public key.") Log.d("Loki", "Got a 421 without an associated public key.")
} }
} }
432 -> {
// The PoW difficulty is too low
val powDifficulty = json?.get("difficulty") as? Int
if (powDifficulty != null) {
if (powDifficulty < 100) {
Log.d("Loki", "Setting proof of work difficulty to $powDifficulty (snode: $snode).")
SnodeAPI.powDifficulty = powDifficulty
} else {
handleBadSnode()
}
} else {
Log.d("Loki", "Failed to update proof of work difficulty.")
}
}
else -> { else -> {
handleBadSnode() handleBadSnode()
Log.d("Loki", "Unhandled response code: ${statusCode}.") Log.d("Loki", "Unhandled response code: ${statusCode}.")
@ -338,8 +306,6 @@ object SnodeAPI {
} }
return null return null
} }
} }
// Type Aliases // Type Aliases

@ -12,12 +12,14 @@ data class SnodeMessage(
// When the proof of work was calculated. // When the proof of work was calculated.
val timestamp: Long val timestamp: Long
) { ) {
internal fun toJSON(): Map<String, String> { internal fun toJSON(): Map<String, String> {
return mutableMapOf( return mapOf(
"pubKey" to if (SnodeAPI.useTestnet) recipient.removing05PrefixIfNeeded() else recipient, "pubKey" to if (SnodeAPI.useTestnet) recipient.removing05PrefixIfNeeded() else recipient,
"data" to data, "data" to data,
"ttl" to ttl.toString(), "ttl" to ttl.toString(),
"timestamp" to timestamp.toString(), "timestamp" to timestamp.toString(),
"nonce" to "") "nonce" to ""
)
} }
} }

@ -3,13 +3,14 @@ package org.session.libsession.snode
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.utilities.Broadcaster import org.session.libsignal.service.loki.utilities.Broadcaster
class SnodeConfiguration(val storage: LokiAPIDatabaseProtocol, val broadcaster: Broadcaster) { class SnodeModule(val storage: LokiAPIDatabaseProtocol, val broadcaster: Broadcaster) {
companion object { companion object {
lateinit var shared: SnodeConfiguration lateinit var shared: SnodeModule
fun configure(storage: LokiAPIDatabaseProtocol, broadcaster: Broadcaster) { fun configure(storage: LokiAPIDatabaseProtocol, broadcaster: Broadcaster) {
if (Companion::shared.isInitialized) { return } if (Companion::shared.isInitialized) { return }
shared = SnodeConfiguration(storage, broadcaster) shared = SnodeModule(storage, broadcaster)
} }
} }
} }

@ -1,6 +1,7 @@
package org.session.libsession.snode package org.session.libsession.snode
interface SnodeStorageProtocol { interface SnodeStorageProtocol {
fun getSnodePool(): Set<Snode> fun getSnodePool(): Set<Snode>
fun setSnodePool(newValue: Set<Snode>) fun setSnodePool(newValue: Set<Snode>)
fun getOnionRequestPaths(): List<List<Snode>> fun getOnionRequestPaths(): List<List<Snode>>

Loading…
Cancel
Save