Merge branch 'dev' of https://github.com/oxen-io/session-android into client-side-nickname

This commit is contained in:
Ryan ZHAO 2021-05-13 14:25:19 +10:00
commit 7b8a025947
28 changed files with 143 additions and 84 deletions

View File

@ -8,7 +8,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.2'
classpath 'com.android.tools.build:gradle:4.1.3'
classpath files('libs/gradle-witness.jar')
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"

View File

@ -5,7 +5,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.utilities.Data;
import org.session.libsignal.utilities.logging.Log;
import java.util.LinkedList;

View File

@ -7,7 +7,7 @@ import androidx.annotation.WorkerThread;
import com.annimon.stream.Stream;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.utilities.Data;
import org.thoughtcrime.securesms.jobmanager.persistence.ConstraintSpec;
import org.thoughtcrime.securesms.jobmanager.persistence.DependencySpec;
import org.thoughtcrime.securesms.jobmanager.persistence.FullSpec;

View File

@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.jobmanager;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.utilities.Data;
import java.util.HashMap;
import java.util.Map;

View File

@ -5,7 +5,7 @@ import android.content.Intent;
import android.os.Build;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.utilities.Data;
import org.thoughtcrime.securesms.jobmanager.impl.DefaultExecutorFactory;
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
import org.thoughtcrime.securesms.jobmanager.persistence.JobStorage;

View File

@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.jobmanager.impl;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.utilities.Data;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsignal.utilities.JsonUtil;

View File

@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.jobs;
import android.graphics.Bitmap;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.utilities.Data;
import org.session.libsession.utilities.DownloadUtilities;
import org.session.libsignal.service.api.crypto.AttachmentCipherInputStream;
import org.thoughtcrime.securesms.database.DatabaseFactory;

View File

@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.utilities.Data;
import org.session.libsignal.utilities.externalstorage.NoExternalStorageException;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.session.libsignal.utilities.logging.Log;

View File

@ -7,7 +7,7 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.avatars.AvatarHelper;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.utilities.Data;
import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.DownloadUtilities;

View File

@ -18,7 +18,7 @@ package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.utilities.Data;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.session.libsignal.utilities.logging.Log;

View File

@ -13,7 +13,7 @@ import androidx.annotation.Nullable;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.utilities.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.session.libsignal.utilities.logging.Log;

View File

@ -5,7 +5,7 @@ import android.os.Build
import org.session.libsignal.utilities.logging.Log
import androidx.annotation.RequiresApi
import org.greenrobot.eventbus.EventBus
import org.session.libsession.messaging.jobs.Data
import org.session.libsession.messaging.utilities.Data
import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachmentAudioExtras

View File

@ -4,6 +4,7 @@ import android.content.ContentValues
import android.content.Context
import net.sqlcipher.Cursor
import org.session.libsession.messaging.jobs.*
import org.session.libsession.messaging.utilities.Data
import org.session.libsignal.utilities.logging.Log
import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper

View File

@ -1,7 +1,7 @@
package org.thoughtcrime.securesms.jobmanager.impl;
import org.junit.Test;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.utilities.Data;
import org.session.libsession.utilities.Util;
import java.io.IOException;

View File

@ -6,7 +6,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.2'
classpath 'com.android.tools.build:gradle:4.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "com.google.gms:google-services:4.3.4"
classpath files('libs/gradle-witness.jar')

View File

@ -5,6 +5,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.file_server.FileServerAPI
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState
import org.session.libsession.messaging.utilities.Data
import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsession.utilities.DownloadUtilities
import org.session.libsignal.service.api.crypto.AttachmentCipherInputStream

View File

@ -8,6 +8,7 @@ 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.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.Data
import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsignal.service.api.crypto.AttachmentCipherOutputStream
import org.session.libsignal.service.api.messages.SignalServiceAttachmentStream

View File

