|
|
@ -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) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|