mirror of
https://github.com/oxen-io/session-android.git
synced 2025-04-23 04:31:29 +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.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.session.libsession.messaging.utilities.SodiumUtilities
|
import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||||
|
import org.session.libsignal.utilities.toHexString
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class SodiumUtilitiesTest {
|
class SodiumUtilitiesTest {
|
||||||
@ -25,4 +26,27 @@ class SodiumUtilitiesTest {
|
|||||||
assertThat(keyPair.publicKey.asHexString.lowercase(), equalTo(blindedKey))
|
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 android.content.Context;
|
||||||
|
|
||||||
import org.session.libsession.messaging.file_server.FileServerAPIV2;
|
import org.session.libsession.messaging.file_server.FileServerApi;
|
||||||
|
|
||||||
public class PushMediaConstraints extends MediaConstraints {
|
public class PushMediaConstraints extends MediaConstraints {
|
||||||
|
|
||||||
@ -21,26 +21,26 @@ public class PushMediaConstraints extends MediaConstraints {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getImageMaxSize(Context context) {
|
public int getImageMaxSize(Context context) {
|
||||||
return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
|
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getGifMaxSize(Context context) {
|
public int getGifMaxSize(Context context) {
|
||||||
return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
|
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getVideoMaxSize(Context context) {
|
public int getVideoMaxSize(Context context) {
|
||||||
return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
|
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAudioMaxSize(Context context) {
|
public int getAudioMaxSize(Context context) {
|
||||||
return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
|
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDocumentMaxSize(Context context) {
|
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 body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||||
val request = Request.Builder().url(url).post(body)
|
val request = Request.Builder().url(url).post(body)
|
||||||
retryIfNeeded(maxRetryCount) {
|
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
|
val code = json["code"] as? Int
|
||||||
if (code != null && code != 0) {
|
if (code != null && code != 0) {
|
||||||
TextSecurePreferences.setIsUsingFCM(context, false)
|
TextSecurePreferences.setIsUsingFCM(context, false)
|
||||||
@ -72,7 +72,7 @@ object LokiPushNotificationManager {
|
|||||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||||
val request = Request.Builder().url(url).post(body)
|
val request = Request.Builder().url(url).post(body)
|
||||||
retryIfNeeded(maxRetryCount) {
|
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
|
val code = json["code"] as? Int
|
||||||
if (code != null && code != 0) {
|
if (code != null && code != 0) {
|
||||||
TextSecurePreferences.setIsUsingFCM(context, true)
|
TextSecurePreferences.setIsUsingFCM(context, true)
|
||||||
@ -100,7 +100,7 @@ object LokiPushNotificationManager {
|
|||||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||||
val request = Request.Builder().url(url).post(body)
|
val request = Request.Builder().url(url).post(body)
|
||||||
retryIfNeeded(maxRetryCount) {
|
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
|
val code = json["code"] as? Int
|
||||||
if (code == null || code == 0) {
|
if (code == null || code == 0) {
|
||||||
Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${json["message"] as? String ?: "null"}.")
|
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.JsonUtil
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
|
|
||||||
object FileServerAPIV2 {
|
object FileServerApi {
|
||||||
|
|
||||||
private const val serverPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59"
|
private const val serverPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59"
|
||||||
const val server = "http://filev2.getsession.org"
|
const val server = "http://filev2.getsession.org"
|
||||||
@ -73,12 +73,12 @@ object FileServerAPIV2 {
|
|||||||
HTTP.Verb.POST -> requestBuilder.post(createBody(request.parameters)!!)
|
HTTP.Verb.POST -> requestBuilder.post(createBody(request.parameters)!!)
|
||||||
HTTP.Verb.DELETE -> requestBuilder.delete(createBody(request.parameters))
|
HTTP.Verb.DELETE -> requestBuilder.delete(createBody(request.parameters))
|
||||||
}
|
}
|
||||||
if (request.useOnionRouting) {
|
return if (request.useOnionRouting) {
|
||||||
return OnionRequestAPI.sendOnionRequest(requestBuilder.build(), server, serverPublicKey).fail { e ->
|
OnionRequestAPI.sendOnionRequest(requestBuilder.build(), server, serverPublicKey).fail { e ->
|
||||||
Log.e("Loki", "File server request failed.", e)
|
Log.e("Loki", "File server request failed.", e)
|
||||||
}
|
}
|
||||||
} else {
|
} 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 nl.komponents.kovenant.Promise
|
||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
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.messages.Message
|
||||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
|
||||||
import org.session.libsession.messaging.open_groups.OpenGroupApiV4
|
import org.session.libsession.messaging.open_groups.OpenGroupApiV4
|
||||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||||
import org.session.libsession.messaging.utilities.Data
|
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)
|
handleSuccess(attachment, keyAndResult.first, keyAndResult.second)
|
||||||
} else {
|
} else {
|
||||||
val keyAndResult = upload(attachment, FileServerAPIV2.server, true) {
|
val keyAndResult = upload(attachment, FileServerApi.server, true) {
|
||||||
FileServerAPIV2.upload(it)
|
FileServerApi.upload(it)
|
||||||
}
|
}
|
||||||
handleSuccess(attachment, keyAndResult.first, keyAndResult.second)
|
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 body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||||
val request = Request.Builder().url(url).post(body)
|
val request = Request.Builder().url(url).post(body)
|
||||||
retryIfNeeded(4) {
|
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
|
val code = json["code"] as? Int
|
||||||
if (code == null || code == 0) {
|
if (code == null || code == 0) {
|
||||||
Log.d("Loki", "Couldn't notify PN server due to error: ${json["message"] as? String ?: "null"}.")
|
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.snode.OnionRequestAPI
|
||||||
import org.session.libsession.utilities.AESGCM
|
import org.session.libsession.utilities.AESGCM
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
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.decode
|
||||||
import org.session.libsignal.utilities.Base64.encodeBytes
|
import org.session.libsignal.utilities.Base64.encodeBytes
|
||||||
import org.session.libsignal.utilities.HTTP
|
import org.session.libsignal.utilities.HTTP
|
||||||
|
@ -38,7 +38,7 @@ object PushNotificationAPI {
|
|||||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||||
val request = Request.Builder().url(url).post(body)
|
val request = Request.Builder().url(url).post(body)
|
||||||
retryIfNeeded(maxRetryCount) {
|
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
|
val code = json["code"] as? Int
|
||||||
if (code != null && code != 0) {
|
if (code != null && code != 0) {
|
||||||
TextSecurePreferences.setIsUsingFCM(context, false)
|
TextSecurePreferences.setIsUsingFCM(context, false)
|
||||||
@ -66,7 +66,7 @@ object PushNotificationAPI {
|
|||||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||||
val request = Request.Builder().url(url).post(body)
|
val request = Request.Builder().url(url).post(body)
|
||||||
retryIfNeeded(maxRetryCount) {
|
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
|
val code = json["code"] as? Int
|
||||||
if (code != null && code != 0) {
|
if (code != null && code != 0) {
|
||||||
TextSecurePreferences.setIsUsingFCM(context, true)
|
TextSecurePreferences.setIsUsingFCM(context, true)
|
||||||
@ -93,7 +93,7 @@ object PushNotificationAPI {
|
|||||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||||
val request = Request.Builder().url(url).post(body)
|
val request = Request.Builder().url(url).post(body)
|
||||||
retryIfNeeded(maxRetryCount) {
|
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
|
val code = json["code"] as? Int
|
||||||
if (code == null || code == 0) {
|
if (code == null || code == 0) {
|
||||||
Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${json["message"] as? String ?: "null"}.")
|
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` */
|
/* 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? {
|
fun blindedKeyPair(serverPublicKey: String, edKeyPair: KeyPair): KeyPair? {
|
||||||
// if (edKeyPair.publicKey.asBytes.size != Sign.PUBLICKEYBYTES ||
|
if (edKeyPair.publicKey.asBytes.size != Sign.PUBLICKEYBYTES ||
|
||||||
// edKeyPair.secretKey.asBytes.size != Sign.SECRETKEYBYTES) return null
|
edKeyPair.secretKey.asBytes.size != Sign.SECRETKEYBYTES) return null
|
||||||
val kBytes = generateBlindingFactor(serverPublicKey)
|
val kBytes = generateBlindingFactor(serverPublicKey)
|
||||||
val aBytes = generatePrivateKeyScalar(edKeyPair.secretKey.asBytes)
|
val aBytes = generatePrivateKeyScalar(edKeyPair.secretKey.asBytes)
|
||||||
// Generate the blinded key pair `ka`, `kA`
|
// Generate the blinded key pair `ka`, `kA`
|
||||||
@ -183,17 +183,17 @@ object SodiumUtilities {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val hexString
|
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");
|
STANDARD("05"), BLINDED("15"), UN_BLINDED("00");
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromValue(prefix: String): IdPrefix? = when(prefix) {
|
fun fromValue(rawValue: String): IdPrefix? = when(rawValue) {
|
||||||
STANDARD.prefix -> STANDARD
|
STANDARD.value -> STANDARD
|
||||||
BLINDED.prefix -> BLINDED
|
BLINDED.value -> BLINDED
|
||||||
UN_BLINDED.prefix -> UN_BLINDED
|
UN_BLINDED.value -> UN_BLINDED
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,58 @@
|
|||||||
package org.session.libsession.snode
|
package org.session.libsession.snode
|
||||||
|
|
||||||
|
import nl.komponents.kovenant.Deferred
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.all
|
import nl.komponents.kovenant.all
|
||||||
import nl.komponents.kovenant.deferred
|
import nl.komponents.kovenant.deferred
|
||||||
import nl.komponents.kovenant.functional.bind
|
import nl.komponents.kovenant.functional.bind
|
||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
import okhttp3.Request
|
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.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.AESGCM.EncryptionResult
|
||||||
import org.session.libsession.utilities.getBodyForOnionRequest
|
import org.session.libsession.utilities.getBodyForOnionRequest
|
||||||
import org.session.libsession.utilities.getHeadersForOnionRequest
|
import org.session.libsession.utilities.getHeadersForOnionRequest
|
||||||
import org.session.libsignal.crypto.getRandomElement
|
import org.session.libsignal.crypto.getRandomElement
|
||||||
import org.session.libsignal.crypto.getRandomElementOrNull
|
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.Broadcaster
|
||||||
import org.session.libsignal.utilities.HTTP
|
import org.session.libsignal.utilities.HTTP
|
||||||
import org.session.libsignal.database.LokiAPIDatabaseProtocol
|
import org.session.libsignal.utilities.JsonUtil
|
||||||
import java.util.*
|
import org.session.libsignal.utilities.Log
|
||||||
import kotlin.math.abs
|
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>
|
private typealias Path = List<Snode>
|
||||||
|
|
||||||
@ -96,7 +127,8 @@ object OnionRequestAPI {
|
|||||||
ThreadUtils.queue { // No need to block the shared context for this
|
ThreadUtils.queue { // No need to block the shared context for this
|
||||||
val url = "${snode.address}:${snode.port}/get_stats/v1"
|
val url = "${snode.address}:${snode.port}/get_stats/v1"
|
||||||
try {
|
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
|
val version = json["version"] as? String
|
||||||
if (version == null) { deferred.reject(Exception("Missing snode version.")); return@queue }
|
if (version == null) { deferred.reject(Exception("Missing snode version.")); return@queue }
|
||||||
if (version >= "2.0.7") {
|
if (version >= "2.0.7") {
|
||||||
@ -209,29 +241,33 @@ object OnionRequestAPI {
|
|||||||
}
|
}
|
||||||
OnionRequestAPI.guardSnodes = guardSnodes
|
OnionRequestAPI.guardSnodes = guardSnodes
|
||||||
fun getPath(paths: List<Path>): Path {
|
fun getPath(paths: List<Path>): Path {
|
||||||
if (snodeToExclude != null) {
|
return if (snodeToExclude != null) {
|
||||||
return paths.filter { !it.contains(snodeToExclude) }.getRandomElement()
|
paths.filter { !it.contains(snodeToExclude) }.getRandomElement()
|
||||||
} else {
|
} else {
|
||||||
return paths.getRandomElement()
|
paths.getRandomElement()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (paths.count() >= targetPathCount) {
|
when {
|
||||||
|
paths.count() >= targetPathCount -> {
|
||||||
return Promise.of(getPath(paths))
|
return Promise.of(getPath(paths))
|
||||||
} else if (paths.isNotEmpty()) {
|
}
|
||||||
if (paths.any { !it.contains(snodeToExclude) }) {
|
paths.isNotEmpty() -> {
|
||||||
|
return if (paths.any { !it.contains(snodeToExclude) }) {
|
||||||
buildPaths(paths) // Re-build paths in the background
|
buildPaths(paths) // Re-build paths in the background
|
||||||
return Promise.of(getPath(paths))
|
Promise.of(getPath(paths))
|
||||||
} else {
|
} else {
|
||||||
return buildPaths(paths).map { newPaths ->
|
buildPaths(paths).map { newPaths ->
|
||||||
getPath(newPaths)
|
getPath(newPaths)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else -> {
|
||||||
return buildPaths(listOf()).map { newPaths ->
|
return buildPaths(listOf()).map { newPaths ->
|
||||||
getPath(newPaths)
|
getPath(newPaths)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun dropGuardSnode(snode: Snode) {
|
private fun dropGuardSnode(snode: Snode) {
|
||||||
guardSnodes = guardSnodes.filter { it != snode }.toSet()
|
guardSnodes = guardSnodes.filter { it != snode }.toSet()
|
||||||
@ -270,7 +306,11 @@ object OnionRequestAPI {
|
|||||||
/**
|
/**
|
||||||
* Builds an onion around `payload` and returns the result.
|
* 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 guardSnode: Snode
|
||||||
lateinit var destinationSymmetricKey: ByteArray // Needed by LokiAPI to decrypt the response sent back by the destination
|
lateinit var destinationSymmetricKey: ByteArray // Needed by LokiAPI to decrypt the response sent back by the destination
|
||||||
lateinit var encryptionResult: EncryptionResult
|
lateinit var encryptionResult: EncryptionResult
|
||||||
@ -281,19 +321,19 @@ object OnionRequestAPI {
|
|||||||
return getPath(snodeToExclude).bind { 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 { r ->
|
OnionRequestEncryption.encryptPayloadForDestination(payload, destination, version).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
|
||||||
@Suppress("NAME_SHADOWING") var path = path
|
@Suppress("NAME_SHADOWING") var path = path
|
||||||
var rhs = destination
|
var rhs = destination
|
||||||
fun addLayer(): Promise<EncryptionResult, Exception> {
|
fun addLayer(): Promise<EncryptionResult, Exception> {
|
||||||
if (path.isEmpty()) {
|
return if (path.isEmpty()) {
|
||||||
return Promise.of(encryptionResult)
|
Promise.of(encryptionResult)
|
||||||
} 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 { r ->
|
OnionRequestEncryption.encryptHop(lhs, rhs, encryptionResult).bind { r ->
|
||||||
encryptionResult = r
|
encryptionResult = r
|
||||||
rhs = lhs
|
rhs = lhs
|
||||||
addLayer()
|
addLayer()
|
||||||
@ -308,15 +348,15 @@ object OnionRequestAPI {
|
|||||||
/**
|
/**
|
||||||
* Sends an onion request to `destination`. Builds new paths as needed.
|
* 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>()
|
val deferred = deferred<Map<*, *>, Exception>()
|
||||||
lateinit var guardSnode: Snode
|
lateinit var guardSnode: Snode
|
||||||
buildOnionForDestination(payload, destination).success { result ->
|
buildOnionForDestination(payload, destination, version).success { result ->
|
||||||
guardSnode = result.guardSnode
|
guardSnode = result.guardSnode
|
||||||
val url = "${guardSnode.address}:${guardSnode.port}/onion_req/v2"
|
val url = "${guardSnode.address}:${guardSnode.port}/onion_req/v2"
|
||||||
val finalEncryptionResult = result.finalEncryptionResult
|
val finalEncryptionResult = result.finalEncryptionResult
|
||||||
val onion = finalEncryptionResult.ciphertext
|
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.")
|
Log.d("Loki", "Approaching request size limit: ~${onion.count()} bytes.")
|
||||||
}
|
}
|
||||||
@Suppress("NAME_SHADOWING") val parameters = mapOf(
|
@Suppress("NAME_SHADOWING") val parameters = mapOf(
|
||||||
@ -331,49 +371,8 @@ object OnionRequestAPI {
|
|||||||
val destinationSymmetricKey = result.destinationSymmetricKey
|
val destinationSymmetricKey = result.destinationSymmetricKey
|
||||||
ThreadUtils.queue {
|
ThreadUtils.queue {
|
||||||
try {
|
try {
|
||||||
val json = HTTP.execute(HTTP.Verb.POST, url, body)
|
val response = HTTP.execute(HTTP.Verb.POST, url, body)
|
||||||
val base64EncodedIVAndCiphertext = json["result"] as? String ?: return@queue deferred.reject(Exception("Invalid JSON"))
|
handleResponse(response, destinationSymmetricKey, destination, version, deferred)
|
||||||
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)
|
|
||||||
}
|
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
deferred.reject(exception)
|
deferred.reject(exception)
|
||||||
}
|
}
|
||||||
@ -440,12 +439,13 @@ object OnionRequestAPI {
|
|||||||
/**
|
/**
|
||||||
* Sends an onion request to `snode`. Builds new paths as needed.
|
* 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(
|
val payload = mapOf(
|
||||||
"method" to method.rawValue,
|
"method" to method.rawValue,
|
||||||
"params" to parameters
|
"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) {
|
val error = when (exception) {
|
||||||
is HTTP.HTTPRequestFailedException -> SnodeAPI.handleSnodeError(exception.statusCode, exception.json, snode, publicKey)
|
is HTTP.HTTPRequestFailedException -> SnodeAPI.handleSnodeError(exception.statusCode, exception.json, snode, publicKey)
|
||||||
is HTTPRequestFailedAtDestinationException -> 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.
|
* `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> {
|
fun sendOnionRequest(request: Request, server: String, x25519PublicKey: String, version: Version = Version.V4): Promise<Map<*, *>, Exception> {
|
||||||
val headers = request.getHeadersForOnionRequest()
|
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 url = request.url()
|
||||||
val urlAsString = url.toString()
|
val urlAsString = url.toString()
|
||||||
val host = url.host()
|
val body = request.getBodyForOnionRequest() ?: "null"
|
||||||
val endpoint = when {
|
val endpoint = when {
|
||||||
server.count() < urlAsString.count() -> urlAsString.substringAfter(server).removePrefix("/")
|
server.count() < urlAsString.count() -> urlAsString.substringAfter(server).removePrefix("/")
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
val body = request.getBodyForOnionRequest() ?: "null"
|
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(
|
val payload = mapOf(
|
||||||
"body" to body,
|
"body" to body,
|
||||||
"endpoint" to endpoint,
|
"endpoint" to endpoint,
|
||||||
"method" to request.method(),
|
"method" to request.method(),
|
||||||
"headers" to headers
|
"headers" to headers
|
||||||
)
|
)
|
||||||
val destination = Destination.Server(host, target, x25519PublicKey, url.scheme(), url.port())
|
JsonUtil.toJson(payload).toByteArray()
|
||||||
return sendOnionRequest(destination, payload).recover { exception ->
|
}
|
||||||
Log.d("Loki", "Couldn't reach server: $urlAsString due to error: $exception.")
|
}
|
||||||
throw exception
|
|
||||||
|
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
|
// 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.Promise
|
||||||
import nl.komponents.kovenant.deferred
|
import nl.komponents.kovenant.deferred
|
||||||
|
import org.session.libsession.snode.OnionRequestAPI.Destination
|
||||||
import org.session.libsession.utilities.AESGCM
|
import org.session.libsession.utilities.AESGCM
|
||||||
import org.session.libsession.utilities.AESGCM.EncryptionResult
|
import org.session.libsession.utilities.AESGCM.EncryptionResult
|
||||||
import org.session.libsignal.utilities.toHexString
|
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.
|
* 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>()
|
val deferred = deferred<EncryptionResult, Exception>()
|
||||||
ThreadUtils.queue {
|
ThreadUtils.queue {
|
||||||
try {
|
try {
|
||||||
|
val plaintext = if (version == OnionRequestAPI.Version.V4) {
|
||||||
|
payload
|
||||||
|
} else {
|
||||||
// Wrapping isn't needed for file server or open group onion requests
|
// Wrapping isn't needed for file server or open group onion requests
|
||||||
when (destination) {
|
when (destination) {
|
||||||
is OnionRequestAPI.Destination.Snode -> {
|
is Destination.Snode -> encode(payload, mapOf("headers" to ""))
|
||||||
val snodeX25519PublicKey = destination.snode.publicKeySet!!.x25519Key
|
is Destination.Server -> payload
|
||||||
val payloadAsData = JsonUtil.toJson(payload).toByteArray()
|
}
|
||||||
val plaintext = encode(payloadAsData, mapOf( "headers" to "" ))
|
}
|
||||||
val result = AESGCM.encrypt(plaintext, snodeX25519PublicKey)
|
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)
|
deferred.resolve(result)
|
||||||
}
|
|
||||||
is OnionRequestAPI.Destination.Server -> {
|
|
||||||
val plaintext = JsonUtil.toJson(payload).toByteArray()
|
|
||||||
val result = AESGCM.encrypt(plaintext, destination.x25519PublicKey)
|
|
||||||
deferred.resolve(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
deferred.reject(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.
|
* 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>()
|
val deferred = deferred<EncryptionResult, Exception>()
|
||||||
ThreadUtils.queue {
|
ThreadUtils.queue {
|
||||||
try {
|
try {
|
||||||
val payload: MutableMap<String, Any>
|
val payload: MutableMap<String, Any> = when (rhs) {
|
||||||
when (rhs) {
|
is Destination.Snode -> {
|
||||||
is OnionRequestAPI.Destination.Snode -> {
|
mutableMapOf( "destination" to rhs.snode.publicKeySet!!.ed25519Key )
|
||||||
payload = mutableMapOf( "destination" to rhs.snode.publicKeySet!!.ed25519Key )
|
|
||||||
}
|
}
|
||||||
is OnionRequestAPI.Destination.Server -> {
|
is Destination.Server -> {
|
||||||
payload = mutableMapOf(
|
mutableMapOf(
|
||||||
"host" to rhs.host,
|
"host" to rhs.host,
|
||||||
"target" to rhs.target,
|
"target" to rhs.target,
|
||||||
"method" to "POST",
|
"method" to "POST",
|
||||||
@ -80,13 +84,12 @@ object OnionRequestEncryption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
payload["ephemeral_key"] = previousEncryptionResult.ephemeralPublicKey.toHexString()
|
payload["ephemeral_key"] = previousEncryptionResult.ephemeralPublicKey.toHexString()
|
||||||
val x25519PublicKey: String
|
val x25519PublicKey = when (lhs) {
|
||||||
when (lhs) {
|
is Destination.Snode -> {
|
||||||
is OnionRequestAPI.Destination.Snode -> {
|
lhs.snode.publicKeySet!!.x25519Key
|
||||||
x25519PublicKey = lhs.snode.publicKeySet!!.x25519Key
|
|
||||||
}
|
}
|
||||||
is OnionRequestAPI.Destination.Server -> {
|
is Destination.Server -> {
|
||||||
x25519PublicKey = lhs.x25519PublicKey
|
lhs.x25519PublicKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val plaintext = encode(previousEncryptionResult.ciphertext, payload)
|
val plaintext = encode(previousEncryptionResult.ciphertext, payload)
|
||||||
|
@ -74,16 +74,23 @@ object SnodeAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Internal API
|
// 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"
|
val url = "${snode.address}:${snode.port}/storage_rpc/v1"
|
||||||
if (useOnionRequests) {
|
if (useOnionRequests) {
|
||||||
return OnionRequestAPI.sendOnionRequest(method, parameters, snode, publicKey)
|
return OnionRequestAPI.sendOnionRequest(method, parameters, snode, version, publicKey)
|
||||||
} else {
|
} else {
|
||||||
val deferred = deferred<Map<*, *>, Exception>()
|
val deferred = deferred<Map<*, *>, Exception>()
|
||||||
ThreadUtils.queue {
|
ThreadUtils.queue {
|
||||||
val payload = mapOf( "method" to method.rawValue, "params" to parameters )
|
val payload = mapOf( "method" to method.rawValue, "params" to parameters )
|
||||||
try {
|
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)
|
deferred.resolve(json)
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
val httpRequestFailedException = exception as? HTTP.HTTPRequestFailedException
|
val httpRequestFailedException = exception as? HTTP.HTTPRequestFailedException
|
||||||
@ -117,7 +124,12 @@ object SnodeAPI {
|
|||||||
deferred<Snode, Exception>()
|
deferred<Snode, Exception>()
|
||||||
ThreadUtils.queue {
|
ThreadUtils.queue {
|
||||||
try {
|
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 intermediate = json["result"] as? Map<*, *>
|
||||||
val rawSnodes = intermediate?.get("service_node_states") as? List<*>
|
val rawSnodes = intermediate?.get("service_node_states") as? List<*>
|
||||||
if (rawSnodes != null) {
|
if (rawSnodes != null) {
|
||||||
@ -192,7 +204,7 @@ object SnodeAPI {
|
|||||||
val promises = (1..validationCount).map {
|
val promises = (1..validationCount).map {
|
||||||
getRandomSnode().bind { snode ->
|
getRandomSnode().bind { snode ->
|
||||||
retryIfNeeded(maxRetryCount) {
|
retryIfNeeded(maxRetryCount) {
|
||||||
invoke(Snode.Method.OxenDaemonRPCCall, snode, null, parameters)
|
invoke(Snode.Method.OxenDaemonRPCCall, snode, parameters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -268,7 +280,7 @@ object SnodeAPI {
|
|||||||
} else {
|
} else {
|
||||||
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, parameters, publicKey)
|
||||||
}.map {
|
}.map {
|
||||||
parseSnodes(it).toSet()
|
parseSnodes(it).toSet()
|
||||||
}.success {
|
}.success {
|
||||||
@ -299,7 +311,7 @@ object SnodeAPI {
|
|||||||
// "pubkey_ed25519" to ed25519PublicKey,
|
// "pubkey_ed25519" to ed25519PublicKey,
|
||||||
// "signature" to Base64.encodeBytes(signature)
|
// "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 {
|
fun getMessages(publicKey: String): MessageListPromise {
|
||||||
@ -311,7 +323,7 @@ object SnodeAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getNetworkTime(snode: Snode): Promise<Pair<Snode,Long>, Exception> {
|
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
|
val timestamp = rawResponse["timestamp"] as? Long ?: -1
|
||||||
snode to timestamp
|
snode to timestamp
|
||||||
}
|
}
|
||||||
@ -323,7 +335,7 @@ object SnodeAPI {
|
|||||||
getTargetSnodes(destination).map { swarm ->
|
getTargetSnodes(destination).map { swarm ->
|
||||||
swarm.map { snode ->
|
swarm.map { snode ->
|
||||||
val parameters = message.toJSON()
|
val parameters = message.toJSON()
|
||||||
invoke(Snode.Method.SendMessage, snode, destination, parameters)
|
invoke(Snode.Method.SendMessage, snode, parameters, destination)
|
||||||
}.toSet()
|
}.toSet()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -345,7 +357,7 @@ object SnodeAPI {
|
|||||||
"messages" to serverHashes,
|
"messages" to serverHashes,
|
||||||
"signature" to Base64.encodeBytes(signature)
|
"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 swarms = rawResponse["swarm"] as? Map<String, Any> ?: return@map mapOf()
|
||||||
val result = swarms.mapNotNull { (hexSnodePublicKey, rawJSON) ->
|
val result = swarms.mapNotNull { (hexSnodePublicKey, rawJSON) ->
|
||||||
val json = rawJSON as? Map<String, Any> ?: return@mapNotNull null
|
val json = rawJSON as? Map<String, Any> ?: return@mapNotNull null
|
||||||
@ -415,7 +427,7 @@ object SnodeAPI {
|
|||||||
"timestamp" to timestamp,
|
"timestamp" to timestamp,
|
||||||
"signature" to Base64.encodeBytes(signature)
|
"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)
|
rawResponse -> parseDeletions(userPublicKey, timestamp, rawResponse)
|
||||||
}.fail { e ->
|
}.fail { e ->
|
||||||
Log.e("Loki", "Failed to clear data", 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
|
400, 500, 502, 503 -> { // Usually indicates that the snode isn't up to date
|
||||||
handleBadSnode()
|
handleBadSnode()
|
||||||
}
|
}
|
||||||
425 -> {
|
406 -> {
|
||||||
Log.d("Loki", "The user's clock is out of sync with the service node network.")
|
Log.d("Loki", "The user's clock is out of sync with the service node network.")
|
||||||
broadcaster.broadcast("clockOutOfSync")
|
broadcaster.broadcast("clockOutOfSync")
|
||||||
return Error.ClockOutOfSync
|
return Error.ClockOutOfSync
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
package org.session.libsession.utilities
|
package org.session.libsession.utilities
|
||||||
|
|
||||||
import okhttp3.HttpUrl
|
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.utilities.Log
|
||||||
import org.session.libsignal.messages.SignalServiceAttachment
|
|
||||||
import java.io.*
|
import java.io.*
|
||||||
|
|
||||||
object DownloadUtilities {
|
object DownloadUtilities {
|
||||||
@ -37,7 +36,7 @@ object DownloadUtilities {
|
|||||||
val url = HttpUrl.parse(urlAsString)!!
|
val url = HttpUrl.parse(urlAsString)!!
|
||||||
val fileID = url.pathSegments().last()
|
val fileID = url.pathSegments().last()
|
||||||
try {
|
try {
|
||||||
FileServerAPIV2.download(fileID.toLong()).get().let {
|
FileServerApi.download(fileID.toLong()).get().let {
|
||||||
outputStream.write(it)
|
outputStream.write(it)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -4,7 +4,7 @@ import android.content.Context
|
|||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.deferred
|
import nl.komponents.kovenant.deferred
|
||||||
import okio.Buffer
|
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.streams.ProfileCipherOutputStream
|
||||||
import org.session.libsignal.utilities.ProfileAvatarData
|
import org.session.libsignal.utilities.ProfileAvatarData
|
||||||
import org.session.libsignal.streams.DigestingRequestBody
|
import org.session.libsignal.streams.DigestingRequestBody
|
||||||
@ -30,13 +30,13 @@ object ProfilePictureUtilities {
|
|||||||
var id: Long = 0
|
var id: Long = 0
|
||||||
try {
|
try {
|
||||||
id = retryIfNeeded(4) {
|
id = retryIfNeeded(4) {
|
||||||
FileServerAPIV2.upload(data)
|
FileServerApi.upload(data)
|
||||||
}.get()
|
}.get()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
deferred.reject(e)
|
deferred.reject(e)
|
||||||
}
|
}
|
||||||
TextSecurePreferences.setLastProfilePictureUpload(context, Date().time)
|
TextSecurePreferences.setLastProfilePictureUpload(context, Date().time)
|
||||||
val url = "${FileServerAPIV2.server}/files/$id"
|
val url = "${FileServerApi.server}/files/$id"
|
||||||
TextSecurePreferences.setProfilePictureURL(context, url)
|
TextSecurePreferences.setProfilePictureURL(context, url)
|
||||||
deferred.resolve(Unit)
|
deferred.resolve(Unit)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package org.session.libsignal.utilities
|
package org.session.libsignal.utilities
|
||||||
|
|
||||||
import okhttp3.*
|
import okhttp3.MediaType
|
||||||
import java.lang.IllegalStateException
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import okhttp3.Response
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -68,26 +71,26 @@ object HTTP {
|
|||||||
/**
|
/**
|
||||||
* Sync. Don't call from the main thread.
|
* 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)
|
return execute(verb = verb, url = url, body = null, timeout = timeout, useSeedNodeConnection = useSeedNodeConnection)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync. Don't call from the main thread.
|
* 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<*, *> {
|
fun execute(verb: Verb, url: String, parameters: Map<String, Any>?, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): ByteArray {
|
||||||
if (parameters != null) {
|
return if (parameters != null) {
|
||||||
val body = JsonUtil.toJson(parameters).toByteArray()
|
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 {
|
} 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.
|
* 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)
|
val request = Request.Builder().url(url)
|
||||||
.removeHeader("User-Agent").addHeader("User-Agent", "WhatsApp") // Set a fake value
|
.removeHeader("User-Agent").addHeader("User-Agent", "WhatsApp") // Set a fake value
|
||||||
.removeHeader("Accept-Language").addHeader("Accept-Language", "en-us") // 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
|
lateinit var response: Response
|
||||||
try {
|
try {
|
||||||
val connection: OkHttpClient
|
val connection = if (timeout != HTTP.timeout) { // Custom timeout
|
||||||
if (timeout != HTTP.timeout) { // Custom timeout
|
|
||||||
if (useSeedNodeConnection) {
|
if (useSeedNodeConnection) {
|
||||||
throw IllegalStateException("Setting a custom timeout is only allowed for requests to snodes.")
|
throw IllegalStateException("Setting a custom timeout is only allowed for requests to snodes.")
|
||||||
}
|
}
|
||||||
connection = getDefaultConnection(timeout)
|
getDefaultConnection(timeout)
|
||||||
} else {
|
} else {
|
||||||
connection = if (useSeedNodeConnection) seedNodeConnection else defaultConnection
|
if (useSeedNodeConnection) seedNodeConnection else defaultConnection
|
||||||
}
|
}
|
||||||
response = connection.newCall(request.build()).execute()
|
response = connection.newCall(request.build()).execute()
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
@ -118,14 +120,9 @@ object HTTP {
|
|||||||
// Override the actual error so that we can correctly catch failed requests in OnionRequestAPI
|
// Override the actual error so that we can correctly catch failed requests in OnionRequestAPI
|
||||||
throw HTTPRequestFailedException(0, null)
|
throw HTTPRequestFailedException(0, null)
|
||||||
}
|
}
|
||||||
when (val statusCode = response.code()) {
|
return when (val statusCode = response.code()) {
|
||||||
200 -> {
|
200 -> {
|
||||||
val bodyAsString = response.body()?.string() ?: throw Exception("An error occurred.")
|
response.body()?.bytes() ?: throw Exception("An error occurred.")
|
||||||
try {
|
|
||||||
return JsonUtil.fromJson(bodyAsString, Map::class.java)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
return mapOf( "result" to bodyAsString)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Log.d("Loki", "${verb.rawValue} request to $url failed with status code: $statusCode.")
|
Log.d("Loki", "${verb.rawValue} request to $url failed with status code: $statusCode.")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user