@ -1,5 +1,7 @@
package org.session.libsession.messaging.jobs
import org.session.libsession.messaging.utilities.Data
interface Job {
var delegate: JobDelegate?
var id: String?

View File

@ -4,6 +4,7 @@ import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.deferred
import org.session.libsession.messaging.sending_receiving.MessageReceiver
import org.session.libsession.messaging.sending_receiving.handle
import org.session.libsession.messaging.utilities.Data
import org.session.libsignal.utilities.logging.Log
class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val openGroupMessageServerID: Long? = null, val openGroupID: String? = null) : Job {

View File

@ -9,6 +9,7 @@ import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.Data
import org.session.libsignal.utilities.logging.Log
class MessageSendJob(val message: Message, val destination: Destination) : Job {

View File

@ -9,6 +9,7 @@ import okhttp3.Request
import okhttp3.RequestBody
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI
import org.session.libsession.messaging.utilities.Data
import org.session.libsession.snode.SnodeMessage
import org.session.libsession.snode.OnionRequestAPI

View File

@ -1,5 +1,7 @@
package org.session.libsession.messaging.jobs
import org.session.libsession.messaging.utilities.Data
class SessionJobInstantiator(private val jobFactories: Map<String, Job.Factory<out Job>>) {
fun instantiate(jobFactoryKey: String, data: Data): Job? {

View File

@ -1,4 +1,4 @@
package org.session.libsession.messaging.jobs;
package org.session.libsession.messaging.utilities;
import android.os.Parcelable;
@ -12,11 +12,7 @@ import org.session.libsession.utilities.ParcelableUtil;
import java.util.HashMap;
import java.util.Map;
// Introduce a dedicated Map<String, byte[]> field specifically for parcelable needs.
public class Data {
public static final Data EMPTY = new Data.Builder().build();
@JsonProperty private final Map<String, String> strings;
@JsonProperty private final Map<String, String[]> stringArrays;
@JsonProperty private final Map<String, Integer> integers;
@ -31,20 +27,23 @@ public class Data {
@JsonProperty private final Map<String, boolean[]> booleanArrays;
@JsonProperty private final Map<String, byte[]> byteArrays;
public Data(@JsonProperty("strings") @NonNull Map<String, String> strings,
@JsonProperty("stringArrays") @NonNull Map<String, String[]> stringArrays,
@JsonProperty("integers") @NonNull Map<String, Integer> integers,
@JsonProperty("integerArrays") @NonNull Map<String, int[]> integerArrays,
@JsonProperty("longs") @NonNull Map<String, Long> longs,
@JsonProperty("longArrays") @NonNull Map<String, long[]> longArrays,
@JsonProperty("floats") @NonNull Map<String, Float> floats,
@JsonProperty("floatArrays") @NonNull Map<String, float[]> floatArrays,
@JsonProperty("doubles") @NonNull Map<String, Double> doubles,
@JsonProperty("doubleArrays") @NonNull Map<String, double[]> doubleArrays,
@JsonProperty("booleans") @NonNull Map<String, Boolean> booleans,
@JsonProperty("booleanArrays") @NonNull Map<String, boolean[]> booleanArrays,
@JsonProperty("byteArrays") @NonNull Map<String, byte[]> byteArrays)
{
public static final Data EMPTY = new Data.Builder().build();
public Data(
@JsonProperty("strings") @NonNull Map<String, String> strings,
@JsonProperty("stringArrays") @NonNull Map<String, String[]> stringArrays,
@JsonProperty("integers") @NonNull Map<String, Integer> integers,
@JsonProperty("integerArrays") @NonNull Map<String, int[]> integerArrays,
@JsonProperty("longs") @NonNull Map<String, Long> longs,
@JsonProperty("longArrays") @NonNull Map<String, long[]> longArrays,
@JsonProperty("floats") @NonNull Map<String, Float> floats,
@JsonProperty("floatArrays") @NonNull Map<String, float[]> floatArrays,
@JsonProperty("doubles") @NonNull Map<String, Double> doubles,
@JsonProperty("doubleArrays") @NonNull Map<String, double[]> doubleArrays,
@JsonProperty("booleans") @NonNull Map<String, Boolean> booleans,
@JsonProperty("booleanArrays") @NonNull Map<String, boolean[]> booleanArrays,
@JsonProperty("byteArrays") @NonNull Map<String, byte[]> byteArrays
) {
this.strings = strings;
this.stringArrays = stringArrays;
this.integers = integers;
@ -75,6 +74,7 @@ public class Data {
}
public boolean hasStringArray(@NonNull String key) {
return stringArrays.containsKey(key);
}
@ -100,6 +100,7 @@ public class Data {
}
public boolean hasIntegerArray(@NonNull String key) {
return integerArrays.containsKey(key);
}
@ -110,6 +111,7 @@ public class Data {
}
public boolean hasLong(@NonNull String key) {
return longs.containsKey(key);
}
@ -125,6 +127,7 @@ public class Data {
}
public boolean hasLongArray(@NonNull String key) {
return longArrays.containsKey(key);
}
@ -135,6 +138,7 @@ public class Data {
}
public boolean hasFloat(@NonNull String key) {
return floats.containsKey(key);
}
@ -150,6 +154,7 @@ public class Data {
}
public boolean hasFloatArray(@NonNull String key) {
return floatArrays.containsKey(key);
}
@ -160,6 +165,7 @@ public class Data {
}
public boolean hasDouble(@NonNull String key) {
return doubles.containsKey(key);
}
@ -175,6 +181,7 @@ public class Data {
}
public boolean hasDoubleArray(@NonNull String key) {
return floatArrays.containsKey(key);
}
@ -185,6 +192,7 @@ public class Data {
}
public boolean hasBoolean(@NonNull String key) {
return booleans.containsKey(key);
}
@ -200,6 +208,7 @@ public class Data {
}
public boolean hasBooleanArray(@NonNull String key) {
return booleanArrays.containsKey(key);
}
@ -209,6 +218,8 @@ public class Data {
return booleanArrays.get(key);
}
public boolean hasByteArray(@NonNull String key) {
return byteArrays.containsKey(key);
}
@ -218,6 +229,8 @@ public class Data {
return byteArrays.get(key);
}
public boolean hasParcelable(@NonNull String key) {
return byteArrays.containsKey(key);
}
@ -228,6 +241,8 @@ public class Data {
return ParcelableUtil.unmarshall(bytes, creator);
}
private void throwIfAbsent(@NonNull Map map, @NonNull String key) {
if (!map.containsKey(key)) {
throw new IllegalStateException("Tried to retrieve a value with key '" + key + "', but it wasn't present.");
@ -236,7 +251,6 @@ public class Data {
public static class Builder {
private final Map<String, String> strings = new HashMap<>();
private final Map<String, String[]> stringArrays = new HashMap<>();
private final Map<String, Integer> integers = new HashMap<>();
@ -323,19 +337,21 @@ public class Data {
}
public Data build() {
return new Data(strings,
stringArrays,
integers,
integerArrays,
longs,
longArrays,
floats,
floatArrays,
doubles,
doubleArrays,
booleans,
booleanArrays,
byteArrays);
return new Data(
strings,
stringArrays,
integers,
integerArrays,
longs,
longArrays,
floats,
floatArrays,
doubles,
doubleArrays,
booleans,
booleanArrays,
byteArrays
);
}
}
@ -343,5 +359,4 @@ public class Data {
@NonNull String serialize(@NonNull Data data);
@NonNull Data deserialize(@NonNull String serialized);
}
}
}

View File

@ -53,11 +53,11 @@ object OnionRequestAPI {
/**
* The number of times a path can fail before it's replaced.
*/
private const val pathFailureThreshold = 1
private const val pathFailureThreshold = 3
/**
* The number of times a snode can fail before it's replaced.
*/
private const val snodeFailureThreshold = 1
private const val snodeFailureThreshold = 3
/**
* The number of guard snodes required to maintain `targetPathCount` paths.
*/
@ -93,7 +93,7 @@ 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)
val json = HTTP.execute(HTTP.Verb.GET, url, 3)
val version = json["version"] as? String
if (version == null) { deferred.reject(Exception("Missing snode version.")); return@queue }
if (version >= "2.0.7") {
@ -463,7 +463,6 @@ object OnionRequestAPI {
"method" to request.method(),
"headers" to headers
)
url.isHttps
val destination = Destination.Server(host, target, x25519PublicKey, url.scheme(), url.port())
return sendOnionRequest(destination, payload, isJSONRequired).recover { exception ->
Log.d("Loki", "Couldn't reach server: $urlAsString due to error: $exception.")

View File

@ -71,11 +71,11 @@ object OnionRequestEncryption {
}
is OnionRequestAPI.Destination.Server -> {
payload = mutableMapOf(
"host" to rhs.host,
"target" to rhs.target,
"method" to "POST",
"protocol" to rhs.scheme,
"port" to rhs.port
"host" to rhs.host,
"target" to rhs.target,
"method" to "POST",
"protocol" to rhs.scheme,
"port" to rhs.port
)
}
}

View File

@ -34,7 +34,7 @@ object SnodeAPI {
// Settings
private val maxRetryCount = 6
private val minimumSnodePoolCount = 12
private val minimumSwarmSnodeCount = 2
private val minimumSwarmSnodeCount = 3
// Use port 4433 if the API level can handle the network security configuration and enforce pinned certificates
private val seedNodePort = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) 443 else 4433
private val seedNodePool by lazy {
@ -44,7 +44,7 @@ object SnodeAPI {
setOf( "https://storage.seed1.loki.network:$seedNodePort ", "https://storage.seed3.loki.network:$seedNodePort ", "https://public.loki.foundation:$seedNodePort" )
}
}
private val snodeFailureThreshold = 4
private val snodeFailureThreshold = 3
private val targetSwarmSnodeCount = 2
private val useOnionRequests = true
@ -252,19 +252,20 @@ object SnodeAPI {
private fun removeDuplicates(publicKey: String, rawMessages: List<*>): List<*> {
val receivedMessageHashValues = database.getReceivedMessageHashValues(publicKey)?.toMutableSet() ?: mutableSetOf()
return rawMessages.filter { rawMessage ->
val result = rawMessages.filter { rawMessage ->
val rawMessageAsJSON = rawMessage as? Map<*, *>
val hashValue = rawMessageAsJSON?.get("hash") as? String
if (hashValue != null) {
val isDuplicate = receivedMessageHashValues.contains(hashValue)
receivedMessageHashValues.add(hashValue)
database.setReceivedMessageHashValues(publicKey, receivedMessageHashValues)
!isDuplicate
} else {
Log.d("Loki", "Missing hash value for message: ${rawMessage?.prettifiedDescription()}.")
false
}
}
database.setReceivedMessageHashValues(publicKey, receivedMessageHashValues)
return result
}
private fun parseEnvelopes(rawMessages: List<*>): List<SignalServiceProtos.Envelope> {
@ -305,7 +306,7 @@ object SnodeAPI {
}
}
when (statusCode) {
400, 500, 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()
}
406 -> {
@ -316,8 +317,20 @@ object SnodeAPI {
421 -> {
// The snode isn't associated with the given public key anymore
if (publicKey != null) {
Log.d("Loki", "Invalidating swarm for: $publicKey.")
dropSnodeFromSwarmIfNeeded(snode, publicKey)
fun invalidateSwarm() {
Log.d("Loki", "Invalidating swarm for: $publicKey.")
dropSnodeFromSwarmIfNeeded(snode, publicKey)
}
if (json != null) {
val snodes = parseSnodes(json)
if (snodes.isNotEmpty()) {
database.setSwarm(publicKey, snodes.toSet())
} else {
invalidateSwarm()
}
} else {
invalidateSwarm()
}
} else {
Log.d("Loki", "Got a 421 without an associated public key.")
}

View File

@ -3,23 +3,33 @@ package org.session.libsession.snode
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
data class SnodeMessage(
// The hex encoded public key of the recipient.
/**
* The hex encoded public key of the recipient.
*/
val recipient: String,
// The content of the message.
/**
* The content of the message.
*/
val data: String,
// The time to live for the message in milliseconds.
/**
* The time to live for the message in milliseconds.
*/
val ttl: Long,
// When the proof of work was calculated.
/**
* When the proof of work was calculated.
*
* **Note:** Expressed as milliseconds since 00:00:00 UTC on 1 January 1970.
*/
val timestamp: Long
) {
internal fun toJSON(): Map<String, String> {
return mapOf(
"pubKey" to if (SnodeAPI.useTestnet) recipient.removing05PrefixIfNeeded() else recipient,
"data" to data,
"ttl" to ttl.toString(),
"timestamp" to timestamp.toString(),
"nonce" to ""
"data" to data,
"ttl" to ttl.toString(),
"timestamp" to timestamp.toString(),
"nonce" to ""
)
}
}

View File

@ -3,6 +3,7 @@ package org.session.libsignal.service.loki
import okhttp3.*
import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.utilities.JsonUtil
import java.lang.IllegalStateException
import java.security.SecureRandom
import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit
@ -25,9 +26,7 @@ object HTTP {
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authorizationType: String?) { }
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authorizationType: String?) { }
override fun getAcceptedIssuers(): Array<X509Certificate> {
return arrayOf()
}
override fun getAcceptedIssuers(): Array<X509Certificate> { return arrayOf() }
}
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, arrayOf( trustManager ), SecureRandom())
@ -40,7 +39,7 @@ object HTTP {
.build()
}
private const val timeout: Long = 20
private const val timeout: Long = 10
class HTTPRequestFailedException(val statusCode: Int, val json: Map<*, *>?)
: kotlin.Exception("HTTP request failed with status code $statusCode.")
@ -52,26 +51,26 @@ object HTTP {
/**
* Sync. Don't call from the main thread.
*/
fun execute(verb: Verb, url: String, useSeedNodeConnection: Boolean = false): Map<*, *> {
return execute(verb = verb, url = url, body = null, useSeedNodeConnection = useSeedNodeConnection)
fun execute(verb: Verb, url: String, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): Map<*, *> {
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>?, useSeedNodeConnection: Boolean = false): Map<*, *> {
fun execute(verb: Verb, url: String, parameters: Map<String, Any>?, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): Map<*, *> {
if (parameters != null) {
val body = JsonUtil.toJson(parameters).toByteArray()
return execute(verb = verb, url = url, body = body, useSeedNodeConnection = useSeedNodeConnection)
return execute(verb = verb, url = url, body = body, timeout = timeout, useSeedNodeConnection = useSeedNodeConnection)
} else {
return execute(verb = verb, url = url, body = null, useSeedNodeConnection = useSeedNodeConnection)
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, body: ByteArray?, useSeedNodeConnection: Boolean = false): Map<*, *> {
fun execute(verb: Verb, url: String, body: ByteArray?, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): Map<*, *> {
val request = Request.Builder().url(url)
when (verb) {
Verb.GET -> request.get()
@ -85,7 +84,20 @@ object HTTP {
}
lateinit var response: Response
try {
val connection = if (useSeedNodeConnection) seedNodeConnection else defaultConnection
val connection: OkHttpClient
if (timeout != HTTP.timeout) { // Custom timeout
if (useSeedNodeConnection) {
throw IllegalStateException("Setting a custom timeout is only allowed for requests to snodes.")
}
connection = OkHttpClient()
.newBuilder()
.connectTimeout(timeout, TimeUnit.SECONDS)
.readTimeout(timeout, TimeUnit.SECONDS)
.writeTimeout(timeout, TimeUnit.SECONDS)
.build()
} else {
connection = if (useSeedNodeConnection) seedNodeConnection else defaultConnection
}
response = connection.newCall(request.build()).execute()
} catch (exception: Exception) {
Log.d("Loki", "${verb.rawValue} request to $url failed due to error: ${exception.localizedMessage}.")