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() jcenter()
} }
dependencies { 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 files('libs/gradle-witness.jar')
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$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.Nullable;
import androidx.annotation.WorkerThread; 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 org.session.libsignal.utilities.logging.Log;
import java.util.LinkedList; import java.util.LinkedList;

View File

@ -7,7 +7,7 @@ import androidx.annotation.WorkerThread;
import com.annimon.stream.Stream; 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.ConstraintSpec;
import org.thoughtcrime.securesms.jobmanager.persistence.DependencySpec; import org.thoughtcrime.securesms.jobmanager.persistence.DependencySpec;
import org.thoughtcrime.securesms.jobmanager.persistence.FullSpec; import org.thoughtcrime.securesms.jobmanager.persistence.FullSpec;

View File

@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.jobmanager;
import androidx.annotation.NonNull; 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.HashMap;
import java.util.Map; import java.util.Map;

View File

@ -5,7 +5,7 @@ import android.content.Intent;
import android.os.Build; import android.os.Build;
import androidx.annotation.NonNull; 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.DefaultExecutorFactory;
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer; import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
import org.thoughtcrime.securesms.jobmanager.persistence.JobStorage; import org.thoughtcrime.securesms.jobmanager.persistence.JobStorage;

View File

@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.jobmanager.impl;
import androidx.annotation.NonNull; 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.logging.Log;
import org.session.libsignal.utilities.JsonUtil; import org.session.libsignal.utilities.JsonUtil;

View File

@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.jobs;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import androidx.annotation.NonNull; 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.libsession.utilities.DownloadUtilities;
import org.session.libsignal.service.api.crypto.AttachmentCipherInputStream; import org.session.libsignal.service.api.crypto.AttachmentCipherInputStream;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;

View File

@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull; 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.session.libsignal.utilities.externalstorage.NoExternalStorageException;
import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.Job;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;

View File

@ -7,7 +7,7 @@ import android.text.TextUtils;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.session.libsession.messaging.avatars.AvatarHelper; 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.Address;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.DownloadUtilities; import org.session.libsession.utilities.DownloadUtilities;

View File

@ -18,7 +18,7 @@ package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull; 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.database.DatabaseFactory;
import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.Job;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;

View File

@ -13,7 +13,7 @@ import androidx.annotation.Nullable;
import com.fasterxml.jackson.annotation.JsonProperty; 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.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.session.libsignal.utilities.logging.Log; 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 org.session.libsignal.utilities.logging.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import org.greenrobot.eventbus.EventBus 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.Attachment
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachmentAudioExtras import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachmentAudioExtras

View File

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

View File

@ -1,7 +1,7 @@
package org.thoughtcrime.securesms.jobmanager.impl; package org.thoughtcrime.securesms.jobmanager.impl;
import org.junit.Test; 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 org.session.libsession.utilities.Util;
import java.io.IOException; import java.io.IOException;

View File

@ -6,7 +6,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { 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 "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "com.google.gms:google-services:4.3.4" classpath "com.google.gms:google-services:4.3.4"
classpath files('libs/gradle-witness.jar') 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.file_server.FileServerAPI
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState 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.messaging.utilities.DotNetAPI
import org.session.libsession.utilities.DownloadUtilities import org.session.libsession.utilities.DownloadUtilities
import org.session.libsignal.service.api.crypto.AttachmentCipherInputStream 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.messages.Message
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
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.DotNetAPI import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsignal.service.api.crypto.AttachmentCipherOutputStream import org.session.libsignal.service.api.crypto.AttachmentCipherOutputStream
import org.session.libsignal.service.api.messages.SignalServiceAttachmentStream import org.session.libsignal.service.api.messages.SignalServiceAttachmentStream

View File

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

View File

@ -4,6 +4,7 @@ import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.deferred import nl.komponents.kovenant.deferred
import org.session.libsession.messaging.sending_receiving.MessageReceiver import org.session.libsession.messaging.sending_receiving.MessageReceiver
import org.session.libsession.messaging.sending_receiving.handle import org.session.libsession.messaging.sending_receiving.handle
import org.session.libsession.messaging.utilities.Data
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val openGroupMessageServerID: Long? = null, val openGroupID: String? = null) : Job { 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.Message
import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.messages.visible.VisibleMessage
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.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
class MessageSendJob(val message: Message, val destination: Destination) : Job { class MessageSendJob(val message: Message, val destination: Destination) : Job {

View File

@ -9,6 +9,7 @@ import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI 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.SnodeMessage
import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.OnionRequestAPI

View File

@ -1,5 +1,7 @@
package org.session.libsession.messaging.jobs package org.session.libsession.messaging.jobs
import org.session.libsession.messaging.utilities.Data
class SessionJobInstantiator(private val jobFactories: Map<String, Job.Factory<out Job>>) { class SessionJobInstantiator(private val jobFactories: Map<String, Job.Factory<out Job>>) {
fun instantiate(jobFactoryKey: String, data: Data): 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; import android.os.Parcelable;
@ -12,11 +12,7 @@ import org.session.libsession.utilities.ParcelableUtil;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
// Introduce a dedicated Map<String, byte[]> field specifically for parcelable needs.
public class Data { 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> strings;
@JsonProperty private final Map<String, String[]> stringArrays; @JsonProperty private final Map<String, String[]> stringArrays;
@JsonProperty private final Map<String, Integer> integers; @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, boolean[]> booleanArrays;
@JsonProperty private final Map<String, byte[]> byteArrays; @JsonProperty private final Map<String, byte[]> byteArrays;
public Data(@JsonProperty("strings") @NonNull Map<String, String> strings, public static final Data EMPTY = new Data.Builder().build();
@JsonProperty("stringArrays") @NonNull Map<String, String[]> stringArrays,
@JsonProperty("integers") @NonNull Map<String, Integer> integers, public Data(
@JsonProperty("integerArrays") @NonNull Map<String, int[]> integerArrays, @JsonProperty("strings") @NonNull Map<String, String> strings,
@JsonProperty("longs") @NonNull Map<String, Long> longs, @JsonProperty("stringArrays") @NonNull Map<String, String[]> stringArrays,
@JsonProperty("longArrays") @NonNull Map<String, long[]> longArrays, @JsonProperty("integers") @NonNull Map<String, Integer> integers,
@JsonProperty("floats") @NonNull Map<String, Float> floats, @JsonProperty("integerArrays") @NonNull Map<String, int[]> integerArrays,
@JsonProperty("floatArrays") @NonNull Map<String, float[]> floatArrays, @JsonProperty("longs") @NonNull Map<String, Long> longs,
@JsonProperty("doubles") @NonNull Map<String, Double> doubles, @JsonProperty("longArrays") @NonNull Map<String, long[]> longArrays,
@JsonProperty("doubleArrays") @NonNull Map<String, double[]> doubleArrays, @JsonProperty("floats") @NonNull Map<String, Float> floats,
@JsonProperty("booleans") @NonNull Map<String, Boolean> booleans, @JsonProperty("floatArrays") @NonNull Map<String, float[]> floatArrays,
@JsonProperty("booleanArrays") @NonNull Map<String, boolean[]> booleanArrays, @JsonProperty("doubles") @NonNull Map<String, Double> doubles,
@JsonProperty("byteArrays") @NonNull Map<String, byte[]> byteArrays) @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.strings = strings;
this.stringArrays = stringArrays; this.stringArrays = stringArrays;
this.integers = integers; this.integers = integers;
@ -75,6 +74,7 @@ public class Data {
} }
public boolean hasStringArray(@NonNull String key) { public boolean hasStringArray(@NonNull String key) {
return stringArrays.containsKey(key); return stringArrays.containsKey(key);
} }
@ -100,6 +100,7 @@ public class Data {
} }
public boolean hasIntegerArray(@NonNull String key) { public boolean hasIntegerArray(@NonNull String key) {
return integerArrays.containsKey(key); return integerArrays.containsKey(key);
} }
@ -110,6 +111,7 @@ public class Data {
} }
public boolean hasLong(@NonNull String key) { public boolean hasLong(@NonNull String key) {
return longs.containsKey(key); return longs.containsKey(key);
} }
@ -125,6 +127,7 @@ public class Data {
} }
public boolean hasLongArray(@NonNull String key) { public boolean hasLongArray(@NonNull String key) {
return longArrays.containsKey(key); return longArrays.containsKey(key);
} }
@ -135,6 +138,7 @@ public class Data {
} }
public boolean hasFloat(@NonNull String key) { public boolean hasFloat(@NonNull String key) {
return floats.containsKey(key); return floats.containsKey(key);
} }
@ -150,6 +154,7 @@ public class Data {
} }
public boolean hasFloatArray(@NonNull String key) { public boolean hasFloatArray(@NonNull String key) {
return floatArrays.containsKey(key); return floatArrays.containsKey(key);
} }
@ -160,6 +165,7 @@ public class Data {
} }
public boolean hasDouble(@NonNull String key) { public boolean hasDouble(@NonNull String key) {
return doubles.containsKey(key); return doubles.containsKey(key);
} }
@ -175,6 +181,7 @@ public class Data {
} }
public boolean hasDoubleArray(@NonNull String key) { public boolean hasDoubleArray(@NonNull String key) {
return floatArrays.containsKey(key); return floatArrays.containsKey(key);
} }
@ -185,6 +192,7 @@ public class Data {
} }
public boolean hasBoolean(@NonNull String key) { public boolean hasBoolean(@NonNull String key) {
return booleans.containsKey(key); return booleans.containsKey(key);
} }
@ -200,6 +208,7 @@ public class Data {
} }
public boolean hasBooleanArray(@NonNull String key) { public boolean hasBooleanArray(@NonNull String key) {
return booleanArrays.containsKey(key); return booleanArrays.containsKey(key);
} }
@ -209,6 +218,8 @@ public class Data {
return booleanArrays.get(key); return booleanArrays.get(key);
} }
public boolean hasByteArray(@NonNull String key) { public boolean hasByteArray(@NonNull String key) {
return byteArrays.containsKey(key); return byteArrays.containsKey(key);
} }
@ -218,6 +229,8 @@ public class Data {
return byteArrays.get(key); return byteArrays.get(key);
} }
public boolean hasParcelable(@NonNull String key) { public boolean hasParcelable(@NonNull String key) {
return byteArrays.containsKey(key); return byteArrays.containsKey(key);
} }
@ -228,6 +241,8 @@ public class Data {
return ParcelableUtil.unmarshall(bytes, creator); return ParcelableUtil.unmarshall(bytes, creator);
} }
private void throwIfAbsent(@NonNull Map map, @NonNull String key) { private void throwIfAbsent(@NonNull Map map, @NonNull String key) {
if (!map.containsKey(key)) { if (!map.containsKey(key)) {
throw new IllegalStateException("Tried to retrieve a value with key '" + key + "', but it wasn't present."); 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 { public static class Builder {
private final Map<String, String> strings = new HashMap<>(); private final Map<String, String> strings = new HashMap<>();
private final Map<String, String[]> stringArrays = new HashMap<>(); private final Map<String, String[]> stringArrays = new HashMap<>();
private final Map<String, Integer> integers = new HashMap<>(); private final Map<String, Integer> integers = new HashMap<>();
@ -323,19 +337,21 @@ public class Data {
} }
public Data build() { public Data build() {
return new Data(strings, return new Data(
stringArrays, strings,
integers, stringArrays,
integerArrays, integers,
longs, integerArrays,
longArrays, longs,
floats, longArrays,
floatArrays, floats,
doubles, floatArrays,
doubleArrays, doubles,
booleans, doubleArrays,
booleanArrays, booleans,
byteArrays); booleanArrays,
byteArrays
);
} }
} }
@ -344,4 +360,3 @@ public class Data {
@NonNull Data deserialize(@NonNull String serialized); @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. * 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. * 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. * 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 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) val json = HTTP.execute(HTTP.Verb.GET, url, 3)
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") {
@ -463,7 +463,6 @@ object OnionRequestAPI {
"method" to request.method(), "method" to request.method(),
"headers" to headers "headers" to headers
) )
url.isHttps
val destination = Destination.Server(host, target, x25519PublicKey, url.scheme(), url.port()) val destination = Destination.Server(host, target, x25519PublicKey, url.scheme(), url.port())
return sendOnionRequest(destination, payload, isJSONRequired).recover { exception -> return sendOnionRequest(destination, payload, isJSONRequired).recover { exception ->
Log.d("Loki", "Couldn't reach server: $urlAsString due to error: $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 -> { is OnionRequestAPI.Destination.Server -> {
payload = mutableMapOf( payload = mutableMapOf(
"host" to rhs.host, "host" to rhs.host,
"target" to rhs.target, "target" to rhs.target,
"method" to "POST", "method" to "POST",
"protocol" to rhs.scheme, "protocol" to rhs.scheme,
"port" to rhs.port "port" to rhs.port
) )
} }
} }

View File

@ -34,7 +34,7 @@ object SnodeAPI {
// Settings // Settings
private val maxRetryCount = 6 private val maxRetryCount = 6
private val minimumSnodePoolCount = 12 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 // 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 seedNodePort = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) 443 else 4433
private val seedNodePool by lazy { 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" ) 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 targetSwarmSnodeCount = 2
private val useOnionRequests = true private val useOnionRequests = true
@ -252,19 +252,20 @@ object SnodeAPI {
private fun removeDuplicates(publicKey: String, rawMessages: List<*>): List<*> { private fun removeDuplicates(publicKey: String, rawMessages: List<*>): List<*> {
val receivedMessageHashValues = database.getReceivedMessageHashValues(publicKey)?.toMutableSet() ?: mutableSetOf() val receivedMessageHashValues = database.getReceivedMessageHashValues(publicKey)?.toMutableSet() ?: mutableSetOf()
return rawMessages.filter { rawMessage -> val result = rawMessages.filter { rawMessage ->
val rawMessageAsJSON = rawMessage as? Map<*, *> val rawMessageAsJSON = rawMessage as? Map<*, *>
val hashValue = rawMessageAsJSON?.get("hash") as? String val hashValue = rawMessageAsJSON?.get("hash") as? String
if (hashValue != null) { if (hashValue != null) {
val isDuplicate = receivedMessageHashValues.contains(hashValue) val isDuplicate = receivedMessageHashValues.contains(hashValue)
receivedMessageHashValues.add(hashValue) receivedMessageHashValues.add(hashValue)
database.setReceivedMessageHashValues(publicKey, receivedMessageHashValues)
!isDuplicate !isDuplicate
} else { } else {
Log.d("Loki", "Missing hash value for message: ${rawMessage?.prettifiedDescription()}.") Log.d("Loki", "Missing hash value for message: ${rawMessage?.prettifiedDescription()}.")
false false
} }
} }
database.setReceivedMessageHashValues(publicKey, receivedMessageHashValues)
return result
} }
private fun parseEnvelopes(rawMessages: List<*>): List<SignalServiceProtos.Envelope> { private fun parseEnvelopes(rawMessages: List<*>): List<SignalServiceProtos.Envelope> {
@ -305,7 +306,7 @@ object SnodeAPI {
} }
} }
when (statusCode) { 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() handleBadSnode()
} }
406 -> { 406 -> {
@ -316,8 +317,20 @@ object SnodeAPI {
421 -> { 421 -> {
// The snode isn't associated with the given public key anymore // The snode isn't associated with the given public key anymore
if (publicKey != null) { if (publicKey != null) {
Log.d("Loki", "Invalidating swarm for: $publicKey.") fun invalidateSwarm() {
dropSnodeFromSwarmIfNeeded(snode, publicKey) 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 { } else {
Log.d("Loki", "Got a 421 without an associated public key.") 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 import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
data class SnodeMessage( data class SnodeMessage(
// The hex encoded public key of the recipient. /**
* The hex encoded public key of the recipient.
*/
val recipient: String, val recipient: String,
// The content of the message. /**
* The content of the message.
*/
val data: String, val data: String,
// The time to live for the message in milliseconds. /**
* The time to live for the message in milliseconds.
*/
val ttl: Long, 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 val timestamp: Long
) { ) {
internal fun toJSON(): Map<String, String> { internal fun toJSON(): Map<String, String> {
return mapOf( return mapOf(
"pubKey" to if (SnodeAPI.useTestnet) recipient.removing05PrefixIfNeeded() else recipient, "pubKey" to if (SnodeAPI.useTestnet) recipient.removing05PrefixIfNeeded() else recipient,
"data" to data, "data" to data,
"ttl" to ttl.toString(), "ttl" to ttl.toString(),
"timestamp" to timestamp.toString(), "timestamp" to timestamp.toString(),
"nonce" to "" "nonce" to ""
) )
} }
} }

View File

@ -3,6 +3,7 @@ package org.session.libsignal.service.loki
import okhttp3.* import okhttp3.*
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.JsonUtil
import java.lang.IllegalStateException
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
@ -25,9 +26,7 @@ object HTTP {
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authorizationType: String?) { } override fun checkClientTrusted(chain: Array<out X509Certificate>?, authorizationType: String?) { }
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authorizationType: String?) { } override fun checkServerTrusted(chain: Array<out X509Certificate>?, authorizationType: String?) { }
override fun getAcceptedIssuers(): Array<X509Certificate> { override fun getAcceptedIssuers(): Array<X509Certificate> { return arrayOf() }
return arrayOf()
}
} }
val sslContext = SSLContext.getInstance("SSL") val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, arrayOf( trustManager ), SecureRandom()) sslContext.init(null, arrayOf( trustManager ), SecureRandom())
@ -40,7 +39,7 @@ object HTTP {
.build() .build()
} }
private const val timeout: Long = 20 private const val timeout: Long = 10
class HTTPRequestFailedException(val statusCode: Int, val json: Map<*, *>?) class HTTPRequestFailedException(val statusCode: Int, val json: Map<*, *>?)
: kotlin.Exception("HTTP request failed with status code $statusCode.") : kotlin.Exception("HTTP request failed with status code $statusCode.")
@ -52,26 +51,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, useSeedNodeConnection: Boolean = false): Map<*, *> { fun execute(verb: Verb, url: String, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): Map<*, *> {
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. * 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) { if (parameters != null) {
val body = JsonUtil.toJson(parameters).toByteArray() 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 { } 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. * 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) val request = Request.Builder().url(url)
when (verb) { when (verb) {
Verb.GET -> request.get() Verb.GET -> request.get()
@ -85,7 +84,20 @@ object HTTP {
} }
lateinit var response: Response lateinit var response: Response
try { 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() response = connection.newCall(request.build()).execute()
} catch (exception: Exception) { } catch (exception: Exception) {
Log.d("Loki", "${verb.rawValue} request to $url failed due to error: ${exception.localizedMessage}.") Log.d("Loki", "${verb.rawValue} request to $url failed due to error: ${exception.localizedMessage}.")