mirror of
https://github.com/oxen-io/session-android.git
synced 2025-04-23 03:11:30 +00:00
Add v4 onion request handling
This commit is contained in:
parent
85456b5ea2
commit
b51013f050
@ -8,6 +8,7 @@ import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SodiumUtilitiesTest {
|
||||
@ -25,4 +26,27 @@ class SodiumUtilitiesTest {
|
||||
assertThat(keyPair.publicKey.asHexString.lowercase(), equalTo(blindedKey))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sharedBlindedEncryptionKey() {
|
||||
val key = ByteArray(0)
|
||||
val encryptionKey = SodiumUtilities.sharedBlindedEncryptionKey(key, key, key, key)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sogsSignature() {
|
||||
// val expectedSignature = "K1N3A+H4dxV/wiN6Mr9cEj9TWUUqxESDoGW1cmoqDp7zMzCuCraTQKPX1tIiPuOBmFvB8VSUuYsHZrfGis1hDA=="
|
||||
// val expectedSignature = "xxLpXHbomAJMB9AtGMyqvBsXrdd2040y+Ol/IKzElWfKJa3EYZRv1GLO6CTLhrDFUwVQe8PPltyGs54Kd7O5Cg=="
|
||||
val expectedSignature = "gYqpWZX6fnF4Gb2xQM3xaXs0WIYEI49+B8q4mUUEg8Rw0ObaHUWfoWjMHMArAtP9QlORfiydsKWz1o6zdPVeCQ=="
|
||||
val keyPair = SodiumUtilities.blindedKeyPair(serverPublicKey, KeyPair(pubKey, secKey))!!
|
||||
|
||||
val signature = SodiumUtilities.sogsSignature(
|
||||
ByteArray(0),
|
||||
secKey.asBytes,
|
||||
keyPair.secretKey.asBytes,
|
||||
keyPair.publicKey.asBytes
|
||||
)!!
|
||||
|
||||
assertThat(signature.toHexString(), equalTo(expectedSignature))
|
||||
}
|
||||
|
||||
}
|
@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.session.libsession.messaging.file_server.FileServerAPIV2;
|
||||
import org.session.libsession.messaging.file_server.FileServerApi;
|
||||
|
||||
public class PushMediaConstraints extends MediaConstraints {
|
||||
|
||||
@ -21,26 +21,26 @@ public class PushMediaConstraints extends MediaConstraints {
|
||||
|
||||
@Override
|
||||
public int getImageMaxSize(Context context) {
|
||||
return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
|
||||
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGifMaxSize(Context context) {
|
||||
return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
|
||||
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVideoMaxSize(Context context) {
|
||||
return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
|
||||
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAudioMaxSize(Context context) {
|
||||
return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
|
||||
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDocumentMaxSize(Context context) {
|
||||
return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
|
||||
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ object LokiPushNotificationManager {
|
||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||
val request = Request.Builder().url(url).post(body)
|
||||
retryIfNeeded(maxRetryCount) {
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, pnServerPublicKey, "/loki/v2/lsrpc").map { json ->
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, pnServerPublicKey, OnionRequestAPI.Version.V2).map { json ->
|
||||
val code = json["code"] as? Int
|
||||
if (code != null && code != 0) {
|
||||
TextSecurePreferences.setIsUsingFCM(context, false)
|
||||
@ -72,7 +72,7 @@ object LokiPushNotificationManager {
|
||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||
val request = Request.Builder().url(url).post(body)
|
||||
retryIfNeeded(maxRetryCount) {
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, pnServerPublicKey, "/loki/v2/lsrpc").map { json ->
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, pnServerPublicKey, OnionRequestAPI.Version.V2).map { json ->
|
||||
val code = json["code"] as? Int
|
||||
if (code != null && code != 0) {
|
||||
TextSecurePreferences.setIsUsingFCM(context, true)
|
||||
@ -100,7 +100,7 @@ object LokiPushNotificationManager {
|
||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||
val request = Request.Builder().url(url).post(body)
|
||||
retryIfNeeded(maxRetryCount) {
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, pnServerPublicKey, "/loki/v2/lsrpc").map { json ->
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, pnServerPublicKey, OnionRequestAPI.Version.V2).map { json ->
|
||||
val code = json["code"] as? Int
|
||||
if (code == null || code == 0) {
|
||||
Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${json["message"] as? String ?: "null"}.")
|
||||
|
@ -13,7 +13,7 @@ import org.session.libsignal.utilities.HTTP
|
||||
import org.session.libsignal.utilities.JsonUtil
|
||||
import org.session.libsignal.utilities.Log
|
||||
|
||||
object FileServerAPIV2 {
|
||||
object FileServerApi {
|
||||
|
||||
private const val serverPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59"
|
||||
const val server = "http://filev2.getsession.org"
|
||||
@ -73,12 +73,12 @@ object FileServerAPIV2 {
|
||||
HTTP.Verb.POST -> requestBuilder.post(createBody(request.parameters)!!)
|
||||
HTTP.Verb.DELETE -> requestBuilder.delete(createBody(request.parameters))
|
||||
}
|
||||
if (request.useOnionRouting) {
|
||||
return OnionRequestAPI.sendOnionRequest(requestBuilder.build(), server, serverPublicKey).fail { e ->
|
||||
return if (request.useOnionRouting) {
|
||||
OnionRequestAPI.sendOnionRequest(requestBuilder.build(), server, serverPublicKey).fail { e ->
|
||||
Log.e("Loki", "File server request failed.", e)
|
||||
}
|
||||
} else {
|
||||
return Promise.ofFail(IllegalStateException("It's currently not allowed to send non onion routed requests."))
|
||||
Promise.ofFail(IllegalStateException("It's currently not allowed to send non onion routed requests."))
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,8 @@ import com.esotericsoftware.kryo.io.Output
|
||||
import nl.komponents.kovenant.Promise
|
||||
import okio.Buffer
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.file_server.FileServerAPIV2
|
||||
import org.session.libsession.messaging.file_server.FileServerApi
|
||||
import org.session.libsession.messaging.messages.Message
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupApiV4
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
import org.session.libsession.messaging.utilities.Data
|
||||
@ -58,8 +57,8 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
|
||||
}
|
||||
handleSuccess(attachment, keyAndResult.first, keyAndResult.second)
|
||||
} else {
|
||||
val keyAndResult = upload(attachment, FileServerAPIV2.server, true) {
|
||||
FileServerAPIV2.upload(it)
|
||||
val keyAndResult = upload(attachment, FileServerApi.server, true) {
|
||||
FileServerApi.upload(it)
|
||||
}
|
||||
handleSuccess(attachment, keyAndResult.first, keyAndResult.second)
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job {
|
||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||
val request = Request.Builder().url(url).post(body)
|
||||
retryIfNeeded(4) {
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, PushNotificationAPI.serverPublicKey, "/loki/v2/lsrpc").map { json ->
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, PushNotificationAPI.serverPublicKey, OnionRequestAPI.Version.V2).map { json ->
|
||||
val code = json["code"] as? Int
|
||||
if (code == null || code == 0) {
|
||||
Log.d("Loki", "Couldn't notify PN server due to error: ${json["message"] as? String ?: "null"}.")
|
||||
|
@ -20,7 +20,6 @@ import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.session.libsession.utilities.AESGCM
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.Base64.decode
|
||||
import org.session.libsignal.utilities.Base64.encodeBytes
|
||||
import org.session.libsignal.utilities.HTTP
|
||||
|
@ -38,7 +38,7 @@ object PushNotificationAPI {
|
||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||
val request = Request.Builder().url(url).post(body)
|
||||
retryIfNeeded(maxRetryCount) {
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, "/loki/v2/lsrpc").map { json ->
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, OnionRequestAPI.Version.V2).map { json ->
|
||||
val code = json["code"] as? Int
|
||||
if (code != null && code != 0) {
|
||||
TextSecurePreferences.setIsUsingFCM(context, false)
|
||||
@ -66,7 +66,7 @@ object PushNotificationAPI {
|
||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||
val request = Request.Builder().url(url).post(body)
|
||||
retryIfNeeded(maxRetryCount) {
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, "/loki/v2/lsrpc").map { json ->
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, OnionRequestAPI.Version.V2).map { json ->
|
||||
val code = json["code"] as? Int
|
||||
if (code != null && code != 0) {
|
||||
TextSecurePreferences.setIsUsingFCM(context, true)
|
||||
@ -93,7 +93,7 @@ object PushNotificationAPI {
|
||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||
val request = Request.Builder().url(url).post(body)
|
||||
retryIfNeeded(maxRetryCount) {
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, "/loki/v2/lsrpc").map { json ->
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, OnionRequestAPI.Version.V2).map { json ->
|
||||
val code = json["code"] as? Int
|
||||
if (code == null || code == 0) {
|
||||
Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${json["message"] as? String ?: "null"}.")
|
||||
|
@ -47,8 +47,8 @@ object SodiumUtilities {
|
||||
|
||||
/* Constructs a "blinded" key pair (`ka, kA`) based on an open group server `publicKey` and an ed25519 `keyPair` */
|
||||
fun blindedKeyPair(serverPublicKey: String, edKeyPair: KeyPair): KeyPair? {
|
||||
// if (edKeyPair.publicKey.asBytes.size != Sign.PUBLICKEYBYTES ||
|
||||
// edKeyPair.secretKey.asBytes.size != Sign.SECRETKEYBYTES) return null
|
||||
if (edKeyPair.publicKey.asBytes.size != Sign.PUBLICKEYBYTES ||
|
||||
edKeyPair.secretKey.asBytes.size != Sign.SECRETKEYBYTES) return null
|
||||
val kBytes = generateBlindingFactor(serverPublicKey)
|
||||
val aBytes = generatePrivateKeyScalar(edKeyPair.secretKey.asBytes)
|
||||
// Generate the blinded key pair `ka`, `kA`
|
||||
@ -183,17 +183,17 @@ object SodiumUtilities {
|
||||
}
|
||||
|
||||
val hexString
|
||||
get() = prefix?.prefix + publicKey
|
||||
get() = prefix?.value + publicKey
|
||||
}
|
||||
|
||||
enum class IdPrefix(val prefix: String) {
|
||||
enum class IdPrefix(val value: String) {
|
||||
STANDARD("05"), BLINDED("15"), UN_BLINDED("00");
|
||||
|
||||
companion object {
|
||||
fun fromValue(prefix: String): IdPrefix? = when(prefix) {
|
||||
STANDARD.prefix -> STANDARD
|
||||
BLINDED.prefix -> BLINDED
|
||||
UN_BLINDED.prefix -> UN_BLINDED
|
||||
fun fromValue(rawValue: String): IdPrefix? = when(rawValue) {
|
||||
STANDARD.value -> STANDARD
|
||||
BLINDED.value -> BLINDED
|
||||
UN_BLINDED.value -> UN_BLINDED
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,58 @@
|
||||
package org.session.libsession.snode
|
||||
|
||||
import nl.komponents.kovenant.Deferred
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.all
|
||||
import nl.komponents.kovenant.deferred
|
||||
import nl.komponents.kovenant.functional.bind
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import okhttp3.Request
|
||||
import org.session.libsession.messaging.file_server.FileServerAPIV2
|
||||
import org.session.libsession.messaging.file_server.FileServerApi
|
||||
import org.session.libsession.utilities.AESGCM
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.*
|
||||
import org.session.libsignal.utilities.Snode
|
||||
import org.session.libsession.utilities.AESGCM.EncryptionResult
|
||||
import org.session.libsession.utilities.getBodyForOnionRequest
|
||||
import org.session.libsession.utilities.getHeadersForOnionRequest
|
||||
import org.session.libsignal.crypto.getRandomElement
|
||||
import org.session.libsignal.crypto.getRandomElementOrNull
|
||||
import org.session.libsignal.database.LokiAPIDatabaseProtocol
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.Broadcaster
|
||||
import org.session.libsignal.utilities.HTTP
|
||||
import org.session.libsignal.database.LokiAPIDatabaseProtocol
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
import org.session.libsignal.utilities.JsonUtil
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.Snode
|
||||
import org.session.libsignal.utilities.ThreadUtils
|
||||
import org.session.libsignal.utilities.recover
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import java.util.Date
|
||||
import kotlin.collections.List
|
||||
import kotlin.collections.Map
|
||||
import kotlin.collections.Set
|
||||
import kotlin.collections.any
|
||||
import kotlin.collections.contains
|
||||
import kotlin.collections.count
|
||||
import kotlin.collections.dropLast
|
||||
import kotlin.collections.filter
|
||||
import kotlin.collections.first
|
||||
import kotlin.collections.firstOrNull
|
||||
import kotlin.collections.flatten
|
||||
import kotlin.collections.forEach
|
||||
import kotlin.collections.get
|
||||
import kotlin.collections.indexOfFirst
|
||||
import kotlin.collections.isNotEmpty
|
||||
import kotlin.collections.last
|
||||
import kotlin.collections.listOf
|
||||
import kotlin.collections.map
|
||||
import kotlin.collections.mapOf
|
||||
import kotlin.collections.minus
|
||||
import kotlin.collections.mutableMapOf
|
||||
import kotlin.collections.mutableSetOf
|
||||
import kotlin.collections.plus
|
||||
import kotlin.collections.set
|
||||
import kotlin.collections.setOf
|
||||
import kotlin.collections.toMutableList
|
||||
import kotlin.collections.toSet
|
||||
import kotlin.collections.toString
|
||||
|
||||
private typealias Path = List<Snode>
|
||||
|
||||
@ -96,7 +127,8 @@ object OnionRequestAPI {
|
||||
ThreadUtils.queue { // No need to block the shared context for this
|
||||
val url = "${snode.address}:${snode.port}/get_stats/v1"
|
||||
try {
|
||||
val json = HTTP.execute(HTTP.Verb.GET, url, 3)
|
||||
val response = HTTP.execute(HTTP.Verb.GET, url, 3).decodeToString()
|
||||
val json = JsonUtil.fromJson(response, Map::class.java)
|
||||
val version = json["version"] as? String
|
||||
if (version == null) { deferred.reject(Exception("Missing snode version.")); return@queue }
|
||||
if (version >= "2.0.7") {
|
||||
@ -209,26 +241,30 @@ object OnionRequestAPI {
|
||||
}
|
||||
OnionRequestAPI.guardSnodes = guardSnodes
|
||||
fun getPath(paths: List<Path>): Path {
|
||||
if (snodeToExclude != null) {
|
||||
return paths.filter { !it.contains(snodeToExclude) }.getRandomElement()
|
||||
return if (snodeToExclude != null) {
|
||||
paths.filter { !it.contains(snodeToExclude) }.getRandomElement()
|
||||
} else {
|
||||
return paths.getRandomElement()
|
||||
paths.getRandomElement()
|
||||
}
|
||||
}
|
||||
if (paths.count() >= targetPathCount) {
|
||||
return Promise.of(getPath(paths))
|
||||
} else if (paths.isNotEmpty()) {
|
||||
if (paths.any { !it.contains(snodeToExclude) }) {
|
||||
buildPaths(paths) // Re-build paths in the background
|
||||
when {
|
||||
paths.count() >= targetPathCount -> {
|
||||
return Promise.of(getPath(paths))
|
||||
} else {
|
||||
return buildPaths(paths).map { newPaths ->
|
||||
getPath(newPaths)
|
||||
}
|
||||
paths.isNotEmpty() -> {
|
||||
return if (paths.any { !it.contains(snodeToExclude) }) {
|
||||
buildPaths(paths) // Re-build paths in the background
|
||||
Promise.of(getPath(paths))
|
||||
} else {
|
||||
buildPaths(paths).map { newPaths ->
|
||||
getPath(newPaths)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return buildPaths(listOf()).map { newPaths ->
|
||||
getPath(newPaths)
|
||||
else -> {
|
||||
return buildPaths(listOf()).map { newPaths ->
|
||||
getPath(newPaths)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -270,7 +306,11 @@ object OnionRequestAPI {
|
||||
/**
|
||||
* Builds an onion around `payload` and returns the result.
|
||||
*/
|
||||
private fun buildOnionForDestination(payload: Map<*, *>, destination: Destination): Promise<OnionBuildingResult, Exception> {
|
||||
private fun buildOnionForDestination(
|
||||
payload: ByteArray,
|
||||
destination: Destination,
|
||||
version: Version
|
||||
): Promise<OnionBuildingResult, Exception> {
|
||||
lateinit var guardSnode: Snode
|
||||
lateinit var destinationSymmetricKey: ByteArray // Needed by LokiAPI to decrypt the response sent back by the destination
|
||||
lateinit var encryptionResult: EncryptionResult
|
||||
@ -281,19 +321,19 @@ object OnionRequestAPI {
|
||||
return getPath(snodeToExclude).bind { path ->
|
||||
guardSnode = path.first()
|
||||
// Encrypt in reverse order, i.e. the destination first
|
||||
OnionRequestEncryption.encryptPayloadForDestination(payload, destination).bind { r ->
|
||||
OnionRequestEncryption.encryptPayloadForDestination(payload, destination, version).bind { r ->
|
||||
destinationSymmetricKey = r.symmetricKey
|
||||
// Recursively encrypt the layers of the onion (again in reverse order)
|
||||
encryptionResult = r
|
||||
@Suppress("NAME_SHADOWING") var path = path
|
||||
var rhs = destination
|
||||
fun addLayer(): Promise<EncryptionResult, Exception> {
|
||||
if (path.isEmpty()) {
|
||||
return Promise.of(encryptionResult)
|
||||
return if (path.isEmpty()) {
|
||||
Promise.of(encryptionResult)
|
||||
} else {
|
||||
val lhs = Destination.Snode(path.last())
|
||||
path = path.dropLast(1)
|
||||
return OnionRequestEncryption.encryptHop(lhs, rhs, encryptionResult).bind { r ->
|
||||
OnionRequestEncryption.encryptHop(lhs, rhs, encryptionResult).bind { r ->
|
||||
encryptionResult = r
|
||||
rhs = lhs
|
||||
addLayer()
|
||||
@ -308,15 +348,15 @@ object OnionRequestAPI {
|
||||
/**
|
||||
* Sends an onion request to `destination`. Builds new paths as needed.
|
||||
*/
|
||||
private fun sendOnionRequest(destination: Destination, payload: Map<*, *>): Promise<Map<*, *>, Exception> {
|
||||
private fun sendOnionRequest(destination: Destination, payload: ByteArray, version: Version): Promise<Map<*, *>, Exception> {
|
||||
val deferred = deferred<Map<*, *>, Exception>()
|
||||
lateinit var guardSnode: Snode
|
||||
buildOnionForDestination(payload, destination).success { result ->
|
||||
buildOnionForDestination(payload, destination, version).success { result ->
|
||||
guardSnode = result.guardSnode
|
||||
val url = "${guardSnode.address}:${guardSnode.port}/onion_req/v2"
|
||||
val finalEncryptionResult = result.finalEncryptionResult
|
||||
val onion = finalEncryptionResult.ciphertext
|
||||
if (destination is Destination.Server && onion.count().toDouble() > 0.75 * FileServerAPIV2.maxFileSize.toDouble()) {
|
||||
if (destination is Destination.Server && onion.count().toDouble() > 0.75 * FileServerApi.maxFileSize.toDouble()) {
|
||||
Log.d("Loki", "Approaching request size limit: ~${onion.count()} bytes.")
|
||||
}
|
||||
@Suppress("NAME_SHADOWING") val parameters = mapOf(
|
||||
@ -331,49 +371,8 @@ object OnionRequestAPI {
|
||||
val destinationSymmetricKey = result.destinationSymmetricKey
|
||||
ThreadUtils.queue {
|
||||
try {
|
||||
val json = HTTP.execute(HTTP.Verb.POST, url, body)
|
||||
val base64EncodedIVAndCiphertext = json["result"] as? String ?: return@queue deferred.reject(Exception("Invalid JSON"))
|
||||
val ivAndCiphertext = Base64.decode(base64EncodedIVAndCiphertext)
|
||||
try {
|
||||
val plaintext = AESGCM.decrypt(ivAndCiphertext, destinationSymmetricKey)
|
||||
try {
|
||||
@Suppress("NAME_SHADOWING") val json = JsonUtil.fromJson(plaintext.toString(Charsets.UTF_8), Map::class.java)
|
||||
val statusCode = json["status_code"] as? Int ?: json["status"] as Int
|
||||
if (statusCode == 406) {
|
||||
@Suppress("NAME_SHADOWING") val body = mapOf( "result" to "Your clock is out of sync with the service node network." )
|
||||
val exception = HTTPRequestFailedAtDestinationException(statusCode, body, destination.description)
|
||||
return@queue deferred.reject(exception)
|
||||
} else if (json["body"] != null) {
|
||||
@Suppress("NAME_SHADOWING") val body: Map<*, *>
|
||||
if (json["body"] is Map<*, *>) {
|
||||
body = json["body"] as Map<*, *>
|
||||
} else {
|
||||
val bodyAsString = json["body"] as String
|
||||
body = JsonUtil.fromJson(bodyAsString, Map::class.java)
|
||||
}
|
||||
if (body["t"] != null) {
|
||||
val timestamp = body["t"] as Long
|
||||
val offset = timestamp - Date().time
|
||||
SnodeAPI.clockOffset = offset
|
||||
}
|
||||
if (statusCode != 200) {
|
||||
val exception = HTTPRequestFailedAtDestinationException(statusCode, body, destination.description)
|
||||
return@queue deferred.reject(exception)
|
||||
}
|
||||
deferred.resolve(body)
|
||||
} else {
|
||||
if (statusCode != 200) {
|
||||
val exception = HTTPRequestFailedAtDestinationException(statusCode, json, destination.description)
|
||||
return@queue deferred.reject(exception)
|
||||
}
|
||||
deferred.resolve(json)
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
deferred.reject(Exception("Invalid JSON: ${plaintext.toString(Charsets.UTF_8)}."))
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
deferred.reject(exception)
|
||||
}
|
||||
val response = HTTP.execute(HTTP.Verb.POST, url, body)
|
||||
handleResponse(response, destinationSymmetricKey, destination, version, deferred)
|
||||
} catch (exception: Exception) {
|
||||
deferred.reject(exception)
|
||||
}
|
||||
@ -440,12 +439,13 @@ object OnionRequestAPI {
|
||||
/**
|
||||
* Sends an onion request to `snode`. Builds new paths as needed.
|
||||
*/
|
||||
internal fun sendOnionRequest(method: Snode.Method, parameters: Map<*, *>, snode: Snode, publicKey: String? = null): Promise<Map<*, *>, Exception> {
|
||||
internal fun sendOnionRequest(method: Snode.Method, parameters: Map<*, *>, snode: Snode, version: Version, publicKey: String? = null): Promise<Map<*, *>, Exception> {
|
||||
val payload = mapOf(
|
||||
"method" to method.rawValue,
|
||||
"params" to parameters
|
||||
)
|
||||
return sendOnionRequest(Destination.Snode(snode), payload).recover { exception ->
|
||||
val payloadData = JsonUtil.toJson(payload).toByteArray()
|
||||
return sendOnionRequest(Destination.Snode(snode), payloadData, version).recover { exception ->
|
||||
val error = when (exception) {
|
||||
is HTTP.HTTPRequestFailedException -> SnodeAPI.handleSnodeError(exception.statusCode, exception.json, snode, publicKey)
|
||||
is HTTPRequestFailedAtDestinationException -> SnodeAPI.handleSnodeError(exception.statusCode, exception.json, snode, publicKey)
|
||||
@ -461,27 +461,198 @@ object OnionRequestAPI {
|
||||
*
|
||||
* `publicKey` is the hex encoded public key of the user the call is associated with. This is needed for swarm cache maintenance.
|
||||
*/
|
||||
fun sendOnionRequest(request: Request, server: String, x25519PublicKey: String, target: String = "/loki/v3/lsrpc"): Promise<Map<*, *>, Exception> {
|
||||
val headers = request.getHeadersForOnionRequest()
|
||||
fun sendOnionRequest(request: Request, server: String, x25519PublicKey: String, version: Version = Version.V4): Promise<Map<*, *>, Exception> {
|
||||
val url = request.url()
|
||||
val payload = generatePayload(request, server, version)
|
||||
val destination = Destination.Server(url.host(), version.value, x25519PublicKey, url.scheme(), url.port())
|
||||
return sendOnionRequest(destination, payload, version).recover { exception ->
|
||||
Log.d("Loki", "Couldn't reach server: $url due to error: $exception.")
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
|
||||
private fun generatePayload(request: Request, server: String, version: Version): ByteArray {
|
||||
val headers = request.getHeadersForOnionRequest().toMutableMap()
|
||||
val url = request.url()
|
||||
val urlAsString = url.toString()
|
||||
val host = url.host()
|
||||
val body = request.getBodyForOnionRequest() ?: "null"
|
||||
val endpoint = when {
|
||||
server.count() < urlAsString.count() -> urlAsString.substringAfter(server).removePrefix("/")
|
||||
else -> ""
|
||||
}
|
||||
val body = request.getBodyForOnionRequest() ?: "null"
|
||||
val payload = mapOf(
|
||||
"body" to body,
|
||||
"endpoint" to endpoint,
|
||||
"method" to request.method(),
|
||||
"headers" to headers
|
||||
)
|
||||
val destination = Destination.Server(host, target, x25519PublicKey, url.scheme(), url.port())
|
||||
return sendOnionRequest(destination, payload).recover { exception ->
|
||||
Log.d("Loki", "Couldn't reach server: $urlAsString due to error: $exception.")
|
||||
throw exception
|
||||
return if (version == Version.V4) {
|
||||
if (request.body() != null && !headers.containsKey("Content-Type")) {
|
||||
headers["Content-Type"] = "application/json"
|
||||
}
|
||||
val requestPayload = mapOf(
|
||||
"endpoint" to endpoint,
|
||||
"method" to request.method(),
|
||||
"headers" to headers
|
||||
)
|
||||
val requestData = JsonUtil.toJson(requestPayload).toByteArray()
|
||||
val prefixData = "l${requestData.size}".toByteArray()
|
||||
val suffixData = "e".toByteArray()
|
||||
if (request.body() != null) {
|
||||
val bodyPayload = mapOf(
|
||||
"body" to body
|
||||
)
|
||||
val bodyData = JsonUtil.toJson(bodyPayload).toByteArray()
|
||||
val bodyLengthData = "${bodyData.size}".toByteArray()
|
||||
prefixData + requestData + bodyLengthData + bodyData + suffixData
|
||||
} else {
|
||||
prefixData + requestData + suffixData
|
||||
}
|
||||
} else {
|
||||
val payload = mapOf(
|
||||
"body" to body,
|
||||
"endpoint" to endpoint,
|
||||
"method" to request.method(),
|
||||
"headers" to headers
|
||||
)
|
||||
JsonUtil.toJson(payload).toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleResponse(
|
||||
response: ByteArray,
|
||||
destinationSymmetricKey: ByteArray,
|
||||
destination: Destination,
|
||||
version: Version,
|
||||
deferred: Deferred<Map<*, *>, Exception>
|
||||
) {
|
||||
if (version == Version.V4) {
|
||||
try {
|
||||
if (response.size <= AESGCM.ivSize) return deferred.reject(Exception("Invalid response"))
|
||||
// The data will be in the form of `l123:jsone` or `l123:json456:bodye` so we need to break the data into
|
||||
// parts to properly process it
|
||||
val plaintext = AESGCM.decrypt(response, destinationSymmetricKey)
|
||||
val plaintextString = plaintext.decodeToString()
|
||||
if (!plaintextString.startsWith("l")) return deferred.reject(Exception("Invalid response"))
|
||||
val infoParts = plaintextString.split(":")
|
||||
val infoLength = infoParts.firstOrNull()?.drop(1)?.toIntOrNull()
|
||||
if (infoParts.size <= 1 || infoLength == null) return deferred.reject(Exception("Invalid response"))
|
||||
val infoStartIndex = "l$infoLength".length + 1
|
||||
val infoEndIndex = infoStartIndex + infoLength
|
||||
val info = plaintextString.substring(infoStartIndex, infoEndIndex)
|
||||
val responseInfo = JsonUtil.fromJson(info, Map::class.java)
|
||||
when (val statusCode = responseInfo["code"].toString().toInt()) {
|
||||
// Custom handle a clock out of sync error (v4 returns '425' but included the '406' just in case)
|
||||
406, 425 -> {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val body =
|
||||
mapOf("result" to "Your clock is out of sync with the service node network.")
|
||||
val exception = HTTPRequestFailedAtDestinationException(
|
||||
statusCode,
|
||||
body,
|
||||
destination.description
|
||||
)
|
||||
return deferred.reject(exception)
|
||||
}
|
||||
// Handle error status codes
|
||||
!in 200..299 -> {
|
||||
val exception = HTTPRequestFailedAtDestinationException(
|
||||
statusCode,
|
||||
responseInfo,
|
||||
destination.description
|
||||
)
|
||||
return deferred.reject(exception)
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no data in the response then just return the ResponseInfo
|
||||
if (info.length < "l${infoLength}${info}e".length) {
|
||||
return deferred.resolve(JsonUtil.fromJson(info, Map::class.java))
|
||||
}
|
||||
// Extract the response data as well
|
||||
val data = plaintextString.substring(infoEndIndex)
|
||||
val dataParts = data.split(":")
|
||||
val dataLength = dataParts.firstOrNull()?.length
|
||||
if (dataParts.size <= 1 || dataLength == null) return deferred.reject(Exception("Invalid JSON"))
|
||||
val dataString = dataParts.last().dropLast(1)
|
||||
return deferred.resolve(JsonUtil.fromJson(dataString, Map::class.java))
|
||||
} catch (exception: Exception) {
|
||||
deferred.reject(exception)
|
||||
}
|
||||
} else {
|
||||
val bodyAsString = response.decodeToString()
|
||||
val json = try {
|
||||
JsonUtil.fromJson(bodyAsString, Map::class.java)
|
||||
} catch (exception: Exception) {
|
||||
mapOf( "result" to bodyAsString)
|
||||
}
|
||||
val base64EncodedIVAndCiphertext = json["result"] as? String ?: return deferred.reject(Exception("Invalid JSON"))
|
||||
val ivAndCiphertext = Base64.decode(base64EncodedIVAndCiphertext)
|
||||
try {
|
||||
val plaintext = AESGCM.decrypt(ivAndCiphertext, destinationSymmetricKey)
|
||||
try {
|
||||
@Suppress("NAME_SHADOWING") val json =
|
||||
JsonUtil.fromJson(plaintext.toString(Charsets.UTF_8), Map::class.java)
|
||||
val statusCode = json["status_code"] as? Int ?: json["status"] as Int
|
||||
when {
|
||||
statusCode == 406 -> {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val body =
|
||||
mapOf("result" to "Your clock is out of sync with the service node network.")
|
||||
val exception = HTTPRequestFailedAtDestinationException(
|
||||
statusCode,
|
||||
body,
|
||||
destination.description
|
||||
)
|
||||
return deferred.reject(exception)
|
||||
}
|
||||
json["body"] != null -> {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val body = if (json["body"] is Map<*, *>) {
|
||||
json["body"] as Map<*, *>
|
||||
} else {
|
||||
val bodyAsString = json["body"] as String
|
||||
JsonUtil.fromJson(bodyAsString, Map::class.java)
|
||||
}
|
||||
if (body["t"] != null) {
|
||||
val timestamp = body["t"] as Long
|
||||
val offset = timestamp - Date().time
|
||||
SnodeAPI.clockOffset = offset
|
||||
}
|
||||
if (statusCode != 200) {
|
||||
val exception = HTTPRequestFailedAtDestinationException(
|
||||
statusCode,
|
||||
body,
|
||||
destination.description
|
||||
)
|
||||
return deferred.reject(exception)
|
||||
}
|
||||
deferred.resolve(body)
|
||||
}
|
||||
else -> {
|
||||
if (statusCode != 200) {
|
||||
val exception = HTTPRequestFailedAtDestinationException(
|
||||
statusCode,
|
||||
json,
|
||||
destination.description
|
||||
)
|
||||
return deferred.reject(exception)
|
||||
}
|
||||
deferred.resolve(json)
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
deferred.reject(Exception("Invalid JSON: ${plaintext.toString(Charsets.UTF_8)}."))
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
deferred.reject(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
enum class Version(val value: String) {
|
||||
V2("/loki/v2/lsrpc"),
|
||||
V3("/loki/v3/lsrpc"),
|
||||
V4("/oxen/v4/lsrpc");
|
||||
}
|
||||
|
||||
data class ResponseInfo(
|
||||
val code: String,
|
||||
val headers: Map<String, String>
|
||||
)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package org.session.libsession.snode
|
||||
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.deferred
|
||||
import org.session.libsession.snode.OnionRequestAPI.Destination
|
||||
import org.session.libsession.utilities.AESGCM
|
||||
import org.session.libsession.utilities.AESGCM.EncryptionResult
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
@ -31,25 +32,29 @@ object OnionRequestEncryption {
|
||||
/**
|
||||
* Encrypts `payload` for `destination` and returns the result. Use this to build the core of an onion request.
|
||||
*/
|
||||
internal fun encryptPayloadForDestination(payload: Map<*, *>, destination: OnionRequestAPI.Destination): Promise<EncryptionResult, Exception> {
|
||||
internal fun encryptPayloadForDestination(
|
||||
payload: ByteArray,
|
||||
destination: Destination,
|
||||
version: OnionRequestAPI.Version
|
||||
): Promise<EncryptionResult, Exception> {
|
||||
val deferred = deferred<EncryptionResult, Exception>()
|
||||
ThreadUtils.queue {
|
||||
try {
|
||||
// Wrapping isn't needed for file server or open group onion requests
|
||||
when (destination) {
|
||||
is OnionRequestAPI.Destination.Snode -> {
|
||||
val snodeX25519PublicKey = destination.snode.publicKeySet!!.x25519Key
|
||||
val payloadAsData = JsonUtil.toJson(payload).toByteArray()
|
||||
val plaintext = encode(payloadAsData, mapOf( "headers" to "" ))
|
||||
val result = AESGCM.encrypt(plaintext, snodeX25519PublicKey)
|
||||
deferred.resolve(result)
|
||||
}
|
||||
is OnionRequestAPI.Destination.Server -> {
|
||||
val plaintext = JsonUtil.toJson(payload).toByteArray()
|
||||
val result = AESGCM.encrypt(plaintext, destination.x25519PublicKey)
|
||||
deferred.resolve(result)
|
||||
val plaintext = if (version == OnionRequestAPI.Version.V4) {
|
||||
payload
|
||||
} else {
|
||||
// Wrapping isn't needed for file server or open group onion requests
|
||||
when (destination) {
|
||||
is Destination.Snode -> encode(payload, mapOf("headers" to ""))
|
||||
is Destination.Server -> payload
|
||||
}
|
||||
}
|
||||
val x25519PublicKey = when (destination) {
|
||||
is Destination.Snode -> destination.snode.publicKeySet!!.x25519Key
|
||||
is Destination.Server -> destination.x25519PublicKey
|
||||
}
|
||||
val result = AESGCM.encrypt(plaintext, x25519PublicKey)
|
||||
deferred.resolve(result)
|
||||
} catch (exception: Exception) {
|
||||
deferred.reject(exception)
|
||||
}
|
||||
@ -60,17 +65,16 @@ object OnionRequestEncryption {
|
||||
/**
|
||||
* Encrypts the previous encryption result (i.e. that of the hop after this one) for this hop. Use this to build the layers of an onion request.
|
||||
*/
|
||||
internal fun encryptHop(lhs: OnionRequestAPI.Destination, rhs: OnionRequestAPI.Destination, previousEncryptionResult: EncryptionResult): Promise<EncryptionResult, Exception> {
|
||||
internal fun encryptHop(lhs: Destination, rhs: Destination, previousEncryptionResult: EncryptionResult): Promise<EncryptionResult, Exception> {
|
||||
val deferred = deferred<EncryptionResult, Exception>()
|
||||
ThreadUtils.queue {
|
||||
try {
|
||||
val payload: MutableMap<String, Any>
|
||||
when (rhs) {
|
||||
is OnionRequestAPI.Destination.Snode -> {
|
||||
payload = mutableMapOf( "destination" to rhs.snode.publicKeySet!!.ed25519Key )
|
||||
val payload: MutableMap<String, Any> = when (rhs) {
|
||||
is Destination.Snode -> {
|
||||
mutableMapOf( "destination" to rhs.snode.publicKeySet!!.ed25519Key )
|
||||
}
|
||||
is OnionRequestAPI.Destination.Server -> {
|
||||
payload = mutableMapOf(
|
||||
is Destination.Server -> {
|
||||
mutableMapOf(
|
||||
"host" to rhs.host,
|
||||
"target" to rhs.target,
|
||||
"method" to "POST",
|
||||
@ -80,13 +84,12 @@ object OnionRequestEncryption {
|
||||
}
|
||||
}
|
||||
payload["ephemeral_key"] = previousEncryptionResult.ephemeralPublicKey.toHexString()
|
||||
val x25519PublicKey: String
|
||||
when (lhs) {
|
||||
is OnionRequestAPI.Destination.Snode -> {
|
||||
x25519PublicKey = lhs.snode.publicKeySet!!.x25519Key
|
||||
val x25519PublicKey = when (lhs) {
|
||||
is Destination.Snode -> {
|
||||
lhs.snode.publicKeySet!!.x25519Key
|
||||
}
|
||||
is OnionRequestAPI.Destination.Server -> {
|
||||
x25519PublicKey = lhs.x25519PublicKey
|
||||
is Destination.Server -> {
|
||||
lhs.x25519PublicKey
|
||||
}
|
||||
}
|
||||
val plaintext = encode(previousEncryptionResult.ciphertext, payload)
|
||||
|
@ -74,16 +74,23 @@ object SnodeAPI {
|
||||
}
|
||||
|
||||
// Internal API
|
||||
internal fun invoke(method: Snode.Method, snode: Snode, publicKey: String? = null, parameters: Map<String, Any>): RawResponsePromise {
|
||||
internal fun invoke(
|
||||
method: Snode.Method,
|
||||
snode: Snode,
|
||||
parameters: Map<String, Any>,
|
||||
publicKey: String? = null,
|
||||
version: OnionRequestAPI.Version = OnionRequestAPI.Version.V3
|
||||
): RawResponsePromise {
|
||||
val url = "${snode.address}:${snode.port}/storage_rpc/v1"
|
||||
if (useOnionRequests) {
|
||||
return OnionRequestAPI.sendOnionRequest(method, parameters, snode, publicKey)
|
||||
return OnionRequestAPI.sendOnionRequest(method, parameters, snode, version, publicKey)
|
||||
} else {
|
||||
val deferred = deferred<Map<*, *>, Exception>()
|
||||
ThreadUtils.queue {
|
||||
val payload = mapOf( "method" to method.rawValue, "params" to parameters )
|
||||
try {
|
||||
val json = HTTP.execute(HTTP.Verb.POST, url, payload)
|
||||
val response = HTTP.execute(HTTP.Verb.POST, url, payload).toString()
|
||||
val json = JsonUtil.fromJson(response, Map::class.java)
|
||||
deferred.resolve(json)
|
||||
} catch (exception: Exception) {
|
||||
val httpRequestFailedException = exception as? HTTP.HTTPRequestFailedException
|
||||
@ -117,7 +124,12 @@ object SnodeAPI {
|
||||
deferred<Snode, Exception>()
|
||||
ThreadUtils.queue {
|
||||
try {
|
||||
val json = HTTP.execute(HTTP.Verb.POST, url, parameters, useSeedNodeConnection = true)
|
||||
val response = HTTP.execute(HTTP.Verb.POST, url, parameters, useSeedNodeConnection = true).toString()
|
||||
val json = try {
|
||||
JsonUtil.fromJson(response, Map::class.java)
|
||||
} catch (exception: Exception) {
|
||||
mapOf( "result" to response)
|
||||
}
|
||||
val intermediate = json["result"] as? Map<*, *>
|
||||
val rawSnodes = intermediate?.get("service_node_states") as? List<*>
|
||||
if (rawSnodes != null) {
|
||||
@ -192,7 +204,7 @@ object SnodeAPI {
|
||||
val promises = (1..validationCount).map {
|
||||
getRandomSnode().bind { snode ->
|
||||
retryIfNeeded(maxRetryCount) {
|
||||
invoke(Snode.Method.OxenDaemonRPCCall, snode, null, parameters)
|
||||
invoke(Snode.Method.OxenDaemonRPCCall, snode, parameters)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -268,7 +280,7 @@ object SnodeAPI {
|
||||
} else {
|
||||
val parameters = mapOf( "pubKey" to if (useTestnet) publicKey.removing05PrefixIfNeeded() else publicKey )
|
||||
return getRandomSnode().bind {
|
||||
invoke(Snode.Method.GetSwarm, it, publicKey, parameters)
|
||||
invoke(Snode.Method.GetSwarm, it, parameters, publicKey)
|
||||
}.map {
|
||||
parseSnodes(it).toSet()
|
||||
}.success {
|
||||
@ -299,7 +311,7 @@ object SnodeAPI {
|
||||
// "pubkey_ed25519" to ed25519PublicKey,
|
||||
// "signature" to Base64.encodeBytes(signature)
|
||||
)
|
||||
return invoke(Snode.Method.GetMessages, snode, publicKey, parameters)
|
||||
return invoke(Snode.Method.GetMessages, snode, parameters, publicKey)
|
||||
}
|
||||
|
||||
fun getMessages(publicKey: String): MessageListPromise {
|
||||
@ -311,7 +323,7 @@ object SnodeAPI {
|
||||
}
|
||||
|
||||
private fun getNetworkTime(snode: Snode): Promise<Pair<Snode,Long>, Exception> {
|
||||
return invoke(Snode.Method.Info, snode, null, emptyMap()).map { rawResponse ->
|
||||
return invoke(Snode.Method.Info, snode, emptyMap()).map { rawResponse ->
|
||||
val timestamp = rawResponse["timestamp"] as? Long ?: -1
|
||||
snode to timestamp
|
||||
}
|
||||
@ -323,7 +335,7 @@ object SnodeAPI {
|
||||
getTargetSnodes(destination).map { swarm ->
|
||||
swarm.map { snode ->
|
||||
val parameters = message.toJSON()
|
||||
invoke(Snode.Method.SendMessage, snode, destination, parameters)
|
||||
invoke(Snode.Method.SendMessage, snode, parameters, destination)
|
||||
}.toSet()
|
||||
}
|
||||
}
|
||||
@ -345,7 +357,7 @@ object SnodeAPI {
|
||||
"messages" to serverHashes,
|
||||
"signature" to Base64.encodeBytes(signature)
|
||||
)
|
||||
invoke(Snode.Method.DeleteMessage, snode, publicKey, deleteMessageParams).map { rawResponse ->
|
||||
invoke(Snode.Method.DeleteMessage, snode, deleteMessageParams, publicKey).map { rawResponse ->
|
||||
val swarms = rawResponse["swarm"] as? Map<String, Any> ?: return@map mapOf()
|
||||
val result = swarms.mapNotNull { (hexSnodePublicKey, rawJSON) ->
|
||||
val json = rawJSON as? Map<String, Any> ?: return@mapNotNull null
|
||||
@ -415,7 +427,7 @@ object SnodeAPI {
|
||||
"timestamp" to timestamp,
|
||||
"signature" to Base64.encodeBytes(signature)
|
||||
)
|
||||
invoke(Snode.Method.DeleteAll, snode, userPublicKey, deleteMessageParams).map {
|
||||
invoke(Snode.Method.DeleteAll, snode, deleteMessageParams, userPublicKey).map {
|
||||
rawResponse -> parseDeletions(userPublicKey, timestamp, rawResponse)
|
||||
}.fail { e ->
|
||||
Log.e("Loki", "Failed to clear data", e)
|
||||
@ -530,7 +542,7 @@ object SnodeAPI {
|
||||
400, 500, 502, 503 -> { // Usually indicates that the snode isn't up to date
|
||||
handleBadSnode()
|
||||
}
|
||||
425 -> {
|
||||
406 -> {
|
||||
Log.d("Loki", "The user's clock is out of sync with the service node network.")
|
||||
broadcaster.broadcast("clockOutOfSync")
|
||||
return Error.ClockOutOfSync
|
||||
|
@ -1,9 +1,8 @@
|
||||
package org.session.libsession.utilities
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import org.session.libsession.messaging.file_server.FileServerAPIV2
|
||||
import org.session.libsession.messaging.file_server.FileServerApi
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.messages.SignalServiceAttachment
|
||||
import java.io.*
|
||||
|
||||
object DownloadUtilities {
|
||||
@ -37,7 +36,7 @@ object DownloadUtilities {
|
||||
val url = HttpUrl.parse(urlAsString)!!
|
||||
val fileID = url.pathSegments().last()
|
||||
try {
|
||||
FileServerAPIV2.download(fileID.toLong()).get().let {
|
||||
FileServerApi.download(fileID.toLong()).get().let {
|
||||
outputStream.write(it)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
@ -4,7 +4,7 @@ import android.content.Context
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.deferred
|
||||
import okio.Buffer
|
||||
import org.session.libsession.messaging.file_server.FileServerAPIV2
|
||||
import org.session.libsession.messaging.file_server.FileServerApi
|
||||
import org.session.libsignal.streams.ProfileCipherOutputStream
|
||||
import org.session.libsignal.utilities.ProfileAvatarData
|
||||
import org.session.libsignal.streams.DigestingRequestBody
|
||||
@ -30,13 +30,13 @@ object ProfilePictureUtilities {
|
||||
var id: Long = 0
|
||||
try {
|
||||
id = retryIfNeeded(4) {
|
||||
FileServerAPIV2.upload(data)
|
||||
FileServerApi.upload(data)
|
||||
}.get()
|
||||
} catch (e: Exception) {
|
||||
deferred.reject(e)
|
||||
}
|
||||
TextSecurePreferences.setLastProfilePictureUpload(context, Date().time)
|
||||
val url = "${FileServerAPIV2.server}/files/$id"
|
||||
val url = "${FileServerApi.server}/files/$id"
|
||||
TextSecurePreferences.setProfilePictureURL(context, url)
|
||||
deferred.resolve(Unit)
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
package org.session.libsignal.utilities
|
||||
|
||||
import okhttp3.*
|
||||
import java.lang.IllegalStateException
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.Response
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -68,26 +71,26 @@ object HTTP {
|
||||
/**
|
||||
* Sync. Don't call from the main thread.
|
||||
*/
|
||||
fun execute(verb: Verb, url: String, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): Map<*, *> {
|
||||
fun execute(verb: Verb, url: String, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): ByteArray {
|
||||
return execute(verb = verb, url = url, body = null, timeout = timeout, useSeedNodeConnection = useSeedNodeConnection)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync. Don't call from the main thread.
|
||||
*/
|
||||
fun execute(verb: Verb, url: String, parameters: Map<String, Any>?, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): Map<*, *> {
|
||||
if (parameters != null) {
|
||||
fun execute(verb: Verb, url: String, parameters: Map<String, Any>?, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): ByteArray {
|
||||
return if (parameters != null) {
|
||||
val body = JsonUtil.toJson(parameters).toByteArray()
|
||||
return execute(verb = verb, url = url, body = body, timeout = timeout, useSeedNodeConnection = useSeedNodeConnection)
|
||||
execute(verb = verb, url = url, body = body, timeout = timeout, useSeedNodeConnection = useSeedNodeConnection)
|
||||
} else {
|
||||
return execute(verb = verb, url = url, body = null, timeout = timeout, useSeedNodeConnection = useSeedNodeConnection)
|
||||
execute(verb = verb, url = url, body = null, timeout = timeout, useSeedNodeConnection = useSeedNodeConnection)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync. Don't call from the main thread.
|
||||
*/
|
||||
fun execute(verb: Verb, url: String, body: ByteArray?, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): Map<*, *> {
|
||||
fun execute(verb: Verb, url: String, body: ByteArray?, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): ByteArray {
|
||||
val request = Request.Builder().url(url)
|
||||
.removeHeader("User-Agent").addHeader("User-Agent", "WhatsApp") // Set a fake value
|
||||
.removeHeader("Accept-Language").addHeader("Accept-Language", "en-us") // Set a fake value
|
||||
@ -103,14 +106,13 @@ object HTTP {
|
||||
}
|
||||
lateinit var response: Response
|
||||
try {
|
||||
val connection: OkHttpClient
|
||||
if (timeout != HTTP.timeout) { // Custom timeout
|
||||
val connection = if (timeout != HTTP.timeout) { // Custom timeout
|
||||
if (useSeedNodeConnection) {
|
||||
throw IllegalStateException("Setting a custom timeout is only allowed for requests to snodes.")
|
||||
}
|
||||
connection = getDefaultConnection(timeout)
|
||||
getDefaultConnection(timeout)
|
||||
} else {
|
||||
connection = if (useSeedNodeConnection) seedNodeConnection else defaultConnection
|
||||
if (useSeedNodeConnection) seedNodeConnection else defaultConnection
|
||||
}
|
||||
response = connection.newCall(request.build()).execute()
|
||||
} catch (exception: Exception) {
|
||||
@ -118,14 +120,9 @@ object HTTP {
|
||||
// Override the actual error so that we can correctly catch failed requests in OnionRequestAPI
|
||||
throw HTTPRequestFailedException(0, null)
|
||||
}
|
||||
when (val statusCode = response.code()) {
|
||||
return when (val statusCode = response.code()) {
|
||||
200 -> {
|
||||
val bodyAsString = response.body()?.string() ?: throw Exception("An error occurred.")
|
||||
try {
|
||||
return JsonUtil.fromJson(bodyAsString, Map::class.java)
|
||||
} catch (exception: Exception) {
|
||||
return mapOf( "result" to bodyAsString)
|
||||
}
|
||||
response.body()?.bytes() ?: throw Exception("An error occurred.")
|
||||
}
|
||||
else -> {
|
||||
Log.d("Loki", "${verb.rawValue} request to $url failed with status code: $statusCode.")
|
||||
|
Loading…
x
Reference in New Issue
Block a user