mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-12 03:17:53 +00:00
Merge remote-tracking branch 'upstream/dev' into fix_profile_nulls
# Conflicts: # app/src/main/java/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt # app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt # libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package org.session.libsession.database
|
||||
|
||||
import org.session.libsession.messaging.open_groups.OpenGroup
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.*
|
||||
import org.session.libsession.messaging.threads.Address
|
||||
import org.session.libsession.messaging.utilities.DotNetAPI
|
||||
@@ -30,7 +31,6 @@ interface MessageDataProvider {
|
||||
fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult)
|
||||
fun updateAttachmentAfterUploadFailed(attachmentId: Long)
|
||||
|
||||
// Quotes
|
||||
fun getMessageForQuote(timestamp: Long, author: Address): Pair<Long, Boolean>?
|
||||
fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List<Attachment>
|
||||
fun getMessageBodyFor(timestamp: Long, author: String): String
|
||||
@@ -38,4 +38,5 @@ interface MessageDataProvider {
|
||||
fun getAttachmentIDsFor(messageID: Long): List<Long>
|
||||
fun getLinkPreviewAttachmentIDFor(messageID: Long): Long?
|
||||
|
||||
fun getOpenGroup(threadID: Long): OpenGroup?
|
||||
}
|
@@ -1,25 +0,0 @@
|
||||
package org.session.libsession.messaging
|
||||
|
||||
import android.content.Context
|
||||
import org.session.libsession.database.MessageDataProvider
|
||||
import org.session.libsignal.service.loki.api.crypto.SessionProtocol
|
||||
|
||||
class MessagingConfiguration(
|
||||
val context: Context,
|
||||
val storage: StorageProtocol,
|
||||
val messageDataProvider: MessageDataProvider,
|
||||
val sessionProtocol: SessionProtocol)
|
||||
{
|
||||
companion object {
|
||||
lateinit var shared: MessagingConfiguration
|
||||
|
||||
fun configure(context: Context,
|
||||
storage: StorageProtocol,
|
||||
messageDataProvider: MessageDataProvider,
|
||||
sessionProtocol: SessionProtocol
|
||||
) {
|
||||
if (Companion::shared.isInitialized) { return }
|
||||
shared = MessagingConfiguration(context, storage, messageDataProvider, sessionProtocol)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package org.session.libsession.messaging
|
||||
|
||||
import android.content.Context
|
||||
import org.session.libsession.database.MessageDataProvider
|
||||
import org.session.libsignal.service.loki.api.crypto.SessionProtocol
|
||||
|
||||
class MessagingModuleConfiguration(
|
||||
val context: Context,
|
||||
val storage: StorageProtocol,
|
||||
val messageDataProvider: MessageDataProvider,
|
||||
val sessionProtocol: SessionProtocol)
|
||||
{
|
||||
|
||||
companion object {
|
||||
lateinit var shared: MessagingModuleConfiguration
|
||||
|
||||
fun configure(context: Context,
|
||||
storage: StorageProtocol,
|
||||
messageDataProvider: MessageDataProvider,
|
||||
sessionProtocol: SessionProtocol
|
||||
) {
|
||||
if (Companion::shared.isInitialized) { return }
|
||||
shared = MessagingModuleConfiguration(context, storage, messageDataProvider, sessionProtocol)
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,11 +9,11 @@ import org.session.libsession.messaging.jobs.MessageSendJob
|
||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
||||
import org.session.libsession.messaging.messages.visible.Attachment
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
import org.session.libsession.messaging.opengroups.OpenGroup
|
||||
import org.session.libsession.messaging.open_groups.OpenGroup
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
|
||||
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage
|
||||
import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
||||
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview
|
||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
|
||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
||||
import org.session.libsession.messaging.threads.Address
|
||||
import org.session.libsession.messaging.threads.GroupRecord
|
||||
@@ -21,7 +21,6 @@ import org.session.libsession.messaging.threads.recipients.Recipient.RecipientSe
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
||||
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
|
||||
import org.session.libsignal.service.api.messages.SignalServiceGroup
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
interface StorageProtocol {
|
||||
|
||||
@@ -109,8 +108,10 @@ interface StorageProtocol {
|
||||
fun createGroup(groupID: String, title: String?, members: List<Address>, avatar: SignalServiceAttachmentPointer?, relay: String?, admins: List<Address>, formationTimestamp: Long)
|
||||
fun isGroupActive(groupPublicKey: String): Boolean
|
||||
fun setActive(groupID: String, value: Boolean)
|
||||
fun getZombieMember(groupID: String): Set<String>
|
||||
fun removeMember(groupID: String, member: Address)
|
||||
fun updateMembers(groupID: String, members: List<Address>)
|
||||
fun updateZombieMembers(groupID: String, members: List<Address>)
|
||||
// Closed Group
|
||||
fun getAllClosedGroupPublicKeys(): Set<String>
|
||||
fun getAllActiveClosedGroupPublicKeys(): Set<String>
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package org.session.libsession.messaging.avatars;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package org.session.libsession.messaging.avatars;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
@@ -19,5 +18,4 @@ public interface ContactPhoto extends Key {
|
||||
@Nullable Uri getUri(@NonNull Context context);
|
||||
|
||||
boolean isProfilePhoto();
|
||||
|
||||
}
|
||||
|
@@ -7,5 +7,4 @@ public interface FallbackContactPhoto {
|
||||
|
||||
public Drawable asDrawable(Context context, int color);
|
||||
public Drawable asDrawable(Context context, int color, boolean inverted);
|
||||
|
||||
}
|
||||
|
@@ -19,7 +19,6 @@ import org.session.libsession.utilities.ViewUtil;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
public class GeneratedContactPhoto implements FallbackContactPhoto {
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile("[^\\p{L}\\p{Nd}\\p{S}]+");
|
||||
|
@@ -1,13 +1,12 @@
|
||||
package org.session.libsession.messaging.avatars;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.session.libsession.messaging.MessagingConfiguration;
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration;
|
||||
import org.session.libsession.messaging.StorageProtocol;
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.session.libsession.messaging.threads.GroupRecord;
|
||||
@@ -32,7 +31,7 @@ public class GroupRecordContactPhoto implements ContactPhoto {
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream(Context context) throws IOException {
|
||||
StorageProtocol groupDatabase = MessagingConfiguration.shared.getStorage();
|
||||
StorageProtocol groupDatabase = MessagingModuleConfiguration.shared.getStorage();
|
||||
Optional<GroupRecord> groupRecord = Optional.of(groupDatabase.getGroup(address.toGroupString()));
|
||||
|
||||
if (groupRecord.isPresent() && groupRecord.get().getAvatar() != null) {
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package org.session.libsession.messaging.avatars;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package org.session.libsession.messaging.avatars;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
|
@@ -1,17 +1,16 @@
|
||||
package org.session.libsession.messaging.fileserver
|
||||
package org.session.libsession.messaging.file_server
|
||||
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import okhttp3.Request
|
||||
import org.session.libsession.messaging.utilities.DotNetAPI
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.JsonUtil
|
||||
import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI
|
||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
||||
import org.session.libsignal.service.loki.utilities.*
|
||||
import java.net.URL
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class FileServerAPI(public val server: String, userPublicKey: String, userPrivateKey: ByteArray, private val database: LokiAPIDatabaseProtocol) : DotNetAPI() {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package org.session.libsession.messaging.jobs
|
||||
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.fileserver.FileServerAPI
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.file_server.FileServerAPI
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState
|
||||
import org.session.libsession.messaging.utilities.DotNetAPI
|
||||
import org.session.libsignal.service.api.crypto.AttachmentCipherInputStream
|
||||
@@ -11,13 +11,10 @@ import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long): Job {
|
||||
|
||||
override var delegate: JobDelegate? = null
|
||||
override var id: String? = null
|
||||
override var failureCount: Int = 0
|
||||
|
||||
private val MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024
|
||||
|
||||
// Error
|
||||
internal sealed class Error(val description: String) : Exception(description) {
|
||||
object NoAttachment : Error("No such attachment.")
|
||||
@@ -28,34 +25,32 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
|
||||
companion object {
|
||||
val KEY: String = "AttachmentDownloadJob"
|
||||
|
||||
//keys used for database storage purpose
|
||||
// Keys used for database storage
|
||||
private val KEY_ATTACHMENT_ID = "attachment_id"
|
||||
private val KEY_TS_INCOMING_MESSAGE_ID = "tsIncoming_message_id"
|
||||
}
|
||||
|
||||
override fun execute() {
|
||||
val handleFailure: (java.lang.Exception) -> Unit = { exception ->
|
||||
if(exception is Error && exception == Error.NoAttachment) {
|
||||
MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID)
|
||||
if (exception == Error.NoAttachment) {
|
||||
MessagingModuleConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID)
|
||||
this.handlePermanentFailure(exception)
|
||||
} else if (exception is DotNetAPI.Error && exception == DotNetAPI.Error.ParsingFailed) {
|
||||
} else if (exception == DotNetAPI.Error.ParsingFailed) {
|
||||
// No need to retry if the response is invalid. Most likely this means we (incorrectly)
|
||||
// got a "Cannot GET ..." error from the file server.
|
||||
MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID)
|
||||
MessagingModuleConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID)
|
||||
this.handlePermanentFailure(exception)
|
||||
} else {
|
||||
this.handleFailure(exception)
|
||||
}
|
||||
}
|
||||
try {
|
||||
val messageDataProvider = MessagingConfiguration.shared.messageDataProvider
|
||||
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
|
||||
val attachment = messageDataProvider.getDatabaseAttachment(attachmentID) ?: return handleFailure(Error.NoAttachment)
|
||||
messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachmentID, this.databaseMessageID)
|
||||
val tempFile = createTempFile()
|
||||
|
||||
FileServerAPI.shared.downloadFile(tempFile, attachment.url, MAX_ATTACHMENT_SIZE, null)
|
||||
|
||||
// DECRYPTION
|
||||
FileServerAPI.shared.downloadFile(tempFile, attachment.url, null)
|
||||
|
||||
// Assume we're retrieving an attachment for an open group server if the digest is not set
|
||||
val stream = if (attachment.digest?.size ?: 0 == 0 || attachment.key.isNullOrEmpty()) FileInputStream(tempFile)
|
||||
@@ -84,17 +79,15 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
|
||||
}
|
||||
|
||||
private fun createTempFile(): File {
|
||||
val file = File.createTempFile("push-attachment", "tmp", MessagingConfiguration.shared.context.cacheDir)
|
||||
val file = File.createTempFile("push-attachment", "tmp", MessagingModuleConfiguration.shared.context.cacheDir)
|
||||
file.deleteOnExit()
|
||||
return file
|
||||
}
|
||||
|
||||
//database functions
|
||||
|
||||
override fun serialize(): Data {
|
||||
return Data.Builder().putLong(KEY_ATTACHMENT_ID, attachmentID)
|
||||
.putLong(KEY_TS_INCOMING_MESSAGE_ID, databaseMessageID)
|
||||
.build();
|
||||
.putLong(KEY_TS_INCOMING_MESSAGE_ID, databaseMessageID)
|
||||
.build();
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String {
|
||||
@@ -102,6 +95,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
|
||||
}
|
||||
|
||||
class Factory: Job.Factory<AttachmentDownloadJob> {
|
||||
|
||||
override fun create(data: Data): AttachmentDownloadJob {
|
||||
return AttachmentDownloadJob(data.getLong(KEY_ATTACHMENT_ID), data.getLong(KEY_TS_INCOMING_MESSAGE_ID))
|
||||
}
|
||||
|
@@ -3,8 +3,8 @@ package org.session.libsession.messaging.jobs
|
||||
import com.esotericsoftware.kryo.Kryo
|
||||
import com.esotericsoftware.kryo.io.Input
|
||||
import com.esotericsoftware.kryo.io.Output
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.fileserver.FileServerAPI
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.file_server.FileServerAPI
|
||||
import org.session.libsession.messaging.messages.Message
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
import org.session.libsession.messaging.utilities.DotNetAPI
|
||||
@@ -18,7 +18,6 @@ import org.session.libsignal.service.loki.utilities.PlaintextOutputStreamFactory
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
|
||||
class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val message: Message, val messageSendJobID: String) : Job {
|
||||
|
||||
override var delegate: JobDelegate? = null
|
||||
override var id: String? = null
|
||||
override var failureCount: Int = 0
|
||||
@@ -34,9 +33,7 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
|
||||
val TAG = AttachmentUploadJob::class.simpleName
|
||||
val KEY: String = "AttachmentUploadJob"
|
||||
|
||||
val maxFailureCount: Int = 20
|
||||
|
||||
//keys used for database storage purpose
|
||||
// Keys used for database storage
|
||||
private val KEY_ATTACHMENT_ID = "attachment_id"
|
||||
private val KEY_THREAD_ID = "thread_id"
|
||||
private val KEY_MESSAGE = "message"
|
||||
@@ -45,17 +42,13 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
|
||||
|
||||
override fun execute() {
|
||||
try {
|
||||
val attachment = MessagingConfiguration.shared.messageDataProvider.getScaledSignalAttachmentStream(attachmentID)
|
||||
?: return handleFailure(Error.NoAttachment)
|
||||
val attachment = MessagingModuleConfiguration.shared.messageDataProvider.getScaledSignalAttachmentStream(attachmentID)
|
||||
?: return handleFailure(Error.NoAttachment)
|
||||
|
||||
var server = FileServerAPI.shared.server
|
||||
var shouldEncrypt = true
|
||||
val usePadding = false
|
||||
val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID)
|
||||
openGroup?.let {
|
||||
server = it.server
|
||||
shouldEncrypt = false
|
||||
}
|
||||
val openGroup = MessagingModuleConfiguration.shared.storage.getOpenGroup(threadID)
|
||||
val server = if (openGroup != null) openGroup.server else FileServerAPI.shared.server
|
||||
val shouldEncrypt = (openGroup == null) // Encrypt if this isn't an open group
|
||||
|
||||
val attachmentKey = Util.getSecretBytes(64)
|
||||
val paddedLength = if (usePadding) PaddingInputStream.getPaddedSize(attachment.length) else attachment.length
|
||||
@@ -67,9 +60,8 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
|
||||
|
||||
val uploadResult = FileServerAPI.shared.uploadAttachment(server, attachmentData)
|
||||
handleSuccess(attachment, attachmentKey, uploadResult)
|
||||
|
||||
} catch (e: java.lang.Exception) {
|
||||
if (e is Error && e == Error.NoAttachment) {
|
||||
if (e == Error.NoAttachment) {
|
||||
this.handlePermanentFailure(e)
|
||||
} else if (e is DotNetAPI.Error && !e.isRetryable) {
|
||||
this.handlePermanentFailure(e)
|
||||
@@ -77,33 +69,32 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
|
||||
this.handleFailure(e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun handleSuccess(attachment: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) {
|
||||
Log.w(TAG, "Attachment uploaded successfully.")
|
||||
delegate?.handleJobSucceeded(this)
|
||||
MessagingConfiguration.shared.messageDataProvider.updateAttachmentAfterUploadSucceeded(attachmentID, attachment, attachmentKey, uploadResult)
|
||||
MessagingConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID)
|
||||
MessagingModuleConfiguration.shared.messageDataProvider.updateAttachmentAfterUploadSucceeded(attachmentID, attachment, attachmentKey, uploadResult)
|
||||
MessagingModuleConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID)
|
||||
}
|
||||
|
||||
private fun handlePermanentFailure(e: Exception) {
|
||||
Log.w(TAG, "Attachment upload failed permanently due to error: $this.")
|
||||
delegate?.handleJobFailedPermanently(this, e)
|
||||
MessagingConfiguration.shared.messageDataProvider.updateAttachmentAfterUploadFailed(attachmentID)
|
||||
MessagingModuleConfiguration.shared.messageDataProvider.updateAttachmentAfterUploadFailed(attachmentID)
|
||||
failAssociatedMessageSendJob(e)
|
||||
}
|
||||
|
||||
private fun handleFailure(e: Exception) {
|
||||
Log.w(TAG, "Attachment upload failed due to error: $this.")
|
||||
delegate?.handleJobFailed(this, e)
|
||||
if (failureCount + 1 == AttachmentUploadJob.maxFailureCount) {
|
||||
if (failureCount + 1 == maxFailureCount) {
|
||||
failAssociatedMessageSendJob(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun failAssociatedMessageSendJob(e: Exception) {
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val messageSendJob = storage.getMessageSendJob(messageSendJobID)
|
||||
MessageSender.handleFailedMessageSend(this.message, e)
|
||||
if (messageSendJob != null) {
|
||||
@@ -111,10 +102,7 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
|
||||
}
|
||||
}
|
||||
|
||||
//database functions
|
||||
|
||||
override fun serialize(): Data {
|
||||
//serialize Message property
|
||||
val kryo = Kryo()
|
||||
kryo.isRegistrationRequired = false
|
||||
val serializedMessage = ByteArray(4096)
|
||||
@@ -122,10 +110,10 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
|
||||
kryo.writeObject(output, message)
|
||||
output.close()
|
||||
return Data.Builder().putLong(KEY_ATTACHMENT_ID, attachmentID)
|
||||
.putString(KEY_THREAD_ID, threadID)
|
||||
.putByteArray(KEY_MESSAGE, serializedMessage)
|
||||
.putString(KEY_MESSAGE_SEND_JOB_ID, messageSendJobID)
|
||||
.build();
|
||||
.putString(KEY_THREAD_ID, threadID)
|
||||
.putByteArray(KEY_MESSAGE, serializedMessage)
|
||||
.putString(KEY_MESSAGE_SEND_JOB_ID, messageSendJobID)
|
||||
.build();
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String {
|
||||
@@ -133,9 +121,9 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
|
||||
}
|
||||
|
||||
class Factory: Job.Factory<AttachmentUploadJob> {
|
||||
|
||||
override fun create(data: Data): AttachmentUploadJob {
|
||||
val serializedMessage = data.getByteArray(KEY_MESSAGE)
|
||||
//deserialize Message property
|
||||
val kryo = Kryo()
|
||||
val input = Input(serializedMessage)
|
||||
val message: Message = kryo.readObject(input, Message::class.java)
|
||||
|
@@ -12,7 +12,6 @@ import org.session.libsession.utilities.ParcelableUtil;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
// TODO AC: For now parcelable objects utilize byteArrays field to store their data into.
|
||||
// Introduce a dedicated Map<String, byte[]> field specifically for parcelable needs.
|
||||
public class Data {
|
||||
|
||||
|
@@ -8,15 +8,13 @@ interface Job {
|
||||
val maxFailureCount: Int
|
||||
|
||||
companion object {
|
||||
//keys used for database storage purpose
|
||||
// Keys used for database storage
|
||||
private val KEY_ID = "id"
|
||||
private val KEY_FAILURE_COUNT = "failure_count"
|
||||
}
|
||||
|
||||
fun execute()
|
||||
|
||||
//database functions
|
||||
|
||||
fun serialize(): Data
|
||||
|
||||
/**
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package org.session.libsession.messaging.jobs
|
||||
|
||||
interface JobDelegate {
|
||||
|
||||
fun handleJobSucceeded(job: Job)
|
||||
fun handleJobFailed(job: Job, error: Exception)
|
||||
fun handleJobFailedPermanently(job: Job, error: Exception)
|
||||
|
@@ -3,7 +3,7 @@ package org.session.libsession.messaging.jobs
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
@@ -14,19 +14,17 @@ import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
|
||||
class JobQueue : JobDelegate {
|
||||
private var hasResumedPendingJobs = false // Just for debugging
|
||||
|
||||
private val jobTimestampMap = ConcurrentHashMap<Long, AtomicInteger>()
|
||||
|
||||
private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
||||
private val scope = GlobalScope + SupervisorJob()
|
||||
private val queue = Channel<Job>(UNLIMITED)
|
||||
|
||||
val timer = Timer()
|
||||
|
||||
init {
|
||||
// process jobs
|
||||
// Process jobs
|
||||
scope.launch(dispatcher) {
|
||||
while (isActive) {
|
||||
queue.receive().let { job ->
|
||||
@@ -49,14 +47,14 @@ class JobQueue : JobDelegate {
|
||||
|
||||
private fun addWithoutExecuting(job: Job) {
|
||||
// When adding multiple jobs in rapid succession, timestamps might not be good enough as a unique ID. To
|
||||
// deal with this we keep track of the number of jobs with a given timestamp and that to the end of the
|
||||
// deal with this we keep track of the number of jobs with a given timestamp and add that to the end of the
|
||||
// timestamp to make it a unique ID. We can't use a random number because we do still want to keep track
|
||||
// of the order in which the jobs were added.
|
||||
val currentTime = System.currentTimeMillis()
|
||||
jobTimestampMap.putIfAbsent(currentTime, AtomicInteger())
|
||||
job.id = currentTime.toString() + jobTimestampMap[currentTime]!!.getAndIncrement().toString()
|
||||
|
||||
MessagingConfiguration.shared.storage.persistJob(job)
|
||||
MessagingModuleConfiguration.shared.storage.persistJob(job)
|
||||
}
|
||||
|
||||
fun resumePendingJobs() {
|
||||
@@ -67,21 +65,21 @@ class JobQueue : JobDelegate {
|
||||
hasResumedPendingJobs = true
|
||||
val allJobTypes = listOf(AttachmentDownloadJob.KEY, AttachmentDownloadJob.KEY, MessageReceiveJob.KEY, MessageSendJob.KEY, NotifyPNServerJob.KEY)
|
||||
allJobTypes.forEach { type ->
|
||||
val allPendingJobs = MessagingConfiguration.shared.storage.getAllPendingJobs(type)
|
||||
val allPendingJobs = MessagingModuleConfiguration.shared.storage.getAllPendingJobs(type)
|
||||
allPendingJobs.sortedBy { it.id }.forEach { job ->
|
||||
Log.i("Jobs", "Resuming pending job of type: ${job::class.simpleName}.")
|
||||
queue.offer(job) // offer always called on unlimited capacity
|
||||
queue.offer(job) // Offer always called on unlimited capacity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleJobSucceeded(job: Job) {
|
||||
MessagingConfiguration.shared.storage.markJobAsSucceeded(job)
|
||||
MessagingModuleConfiguration.shared.storage.markJobAsSucceeded(job)
|
||||
}
|
||||
|
||||
override fun handleJobFailed(job: Job, error: Exception) {
|
||||
job.failureCount += 1
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
if (storage.isJobCanceled(job)) { return Log.i("Jobs", "${job::class.simpleName} canceled.")}
|
||||
storage.persistJob(job)
|
||||
if (job.failureCount == job.maxFailureCount) {
|
||||
@@ -98,7 +96,7 @@ class JobQueue : JobDelegate {
|
||||
|
||||
override fun handleJobFailedPermanently(job: Job, error: Exception) {
|
||||
job.failureCount += 1
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
storage.persistJob(job)
|
||||
storage.markJobAsFailed(job)
|
||||
}
|
||||
|
@@ -7,7 +7,6 @@ import org.session.libsession.messaging.sending_receiving.handle
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
|
||||
class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val openGroupMessageServerID: Long? = null, val openGroupID: String? = null) : Job {
|
||||
|
||||
override var delegate: JobDelegate? = null
|
||||
override var id: String? = null
|
||||
override var failureCount: Int = 0
|
||||
@@ -20,7 +19,7 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val
|
||||
|
||||
private val RECEIVE_LOCK = Object()
|
||||
|
||||
//keys used for database storage purpose
|
||||
// Keys used for database storage
|
||||
private val KEY_DATA = "data"
|
||||
private val KEY_IS_BACKGROUND_POLL = "is_background_poll"
|
||||
private val KEY_OPEN_GROUP_MESSAGE_SERVER_ID = "openGroupMessageServerID"
|
||||
@@ -68,8 +67,6 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val
|
||||
delegate?.handleJobFailed(this, e)
|
||||
}
|
||||
|
||||
//database functions
|
||||
|
||||
override fun serialize(): Data {
|
||||
val builder = Data.Builder().putByteArray(KEY_DATA, data)
|
||||
.putBoolean(KEY_IS_BACKGROUND_POLL, isBackgroundPoll)
|
||||
@@ -83,6 +80,7 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val
|
||||
}
|
||||
|
||||
class Factory: Job.Factory<MessageReceiveJob> {
|
||||
|
||||
override fun create(data: Data): MessageReceiveJob {
|
||||
return MessageReceiveJob(data.getByteArray(KEY_DATA), data.getBoolean(KEY_IS_BACKGROUND_POLL), data.getLong(KEY_OPEN_GROUP_MESSAGE_SERVER_ID), data.getString(KEY_OPEN_GROUP_ID))
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ package org.session.libsession.messaging.jobs
|
||||
import com.esotericsoftware.kryo.Kryo
|
||||
import com.esotericsoftware.kryo.io.Input
|
||||
import com.esotericsoftware.kryo.io.Output
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.messages.Destination
|
||||
import org.session.libsession.messaging.messages.Message
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
@@ -11,7 +11,6 @@ import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
|
||||
class MessageSendJob(val message: Message, val destination: Destination) : Job {
|
||||
|
||||
override var delegate: JobDelegate? = null
|
||||
override var id: String? = null
|
||||
override var failureCount: Int = 0
|
||||
@@ -22,13 +21,13 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
|
||||
val TAG = MessageSendJob::class.simpleName
|
||||
val KEY: String = "MessageSendJob"
|
||||
|
||||
//keys used for database storage purpose
|
||||
// Keys used for database storage
|
||||
private val KEY_MESSAGE = "message"
|
||||
private val KEY_DESTINATION = "destination"
|
||||
}
|
||||
|
||||
override fun execute() {
|
||||
val messageDataProvider = MessagingConfiguration.shared.messageDataProvider
|
||||
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
|
||||
val message = message as? VisibleMessage
|
||||
message?.let {
|
||||
if(!messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted
|
||||
@@ -39,7 +38,7 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
|
||||
val attachments = attachmentIDs.mapNotNull { messageDataProvider.getDatabaseAttachment(it) }
|
||||
val attachmentsToUpload = attachments.filter { it.url.isNullOrEmpty() }
|
||||
attachmentsToUpload.forEach {
|
||||
if (MessagingConfiguration.shared.storage.getAttachmentUploadJob(it.attachmentId.rowId) != null) {
|
||||
if (MessagingModuleConfiguration.shared.storage.getAttachmentUploadJob(it.attachmentId.rowId) != null) {
|
||||
// Wait for it to finish
|
||||
} else {
|
||||
val job = AttachmentUploadJob(it.attachmentId.rowId, message.threadID!!.toString(), message, id!!)
|
||||
@@ -72,15 +71,12 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
|
||||
Log.w(TAG, "Failed to send $message::class.simpleName.")
|
||||
val message = message as? VisibleMessage
|
||||
message?.let {
|
||||
if(!MessagingConfiguration.shared.messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted
|
||||
if(!MessagingModuleConfiguration.shared.messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted
|
||||
}
|
||||
delegate?.handleJobFailed(this, error)
|
||||
}
|
||||
|
||||
//database functions
|
||||
|
||||
override fun serialize(): Data {
|
||||
//serialize Message and Destination properties
|
||||
val kryo = Kryo()
|
||||
kryo.isRegistrationRequired = false
|
||||
val output = Output(ByteArray(4096), -1) // maxBufferSize '-1' will dynamically grow internally if we run out of room serializing the message
|
||||
@@ -92,8 +88,8 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
|
||||
output.close()
|
||||
val serializedDestination = output.toBytes()
|
||||
return Data.Builder().putByteArray(KEY_MESSAGE, serializedMessage)
|
||||
.putByteArray(KEY_DESTINATION, serializedDestination)
|
||||
.build();
|
||||
.putByteArray(KEY_DESTINATION, serializedDestination)
|
||||
.build();
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String {
|
||||
@@ -101,10 +97,10 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
|
||||
}
|
||||
|
||||
class Factory: Job.Factory<MessageSendJob> {
|
||||
|
||||
override fun create(data: Data): MessageSendJob {
|
||||
val serializedMessage = data.getByteArray(KEY_MESSAGE)
|
||||
val serializedDestination = data.getByteArray(KEY_DESTINATION)
|
||||
//deserialize Message and Destination properties
|
||||
val kryo = Kryo()
|
||||
var input = Input(serializedMessage)
|
||||
val message = kryo.readClassAndObject(input) as Message
|
||||
|
@@ -26,7 +26,7 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job {
|
||||
companion object {
|
||||
val KEY: String = "NotifyPNServerJob"
|
||||
|
||||
//keys used for database storage purpose
|
||||
// Keys used for database storage
|
||||
private val KEY_MESSAGE = "message"
|
||||
}
|
||||
|
||||
@@ -61,18 +61,14 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job {
|
||||
delegate?.handleJobFailed(this, error)
|
||||
}
|
||||
|
||||
//database functions
|
||||
|
||||
override fun serialize(): Data {
|
||||
//serialize SnodeMessage property
|
||||
val kryo = Kryo()
|
||||
kryo.isRegistrationRequired = false
|
||||
val serializedMessage = ByteArray(4096)
|
||||
val output = Output(serializedMessage)
|
||||
kryo.writeObject(output, message)
|
||||
output.close()
|
||||
return Data.Builder().putByteArray(KEY_MESSAGE, serializedMessage)
|
||||
.build();
|
||||
return Data.Builder().putByteArray(KEY_MESSAGE, serializedMessage).build();
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String {
|
||||
@@ -80,9 +76,9 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job {
|
||||
}
|
||||
|
||||
class Factory: Job.Factory<NotifyPNServerJob> {
|
||||
|
||||
override fun create(data: Data): NotifyPNServerJob {
|
||||
val serializedMessage = data.getByteArray(KEY_MESSAGE)
|
||||
//deserialize SnodeMessage property
|
||||
val kryo = Kryo()
|
||||
val input = Input(serializedMessage)
|
||||
val message: SnodeMessage = kryo.readObject(input, SnodeMessage::class.java)
|
||||
|
@@ -1,7 +1,5 @@
|
||||
package org.session.libsession.messaging.jobs
|
||||
|
||||
import java.util.*
|
||||
|
||||
class SessionJobInstantiator(private val jobFactories: Map<String, Job.Factory<out Job>>) {
|
||||
|
||||
fun instantiate(jobFactoryKey: String, data: Data): Job {
|
||||
|
@@ -1,7 +1,5 @@
|
||||
package org.session.libsession.messaging.jobs
|
||||
|
||||
import java.util.*
|
||||
|
||||
class SessionJobManagerFactories {
|
||||
|
||||
companion object {
|
||||
|
@@ -0,0 +1,58 @@
|
||||
package org.session.libsession.messaging.mentions
|
||||
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsignal.service.loki.utilities.mentions.Mention
|
||||
|
||||
import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol
|
||||
|
||||
class MentionsManager(private val userPublicKey: String, private val userDatabase: LokiUserDatabaseProtocol) {
|
||||
var userPublicKeyCache = mutableMapOf<Long, Set<String>>() // Thread ID to set of user hex encoded public keys
|
||||
|
||||
companion object {
|
||||
|
||||
public lateinit var shared: MentionsManager
|
||||
|
||||
public fun configureIfNeeded(userPublicKey: String, userDatabase: LokiUserDatabaseProtocol) {
|
||||
if (::shared.isInitialized) { return; }
|
||||
shared = MentionsManager(userPublicKey, userDatabase)
|
||||
}
|
||||
}
|
||||
|
||||
fun cache(publicKey: String, threadID: Long) {
|
||||
val cache = userPublicKeyCache[threadID]
|
||||
if (cache != null) {
|
||||
userPublicKeyCache[threadID] = cache.plus(publicKey)
|
||||
} else {
|
||||
userPublicKeyCache[threadID] = setOf( publicKey )
|
||||
}
|
||||
}
|
||||
|
||||
fun getMentionCandidates(query: String, threadID: Long): List<Mention> {
|
||||
// Prepare
|
||||
val cache = userPublicKeyCache[threadID] ?: return listOf()
|
||||
// Gather candidates
|
||||
val publicChat = MessagingModuleConfiguration.shared.messageDataProvider.getOpenGroup(threadID)
|
||||
var candidates: List<Mention> = cache.mapNotNull { publicKey ->
|
||||
val displayName: String?
|
||||
if (publicChat != null) {
|
||||
displayName = userDatabase.getServerDisplayName(publicChat.id, publicKey)
|
||||
} else {
|
||||
displayName = userDatabase.getDisplayName(publicKey)
|
||||
}
|
||||
if (displayName == null) { return@mapNotNull null }
|
||||
if (displayName.startsWith("Anonymous")) { return@mapNotNull null }
|
||||
Mention(publicKey, displayName)
|
||||
}
|
||||
candidates = candidates.filter { it.publicKey != userPublicKey }
|
||||
// Sort alphabetically first
|
||||
candidates.sortedBy { it.displayName }
|
||||
if (query.length >= 2) {
|
||||
// Filter out any non-matching candidates
|
||||
candidates = candidates.filter { it.displayName.toLowerCase().contains(query.toLowerCase()) }
|
||||
// Sort based on where in the candidate the query occurs
|
||||
candidates.sortedBy { it.displayName.toLowerCase().indexOf(query.toLowerCase()) }
|
||||
}
|
||||
// Return
|
||||
return candidates
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
package org.session.libsession.messaging.messages
|
||||
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.threads.Address
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsignal.service.loki.utilities.toHexString
|
||||
@@ -29,8 +29,8 @@ sealed class Destination {
|
||||
ClosedGroup(groupPublicKey)
|
||||
}
|
||||
address.isOpenGroup -> {
|
||||
val threadID = MessagingConfiguration.shared.storage.getThreadID(address.contactIdentifier())!!
|
||||
val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID)!!
|
||||
val threadID = MessagingModuleConfiguration.shared.storage.getThreadID(address.contactIdentifier())!!
|
||||
val openGroup = MessagingModuleConfiguration.shared.storage.getOpenGroup(threadID)!!
|
||||
OpenGroup(openGroup.channel, openGroup.server)
|
||||
}
|
||||
else -> {
|
||||
|
@@ -5,7 +5,6 @@ import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
abstract class Message {
|
||||
|
||||
var id: Long? = null
|
||||
var threadID: Long? = null
|
||||
var sentTimestamp: Long? = null
|
||||
@@ -15,10 +14,9 @@ abstract class Message {
|
||||
var groupPublicKey: String? = null
|
||||
var openGroupServerMessageID: Long? = null
|
||||
|
||||
open val ttl: Long = 2 * 24 * 60 * 60 * 1000
|
||||
open val ttl: Long = 14 * 24 * 60 * 60 * 1000
|
||||
open val isSelfSendValid: Boolean = false
|
||||
|
||||
// validation
|
||||
open fun isValid(): Boolean {
|
||||
sentTimestamp?.let {
|
||||
if (it <= 0) return false
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package org.session.libsession.messaging.messages.control
|
||||
|
||||
import com.google.protobuf.ByteString
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.threads.Address
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
@@ -19,8 +19,8 @@ class ClosedGroupControlMessage() : ControlMessage() {
|
||||
|
||||
override val ttl: Long = run {
|
||||
when (kind) {
|
||||
is Kind.EncryptionKeyPair -> return@run 4 * 24 * 60 * 60 * 1000
|
||||
else -> return@run 2 * 24 * 60 * 60 * 1000
|
||||
is Kind.EncryptionKeyPair -> 14 * 24 * 60 * 60 * 1000
|
||||
else -> 14 * 24 * 60 * 60 * 1000
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,15 +28,10 @@ class ClosedGroupControlMessage() : ControlMessage() {
|
||||
|
||||
var kind: Kind? = null
|
||||
|
||||
// Kind enum
|
||||
sealed class Kind {
|
||||
class New(var publicKey: ByteString, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List<ByteString>, var admins: List<ByteString>) : Kind() {
|
||||
internal constructor(): this(ByteString.EMPTY, "", null, listOf(), listOf())
|
||||
}
|
||||
/// - Note: Deprecated in favor of more explicit group updates.
|
||||
class Update(var name: String, var members: List<ByteString>) : Kind() {
|
||||
internal constructor(): this("", listOf())
|
||||
}
|
||||
/// An encryption key pair encrypted for each member individually.
|
||||
///
|
||||
/// - Note: `publicKey` is only set when an encryption key pair is sent in a one-to-one context (i.e. not in a group).
|
||||
@@ -53,18 +48,15 @@ class ClosedGroupControlMessage() : ControlMessage() {
|
||||
internal constructor(): this(listOf())
|
||||
}
|
||||
class MemberLeft() : Kind()
|
||||
class EncryptionKeyPairRequest(): Kind()
|
||||
|
||||
val description: String =
|
||||
when(this) {
|
||||
is New -> "new"
|
||||
is Update -> "update"
|
||||
is EncryptionKeyPair -> "encryptionKeyPair"
|
||||
is NameChange -> "nameChange"
|
||||
is MembersAdded -> "membersAdded"
|
||||
is MembersRemoved -> "membersRemoved"
|
||||
is MemberLeft -> "memberLeft"
|
||||
is EncryptionKeyPairRequest -> "encryptionKeyPairRequest"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,12 +67,11 @@ class ClosedGroupControlMessage() : ControlMessage() {
|
||||
if (!proto.hasDataMessage() || !proto.dataMessage.hasClosedGroupControlMessage()) return null
|
||||
val closedGroupControlMessageProto = proto.dataMessage?.closedGroupControlMessage!!
|
||||
val kind: Kind
|
||||
when(closedGroupControlMessageProto.type) {
|
||||
when (closedGroupControlMessageProto.type) {
|
||||
DataMessage.ClosedGroupControlMessage.Type.NEW -> {
|
||||
val publicKey = closedGroupControlMessageProto.publicKey ?: return null
|
||||
val name = closedGroupControlMessageProto.name ?: return null
|
||||
val encryptionKeyPairAsProto = closedGroupControlMessageProto.encryptionKeyPair ?: return null
|
||||
|
||||
try {
|
||||
val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairAsProto.publicKey.toByteArray()), DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray()))
|
||||
kind = Kind.New(publicKey, name, encryptionKeyPair, closedGroupControlMessageProto.membersList, closedGroupControlMessageProto.adminsList)
|
||||
@@ -89,10 +80,6 @@ class ClosedGroupControlMessage() : ControlMessage() {
|
||||
return null
|
||||
}
|
||||
}
|
||||
DataMessage.ClosedGroupControlMessage.Type.UPDATE -> {
|
||||
val name = closedGroupControlMessageProto.name ?: return null
|
||||
kind = Kind.Update(name, closedGroupControlMessageProto.membersList)
|
||||
}
|
||||
DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> {
|
||||
val publicKey = closedGroupControlMessageProto.publicKey
|
||||
val wrappers = closedGroupControlMessageProto.wrappersList.mapNotNull { KeyPairWrapper.fromProto(it) }
|
||||
@@ -111,35 +98,28 @@ class ClosedGroupControlMessage() : ControlMessage() {
|
||||
DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> {
|
||||
kind = Kind.MemberLeft()
|
||||
}
|
||||
DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR_REQUEST -> {
|
||||
kind = Kind.EncryptionKeyPairRequest()
|
||||
}
|
||||
}
|
||||
return ClosedGroupControlMessage(kind)
|
||||
}
|
||||
}
|
||||
|
||||
// constructor
|
||||
internal constructor(kind: Kind?) : this() {
|
||||
this.kind = kind
|
||||
}
|
||||
|
||||
// validation
|
||||
override fun isValid(): Boolean {
|
||||
if (!super.isValid()) return false
|
||||
val kind = kind ?: return false
|
||||
return when(kind) {
|
||||
is Kind.New -> {
|
||||
!kind.publicKey.isEmpty && kind.name.isNotEmpty() && kind.encryptionKeyPair!!.publicKey != null
|
||||
&& kind.encryptionKeyPair!!.privateKey != null && kind.members.isNotEmpty() && kind.admins.isNotEmpty()
|
||||
&& kind.encryptionKeyPair!!.privateKey != null && kind.members.isNotEmpty() && kind.admins.isNotEmpty()
|
||||
}
|
||||
is Kind.Update -> kind.name.isNotEmpty()
|
||||
is Kind.EncryptionKeyPair -> true
|
||||
is Kind.NameChange -> kind.name.isNotEmpty()
|
||||
is Kind.MembersAdded -> kind.members.isNotEmpty()
|
||||
is Kind.MembersRemoved -> kind.members.isNotEmpty()
|
||||
is Kind.MemberLeft -> true
|
||||
is Kind.EncryptionKeyPairRequest -> true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,14 +143,9 @@ class ClosedGroupControlMessage() : ControlMessage() {
|
||||
closedGroupControlMessage.addAllMembers(kind.members)
|
||||
closedGroupControlMessage.addAllAdmins(kind.admins)
|
||||
}
|
||||
is Kind.Update -> {
|
||||
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.UPDATE
|
||||
closedGroupControlMessage.name = kind.name
|
||||
closedGroupControlMessage.addAllMembers(kind.members)
|
||||
}
|
||||
is Kind.EncryptionKeyPair -> {
|
||||
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR
|
||||
closedGroupControlMessage.publicKey = kind.publicKey
|
||||
closedGroupControlMessage.publicKey = kind.publicKey ?: ByteString.EMPTY
|
||||
closedGroupControlMessage.addAllWrappers(kind.wrappers.map { it.toProto() })
|
||||
}
|
||||
is Kind.NameChange -> {
|
||||
@@ -188,9 +163,6 @@ class ClosedGroupControlMessage() : ControlMessage() {
|
||||
is Kind.MemberLeft -> {
|
||||
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT
|
||||
}
|
||||
is Kind.EncryptionKeyPairRequest -> {
|
||||
// TODO: closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR_REQUEST
|
||||
}
|
||||
}
|
||||
val contentProto = SignalServiceProtos.Content.newBuilder()
|
||||
val dataMessageProto = DataMessage.newBuilder()
|
||||
@@ -199,8 +171,8 @@ class ClosedGroupControlMessage() : ControlMessage() {
|
||||
setGroupContext(dataMessageProto)
|
||||
// Expiration timer
|
||||
// TODO: We * want * expiration timer updates to be explicit. But currently Android will disable the expiration timer for a conversation
|
||||
// if it receives a message without the current expiration timer value attached to it...
|
||||
dataMessageProto.expireTimer = Recipient.from(MessagingConfiguration.shared.context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(recipient!!)), false).expireMessages
|
||||
// if it receives a message without the current expiration timer value attached to it...
|
||||
dataMessageProto.expireTimer = Recipient.from(MessagingModuleConfiguration.shared.context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(recipient!!)), false).expireMessages
|
||||
contentProto.dataMessage = dataMessageProto.build()
|
||||
return contentProto.build()
|
||||
} catch (e: Exception) {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package org.session.libsession.messaging.messages.control
|
||||
|
||||
import com.google.protobuf.ByteString
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.threads.Address
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
@@ -26,13 +26,14 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.ConfigurationMessage.ClosedGroup): ClosedGroup? {
|
||||
if (!proto.hasPublicKey() || !proto.hasName() || !proto.hasEncryptionKeyPair()) return null
|
||||
val publicKey = proto.publicKey.toByteArray().toHexString()
|
||||
val name = proto.name
|
||||
val encryptionKeyPairAsProto = proto.encryptionKeyPair
|
||||
val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairAsProto.publicKey.toByteArray().removing05PrefixIfNeeded()),
|
||||
DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray()))
|
||||
DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray()))
|
||||
val members = proto.membersList.map { it.toByteArray().toHexString() }
|
||||
val admins = proto.adminsList.map { it.toByteArray().toHexString() }
|
||||
return ClosedGroup(publicKey, name, encryptionKeyPair, members, admins)
|
||||
@@ -58,6 +59,7 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
|
||||
internal constructor(): this("", "", null, null)
|
||||
|
||||
companion object {
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.ConfigurationMessage.Contact): Contact? {
|
||||
if (!proto.hasName() || !proto.hasProfileKey()) return null
|
||||
val publicKey = proto.publicKey.toByteArray().toHexString()
|
||||
@@ -87,7 +89,6 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
|
||||
}
|
||||
}
|
||||
|
||||
override val ttl: Long = 4 * 24 * 60 * 60 * 1000
|
||||
override val isSelfSendValid: Boolean = true
|
||||
|
||||
companion object {
|
||||
@@ -95,7 +96,7 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
|
||||
fun getCurrent(contacts: List<Contact>): ConfigurationMessage? {
|
||||
val closedGroups = mutableListOf<ClosedGroup>()
|
||||
val openGroups = mutableListOf<String>()
|
||||
val sharedConfig = MessagingConfiguration.shared
|
||||
val sharedConfig = MessagingModuleConfiguration.shared
|
||||
val storage = sharedConfig.storage
|
||||
val context = sharedConfig.context
|
||||
val displayName = TextSecurePreferences.getProfileName(context) ?: return null
|
||||
|
@@ -6,13 +6,12 @@ import org.session.libsignal.utilities.logging.Log
|
||||
class DataExtractionNotification(): ControlMessage() {
|
||||
var kind: Kind? = null
|
||||
|
||||
// Kind enum
|
||||
sealed class Kind {
|
||||
class Screenshot() : Kind()
|
||||
class MediaSaved(val timestamp: Long) : Kind()
|
||||
|
||||
val description: String =
|
||||
when(this) {
|
||||
when (this) {
|
||||
is Screenshot -> "screenshot"
|
||||
is MediaSaved -> "mediaSaved"
|
||||
}
|
||||
@@ -35,12 +34,10 @@ class DataExtractionNotification(): ControlMessage() {
|
||||
}
|
||||
}
|
||||
|
||||
//constructor
|
||||
internal constructor(kind: Kind) : this() {
|
||||
this.kind = kind
|
||||
}
|
||||
|
||||
// MARK: Validation
|
||||
override fun isValid(): Boolean {
|
||||
if (!super.isValid()) return false
|
||||
val kind = kind ?: return false
|
||||
|
@@ -1,12 +1,11 @@
|
||||
package org.session.libsession.messaging.messages.control
|
||||
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
class ExpirationTimerUpdate() : ControlMessage() {
|
||||
|
||||
/// In the case of a sync message, the public key of the person the message was targeted at.
|
||||
/// - Note: `nil` if this isn't a sync message.
|
||||
var syncTarget: String? = null
|
||||
@@ -27,7 +26,6 @@ class ExpirationTimerUpdate() : ControlMessage() {
|
||||
}
|
||||
}
|
||||
|
||||
//constructor
|
||||
internal constructor(syncTarget: String?, duration: Int) : this() {
|
||||
this.syncTarget = syncTarget
|
||||
this.duration = duration
|
||||
@@ -38,7 +36,6 @@ class ExpirationTimerUpdate() : ControlMessage() {
|
||||
this.duration = duration
|
||||
}
|
||||
|
||||
// validation
|
||||
override fun isValid(): Boolean {
|
||||
if (!super.isValid()) return false
|
||||
return duration != null
|
||||
@@ -58,7 +55,7 @@ class ExpirationTimerUpdate() : ControlMessage() {
|
||||
dataMessageProto.syncTarget = syncTarget
|
||||
}
|
||||
// Group context
|
||||
if (MessagingConfiguration.shared.storage.isClosedGroup(recipient!!)) {
|
||||
if (MessagingModuleConfiguration.shared.storage.isClosedGroup(recipient!!)) {
|
||||
try {
|
||||
setGroupContext(dataMessageProto)
|
||||
} catch(e: Exception) {
|
||||
|
@@ -4,7 +4,6 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
|
||||
class ReadReceipt() : ControlMessage() {
|
||||
|
||||
var timestamps: List<Long>? = null
|
||||
|
||||
companion object {
|
||||
@@ -19,12 +18,10 @@ class ReadReceipt() : ControlMessage() {
|
||||
}
|
||||
}
|
||||
|
||||
//constructor
|
||||
internal constructor(timestamps: List<Long>?) : this() {
|
||||
this.timestamps = timestamps
|
||||
}
|
||||
|
||||
// validation
|
||||
override fun isValid(): Boolean {
|
||||
if (!super.isValid()) return false
|
||||
val timestamps = timestamps ?: return false
|
||||
|
@@ -4,8 +4,8 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
|
||||
class TypingIndicator() : ControlMessage() {
|
||||
|
||||
override val ttl: Long = 30 * 1000
|
||||
var kind: Kind? = null
|
||||
|
||||
companion object {
|
||||
const val TAG = "TypingIndicator"
|
||||
@@ -17,11 +17,8 @@ class TypingIndicator() : ControlMessage() {
|
||||
}
|
||||
}
|
||||
|
||||
// Kind enum
|
||||
enum class Kind {
|
||||
STARTED,
|
||||
STOPPED,
|
||||
;
|
||||
STARTED, STOPPED;
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@@ -40,14 +37,10 @@ class TypingIndicator() : ControlMessage() {
|
||||
}
|
||||
}
|
||||
|
||||
var kind: Kind? = null
|
||||
|
||||
//constructor
|
||||
internal constructor(kind: Kind) : this() {
|
||||
this.kind = kind
|
||||
}
|
||||
|
||||
// validation
|
||||
override fun isValid(): Boolean {
|
||||
if (!super.isValid()) return false
|
||||
return kind != null
|
||||
|
@@ -1,7 +1,5 @@
|
||||
package org.session.libsession.messaging.messages.signal;
|
||||
|
||||
import static org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext;
|
||||
|
||||
public class IncomingGroupMessage extends IncomingTextMessage {
|
||||
|
||||
private final String groupID;
|
||||
|
@@ -3,10 +3,10 @@ package org.session.libsession.messaging.messages.signal;
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage;
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment;
|
||||
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage;
|
||||
import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage;
|
||||
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
|
||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
|
||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
|
||||
import org.session.libsession.utilities.GroupUtil;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
|
@@ -6,14 +6,10 @@ import androidx.annotation.Nullable;
|
||||
import org.session.libsession.messaging.threads.DistributionTypes;
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
||||
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
|
||||
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
|
||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
|
||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||
import org.session.libsignal.utilities.Base64;
|
||||
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
|
@@ -9,7 +9,7 @@ import org.session.libsession.database.documents.IdentityKeyMismatch;
|
||||
import org.session.libsession.database.documents.NetworkFailure;
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
||||
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
|
||||
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
|
||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
|
||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||
|
||||
|
@@ -5,7 +5,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
||||
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
|
||||
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
|
||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
|
||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient;
|
||||
|
||||
|
@@ -11,7 +11,6 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
import java.io.File
|
||||
|
||||
class Attachment {
|
||||
|
||||
var fileName: String? = null
|
||||
var contentType: String? = null
|
||||
var key: ByteArray? = null
|
||||
@@ -23,6 +22,7 @@ class Attachment {
|
||||
var url: String? = null
|
||||
|
||||
companion object {
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.AttachmentPointer): Attachment {
|
||||
val result = Attachment()
|
||||
result.fileName = proto.fileName
|
||||
@@ -88,7 +88,6 @@ class Attachment {
|
||||
GENERIC
|
||||
}
|
||||
|
||||
// validation
|
||||
fun isValid(): Boolean {
|
||||
// key and digest can be nil for open group attachments
|
||||
return (contentType != null && kind != null && size != null && sizeInBytes != null && url != null)
|
||||
|
@@ -1,17 +0,0 @@
|
||||
package org.session.libsession.messaging.messages.visible
|
||||
|
||||
import org.session.libsession.database.MessageDataProvider
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
class Contact() {
|
||||
|
||||
companion object {
|
||||
fun fromProto(proto: SignalServiceProtos.Content): Contact? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
fun toProto(): SignalServiceProtos.DataMessage.Contact? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@@ -1,12 +1,11 @@
|
||||
package org.session.libsession.messaging.messages.visible
|
||||
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview as SignalLinkPreiview
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview as SignalLinkPreiview
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
class LinkPreview() {
|
||||
|
||||
var title: String? = null
|
||||
var url: String? = null
|
||||
var attachmentID: Long? = 0
|
||||
@@ -29,15 +28,12 @@ class LinkPreview() {
|
||||
}
|
||||
}
|
||||
|
||||
//constructor
|
||||
internal constructor(title: String?, url: String, attachmentID: Long?) : this() {
|
||||
this.title = title
|
||||
this.url = url
|
||||
this.attachmentID = attachmentID
|
||||
}
|
||||
|
||||
|
||||
// validation
|
||||
fun isValid(): Boolean {
|
||||
return (title != null && url != null && attachmentID != null)
|
||||
}
|
||||
@@ -53,7 +49,7 @@ class LinkPreview() {
|
||||
title?.let { linkPreviewProto.title = title }
|
||||
val attachmentID = attachmentID
|
||||
attachmentID?.let {
|
||||
MessagingConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(attachmentID)?.let {
|
||||
MessagingModuleConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(attachmentID)?.let {
|
||||
val attachmentProto = Attachment.createAttachmentPointer(it)
|
||||
linkPreviewProto.image = attachmentProto
|
||||
}
|
||||
|
@@ -5,7 +5,6 @@ import org.session.libsignal.utilities.logging.Log
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
class Profile() {
|
||||
|
||||
var displayName: String? = null
|
||||
var profileKey: ByteArray? = null
|
||||
var profilePictureURL: String? = null
|
||||
@@ -27,7 +26,6 @@ class Profile() {
|
||||
}
|
||||
}
|
||||
|
||||
//constructor
|
||||
internal constructor(displayName: String, profileKey: ByteArray? = null, profilePictureURL: String? = null) : this() {
|
||||
this.displayName = displayName
|
||||
this.profileKey = profileKey
|
||||
|
@@ -1,14 +1,13 @@
|
||||
package org.session.libsession.messaging.messages.visible
|
||||
|
||||
import com.goterl.lazycode.lazysodium.BuildConfig
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
class Quote() {
|
||||
|
||||
var timestamp: Long? = 0
|
||||
var publicKey: String? = null
|
||||
var text: String? = null
|
||||
@@ -34,7 +33,6 @@ class Quote() {
|
||||
}
|
||||
}
|
||||
|
||||
//constructor
|
||||
internal constructor(timestamp: Long, publicKey: String, text: String?, attachmentID: Long?) : this() {
|
||||
this.timestamp = timestamp
|
||||
this.publicKey = publicKey
|
||||
@@ -42,7 +40,6 @@ class Quote() {
|
||||
this.attachmentID = attachmentID
|
||||
}
|
||||
|
||||
// validation
|
||||
fun isValid(): Boolean {
|
||||
return (timestamp != null && publicKey != null)
|
||||
}
|
||||
@@ -70,7 +67,7 @@ class Quote() {
|
||||
|
||||
private fun addAttachmentsIfNeeded(quoteProto: SignalServiceProtos.DataMessage.Quote.Builder) {
|
||||
if (attachmentID == null) return
|
||||
val attachment = MessagingConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(attachmentID!!)
|
||||
val attachment = MessagingModuleConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(attachmentID!!)
|
||||
if (attachment == null) {
|
||||
Log.w(TAG, "Ignoring invalid attachment for quoted message.")
|
||||
return
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package org.session.libsession.messaging.messages.visible
|
||||
|
||||
import com.goterl.lazycode.lazysodium.BuildConfig
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.messages.Message
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
||||
import org.session.libsession.messaging.threads.Address
|
||||
@@ -12,13 +12,11 @@ import org.session.libsignal.utilities.logging.Log
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment
|
||||
|
||||
class VisibleMessage : Message() {
|
||||
|
||||
var syncTarget: String? = null
|
||||
var text: String? = null
|
||||
val attachmentIDs: MutableList<Long> = mutableListOf()
|
||||
var quote: Quote? = null
|
||||
var linkPreview: LinkPreview? = null
|
||||
var contact: Contact? = null
|
||||
var profile: Profile? = null
|
||||
|
||||
override val isSelfSendValid: Boolean = true
|
||||
@@ -60,10 +58,9 @@ class VisibleMessage : Message() {
|
||||
}
|
||||
|
||||
fun isMediaMessage(): Boolean {
|
||||
return attachmentIDs.isNotEmpty() || quote != null || linkPreview != null || contact != null
|
||||
return attachmentIDs.isNotEmpty() || quote != null || linkPreview != null
|
||||
}
|
||||
|
||||
// validation
|
||||
override fun isValid(): Boolean {
|
||||
if (!super.isValid()) return false
|
||||
if (attachmentIDs.isNotEmpty()) return true
|
||||
@@ -98,7 +95,7 @@ class VisibleMessage : Message() {
|
||||
}
|
||||
}
|
||||
//Attachments
|
||||
val attachments = attachmentIDs.mapNotNull { MessagingConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(it) }
|
||||
val attachments = attachmentIDs.mapNotNull { MessagingModuleConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(it) }
|
||||
if (!attachments.all { !it.url.isNullOrEmpty() }) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
//TODO equivalent to iOS's preconditionFailure
|
||||
@@ -111,8 +108,8 @@ class VisibleMessage : Message() {
|
||||
// Expiration timer
|
||||
// TODO: We * want * expiration timer updates to be explicit. But currently Android will disable the expiration timer for a conversation
|
||||
// if it receives a message without the current expiration timer value attached to it...
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val expiration = if (storage.isClosedGroup(recipient!!)) Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(recipient!!)), false).expireMessages
|
||||
else Recipient.from(context, Address.fromSerialized(recipient!!), false).expireMessages
|
||||
dataMessage.expireTimer = expiration
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package org.session.libsession.messaging.opengroups
|
||||
package org.session.libsession.messaging.open_groups
|
||||
|
||||
import org.session.libsignal.service.loki.api.opengroups.PublicChat
|
||||
import org.session.libsignal.utilities.JsonUtil
|
||||
|
||||
data class OpenGroup(
|
||||
@@ -14,9 +13,6 @@ data class OpenGroup(
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic fun from(publicChat: PublicChat): OpenGroup =
|
||||
OpenGroup(publicChat.channel, publicChat.server, publicChat.displayName, publicChat.isDeletable)
|
||||
|
||||
@JvmStatic fun getId(channel: Long, server: String): String {
|
||||
return "$server.$channel"
|
||||
}
|
@@ -1,19 +1,20 @@
|
||||
package org.session.libsession.messaging.opengroups
|
||||
package org.session.libsession.messaging.open_groups
|
||||
|
||||
import nl.komponents.kovenant.Kovenant
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.deferred
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import nl.komponents.kovenant.then
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.fileserver.FileServerAPI
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.file_server.FileServerAPI
|
||||
import org.session.libsession.messaging.utilities.DotNetAPI
|
||||
import org.session.libsignal.service.loki.utilities.DownloadUtilities
|
||||
import org.session.libsession.utilities.DownloadUtilities
|
||||
import org.session.libsignal.service.loki.utilities.retryIfNeeded
|
||||
import org.session.libsignal.utilities.*
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@@ -51,7 +52,7 @@ object OpenGroupAPI: DotNetAPI() {
|
||||
// region Public API
|
||||
fun getMessages(channel: Long, server: String): Promise<List<OpenGroupMessage>, Exception> {
|
||||
Log.d("Loki", "Getting messages for open group with ID: $channel on server: $server.")
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val parameters = mutableMapOf<String, Any>( "include_annotations" to 1 )
|
||||
val lastMessageServerID = storage.getLastMessageServerID(channel, server)
|
||||
if (lastMessageServerID != null) {
|
||||
@@ -157,7 +158,7 @@ object OpenGroupAPI: DotNetAPI() {
|
||||
@JvmStatic
|
||||
fun getDeletedMessageServerIDs(channel: Long, server: String): Promise<List<Long>, Exception> {
|
||||
Log.d("Loki", "Getting deleted messages for open group with ID: $channel on server: $server.")
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val parameters = mutableMapOf<String, Any>()
|
||||
val lastDeletionServerID = storage.getLastDeletionServerID(channel, server)
|
||||
if (lastDeletionServerID != null) {
|
||||
@@ -190,7 +191,7 @@ object OpenGroupAPI: DotNetAPI() {
|
||||
@JvmStatic
|
||||
fun sendMessage(message: OpenGroupMessage, channel: Long, server: String): Promise<OpenGroupMessage, Exception> {
|
||||
val deferred = deferred<OpenGroupMessage, Exception>()
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val userKeyPair = storage.getUserKeyPair() ?: throw Error.Generic
|
||||
val userDisplayName = storage.getUserDisplayName() ?: throw Error.Generic
|
||||
ThreadUtils.queue {
|
||||
@@ -286,7 +287,7 @@ object OpenGroupAPI: DotNetAPI() {
|
||||
val memberCount = countInfo["subscribers"] as? Int ?: (countInfo["subscribers"] as? Long)?.toInt() ?: (countInfo["subscribers"] as String).toInt()
|
||||
val profilePictureURL = info["avatar"] as String
|
||||
val publicChatInfo = OpenGroupInfo(displayName, profilePictureURL, memberCount)
|
||||
MessagingConfiguration.shared.storage.setUserCount(channel, server, memberCount)
|
||||
MessagingModuleConfiguration.shared.storage.setUserCount(channel, server, memberCount)
|
||||
publicChatInfo
|
||||
} catch (exception: Exception) {
|
||||
Log.d("Loki", "Couldn't parse info for open group with ID: $channel on server: $server.")
|
||||
@@ -298,7 +299,7 @@ object OpenGroupAPI: DotNetAPI() {
|
||||
|
||||
@JvmStatic
|
||||
fun updateProfileIfNeeded(channel: Long, server: String, groupID: String, info: OpenGroupInfo, isForcedUpdate: Boolean) {
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
storage.setUserCount(channel, server, info.memberCount)
|
||||
storage.updateTitle(groupID, info.displayName)
|
||||
// Download and update profile picture if needed
|
@@ -1,4 +1,4 @@
|
||||
package org.session.libsession.messaging.opengroups
|
||||
package org.session.libsession.messaging.open_groups
|
||||
|
||||
data class OpenGroupInfo (
|
||||
val displayName: String,
|
@@ -1,6 +1,6 @@
|
||||
package org.session.libsession.messaging.opengroups
|
||||
package org.session.libsession.messaging.open_groups
|
||||
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||
import org.session.libsignal.utilities.Hex
|
||||
@@ -24,7 +24,7 @@ data class OpenGroupMessage(
|
||||
// region Settings
|
||||
companion object {
|
||||
fun from(message: VisibleMessage, server: String): OpenGroupMessage? {
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val userPublicKey = storage.getUserPublicKey() ?: return null
|
||||
val attachmentIDs = message.attachmentIDs
|
||||
// Validation
|
||||
@@ -50,7 +50,7 @@ data class OpenGroupMessage(
|
||||
linkPreview?.let {
|
||||
if (!linkPreview.isValid()) { return@let }
|
||||
val attachmentID = linkPreview.attachmentID ?: return@let
|
||||
val attachment = MessagingConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(attachmentID) ?: return@let
|
||||
val attachment = MessagingModuleConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(attachmentID) ?: return@let
|
||||
val openGroupLinkPreview = Attachment(
|
||||
Attachment.Kind.LinkPreview,
|
||||
server,
|
||||
@@ -69,7 +69,7 @@ data class OpenGroupMessage(
|
||||
}
|
||||
// Attachments
|
||||
val attachments = message.attachmentIDs.mapNotNull {
|
||||
val attachment = MessagingConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(it) ?: return@mapNotNull null
|
||||
val attachment = MessagingModuleConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(it) ?: return@mapNotNull null
|
||||
return@mapNotNull Attachment(
|
||||
Attachment.Kind.Attachment,
|
||||
server,
|
@@ -1,6 +1,6 @@
|
||||
package org.session.libsession.messaging.sending_receiving
|
||||
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.messages.Message
|
||||
import org.session.libsession.messaging.messages.control.*
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
@@ -44,7 +44,7 @@ object MessageReceiver {
|
||||
}
|
||||
|
||||
internal fun parse(data: ByteArray, openGroupServerID: Long?, isRetry: Boolean = false): Pair<Message, SignalServiceProtos.Content> {
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val userPublicKey = storage.getUserPublicKey()
|
||||
val isOpenGroupMessage = openGroupServerID != null
|
||||
// Parse the envelope
|
||||
@@ -63,18 +63,18 @@ object MessageReceiver {
|
||||
sender = envelope.source
|
||||
} else {
|
||||
when (envelope.type) {
|
||||
SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER -> {
|
||||
val userX25519KeyPair = MessagingConfiguration.shared.storage.getUserX25519KeyPair()
|
||||
SignalServiceProtos.Envelope.Type.SESSION_MESSAGE -> {
|
||||
val userX25519KeyPair = MessagingModuleConfiguration.shared.storage.getUserX25519KeyPair()
|
||||
val decryptionResult = MessageReceiverDecryption.decryptWithSessionProtocol(ciphertext.toByteArray(), userX25519KeyPair)
|
||||
plaintext = decryptionResult.first
|
||||
sender = decryptionResult.second
|
||||
}
|
||||
SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT -> {
|
||||
SignalServiceProtos.Envelope.Type.CLOSED_GROUP_MESSAGE -> {
|
||||
val hexEncodedGroupPublicKey = envelope.source
|
||||
if (hexEncodedGroupPublicKey == null || !MessagingConfiguration.shared.storage.isClosedGroup(hexEncodedGroupPublicKey)) {
|
||||
if (hexEncodedGroupPublicKey == null || !MessagingModuleConfiguration.shared.storage.isClosedGroup(hexEncodedGroupPublicKey)) {
|
||||
throw Error.InvalidGroupPublicKey
|
||||
}
|
||||
val encryptionKeyPairs = MessagingConfiguration.shared.storage.getClosedGroupEncryptionKeyPairs(hexEncodedGroupPublicKey)
|
||||
val encryptionKeyPairs = MessagingModuleConfiguration.shared.storage.getClosedGroupEncryptionKeyPairs(hexEncodedGroupPublicKey)
|
||||
if (encryptionKeyPairs.isEmpty()) { throw Error.NoGroupKeyPair }
|
||||
// Loop through all known group key pairs in reverse order (i.e. try the latest key pair first (which'll more than
|
||||
// likely be the one we want) but try older ones in case that didn't work)
|
||||
@@ -107,12 +107,12 @@ object MessageReceiver {
|
||||
val proto = SignalServiceProtos.Content.parseFrom(PushTransportDetails.getStrippedPaddingMessageBody(plaintext))
|
||||
// Parse the message
|
||||
val message: Message = ReadReceipt.fromProto(proto) ?:
|
||||
TypingIndicator.fromProto(proto) ?:
|
||||
ClosedGroupControlMessage.fromProto(proto) ?:
|
||||
DataExtractionNotification.fromProto(proto) ?:
|
||||
ExpirationTimerUpdate.fromProto(proto) ?:
|
||||
ConfigurationMessage.fromProto(proto) ?:
|
||||
VisibleMessage.fromProto(proto) ?: throw Error.UnknownMessage
|
||||
TypingIndicator.fromProto(proto) ?:
|
||||
ClosedGroupControlMessage.fromProto(proto) ?:
|
||||
DataExtractionNotification.fromProto(proto) ?:
|
||||
ExpirationTimerUpdate.fromProto(proto) ?:
|
||||
ConfigurationMessage.fromProto(proto) ?:
|
||||
VisibleMessage.fromProto(proto) ?: throw Error.UnknownMessage
|
||||
// Ignore self sends if needed
|
||||
if (!message.isSelfSendValid && sender == userPublicKey) throw Error.SelfSend
|
||||
// Guard against control messages in open groups
|
||||
|
@@ -1,11 +1,11 @@
|
||||
package org.session.libsession.messaging.sending_receiving
|
||||
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
||||
|
||||
object MessageReceiverDecryption {
|
||||
|
||||
internal fun decryptWithSessionProtocol(ciphertext: ByteArray, x25519KeyPair: ECKeyPair): Pair<ByteArray, String> {
|
||||
return MessagingConfiguration.shared.sessionProtocol.decrypt(ciphertext, x25519KeyPair)
|
||||
return MessagingModuleConfiguration.shared.sessionProtocol.decrypt(ciphertext, x25519KeyPair)
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@ package org.session.libsession.messaging.sending_receiving
|
||||
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.deferred
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.jobs.MessageSendJob
|
||||
import org.session.libsession.messaging.jobs.NotifyPNServerJob
|
||||
@@ -12,26 +12,24 @@ import org.session.libsession.messaging.messages.control.ClosedGroupControlMessa
|
||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
||||
import org.session.libsession.messaging.messages.visible.*
|
||||
import org.session.libsession.messaging.opengroups.OpenGroupAPI
|
||||
import org.session.libsession.messaging.opengroups.OpenGroupMessage
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPI
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupMessage
|
||||
import org.session.libsession.messaging.threads.Address
|
||||
import org.session.libsession.messaging.utilities.MessageWrapper
|
||||
import org.session.libsession.snode.RawResponsePromise
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.snode.SnodeConfiguration
|
||||
import org.session.libsession.snode.SnodeModule
|
||||
import org.session.libsession.snode.SnodeMessage
|
||||
import org.session.libsession.utilities.SSKEnvironment
|
||||
import org.session.libsignal.service.internal.push.PushTransportDetails
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
import org.session.libsignal.service.loki.api.crypto.ProofOfWork
|
||||
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment
|
||||
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview as SignalLinkPreview
|
||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview as SignalLinkPreview
|
||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote
|
||||
|
||||
|
||||
object MessageSender {
|
||||
|
||||
// Error
|
||||
@@ -74,7 +72,7 @@ object MessageSender {
|
||||
private fun sendToSnodeDestination(destination: Destination, message: Message, isSyncMessage: Boolean = false): Promise<Unit, Exception> {
|
||||
val deferred = deferred<Unit, Exception>()
|
||||
val promise = deferred.promise
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val userPublicKey = storage.getUserPublicKey()
|
||||
// Set the timestamp, sender and recipient
|
||||
message.sentTimestamp ?: run { message.sentTimestamp = System.currentTimeMillis() } /* Visible messages will already have their sent timestamp set */
|
||||
@@ -84,7 +82,7 @@ object MessageSender {
|
||||
fun handleFailure(error: Exception) {
|
||||
handleFailedMessageSend(message, error)
|
||||
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
|
||||
SnodeConfiguration.shared.broadcaster.broadcast("messageFailed", message.sentTimestamp!!)
|
||||
SnodeModule.shared.broadcaster.broadcast("messageFailed", message.sentTimestamp!!)
|
||||
}
|
||||
deferred.reject(error)
|
||||
}
|
||||
@@ -127,7 +125,7 @@ object MessageSender {
|
||||
when (destination) {
|
||||
is Destination.Contact -> ciphertext = MessageSenderEncryption.encryptWithSessionProtocol(plaintext, destination.publicKey)
|
||||
is Destination.ClosedGroup -> {
|
||||
val encryptionKeyPair = MessagingConfiguration.shared.storage.getLatestClosedGroupEncryptionKeyPair(destination.groupPublicKey)!!
|
||||
val encryptionKeyPair = MessagingModuleConfiguration.shared.storage.getLatestClosedGroupEncryptionKeyPair(destination.groupPublicKey)!!
|
||||
ciphertext = MessageSenderEncryption.encryptWithSessionProtocol(plaintext, encryptionKeyPair.hexEncodedPublicKey)
|
||||
}
|
||||
is Destination.OpenGroup -> throw Error.PreconditionFailure("Destination should not be open groups!")
|
||||
@@ -137,27 +135,24 @@ object MessageSender {
|
||||
val senderPublicKey: String
|
||||
when (destination) {
|
||||
is Destination.Contact -> {
|
||||
kind = SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER
|
||||
kind = SignalServiceProtos.Envelope.Type.SESSION_MESSAGE
|
||||
senderPublicKey = ""
|
||||
}
|
||||
is Destination.ClosedGroup -> {
|
||||
kind = SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT
|
||||
kind = SignalServiceProtos.Envelope.Type.CLOSED_GROUP_MESSAGE
|
||||
senderPublicKey = destination.groupPublicKey
|
||||
}
|
||||
is Destination.OpenGroup -> throw Error.PreconditionFailure("Destination should not be open groups!")
|
||||
}
|
||||
val wrappedMessage = MessageWrapper.wrap(kind, message.sentTimestamp!!, senderPublicKey, ciphertext)
|
||||
// Calculate proof of work
|
||||
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
|
||||
SnodeConfiguration.shared.broadcaster.broadcast("calculatingPoW", message.sentTimestamp!!)
|
||||
}
|
||||
val recipient = message.recipient!!
|
||||
val base64EncodedData = Base64.encodeBytes(wrappedMessage)
|
||||
val nonce = ProofOfWork.calculate(base64EncodedData, recipient, message.sentTimestamp!!, message.ttl.toInt()) ?: throw Error.ProofOfWorkCalculationFailed
|
||||
// Send the result
|
||||
val snodeMessage = SnodeMessage(recipient, base64EncodedData, message.ttl, message.sentTimestamp!!, nonce)
|
||||
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
|
||||
SnodeConfiguration.shared.broadcaster.broadcast("sendingMessage", message.sentTimestamp!!)
|
||||
SnodeModule.shared.broadcaster.broadcast("calculatingPoW", message.sentTimestamp!!)
|
||||
}
|
||||
val base64EncodedData = Base64.encodeBytes(wrappedMessage)
|
||||
val snodeMessage = SnodeMessage(message.recipient!!, base64EncodedData, message.ttl, message.sentTimestamp!!)
|
||||
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
|
||||
SnodeModule.shared.broadcaster.broadcast("sendingMessage", message.sentTimestamp!!)
|
||||
}
|
||||
SnodeAPI.sendMessage(snodeMessage).success { promises: Set<RawResponsePromise> ->
|
||||
var isSuccess = false
|
||||
@@ -168,7 +163,7 @@ object MessageSender {
|
||||
if (isSuccess) { return@success } // Succeed as soon as the first promise succeeds
|
||||
isSuccess = true
|
||||
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
|
||||
SnodeConfiguration.shared.broadcaster.broadcast("messageSent", message.sentTimestamp!!)
|
||||
SnodeModule.shared.broadcaster.broadcast("messageSent", message.sentTimestamp!!)
|
||||
}
|
||||
handleSuccessfulMessageSend(message, destination, isSyncMessage)
|
||||
var shouldNotify = (message is VisibleMessage && !isSyncMessage)
|
||||
@@ -200,7 +195,7 @@ object MessageSender {
|
||||
// Open Groups
|
||||
private fun sendToOpenGroupDestination(destination: Destination, message: Message): Promise<Unit, Exception> {
|
||||
val deferred = deferred<Unit, Exception>()
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
message.sentTimestamp ?: run { message.sentTimestamp = System.currentTimeMillis() }
|
||||
message.sender = storage.getUserPublicKey()
|
||||
// Set the failure handler (need it here already for precondition failure handling)
|
||||
@@ -244,7 +239,7 @@ object MessageSender {
|
||||
|
||||
// Result Handling
|
||||
fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false) {
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val userPublicKey = storage.getUserPublicKey()!!
|
||||
val messageId = storage.getMessageIdInDatabase(message.sentTimestamp!!, message.sender?:userPublicKey) ?: return
|
||||
// Ignore future self-sends
|
||||
@@ -272,7 +267,7 @@ object MessageSender {
|
||||
}
|
||||
|
||||
fun handleFailedMessageSend(message: Message, error: Exception) {
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val userPublicKey = storage.getUserPublicKey()!!
|
||||
storage.setErrorMessage(message.sentTimestamp!!, message.sender?:userPublicKey, error)
|
||||
}
|
||||
@@ -280,7 +275,7 @@ object MessageSender {
|
||||
// Convenience
|
||||
@JvmStatic
|
||||
fun send(message: VisibleMessage, address: Address, attachments: List<SignalAttachment>, quote: SignalQuote?, linkPreview: SignalLinkPreview?) {
|
||||
val dataProvider = MessagingConfiguration.shared.messageDataProvider
|
||||
val dataProvider = MessagingModuleConfiguration.shared.messageDataProvider
|
||||
val attachmentIDs = dataProvider.getAttachmentIDsFor(message.id!!)
|
||||
message.attachmentIDs.addAll(attachmentIDs)
|
||||
message.quote = Quote.from(quote)
|
||||
@@ -298,7 +293,7 @@ object MessageSender {
|
||||
|
||||
@JvmStatic
|
||||
fun send(message: Message, address: Address) {
|
||||
val threadID = MessagingConfiguration.shared.storage.getOrCreateThreadIdFor(address)
|
||||
val threadID = MessagingModuleConfiguration.shared.storage.getOrCreateThreadIdFor(address)
|
||||
message.threadID = threadID
|
||||
val destination = Destination.from(address)
|
||||
val job = MessageSendJob(message, destination)
|
||||
@@ -306,13 +301,13 @@ object MessageSender {
|
||||
}
|
||||
|
||||
fun sendNonDurably(message: VisibleMessage, attachments: List<SignalAttachment>, address: Address): Promise<Unit, Exception> {
|
||||
val attachmentIDs = MessagingConfiguration.shared.messageDataProvider.getAttachmentIDsFor(message.id!!)
|
||||
val attachmentIDs = MessagingModuleConfiguration.shared.messageDataProvider.getAttachmentIDsFor(message.id!!)
|
||||
message.attachmentIDs.addAll(attachmentIDs)
|
||||
return sendNonDurably(message, address)
|
||||
}
|
||||
|
||||
fun sendNonDurably(message: Message, address: Address): Promise<Unit, Exception> {
|
||||
val threadID = MessagingConfiguration.shared.storage.getOrCreateThreadIdFor(address)
|
||||
val threadID = MessagingModuleConfiguration.shared.storage.getOrCreateThreadIdFor(address)
|
||||
message.threadID = threadID
|
||||
val destination = Destination.from(address)
|
||||
return send(message, destination)
|
||||
|
@@ -5,7 +5,7 @@ package org.session.libsession.messaging.sending_receiving
|
||||
import com.google.protobuf.ByteString
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.deferred
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender.Error
|
||||
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI
|
||||
@@ -32,8 +32,8 @@ fun MessageSender.create(name: String, members: Collection<String>): Promise<Str
|
||||
val deferred = deferred<String, Exception>()
|
||||
ThreadUtils.queue {
|
||||
// Prepare
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val userPublicKey = storage.getUserPublicKey()!!
|
||||
val membersAsData = members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }
|
||||
// Generate the group's public key
|
||||
@@ -72,8 +72,8 @@ fun MessageSender.create(name: String, members: Collection<String>): Promise<Str
|
||||
}
|
||||
|
||||
fun MessageSender.update(groupPublicKey: String, members: List<String>, name: String) {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
val group = storage.getGroup(groupID) ?: run {
|
||||
Log.d("Loki", "Can't update nonexistent closed group.")
|
||||
@@ -90,8 +90,8 @@ fun MessageSender.update(groupPublicKey: String, members: List<String>, name: St
|
||||
}
|
||||
|
||||
fun MessageSender.setName(groupPublicKey: String, newName: String) {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
val group = storage.getGroup(groupID) ?: run {
|
||||
Log.d("Loki", "Can't change name for nonexistent closed group.")
|
||||
@@ -114,8 +114,8 @@ fun MessageSender.setName(groupPublicKey: String, newName: String) {
|
||||
}
|
||||
|
||||
fun MessageSender.addMembers(groupPublicKey: String, membersToAdd: List<String>) {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
val group = storage.getGroup(groupID) ?: run {
|
||||
Log.d("Loki", "Can't add members to nonexistent closed group.")
|
||||
@@ -157,8 +157,8 @@ fun MessageSender.addMembers(groupPublicKey: String, membersToAdd: List<String>)
|
||||
}
|
||||
|
||||
fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List<String>) {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val userPublicKey = storage.getUserPublicKey()!!
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
val group = storage.getGroup(groupID) ?: run {
|
||||
@@ -169,15 +169,24 @@ fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List<St
|
||||
Log.d("Loki", "Invalid closed group update.")
|
||||
throw Error.InvalidClosedGroupUpdate
|
||||
}
|
||||
val updatedMembers = group.members.map { it.serialize() }.toSet() - membersToRemove
|
||||
// Save the new group members
|
||||
storage.updateMembers(groupID, updatedMembers.map { Address.fromSerialized(it) })
|
||||
val removeMembersAsData = membersToRemove.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }
|
||||
val admins = group.admins.map { it.serialize() }
|
||||
if (!admins.contains(userPublicKey)) {
|
||||
Log.d("Loki", "Only an admin can remove members from a group.")
|
||||
throw Error.InvalidClosedGroupUpdate
|
||||
}
|
||||
val updatedMembers = group.members.map { it.serialize() }.toSet() - membersToRemove
|
||||
if (membersToRemove.any { it in admins } && updatedMembers.isNotEmpty()) {
|
||||
Log.d("Loki", "Can't remove admin from closed group unless the group is destroyed entirely.")
|
||||
throw Error.InvalidClosedGroupUpdate
|
||||
}
|
||||
|
||||
// Save the new group members
|
||||
storage.updateMembers(groupID, updatedMembers.map { Address.fromSerialized(it) })
|
||||
// Update the zombie list
|
||||
val oldZombies = storage.getZombieMember(groupID)
|
||||
storage.updateZombieMembers(groupID, oldZombies.minus(membersToRemove).map { Address.fromSerialized(it) })
|
||||
|
||||
val removeMembersAsData = membersToRemove.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }
|
||||
val name = group.title
|
||||
// Send the update to the group
|
||||
val memberUpdateKind = ClosedGroupControlMessage.Kind.MembersRemoved(removeMembersAsData)
|
||||
@@ -185,21 +194,28 @@ fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List<St
|
||||
val closedGroupControlMessage = ClosedGroupControlMessage(memberUpdateKind)
|
||||
closedGroupControlMessage.sentTimestamp = sentTime
|
||||
send(closedGroupControlMessage, Address.fromSerialized(groupID))
|
||||
val isCurrentUserAdmin = admins.contains(userPublicKey)
|
||||
if (isCurrentUserAdmin) {
|
||||
generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMembers)
|
||||
}
|
||||
|
||||
// Send the new encryption key pair to the remaining group members
|
||||
// At this stage we know the user is admin, no need to test
|
||||
generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMembers)
|
||||
// Notify the user
|
||||
val infoType = SignalServiceGroup.Type.MEMBER_REMOVED
|
||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||
storage.insertOutgoingInfoMessage(context, groupID, infoType, name, membersToRemove, admins, threadID, sentTime)
|
||||
|
||||
// Insert an outgoing notification
|
||||
// we don't display zombie members in the notification as users have already been notified when those members left
|
||||
val notificationMembers = membersToRemove.minus(oldZombies)
|
||||
if (notificationMembers.isNotEmpty()) {
|
||||
// no notification to display when only zombies have been removed
|
||||
val infoType = SignalServiceGroup.Type.MEMBER_REMOVED
|
||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||
storage.insertOutgoingInfoMessage(context, groupID, infoType, name, notificationMembers, admins, threadID, sentTime)
|
||||
}
|
||||
}
|
||||
|
||||
fun MessageSender.leave(groupPublicKey: String, notifyUser: Boolean = true): Promise<Unit, Exception> {
|
||||
val deferred = deferred<Unit, Exception>()
|
||||
ThreadUtils.queue {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
val group = storage.getGroup(groupID) ?: return@queue deferred.reject(Error.NoThread)
|
||||
@@ -230,7 +246,7 @@ fun MessageSender.leave(groupPublicKey: String, notifyUser: Boolean = true): Pro
|
||||
|
||||
fun MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey: String, targetMembers: Collection<String>) {
|
||||
// Prepare
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val userPublicKey = storage.getUserPublicKey()!!
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
val group = storage.getGroup(groupID) ?: run {
|
||||
@@ -278,25 +294,8 @@ fun MessageSender.sendEncryptionKeyPair(groupPublicKey: String, newKeyPair: ECKe
|
||||
}
|
||||
}
|
||||
|
||||
/// Note: Shouldn't currently be in use.
|
||||
fun MessageSender.requestEncryptionKeyPair(groupPublicKey: String) {
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
val group = storage.getGroup(groupID) ?: run {
|
||||
Log.d("Loki", "Can't request encryption key pair for nonexistent closed group.")
|
||||
throw Error.NoThread
|
||||
}
|
||||
val members = group.members.map { it.serialize() }.toSet()
|
||||
if (!members.contains(storage.getUserPublicKey()!!)) return
|
||||
// Send the request to the group
|
||||
val sentTime = System.currentTimeMillis()
|
||||
val closedGroupControlMessage = ClosedGroupControlMessage(ClosedGroupControlMessage.Kind.EncryptionKeyPairRequest())
|
||||
closedGroupControlMessage.sentTimestamp = sentTime
|
||||
send(closedGroupControlMessage, Address.fromSerialized(groupID))
|
||||
}
|
||||
|
||||
fun MessageSender.sendLatestEncryptionKeyPair(publicKey: String, groupPublicKey: String) {
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
val group = storage.getGroup(groupID) ?: run {
|
||||
Log.d("Loki", "Can't send encryption key pair for nonexistent closed group.")
|
@@ -5,7 +5,7 @@ import com.goterl.lazycode.lazysodium.SodiumAndroid
|
||||
import com.goterl.lazycode.lazysodium.interfaces.Box
|
||||
import com.goterl.lazycode.lazysodium.interfaces.Sign
|
||||
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender.Error
|
||||
import org.session.libsession.utilities.KeyPairUtilities
|
||||
|
||||
@@ -26,7 +26,7 @@ object MessageSenderEncryption {
|
||||
* @return the encrypted message.
|
||||
*/
|
||||
internal fun encryptWithSessionProtocol(plaintext: ByteArray, recipientHexEncodedX25519PublicKey: String): ByteArray{
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val userED25519KeyPair = KeyPairUtilities.getUserED25519KeyPair(context) ?: throw Error.NoUserED25519KeyPair
|
||||
val recipientX25519PublicKey = Hex.fromStringCondensed(recipientHexEncodedX25519PublicKey.removing05PrefixIfNeeded())
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package org.session.libsession.messaging.sending_receiving
|
||||
|
||||
import android.text.TextUtils
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.jobs.AttachmentDownloadJob
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.messages.Message
|
||||
@@ -9,8 +9,8 @@ import org.session.libsession.messaging.messages.control.*
|
||||
import org.session.libsession.messaging.messages.visible.Attachment
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment
|
||||
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage
|
||||
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview
|
||||
import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage
|
||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
|
||||
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI
|
||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
||||
import org.session.libsession.messaging.threads.Address
|
||||
@@ -35,7 +35,7 @@ import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
internal fun MessageReceiver.isBlock(publicKey: String): Boolean {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
|
||||
return recipient.isBlocked
|
||||
}
|
||||
@@ -53,7 +53,7 @@ fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content,
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleReadReceipt(message: ReadReceipt) {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
SSKEnvironment.shared.readReceiptManager.processReadReceipts(context, message.sender!!, message.timestamps!!, message.receivedTimestamp!!)
|
||||
}
|
||||
|
||||
@@ -65,23 +65,23 @@ private fun MessageReceiver.handleTypingIndicator(message: TypingIndicator) {
|
||||
}
|
||||
|
||||
fun MessageReceiver.showTypingIndicatorIfNeeded(senderPublicKey: String) {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val address = Address.fromSerialized(senderPublicKey)
|
||||
val threadID = MessagingConfiguration.shared.storage.getThreadIdFor(address) ?: return
|
||||
val threadID = MessagingModuleConfiguration.shared.storage.getThreadIdFor(address) ?: return
|
||||
SSKEnvironment.shared.typingIndicators.didReceiveTypingStartedMessage(context, threadID, address, 1)
|
||||
}
|
||||
|
||||
fun MessageReceiver.hideTypingIndicatorIfNeeded(senderPublicKey: String) {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val address = Address.fromSerialized(senderPublicKey)
|
||||
val threadID = MessagingConfiguration.shared.storage.getThreadIdFor(address) ?: return
|
||||
val threadID = MessagingModuleConfiguration.shared.storage.getThreadIdFor(address) ?: return
|
||||
SSKEnvironment.shared.typingIndicators.didReceiveTypingStoppedMessage(context, threadID, address, 1, false)
|
||||
}
|
||||
|
||||
fun MessageReceiver.cancelTypingIndicatorsIfNeeded(senderPublicKey: String) {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val address = Address.fromSerialized(senderPublicKey)
|
||||
val threadID = MessagingConfiguration.shared.storage.getThreadIdFor(address) ?: return
|
||||
val threadID = MessagingModuleConfiguration.shared.storage.getThreadIdFor(address) ?: return
|
||||
SSKEnvironment.shared.typingIndicators.didReceiveIncomingMessage(context, threadID, address, 1)
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ private fun MessageReceiver.handleDataExtractionNotification(message: DataExtrac
|
||||
// we don't handle data extraction messages for groups (they shouldn't be sent, but in case we filter them here too)
|
||||
if (message.groupPublicKey != null) return
|
||||
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val senderPublicKey = message.sender!!
|
||||
val notification: DataExtractionNotificationInfoMessage = when(message.kind) {
|
||||
is DataExtractionNotification.Kind.Screenshot -> DataExtractionNotificationInfoMessage(DataExtractionNotificationInfoMessage.Kind.SCREENSHOT)
|
||||
@@ -112,8 +112,8 @@ private fun MessageReceiver.handleDataExtractionNotification(message: DataExtrac
|
||||
// Configuration message handling
|
||||
|
||||
private fun handleConfigurationMessage(message: ConfigurationMessage) {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
if (TextSecurePreferences.getConfigurationMessageSynced(context) && !TextSecurePreferences.shouldUpdateProfile(context, message.sentTimestamp!!)) return
|
||||
val userPublicKey = storage.getUserPublicKey()
|
||||
if (userPublicKey == null || message.sender != storage.getUserPublicKey()) return
|
||||
@@ -146,8 +146,8 @@ private fun handleConfigurationMessage(message: ConfigurationMessage) {
|
||||
}
|
||||
|
||||
fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalServiceProtos.Content, openGroupID: String?) {
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val userPublicKey = storage.getUserPublicKey()
|
||||
// Update profile if needed
|
||||
val newProfile = message.profile
|
||||
@@ -178,10 +178,10 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS
|
||||
if (message.quote != null && proto.dataMessage.hasQuote()) {
|
||||
val quote = proto.dataMessage.quote
|
||||
val author = Address.fromSerialized(quote.author)
|
||||
val messageInfo = MessagingConfiguration.shared.messageDataProvider.getMessageForQuote(quote.id, author)
|
||||
val messageInfo = MessagingModuleConfiguration.shared.messageDataProvider.getMessageForQuote(quote.id, author)
|
||||
if (messageInfo != null) {
|
||||
val attachments = if (messageInfo.second) MessagingConfiguration.shared.messageDataProvider.getAttachmentsAndLinkPreviewFor(messageInfo.first) else ArrayList()
|
||||
quoteModel = QuoteModel(quote.id, author, MessagingConfiguration.shared.messageDataProvider.getMessageBodyFor(quote.id, quote.author), false, attachments)
|
||||
val attachments = if (messageInfo.second) MessagingModuleConfiguration.shared.messageDataProvider.getAttachmentsAndLinkPreviewFor(messageInfo.first) else ArrayList()
|
||||
quoteModel = QuoteModel(quote.id, author, MessagingModuleConfiguration.shared.messageDataProvider.getMessageBodyFor(quote.id, quote.author), false, attachments)
|
||||
} else {
|
||||
quoteModel = QuoteModel(quote.id, author, quote.text, true, PointerAttachment.forPointers(proto.dataMessage.quote.attachmentsList))
|
||||
}
|
||||
@@ -231,13 +231,11 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS
|
||||
private fun MessageReceiver.handleClosedGroupControlMessage(message: ClosedGroupControlMessage) {
|
||||
when (message.kind!!) {
|
||||
is ClosedGroupControlMessage.Kind.New -> handleNewClosedGroup(message)
|
||||
is ClosedGroupControlMessage.Kind.Update -> handleClosedGroupUpdated(message)
|
||||
is ClosedGroupControlMessage.Kind.EncryptionKeyPair -> handleClosedGroupEncryptionKeyPair(message)
|
||||
is ClosedGroupControlMessage.Kind.NameChange -> handleClosedGroupNameChanged(message)
|
||||
is ClosedGroupControlMessage.Kind.MembersAdded -> handleClosedGroupMembersAdded(message)
|
||||
is ClosedGroupControlMessage.Kind.MembersRemoved -> handleClosedGroupMembersRemoved(message)
|
||||
is ClosedGroupControlMessage.Kind.MemberLeft -> handleClosedGroupMemberLeft(message)
|
||||
is ClosedGroupControlMessage.Kind.EncryptionKeyPairRequest -> handleClosedGroupEncryptionKeyPairRequest(message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,8 +249,8 @@ private fun MessageReceiver.handleNewClosedGroup(message: ClosedGroupControlMess
|
||||
|
||||
// Parameter @sender:String is just for inserting incoming info message
|
||||
private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPublicKey: String, name: String, encryptionKeyPair: ECKeyPair, members: List<String>, admins: List<String>, formationTimestamp: Long) {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
// Create the group
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
if (storage.getGroup(groupID) != null) {
|
||||
@@ -281,65 +279,13 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli
|
||||
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, storage.getUserPublicKey()!!)
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleClosedGroupUpdated(message: ClosedGroupControlMessage) {
|
||||
// Prepare
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val senderPublicKey = message.sender ?: return
|
||||
val kind = message.kind!! as? ClosedGroupControlMessage.Kind.Update ?: return
|
||||
val groupPublicKey = message.groupPublicKey ?: return
|
||||
val userPublicKey = storage.getUserPublicKey()!!
|
||||
// Unwrap the message
|
||||
val name = kind.name
|
||||
val members = kind.members.map { it.toByteArray().toHexString() }
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
val group = storage.getGroup(groupID) ?: run {
|
||||
Log.d("Loki", "Ignoring closed group info message for nonexistent group.")
|
||||
return
|
||||
}
|
||||
if (!group.isActive) {
|
||||
Log.d("Loki", "Ignoring closed group info message for inactive group")
|
||||
return
|
||||
}
|
||||
val oldMembers = group.members.map { it.serialize() }
|
||||
// Check common group update logic
|
||||
if (!isValidGroupUpdate(group, message.sentTimestamp!!, senderPublicKey)) {
|
||||
return
|
||||
}
|
||||
// Check that the admin wasn't removed unless the group was destroyed entirely
|
||||
if (!members.contains(group.admins.first().toString()) && members.isNotEmpty()) {
|
||||
android.util.Log.d("Loki", "Ignoring invalid closed group update message.")
|
||||
return
|
||||
}
|
||||
// Remove the group from the user's set of public keys to poll for if the current user was removed
|
||||
val wasCurrentUserRemoved = !members.contains(userPublicKey)
|
||||
if (wasCurrentUserRemoved) {
|
||||
disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey)
|
||||
}
|
||||
// Generate and distribute a new encryption key pair if needed
|
||||
val wasAnyUserRemoved = (members.toSet().intersect(oldMembers) != oldMembers.toSet())
|
||||
val isCurrentUserAdmin = group.admins.map { it.toString() }.contains(userPublicKey)
|
||||
if (wasAnyUserRemoved && isCurrentUserAdmin) {
|
||||
MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, members)
|
||||
}
|
||||
// Update the group
|
||||
storage.updateTitle(groupID, name)
|
||||
if (!wasCurrentUserRemoved) {
|
||||
// The call below sets isActive to true, so if the user is leaving we have to use groupDB.remove(...) instead
|
||||
storage.updateMembers(groupID, members.map { Address.fromSerialized(it) })
|
||||
}
|
||||
// Notify the user
|
||||
val wasSenderRemoved = !members.contains(senderPublicKey)
|
||||
val type = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.MEMBER_REMOVED
|
||||
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, members, group.admins.map { it.toString() }, message.sentTimestamp!!)
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGroupControlMessage) {
|
||||
// Prepare
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val senderPublicKey = message.sender ?: return
|
||||
val kind = message.kind!! as? ClosedGroupControlMessage.Kind.EncryptionKeyPair ?: return
|
||||
val groupPublicKey = kind.publicKey?.toByteArray()?.toHexString() ?: message.groupPublicKey ?: return
|
||||
var groupPublicKey = kind.publicKey?.toByteArray()?.toHexString()
|
||||
if (groupPublicKey.isNullOrEmpty()) groupPublicKey = message.groupPublicKey ?: return
|
||||
val userPublicKey = storage.getUserPublicKey()!!
|
||||
val userKeyPair = storage.getUserX25519KeyPair()
|
||||
// Unwrap the message
|
||||
@@ -353,7 +299,7 @@ private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGr
|
||||
return
|
||||
}
|
||||
if (!group.admins.map { it.toString() }.contains(senderPublicKey)) {
|
||||
Log.d("Loki", "Ignoring closed group encryption key pair from non-member.")
|
||||
Log.d("Loki", "Ignoring closed group encryption key pair from non-admin.")
|
||||
return
|
||||
}
|
||||
// Find our wrapper and decrypt it if possible
|
||||
@@ -374,8 +320,8 @@ private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGr
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleClosedGroupNameChanged(message: ClosedGroupControlMessage) {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val senderPublicKey = message.sender ?: return
|
||||
val kind = message.kind!! as? ClosedGroupControlMessage.Kind.NameChange ?: return
|
||||
@@ -410,8 +356,8 @@ private fun MessageReceiver.handleClosedGroupNameChanged(message: ClosedGroupCon
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupControlMessage) {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val userPublicKey = storage.getUserPublicKey()!!
|
||||
val senderPublicKey = message.sender ?: return
|
||||
val kind = message.kind!! as? ClosedGroupControlMessage.Kind.MembersAdded ?: return
|
||||
@@ -434,6 +380,7 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo
|
||||
val updateMembers = kind.members.map { it.toByteArray().toHexString() }
|
||||
val newMembers = members + updateMembers
|
||||
storage.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
|
||||
|
||||
// Notify the user
|
||||
if (userPublicKey == senderPublicKey) {
|
||||
// sender is a linked device
|
||||
@@ -456,9 +403,14 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the given members from the group IF
|
||||
/// • it wasn't the admin that was removed (that should happen through a `MEMBER_LEFT` message).
|
||||
/// • the admin sent the message (only the admin can truly remove members).
|
||||
/// If we're among the users that were removed, delete all encryption key pairs and the group public key, unsubscribe
|
||||
/// from push notifications for this closed group, and remove the given members from the zombie list for this group.
|
||||
private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroupControlMessage) {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val userPublicKey = storage.getUserPublicKey()!!
|
||||
val senderPublicKey = message.sender ?: return
|
||||
val kind = message.kind!! as? ClosedGroupControlMessage.Kind.MembersRemoved ?: return
|
||||
@@ -469,7 +421,7 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup
|
||||
return
|
||||
}
|
||||
if (!group.isActive) {
|
||||
Log.d("Loki", "Ignoring closed group info message for inactive group")
|
||||
Log.d("Loki", "Ignoring closed group info message for inactive group.")
|
||||
return
|
||||
}
|
||||
val name = group.title
|
||||
@@ -480,6 +432,18 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup
|
||||
// Users that are part of this remove update
|
||||
val updateMembers = kind.members.map { it.toByteArray().toHexString() }
|
||||
|
||||
// Check that the admin wasn't removed
|
||||
if (updateMembers.contains(admins.first())) {
|
||||
Log.d("Loki", "Ignoring invalid closed group update.")
|
||||
return
|
||||
}
|
||||
|
||||
// Check that the message was sent by the group admin
|
||||
if (!admins.contains(senderPublicKey)) {
|
||||
Log.d("Loki", "Ignoring invalid closed group update.")
|
||||
return
|
||||
}
|
||||
|
||||
if (!isValidGroupUpdate(group, message.sentTimestamp!!, senderPublicKey)) { return }
|
||||
// If admin leaves the group is disbanded
|
||||
val didAdminLeave = admins.any { it in updateMembers }
|
||||
@@ -496,28 +460,37 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup
|
||||
if (didAdminLeave || wasCurrentUserRemoved) {
|
||||
disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey)
|
||||
} else {
|
||||
val isCurrentUserAdmin = admins.contains(userPublicKey)
|
||||
storage.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
|
||||
if (isCurrentUserAdmin) {
|
||||
MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, newMembers)
|
||||
}
|
||||
}
|
||||
// update zombie members
|
||||
val zombies = storage.getZombieMember(groupID)
|
||||
storage.updateZombieMembers(groupID, zombies.minus(updateMembers).map { Address.fromSerialized(it) })
|
||||
|
||||
val type = if (senderLeft) SignalServiceGroup.Type.QUIT
|
||||
else SignalServiceGroup.Type.MEMBER_REMOVED
|
||||
|
||||
// Notify the user
|
||||
if (userPublicKey == senderPublicKey) {
|
||||
// sender is a linked device
|
||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||
storage.insertOutgoingInfoMessage(context, groupID, type, name, updateMembers, admins, threadID, message.sentTimestamp!!)
|
||||
} else {
|
||||
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, updateMembers, admins, message.sentTimestamp!!)
|
||||
// we don't display zombie members in the notification as users have already been notified when those members left
|
||||
val notificationMembers = updateMembers.minus(zombies)
|
||||
if (notificationMembers.isNotEmpty()) {
|
||||
// no notification to display when only zombies have been removed
|
||||
if (userPublicKey == senderPublicKey) {
|
||||
// sender is a linked device
|
||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||
storage.insertOutgoingInfoMessage(context, groupID, type, name, notificationMembers, admins, threadID, message.sentTimestamp!!)
|
||||
} else {
|
||||
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, notificationMembers, admins, message.sentTimestamp!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If a regular member left:
|
||||
/// • Mark them as a zombie (to be removed by the admin later).
|
||||
/// If the admin left:
|
||||
/// • Unsubscribe from PNs, delete the group public key, etc. as the group will be disbanded.
|
||||
private fun MessageReceiver.handleClosedGroupMemberLeft(message: ClosedGroupControlMessage) {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val senderPublicKey = message.sender ?: return
|
||||
val userPublicKey = storage.getUserPublicKey()!!
|
||||
if (message.kind!! !is ClosedGroupControlMessage.Kind.MemberLeft) return
|
||||
@@ -547,11 +520,10 @@ private fun MessageReceiver.handleClosedGroupMemberLeft(message: ClosedGroupCont
|
||||
// admin left the group of linked device left the group
|
||||
disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey)
|
||||
} else {
|
||||
val isCurrentUserAdmin = admins.contains(userPublicKey)
|
||||
storage.updateMembers(groupID, updatedMemberList.map { Address.fromSerialized(it) })
|
||||
if (isCurrentUserAdmin) {
|
||||
MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMemberList)
|
||||
}
|
||||
// update zombie members
|
||||
val zombies = storage.getZombieMember(groupID)
|
||||
storage.updateZombieMembers(groupID, zombies.plus(senderPublicKey).map { Address.fromSerialized(it) })
|
||||
}
|
||||
// Notify the user
|
||||
if (userLeft) {
|
||||
@@ -563,31 +535,6 @@ private fun MessageReceiver.handleClosedGroupMemberLeft(message: ClosedGroupCont
|
||||
}
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleClosedGroupEncryptionKeyPairRequest(message: ClosedGroupControlMessage) {
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val senderPublicKey = message.sender ?: return
|
||||
val userPublicKey = storage.getUserPublicKey()!!
|
||||
if (message.kind!! !is ClosedGroupControlMessage.Kind.EncryptionKeyPairRequest) return
|
||||
if (senderPublicKey == userPublicKey) {
|
||||
Log.d("Loki", "Ignoring invalid closed group update.")
|
||||
return
|
||||
}
|
||||
val groupPublicKey = message.groupPublicKey ?: return
|
||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||
val group = storage.getGroup(groupID) ?: run {
|
||||
Log.d("Loki", "Ignoring closed group info message for nonexistent group.")
|
||||
return
|
||||
}
|
||||
if (!isValidGroupUpdate(group, message.sentTimestamp!!, senderPublicKey)) { return }
|
||||
val encryptionKeyPair = pendingKeyPair[groupPublicKey]?.orNull()
|
||||
?: storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey)
|
||||
if (encryptionKeyPair == null) {
|
||||
Log.d("Loki", "Couldn't get encryption key pair for closed group.")
|
||||
} else {
|
||||
MessageSender.sendEncryptionKeyPair(groupPublicKey, encryptionKeyPair, setOf(senderPublicKey), targetUser = senderPublicKey, force = false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isValidGroupUpdate(group: GroupRecord,
|
||||
sentTimestamp: Long,
|
||||
senderPublicKey: String): Boolean {
|
||||
@@ -606,7 +553,7 @@ private fun isValidGroupUpdate(group: GroupRecord,
|
||||
}
|
||||
|
||||
fun MessageReceiver.disableLocalGroupAndUnsubscribe(groupPublicKey: String, groupID: String, userPublicKey: String) {
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
storage.removeClosedGroupPublicKey(groupPublicKey)
|
||||
// Remove the key pairs
|
||||
storage.removeAllClosedGroupEncryptionKeyPairs(groupPublicKey)
|
@@ -4,7 +4,7 @@ import android.net.Uri;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.session.libsession.messaging.MessagingConfiguration;
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration;
|
||||
|
||||
public class DatabaseAttachment extends Attachment {
|
||||
|
||||
@@ -33,7 +33,7 @@ public class DatabaseAttachment extends Attachment {
|
||||
@Nullable
|
||||
public Uri getDataUri() {
|
||||
if (hasData) {
|
||||
return MessagingConfiguration.shared.getStorage().getAttachmentDataUri(attachmentId);
|
||||
return MessagingModuleConfiguration.shared.getStorage().getAttachmentDataUri(attachmentId);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class DatabaseAttachment extends Attachment {
|
||||
@Nullable
|
||||
public Uri getThumbnailUri() {
|
||||
if (hasThumbnail) {
|
||||
return MessagingConfiguration.shared.getStorage().getAttachmentThumbnailUri(attachmentId);
|
||||
return MessagingModuleConfiguration.shared.getStorage().getAttachmentThumbnailUri(attachmentId);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package org.session.libsession.messaging.sending_receiving.dataextraction
|
||||
package org.session.libsession.messaging.sending_receiving.data_extraction
|
||||
|
||||
class DataExtractionNotificationInfoMessage {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package org.session.libsession.messaging.sending_receiving.linkpreview;
|
||||
package org.session.libsession.messaging.sending_receiving.link_preview;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
@@ -5,16 +5,16 @@ import nl.komponents.kovenant.functional.map
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI
|
||||
import org.session.libsignal.service.loki.utilities.retryIfNeeded
|
||||
import org.session.libsignal.utilities.JsonUtil
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
object PushNotificationAPI {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val server = "https://live.apns.getsession.org"
|
||||
val serverPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049"
|
||||
private val maxRetryCount = 4
|
||||
@@ -50,8 +50,8 @@ object PushNotificationAPI {
|
||||
}
|
||||
}
|
||||
// Unsubscribe from all closed groups
|
||||
val allClosedGroupPublicKeys = MessagingConfiguration.shared.storage.getAllClosedGroupPublicKeys()
|
||||
val userPublicKey = MessagingConfiguration.shared.storage.getUserPublicKey()!!
|
||||
val allClosedGroupPublicKeys = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys()
|
||||
val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!!
|
||||
allClosedGroupPublicKeys.forEach { closedGroup ->
|
||||
performOperation(ClosedGroupOperation.Unsubscribe, closedGroup, userPublicKey)
|
||||
}
|
||||
@@ -80,7 +80,7 @@ object PushNotificationAPI {
|
||||
}
|
||||
}
|
||||
// Subscribe to all closed groups
|
||||
val allClosedGroupPublicKeys = MessagingConfiguration.shared.storage.getAllClosedGroupPublicKeys()
|
||||
val allClosedGroupPublicKeys = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys()
|
||||
allClosedGroupPublicKeys.forEach { closedGroup ->
|
||||
performOperation(ClosedGroupOperation.Subscribe, closedGroup, publicKey)
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ import android.os.Handler
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.functional.bind
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
||||
import org.session.libsession.messaging.utilities.MessageWrapper
|
||||
@@ -59,7 +59,7 @@ class ClosedGroupPoller {
|
||||
// region Private API
|
||||
private fun poll(): List<Promise<Unit, Exception>> {
|
||||
if (!isPolling) { return listOf() }
|
||||
val publicKeys = MessagingConfiguration.shared.storage.getAllActiveClosedGroupPublicKeys()
|
||||
val publicKeys = MessagingModuleConfiguration.shared.storage.getAllActiveClosedGroupPublicKeys()
|
||||
return publicKeys.map { publicKey ->
|
||||
val promise = SnodeAPI.getSwarm(publicKey).bind { swarm ->
|
||||
val snode = swarm.getRandomElementOrNull() ?: throw InsufficientSnodesException() // Should be cryptographically secure
|
||||
@@ -67,7 +67,7 @@ class ClosedGroupPoller {
|
||||
SnodeAPI.getRawMessages(snode, publicKey).map {SnodeAPI.parseRawMessagesResponse(it, snode, publicKey) }
|
||||
}
|
||||
promise.successBackground { messages ->
|
||||
if (!MessagingConfiguration.shared.storage.isGroupActive(publicKey)) {
|
||||
if (!MessagingModuleConfiguration.shared.storage.isGroupActive(publicKey)) {
|
||||
// ignore inactive group's messages
|
||||
return@successBackground
|
||||
}
|
||||
@@ -78,7 +78,7 @@ class ClosedGroupPoller {
|
||||
val rawMessageAsJSON = message as? Map<*, *>
|
||||
val base64EncodedData = rawMessageAsJSON?.get("data") as? String
|
||||
val data = base64EncodedData?.let { Base64.decode(it) } ?: return@forEach
|
||||
val job = MessageReceiveJob(MessageWrapper.unwrap(data), false)
|
||||
val job = MessageReceiveJob(MessageWrapper.unwrap(data).toByteArray(), false)
|
||||
JobQueue.shared.add(job)
|
||||
}
|
||||
}
|
||||
|
@@ -3,12 +3,12 @@ package org.session.libsession.messaging.sending_receiving.pollers
|
||||
import com.google.protobuf.ByteString
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.deferred
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
||||
import org.session.libsession.messaging.opengroups.OpenGroup
|
||||
import org.session.libsession.messaging.opengroups.OpenGroupAPI
|
||||
import org.session.libsession.messaging.opengroups.OpenGroupMessage
|
||||
import org.session.libsession.messaging.open_groups.OpenGroup
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPI
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupMessage
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.*
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.session.libsignal.utilities.successBackground
|
||||
@@ -26,7 +26,7 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ
|
||||
private val cancellableFutures = mutableListOf<ScheduledFuture<out Any>>()
|
||||
|
||||
// region Convenience
|
||||
private val userHexEncodedPublicKey = MessagingConfiguration.shared.storage.getUserPublicKey() ?: ""
|
||||
private val userHexEncodedPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey() ?: ""
|
||||
private var displayNameUpdates = setOf<String>()
|
||||
// endregion
|
||||
|
||||
@@ -43,10 +43,10 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ
|
||||
fun startIfNeeded() {
|
||||
if (hasStarted || executorService == null) return
|
||||
cancellableFutures += listOf(
|
||||
executorService.scheduleAtFixedRate(::pollForNewMessages,0, pollForNewMessagesInterval, TimeUnit.MILLISECONDS),
|
||||
executorService.scheduleAtFixedRate(::pollForDeletedMessages,0, pollForDeletedMessagesInterval, TimeUnit.MILLISECONDS),
|
||||
executorService.scheduleAtFixedRate(::pollForModerators,0, pollForModeratorsInterval, TimeUnit.MILLISECONDS),
|
||||
executorService.scheduleAtFixedRate(::pollForDisplayNames,0, pollForDisplayNamesInterval, TimeUnit.MILLISECONDS)
|
||||
executorService.scheduleAtFixedRate(::pollForNewMessages,0, pollForNewMessagesInterval, TimeUnit.MILLISECONDS),
|
||||
executorService.scheduleAtFixedRate(::pollForDeletedMessages,0, pollForDeletedMessagesInterval, TimeUnit.MILLISECONDS),
|
||||
executorService.scheduleAtFixedRate(::pollForModerators,0, pollForModeratorsInterval, TimeUnit.MILLISECONDS),
|
||||
executorService.scheduleAtFixedRate(::pollForDisplayNames,0, pollForDisplayNamesInterval, TimeUnit.MILLISECONDS)
|
||||
)
|
||||
hasStarted = true
|
||||
}
|
||||
@@ -62,10 +62,10 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ
|
||||
|
||||
// region Polling
|
||||
fun pollForNewMessages(): Promise<Unit, Exception> {
|
||||
return pollForNewMessages(false)
|
||||
return pollForNewMessagesInternal(false)
|
||||
}
|
||||
|
||||
private fun pollForNewMessages(isBackgroundPoll: Boolean): Promise<Unit, Exception> {
|
||||
private fun pollForNewMessagesInternal(isBackgroundPoll: Boolean): Promise<Unit, Exception> {
|
||||
if (isPollOngoing) { return Promise.of(Unit) }
|
||||
isPollOngoing = true
|
||||
val deferred = deferred<Unit, Exception>()
|
||||
@@ -79,7 +79,7 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ
|
||||
fun generateDisplayName(rawDisplayName: String): String {
|
||||
return "$rawDisplayName (...${senderPublicKey.takeLast(8)})"
|
||||
}
|
||||
val senderDisplayName = MessagingConfiguration.shared.storage.getOpenGroupDisplayName(senderPublicKey, openGroup.channel, openGroup.server) ?: generateDisplayName(message.displayName)
|
||||
val senderDisplayName = MessagingModuleConfiguration.shared.storage.getOpenGroupDisplayName(senderPublicKey, openGroup.channel, openGroup.server) ?: generateDisplayName(message.displayName)
|
||||
val id = openGroup.id.toByteArray()
|
||||
// Main message
|
||||
val dataMessageProto = DataMessage.newBuilder()
|
||||
@@ -164,7 +164,7 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ
|
||||
content.setDataMessage(dataMessageProto.build())
|
||||
// Envelope
|
||||
val builder = Envelope.newBuilder()
|
||||
builder.type = Envelope.Type.UNIDENTIFIED_SENDER
|
||||
builder.type = Envelope.Type.SESSION_MESSAGE
|
||||
builder.source = senderPublicKey
|
||||
builder.sourceDevice = 1
|
||||
builder.setContent(content.build().toByteString())
|
||||
@@ -203,7 +203,7 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ
|
||||
for (pair in mapping.entries) {
|
||||
if (pair.key == userHexEncodedPublicKey) continue
|
||||
val senderDisplayName = "${pair.value} (...${pair.key.substring(pair.key.count() - 8)})"
|
||||
MessagingConfiguration.shared.storage.setOpenGroupDisplayName(pair.key, openGroup.channel, openGroup.server, senderDisplayName)
|
||||
MessagingModuleConfiguration.shared.storage.setOpenGroupDisplayName(pair.key, openGroup.channel, openGroup.server, senderDisplayName)
|
||||
}
|
||||
}.fail {
|
||||
displayNameUpdates = displayNameUpdates.union(hexEncodedPublicKeys)
|
||||
@@ -212,9 +212,9 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ
|
||||
|
||||
private fun pollForDeletedMessages() {
|
||||
OpenGroupAPI.getDeletedMessageServerIDs(openGroup.channel, openGroup.server).success { deletedMessageServerIDs ->
|
||||
val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { MessagingConfiguration.shared.messageDataProvider.getMessageID(it) }
|
||||
val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { MessagingModuleConfiguration.shared.messageDataProvider.getMessageID(it) }
|
||||
deletedMessageIDs.forEach {
|
||||
MessagingConfiguration.shared.messageDataProvider.deleteMessage(it)
|
||||
MessagingModuleConfiguration.shared.messageDataProvider.deleteMessage(it)
|
||||
}
|
||||
}.fail {
|
||||
Log.d("Loki", "Failed to get deleted messages for group chat with ID: ${openGroup.channel} on server: ${openGroup.server}.")
|
||||
|
@@ -2,13 +2,13 @@ package org.session.libsession.messaging.sending_receiving.pollers
|
||||
|
||||
import nl.komponents.kovenant.*
|
||||
import nl.komponents.kovenant.functional.bind
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
||||
import org.session.libsession.messaging.utilities.MessageWrapper
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.snode.SnodeConfiguration
|
||||
import org.session.libsignal.service.loki.api.Snode
|
||||
import org.session.libsession.snode.SnodeModule
|
||||
import org.session.libsignal.service.loki.Snode
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import java.security.SecureRandom
|
||||
@@ -17,7 +17,7 @@ import java.util.*
|
||||
private class PromiseCanceledException : Exception("Promise canceled.")
|
||||
|
||||
class Poller {
|
||||
var userPublicKey = MessagingConfiguration.shared.storage.getUserPublicKey() ?: ""
|
||||
var userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey() ?: ""
|
||||
private var hasStarted: Boolean = false
|
||||
private val usedSnodes: MutableSet<Snode> = mutableSetOf()
|
||||
public var isCaughtUp = false
|
||||
@@ -47,9 +47,9 @@ class Poller {
|
||||
private fun setUpPolling() {
|
||||
if (!hasStarted) { return; }
|
||||
val thread = Thread.currentThread()
|
||||
SnodeAPI.getSwarm(userPublicKey).bind(SnodeAPI.messagePollingContext) {
|
||||
SnodeAPI.getSwarm(userPublicKey).bind {
|
||||
usedSnodes.clear()
|
||||
val deferred = deferred<Unit, Exception>(SnodeAPI.messagePollingContext)
|
||||
val deferred = deferred<Unit, Exception>()
|
||||
pollNextSnode(deferred)
|
||||
deferred.promise
|
||||
}.always {
|
||||
@@ -63,7 +63,7 @@ class Poller {
|
||||
}
|
||||
|
||||
private fun pollNextSnode(deferred: Deferred<Unit, Exception>) {
|
||||
val swarm = SnodeConfiguration.shared.storage.getSwarm(userPublicKey) ?: setOf()
|
||||
val swarm = SnodeModule.shared.storage.getSwarm(userPublicKey) ?: setOf()
|
||||
val unusedSnodes = swarm.subtract(usedSnodes)
|
||||
if (unusedSnodes.isNotEmpty()) {
|
||||
val index = SecureRandom().nextInt(unusedSnodes.size)
|
||||
@@ -87,17 +87,14 @@ class Poller {
|
||||
|
||||
private fun poll(snode: Snode, deferred: Deferred<Unit, Exception>): Promise<Unit, Exception> {
|
||||
if (!hasStarted) { return Promise.ofFail(PromiseCanceledException()) }
|
||||
return SnodeAPI.getRawMessages(snode, userPublicKey).bind(SnodeAPI.messagePollingContext) { rawResponse ->
|
||||
return SnodeAPI.getRawMessages(snode, userPublicKey).bind { rawResponse ->
|
||||
isCaughtUp = true
|
||||
if (deferred.promise.isDone()) {
|
||||
task { Unit } // The long polling connection has been canceled; don't recurse
|
||||
} else {
|
||||
val messages = SnodeAPI.parseRawMessagesResponse(rawResponse, snode, userPublicKey)
|
||||
messages.forEach { message ->
|
||||
val rawMessageAsJSON = message as? Map<*, *>
|
||||
val base64EncodedData = rawMessageAsJSON?.get("data") as? String
|
||||
val data = base64EncodedData?.let { Base64.decode(it) } ?: return@forEach
|
||||
val job = MessageReceiveJob(MessageWrapper.unwrap(data), false)
|
||||
messages.forEach { envelope ->
|
||||
val job = MessageReceiveJob(envelope.toByteArray(), false)
|
||||
JobQueue.shared.add(job)
|
||||
}
|
||||
poll(snode, deferred)
|
||||
|
@@ -4,8 +4,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment
|
||||
import org.session.libsession.messaging.threads.Address
|
||||
|
||||
class QuoteModel(val id: Long,
|
||||
val author: Address,
|
||||
val text: String,
|
||||
val missing: Boolean,
|
||||
val attachments: List<Attachment>?) {
|
||||
}
|
||||
val author: Address,
|
||||
val text: String,
|
||||
val missing: Boolean,
|
||||
val attachments: List<Attachment>?)
|
||||
|
@@ -13,6 +13,7 @@ class GroupRecord(
|
||||
) {
|
||||
var members: List<Address> = LinkedList<Address>()
|
||||
var admins: List<Address> = LinkedList<Address>()
|
||||
|
||||
fun getId(): ByteArray {
|
||||
return try {
|
||||
GroupUtil.getDecodedGroupIDAsData(encodedId)
|
||||
|
@@ -28,7 +28,7 @@ import androidx.annotation.Nullable;
|
||||
import com.annimon.stream.function.Consumer;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.session.libsession.messaging.MessagingConfiguration;
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration;
|
||||
import org.session.libsession.messaging.avatars.TransparentContactPhoto;
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.session.libsession.messaging.threads.GroupRecord;
|
||||
@@ -286,7 +286,7 @@ public class Recipient implements RecipientModifiedListener {
|
||||
}
|
||||
|
||||
public synchronized @Nullable String getName() {
|
||||
String displayName = MessagingConfiguration.shared.getStorage().getDisplayName(this.address.toString());
|
||||
String displayName = MessagingModuleConfiguration.shared.getStorage().getDisplayName(this.address.toString());
|
||||
if (displayName != null) { return displayName; }
|
||||
|
||||
if (this.name == null && isMmsGroupRecipient()) {
|
||||
|
@@ -23,7 +23,7 @@ import android.text.TextUtils;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.session.libsession.messaging.MessagingConfiguration;
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.session.libsession.utilities.color.MaterialColor;
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
@@ -115,7 +115,7 @@ class RecipientProvider {
|
||||
|
||||
private @NonNull RecipientDetails getIndividualRecipientDetails(Context context, @NonNull Address address, Optional<RecipientSettings> settings) {
|
||||
if (!settings.isPresent()) {
|
||||
settings = Optional.fromNullable(MessagingConfiguration.shared.getStorage().getRecipientSettings(address));
|
||||
settings = Optional.fromNullable(MessagingModuleConfiguration.shared.getStorage().getRecipientSettings(address));
|
||||
}
|
||||
|
||||
if (!settings.isPresent() && STATIC_DETAILS.containsKey(address.serialize())) {
|
||||
@@ -130,12 +130,12 @@ class RecipientProvider {
|
||||
private @NonNull RecipientDetails getGroupRecipientDetails(Context context, Address groupId, Optional<GroupRecord> groupRecord, Optional<RecipientSettings> settings, boolean asynchronous) {
|
||||
|
||||
if (!groupRecord.isPresent()) {
|
||||
groupRecord = Optional.fromNullable(MessagingConfiguration.shared.getStorage().getGroup(groupId.toGroupString()));
|
||||
groupRecord = Optional.fromNullable(MessagingModuleConfiguration.shared.getStorage().getGroup(groupId.toGroupString()));
|
||||
}
|
||||
|
||||
if (!settings.isPresent()) {
|
||||
|
||||
settings = Optional.fromNullable(MessagingConfiguration.shared.getStorage().getRecipientSettings(groupId));
|
||||
settings = Optional.fromNullable(MessagingModuleConfiguration.shared.getStorage().getRecipientSettings(groupId));
|
||||
}
|
||||
|
||||
if (groupRecord.isPresent()) {
|
||||
|
@@ -2,39 +2,38 @@ package org.session.libsession.messaging.utilities
|
||||
|
||||
import android.content.Context
|
||||
import org.session.libsession.R
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage
|
||||
import org.session.libsession.utilities.ExpirationUtil
|
||||
import org.session.libsignal.service.api.messages.SignalServiceGroup
|
||||
|
||||
object UpdateMessageBuilder {
|
||||
object ClosedGroupUpdateMessageBuilder {
|
||||
|
||||
fun buildGroupUpdateMessage(context: Context, updateMessageData: UpdateMessageData, sender: String? = null, isOutgoing: Boolean = false): String {
|
||||
fun buildGroupUpdateMessage(context: Context, updateMessageData: ClosedGroupUpdateMessageData, sender: String? = null, isOutgoing: Boolean = false): String {
|
||||
var message = ""
|
||||
val updateData = updateMessageData.kind ?: return message
|
||||
if (!isOutgoing && sender == null) return message
|
||||
val senderName: String = if (!isOutgoing) {
|
||||
MessagingConfiguration.shared.storage.getDisplayNameForRecipient(sender!!) ?: sender
|
||||
MessagingModuleConfiguration.shared.storage.getDisplayNameForRecipient(sender!!) ?: sender
|
||||
} else { context.getString(R.string.MessageRecord_you) }
|
||||
|
||||
when (updateData) {
|
||||
is UpdateMessageData.Kind.GroupCreation -> {
|
||||
is ClosedGroupUpdateMessageData.Kind.GroupCreation -> {
|
||||
message = if (isOutgoing) {
|
||||
context.getString(R.string.MessageRecord_you_created_a_new_group)
|
||||
} else {
|
||||
context.getString(R.string.MessageRecord_s_added_you_to_the_group, senderName)
|
||||
}
|
||||
}
|
||||
is UpdateMessageData.Kind.GroupNameChange -> {
|
||||
is ClosedGroupUpdateMessageData.Kind.GroupNameChange -> {
|
||||
message = if (isOutgoing) {
|
||||
context.getString(R.string.MessageRecord_you_renamed_the_group_to_s, updateData.name)
|
||||
} else {
|
||||
context.getString(R.string.MessageRecord_s_renamed_the_group_to_s, senderName, updateData.name)
|
||||
}
|
||||
}
|
||||
is UpdateMessageData.Kind.GroupMemberAdded -> {
|
||||
is ClosedGroupUpdateMessageData.Kind.GroupMemberAdded -> {
|
||||
val members = updateData.updatedMembers.joinToString(", ") {
|
||||
MessagingConfiguration.shared.storage.getDisplayNameForRecipient(it) ?: it
|
||||
MessagingModuleConfiguration.shared.storage.getDisplayNameForRecipient(it) ?: it
|
||||
}
|
||||
message = if (isOutgoing) {
|
||||
context.getString(R.string.MessageRecord_you_added_s_to_the_group, members)
|
||||
@@ -42,8 +41,8 @@ object UpdateMessageBuilder {
|
||||
context.getString(R.string.MessageRecord_s_added_s_to_the_group, senderName, members)
|
||||
}
|
||||
}
|
||||
is UpdateMessageData.Kind.GroupMemberRemoved -> {
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
is ClosedGroupUpdateMessageData.Kind.GroupMemberRemoved -> {
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val userPublicKey = storage.getUserPublicKey()!!
|
||||
// 1st case: you are part of the removed members
|
||||
message = if (userPublicKey in updateData.updatedMembers) {
|
||||
@@ -64,7 +63,7 @@ object UpdateMessageBuilder {
|
||||
}
|
||||
}
|
||||
}
|
||||
is UpdateMessageData.Kind.GroupMemberLeft -> {
|
||||
is ClosedGroupUpdateMessageData.Kind.GroupMemberLeft -> {
|
||||
message = if (isOutgoing) {
|
||||
context.getString(R.string.MessageRecord_left_group)
|
||||
} else {
|
||||
@@ -78,7 +77,7 @@ object UpdateMessageBuilder {
|
||||
fun buildExpirationTimerMessage(context: Context, duration: Long, sender: String? = null, isOutgoing: Boolean = false): String {
|
||||
if (!isOutgoing && sender == null) return ""
|
||||
val senderName: String? = if (!isOutgoing) {
|
||||
MessagingConfiguration.shared.storage.getDisplayNameForRecipient(sender!!) ?: sender
|
||||
MessagingModuleConfiguration.shared.storage.getDisplayNameForRecipient(sender!!) ?: sender
|
||||
} else { context.getString(R.string.MessageRecord_you) }
|
||||
return if (duration <= 0) {
|
||||
if (isOutgoing) context.getString(R.string.MessageRecord_you_disabled_disappearing_messages)
|
||||
@@ -91,7 +90,7 @@ object UpdateMessageBuilder {
|
||||
}
|
||||
|
||||
fun buildDataExtractionMessage(context: Context, kind: DataExtractionNotificationInfoMessage.Kind, sender: String? = null): String {
|
||||
val senderName = MessagingConfiguration.shared.storage.getDisplayNameForRecipient(sender!!) ?: sender
|
||||
val senderName = MessagingModuleConfiguration.shared.storage.getDisplayNameForRecipient(sender!!) ?: sender
|
||||
return when (kind) {
|
||||
DataExtractionNotificationInfoMessage.Kind.SCREENSHOT ->
|
||||
context.getString(R.string.MessageRecord_s_took_a_screenshot, senderName)
|
@@ -9,7 +9,7 @@ import org.session.libsignal.utilities.logging.Log
|
||||
import java.util.*
|
||||
|
||||
// class used to save update messages details
|
||||
class UpdateMessageData () {
|
||||
class ClosedGroupUpdateMessageData () {
|
||||
|
||||
var kind: Kind? = null
|
||||
|
||||
@@ -41,22 +41,22 @@ class UpdateMessageData () {
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG = UpdateMessageData::class.simpleName
|
||||
val TAG = ClosedGroupUpdateMessageData::class.simpleName
|
||||
|
||||
fun buildGroupUpdate(type: SignalServiceGroup.Type, name: String, members: Collection<String>): UpdateMessageData? {
|
||||
fun buildGroupUpdate(type: SignalServiceGroup.Type, name: String, members: Collection<String>): ClosedGroupUpdateMessageData? {
|
||||
return when(type) {
|
||||
SignalServiceGroup.Type.CREATION -> UpdateMessageData(Kind.GroupCreation())
|
||||
SignalServiceGroup.Type.NAME_CHANGE -> UpdateMessageData(Kind.GroupNameChange(name))
|
||||
SignalServiceGroup.Type.MEMBER_ADDED -> UpdateMessageData(Kind.GroupMemberAdded(members))
|
||||
SignalServiceGroup.Type.MEMBER_REMOVED -> UpdateMessageData(Kind.GroupMemberRemoved(members))
|
||||
SignalServiceGroup.Type.QUIT -> UpdateMessageData(Kind.GroupMemberLeft())
|
||||
SignalServiceGroup.Type.CREATION -> ClosedGroupUpdateMessageData(Kind.GroupCreation())
|
||||
SignalServiceGroup.Type.NAME_CHANGE -> ClosedGroupUpdateMessageData(Kind.GroupNameChange(name))
|
||||
SignalServiceGroup.Type.MEMBER_ADDED -> ClosedGroupUpdateMessageData(Kind.GroupMemberAdded(members))
|
||||
SignalServiceGroup.Type.MEMBER_REMOVED -> ClosedGroupUpdateMessageData(Kind.GroupMemberRemoved(members))
|
||||
SignalServiceGroup.Type.QUIT -> ClosedGroupUpdateMessageData(Kind.GroupMemberLeft())
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun fromJSON(json: String): UpdateMessageData? {
|
||||
fun fromJSON(json: String): ClosedGroupUpdateMessageData? {
|
||||
return try {
|
||||
JsonUtil.fromJson(json, UpdateMessageData::class.java)
|
||||
JsonUtil.fromJson(json, ClosedGroupUpdateMessageData::class.java)
|
||||
} catch (e: JsonParseException) {
|
||||
Log.e(TAG, "${e.message}")
|
||||
null
|
@@ -6,10 +6,9 @@ import nl.komponents.kovenant.functional.map
|
||||
import nl.komponents.kovenant.then
|
||||
import okhttp3.*
|
||||
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.messaging.fileserver.FileServerAPI
|
||||
import org.session.libsession.messaging.file_server.FileServerAPI
|
||||
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.session.libsignal.utilities.DiffieHellman
|
||||
@@ -62,7 +61,7 @@ open class DotNetAPI {
|
||||
public data class UploadResult(val id: Long, val url: String, val digest: ByteArray?)
|
||||
|
||||
fun getAuthToken(server: String): Promise<String, Exception> {
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val token = storage.getAuthToken(server)
|
||||
if (token != null) { return Promise.of(token) }
|
||||
// Avoid multiple token requests to the server by caching
|
||||
@@ -81,9 +80,9 @@ open class DotNetAPI {
|
||||
|
||||
private fun requestNewAuthToken(server: String): Promise<String, Exception> {
|
||||
Log.d("Loki", "Requesting auth token for server: $server.")
|
||||
val userKeyPair = MessagingConfiguration.shared.storage.getUserKeyPair() ?: throw Error.Generic
|
||||
val userKeyPair = MessagingModuleConfiguration.shared.storage.getUserKeyPair() ?: throw Error.Generic
|
||||
val parameters: Map<String, Any> = mapOf( "pubKey" to userKeyPair.first )
|
||||
return execute(HTTPVerb.GET, server, "loki/v1/get_challenge", false, parameters).map(SnodeAPI.sharedContext) { json ->
|
||||
return execute(HTTPVerb.GET, server, "loki/v1/get_challenge", false, parameters).map { json ->
|
||||
try {
|
||||
val base64EncodedChallenge = json["cipherText64"] as String
|
||||
val challenge = Base64.decode(base64EncodedChallenge)
|
||||
@@ -107,7 +106,7 @@ open class DotNetAPI {
|
||||
|
||||
private fun submitAuthToken(token: String, server: String): Promise<String, Exception> {
|
||||
Log.d("Loki", "Submitting auth token for server: $server.")
|
||||
val userPublicKey = MessagingConfiguration.shared.storage.getUserPublicKey() ?: throw Error.Generic
|
||||
val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey() ?: throw Error.Generic
|
||||
val parameters = mapOf( "pubKey" to userPublicKey, "token" to token )
|
||||
return execute(HTTPVerb.POST, server, "loki/v1/submit_challenge", false, parameters, isJSONRequired = false).map { token }
|
||||
}
|
||||
@@ -146,7 +145,7 @@ open class DotNetAPI {
|
||||
if (exception is HTTP.HTTPRequestFailedException) {
|
||||
val statusCode = exception.statusCode
|
||||
if (statusCode == 401 || statusCode == 403) {
|
||||
MessagingConfiguration.shared.storage.setAuthToken(server, null)
|
||||
MessagingModuleConfiguration.shared.storage.setAuthToken(server, null)
|
||||
throw Error.TokenExpired
|
||||
}
|
||||
}
|
||||
@@ -185,14 +184,14 @@ open class DotNetAPI {
|
||||
/**
|
||||
* Blocks the calling thread.
|
||||
*/
|
||||
fun downloadFile(destination: File, url: String, maxSize: Int, listener: SignalServiceAttachment.ProgressListener?) {
|
||||
fun downloadFile(destination: File, url: String, listener: SignalServiceAttachment.ProgressListener?) {
|
||||
val outputStream = FileOutputStream(destination) // Throws
|
||||
var remainingAttempts = 4
|
||||
var exception: Exception? = null
|
||||
while (remainingAttempts > 0) {
|
||||
remainingAttempts -= 1
|
||||
try {
|
||||
downloadFile(outputStream, url, maxSize, listener)
|
||||
downloadFile(outputStream, url, listener)
|
||||
exception = null
|
||||
break
|
||||
} catch (e: Exception) {
|
||||
@@ -205,7 +204,7 @@ open class DotNetAPI {
|
||||
/**
|
||||
* Blocks the calling thread.
|
||||
*/
|
||||
fun downloadFile(outputStream: OutputStream, url: String, maxSize: Int, listener: SignalServiceAttachment.ProgressListener?) {
|
||||
fun downloadFile(outputStream: OutputStream, url: String, listener: SignalServiceAttachment.ProgressListener?) {
|
||||
// We need to throw a PushNetworkException or NonSuccessfulResponseCodeException
|
||||
// because the underlying Signal logic requires these to work correctly
|
||||
val oldPrefixedHost = "https://" + HttpUrl.get(url).host()
|
||||
@@ -228,7 +227,7 @@ open class DotNetAPI {
|
||||
throw PushNetworkException("Missing response body.")
|
||||
}
|
||||
val body = Base64.decode(result)
|
||||
if (body.size > maxSize) {
|
||||
if (body.size > FileServerAPI.maxFileSize) {
|
||||
Log.d("Loki", "Attachment size limit exceeded.")
|
||||
throw PushNetworkException("Max response size exceeded.")
|
||||
}
|
||||
@@ -239,7 +238,7 @@ open class DotNetAPI {
|
||||
while (bytes >= 0) {
|
||||
outputStream.write(buffer, 0, bytes)
|
||||
count += bytes
|
||||
if (count > maxSize) {
|
||||
if (count > FileServerAPI.maxFileSize) {
|
||||
Log.d("Loki", "Attachment size limit exceeded.")
|
||||
throw PushNetworkException("Max response size exceeded.")
|
||||
}
|
||||
@@ -335,7 +334,7 @@ open class DotNetAPI {
|
||||
if (exception is HTTP.HTTPRequestFailedException) {
|
||||
val statusCode = exception.statusCode
|
||||
if (statusCode == 401 || statusCode == 403) {
|
||||
MessagingConfiguration.shared.storage.setAuthToken(server, null)
|
||||
MessagingModuleConfiguration.shared.storage.setAuthToken(server, null)
|
||||
}
|
||||
throw NonSuccessfulResponseCodeException("Request returned with status code ${exception.statusCode}.")
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package org.session.libsession.messaging.utilities
|
||||
|
||||
import com.google.protobuf.ByteString
|
||||
import org.session.libsignal.metadata.SignalProtos
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.Envelope
|
||||
import org.session.libsignal.service.internal.websocket.WebSocketProtos.WebSocketMessage
|
||||
@@ -69,11 +70,11 @@ object MessageWrapper {
|
||||
/**
|
||||
* `data` shouldn't be base 64 encoded.
|
||||
*/
|
||||
fun unwrap(data: ByteArray): ByteArray {
|
||||
fun unwrap(data: ByteArray): Envelope {
|
||||
try {
|
||||
val webSocketMessage = WebSocketMessage.parseFrom(data)
|
||||
val envelopeAsData = webSocketMessage.request.body
|
||||
return envelopeAsData.toByteArray()
|
||||
return Envelope.parseFrom(envelopeAsData);
|
||||
} catch (e: Exception) {
|
||||
Log.d("Loki", "Failed to unwrap data: ${e.message}.")
|
||||
throw Error.FailedToUnwrapData
|
||||
|
@@ -6,17 +6,18 @@ import nl.komponents.kovenant.deferred
|
||||
import nl.komponents.kovenant.functional.bind
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import okhttp3.Request
|
||||
import org.session.libsession.messaging.file_server.FileServerAPI
|
||||
import org.session.libsession.utilities.AESGCM
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.*
|
||||
import org.session.libsignal.service.loki.api.Snode
|
||||
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI
|
||||
import org.session.libsignal.service.loki.Snode
|
||||
import org.session.libsignal.service.loki.api.utilities.*
|
||||
import org.session.libsession.utilities.AESGCM.EncryptionResult
|
||||
import org.session.libsignal.utilities.ThreadUtils
|
||||
import org.session.libsession.utilities.getBodyForOnionRequest
|
||||
import org.session.libsession.utilities.getHeadersForOnionRequest
|
||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
||||
import org.session.libsignal.service.loki.utilities.*
|
||||
|
||||
private typealias Path = List<Snode>
|
||||
@@ -25,16 +26,21 @@ private typealias Path = List<Snode>
|
||||
* See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information.
|
||||
*/
|
||||
object OnionRequestAPI {
|
||||
private val database: LokiAPIDatabaseProtocol
|
||||
get() = SnodeModule.shared.storage
|
||||
private val broadcaster: Broadcaster
|
||||
get() = SnodeModule.shared.broadcaster
|
||||
private val pathFailureCount = mutableMapOf<Path, Int>()
|
||||
private val snodeFailureCount = mutableMapOf<Snode, Int>()
|
||||
|
||||
var guardSnodes = setOf<Snode>()
|
||||
var paths: List<Path> // Not a set to ensure we consistently show the same path to the user
|
||||
get() = SnodeAPI.database.getOnionRequestPaths()
|
||||
get() = database.getOnionRequestPaths()
|
||||
set(newValue) {
|
||||
if (newValue.isEmpty()) {
|
||||
SnodeAPI.database.clearOnionRequestPaths()
|
||||
database.clearOnionRequestPaths()
|
||||
} else {
|
||||
SnodeAPI.database.setOnionRequestPaths(newValue)
|
||||
database.setOnionRequestPaths(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,16 +57,15 @@ object OnionRequestAPI {
|
||||
* The number of times a snode can fail before it's replaced.
|
||||
*/
|
||||
private const val snodeFailureThreshold = 1
|
||||
/**
|
||||
* The number of paths to maintain.
|
||||
*/
|
||||
const val targetPathCount = 2 // A main path and a backup path for the case where the target snode is in the main path
|
||||
|
||||
/**
|
||||
* The number of guard snodes required to maintain `targetPathCount` paths.
|
||||
*/
|
||||
private val targetGuardSnodeCount
|
||||
get() = targetPathCount // One per path
|
||||
/**
|
||||
* The number of paths to maintain.
|
||||
*/
|
||||
const val targetPathCount = 2 // A main path and a backup path for the case where the target snode is in the main path
|
||||
// endregion
|
||||
|
||||
class HTTPRequestFailedAtDestinationException(val statusCode: Int, val json: Map<*, *>)
|
||||
@@ -74,7 +79,7 @@ object OnionRequestAPI {
|
||||
)
|
||||
|
||||
internal sealed class Destination {
|
||||
class Snode(val snode: org.session.libsignal.service.loki.api.Snode) : Destination()
|
||||
class Snode(val snode: org.session.libsignal.service.loki.Snode) : Destination()
|
||||
class Server(val host: String, val target: String, val x25519PublicKey: String) : Destination()
|
||||
}
|
||||
|
||||
@@ -113,7 +118,7 @@ object OnionRequestAPI {
|
||||
return Promise.of(guardSnodes)
|
||||
} else {
|
||||
Log.d("Loki", "Populating guard snode cache.")
|
||||
return SnodeAPI.getRandomSnode().bind(SnodeAPI.sharedContext) { // Just used to populate the snode pool
|
||||
return SnodeAPI.getRandomSnode().bind { // Just used to populate the snode pool
|
||||
var unusedSnodes = SnodeAPI.snodePool.minus(reusableGuardSnodes)
|
||||
val reusableGuardSnodeCount = reusableGuardSnodes.count()
|
||||
if (unusedSnodes.count() < (targetGuardSnodeCount - reusableGuardSnodeCount)) { throw InsufficientSnodesException() }
|
||||
@@ -138,7 +143,7 @@ object OnionRequestAPI {
|
||||
return deferred.promise
|
||||
}
|
||||
val promises = (0 until (targetGuardSnodeCount - reusableGuardSnodeCount)).map { getGuardSnode() }
|
||||
all(promises).map(SnodeAPI.sharedContext) { guardSnodes ->
|
||||
all(promises).map { guardSnodes ->
|
||||
val guardSnodesAsSet = (guardSnodes + reusableGuardSnodes).toSet()
|
||||
OnionRequestAPI.guardSnodes = guardSnodesAsSet
|
||||
guardSnodesAsSet
|
||||
@@ -153,10 +158,10 @@ object OnionRequestAPI {
|
||||
*/
|
||||
private fun buildPaths(reusablePaths: List<Path>): Promise<List<Path>, Exception> {
|
||||
Log.d("Loki", "Building onion request paths.")
|
||||
SnodeAPI.broadcaster.broadcast("buildingPaths")
|
||||
return SnodeAPI.getRandomSnode().bind(SnodeAPI.sharedContext) { // Just used to populate the snode pool
|
||||
broadcaster.broadcast("buildingPaths")
|
||||
return SnodeAPI.getRandomSnode().bind { // Just used to populate the snode pool
|
||||
val reusableGuardSnodes = reusablePaths.map { it[0] }
|
||||
getGuardSnodes(reusableGuardSnodes).map(SnodeAPI.sharedContext) { guardSnodes ->
|
||||
getGuardSnodes(reusableGuardSnodes).map { guardSnodes ->
|
||||
var unusedSnodes = SnodeAPI.snodePool.minus(guardSnodes).minus(reusablePaths.flatten())
|
||||
val reusableGuardSnodeCount = reusableGuardSnodes.count()
|
||||
val pathSnodeCount = (targetGuardSnodeCount - reusableGuardSnodeCount) * pathSize - (targetGuardSnodeCount - reusableGuardSnodeCount)
|
||||
@@ -173,7 +178,7 @@ object OnionRequestAPI {
|
||||
}
|
||||
}.map { paths ->
|
||||
OnionRequestAPI.paths = paths + reusablePaths
|
||||
SnodeAPI.broadcaster.broadcast("pathsBuilt")
|
||||
broadcaster.broadcast("pathsBuilt")
|
||||
paths
|
||||
}
|
||||
}
|
||||
@@ -207,12 +212,12 @@ object OnionRequestAPI {
|
||||
buildPaths(paths) // Re-build paths in the background
|
||||
return Promise.of(getPath(paths))
|
||||
} else {
|
||||
return buildPaths(paths).map(SnodeAPI.sharedContext) { newPaths ->
|
||||
return buildPaths(paths).map { newPaths ->
|
||||
getPath(newPaths)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return buildPaths(listOf()).map(SnodeAPI.sharedContext) { newPaths ->
|
||||
return buildPaths(listOf()).map { newPaths ->
|
||||
getPath(newPaths)
|
||||
}
|
||||
}
|
||||
@@ -263,10 +268,10 @@ object OnionRequestAPI {
|
||||
is Destination.Snode -> destination.snode
|
||||
is Destination.Server -> null
|
||||
}
|
||||
return getPath(snodeToExclude).bind(SnodeAPI.sharedContext) { path ->
|
||||
return getPath(snodeToExclude).bind { path ->
|
||||
guardSnode = path.first()
|
||||
// Encrypt in reverse order, i.e. the destination first
|
||||
OnionRequestEncryption.encryptPayloadForDestination(payload, destination).bind(SnodeAPI.sharedContext) { r ->
|
||||
OnionRequestEncryption.encryptPayloadForDestination(payload, destination).bind { r ->
|
||||
destinationSymmetricKey = r.symmetricKey
|
||||
// Recursively encrypt the layers of the onion (again in reverse order)
|
||||
encryptionResult = r
|
||||
@@ -278,7 +283,7 @@ object OnionRequestAPI {
|
||||
} else {
|
||||
val lhs = Destination.Snode(path.last())
|
||||
path = path.dropLast(1)
|
||||
return OnionRequestEncryption.encryptHop(lhs, rhs, encryptionResult).bind(SnodeAPI.sharedContext) { r ->
|
||||
return OnionRequestEncryption.encryptHop(lhs, rhs, encryptionResult).bind { r ->
|
||||
encryptionResult = r
|
||||
rhs = lhs
|
||||
addLayer()
|
||||
@@ -287,7 +292,7 @@ object OnionRequestAPI {
|
||||
}
|
||||
addLayer()
|
||||
}
|
||||
}.map(SnodeAPI.sharedContext) { OnionBuildingResult(guardSnode, encryptionResult, destinationSymmetricKey) }
|
||||
}.map { OnionBuildingResult(guardSnode, encryptionResult, destinationSymmetricKey) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,34 +0,0 @@
|
||||
package org.session.libsession.snode
|
||||
|
||||
class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) {
|
||||
|
||||
val ip: String get() = address.removePrefix("https://")
|
||||
|
||||
internal enum class Method(val rawValue: String) {
|
||||
/**
|
||||
* Only supported by snode targets.
|
||||
*/
|
||||
GetSwarm("get_snodes_for_pubkey"),
|
||||
/**
|
||||
* Only supported by snode targets.
|
||||
*/
|
||||
GetMessages("retrieve"),
|
||||
SendMessage("store")
|
||||
}
|
||||
|
||||
data class KeySet(val ed25519Key: String, val x25519Key: String)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return if (other is Snode) {
|
||||
address == other.address && port == other.port
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return address.hashCode() xor port.hashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String { return "$address:$port" }
|
||||
}
|
@@ -6,24 +6,25 @@ import android.os.Build
|
||||
import nl.komponents.kovenant.*
|
||||
import nl.komponents.kovenant.functional.bind
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import org.session.libsession.messaging.utilities.MessageWrapper
|
||||
import org.session.libsession.snode.utilities.getRandomElement
|
||||
import org.session.libsignal.service.loki.api.Snode
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
import org.session.libsignal.service.loki.Snode
|
||||
import org.session.libsignal.service.loki.api.utilities.HTTP
|
||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
||||
import org.session.libsignal.service.loki.utilities.Broadcaster
|
||||
import org.session.libsignal.service.loki.utilities.prettifiedDescription
|
||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||
import org.session.libsignal.service.loki.utilities.retryIfNeeded
|
||||
import org.session.libsignal.utilities.*
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import java.security.SecureRandom
|
||||
|
||||
object SnodeAPI {
|
||||
val database: LokiAPIDatabaseProtocol
|
||||
get() = SnodeConfiguration.shared.storage
|
||||
val broadcaster: Broadcaster
|
||||
get() = SnodeConfiguration.shared.broadcaster
|
||||
val sharedContext = Kovenant.createContext()
|
||||
val messagePollingContext = Kovenant.createContext()
|
||||
private val database: LokiAPIDatabaseProtocol
|
||||
get() = SnodeModule.shared.storage
|
||||
private val broadcaster: Broadcaster
|
||||
get() = SnodeModule.shared.broadcaster
|
||||
|
||||
internal var snodeFailureCount: MutableMap<Snode, Int> = mutableMapOf()
|
||||
internal var snodePool: Set<Snode>
|
||||
@@ -32,28 +33,27 @@ object SnodeAPI {
|
||||
|
||||
// Settings
|
||||
private val maxRetryCount = 6
|
||||
private val minimumSnodePoolCount = 64
|
||||
private val minimumSnodePoolCount = 24
|
||||
private val minimumSwarmSnodeCount = 2
|
||||
|
||||
// use port 4433 if API level can handle network security config and enforce pinned certificates
|
||||
private val seedPort = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) 443 else 4433
|
||||
private val seedNodePool: Set<String> = setOf(
|
||||
"https://storage.seed1.loki.network:$seedPort",
|
||||
"https://storage.seed3.loki.network:$seedPort",
|
||||
"https://public.loki.foundation:$seedPort"
|
||||
)
|
||||
internal val snodeFailureThreshold = 4
|
||||
// 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 {
|
||||
if (useTestnet) {
|
||||
setOf( "http://public.loki.foundation:38157" )
|
||||
} else {
|
||||
setOf( "https://storage.seed1.loki.network:$seedNodePort", "https://storage.seed3.loki.network:$seedNodePort", "https://public.loki.foundation:$seedNodePort" )
|
||||
}
|
||||
}
|
||||
private val snodeFailureThreshold = 4
|
||||
private val targetSwarmSnodeCount = 2
|
||||
|
||||
private val useOnionRequests = true
|
||||
|
||||
internal var powDifficulty = 1
|
||||
internal val useTestnet = false
|
||||
|
||||
// Error
|
||||
internal sealed class Error(val description: String) : Exception(description) {
|
||||
object Generic : Error("An error occurred.")
|
||||
object ClockOutOfSync : Error("The user's clock is out of sync with the service node network.")
|
||||
object RandomSnodePoolUpdatingFailed : Error("Failed to update random service node pool.")
|
||||
object ClockOutOfSync : Error("Your clock is out of sync with the Service Node network.")
|
||||
}
|
||||
|
||||
// Internal API
|
||||
@@ -91,12 +91,12 @@ object SnodeAPI {
|
||||
val parameters = mapOf(
|
||||
"method" to "get_n_service_nodes",
|
||||
"params" to mapOf(
|
||||
"active_only" to true,
|
||||
"fields" to mapOf( "public_ip" to true, "storage_port" to true, "pubkey_x25519" to true, "pubkey_ed25519" to true )
|
||||
"active_only" to true,
|
||||
"fields" to mapOf( "public_ip" to true, "storage_port" to true, "pubkey_x25519" to true, "pubkey_ed25519" to true )
|
||||
)
|
||||
)
|
||||
val deferred = deferred<Snode, Exception>()
|
||||
deferred<org.session.libsignal.service.loki.api.Snode, Exception>(SnodeAPI.sharedContext)
|
||||
deferred<Snode, Exception>()
|
||||
ThreadUtils.queue {
|
||||
try {
|
||||
val json = HTTP.execute(HTTP.Verb.POST, url, parameters, useSeedNodeConnection = true)
|
||||
@@ -164,10 +164,10 @@ object SnodeAPI {
|
||||
cachedSwarmCopy.addAll(cachedSwarm)
|
||||
return task { cachedSwarmCopy }
|
||||
} else {
|
||||
val parameters = mapOf( "pubKey" to publicKey )
|
||||
val parameters = mapOf( "pubKey" to if (useTestnet) publicKey.removing05PrefixIfNeeded() else publicKey )
|
||||
return getRandomSnode().bind {
|
||||
invoke(Snode.Method.GetSwarm, it, publicKey, parameters)
|
||||
}.map(sharedContext) {
|
||||
}.map {
|
||||
parseSnodes(it).toSet()
|
||||
}.success {
|
||||
database.setSwarm(publicKey, it)
|
||||
@@ -177,38 +177,26 @@ object SnodeAPI {
|
||||
|
||||
fun getRawMessages(snode: Snode, publicKey: String): RawResponsePromise {
|
||||
val lastHashValue = database.getLastMessageHashValue(snode, publicKey) ?: ""
|
||||
val parameters = mapOf( "pubKey" to publicKey, "lastHash" to lastHashValue )
|
||||
val parameters = mapOf( "pubKey" to if (useTestnet) publicKey.removing05PrefixIfNeeded() else publicKey, "lastHash" to lastHashValue )
|
||||
return invoke(Snode.Method.GetMessages, snode, publicKey, parameters)
|
||||
}
|
||||
|
||||
fun getMessages(publicKey: String): MessageListPromise {
|
||||
return retryIfNeeded(maxRetryCount) {
|
||||
getSingleTargetSnode(publicKey).bind(messagePollingContext) { snode ->
|
||||
getRawMessages(snode, publicKey).map(messagePollingContext) { parseRawMessagesResponse(it, snode, publicKey) }
|
||||
getSingleTargetSnode(publicKey).bind { snode ->
|
||||
getRawMessages(snode, publicKey).map { parseRawMessagesResponse(it, snode, publicKey) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendMessage(message: SnodeMessage): Promise<Set<RawResponsePromise>, Exception> {
|
||||
val destination = message.recipient
|
||||
val destination = if (useTestnet) message.recipient.removing05PrefixIfNeeded() else message.recipient
|
||||
return retryIfNeeded(maxRetryCount) {
|
||||
getTargetSnodes(destination).map { swarm ->
|
||||
swarm.map { snode ->
|
||||
val parameters = message.toJSON()
|
||||
retryIfNeeded(maxRetryCount) {
|
||||
invoke(Snode.Method.SendMessage, snode, destination, parameters).map { rawResponse ->
|
||||
val json = rawResponse as? Map<*, *>
|
||||
val powDifficulty = json?.get("difficulty") as? Int
|
||||
if (powDifficulty != null) {
|
||||
if (powDifficulty != SnodeAPI.powDifficulty && powDifficulty < 100) {
|
||||
Log.d("Loki", "Setting proof of work difficulty to $powDifficulty (snode: $snode).")
|
||||
SnodeAPI.powDifficulty = powDifficulty
|
||||
}
|
||||
} else {
|
||||
Log.d("Loki", "Failed to update proof of work difficulty from: ${rawResponse.prettifiedDescription()}.")
|
||||
}
|
||||
rawResponse
|
||||
}
|
||||
invoke(Snode.Method.SendMessage, snode, destination, parameters)
|
||||
}
|
||||
}.toSet()
|
||||
}
|
||||
@@ -240,20 +228,20 @@ object SnodeAPI {
|
||||
}
|
||||
}
|
||||
|
||||
fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String): List<*> {
|
||||
fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String): List<SignalServiceProtos.Envelope> {
|
||||
val messages = rawResponse["messages"] as? List<*>
|
||||
return if (messages != null) {
|
||||
updateLastMessageHashValueIfPossible(snode, publicKey, messages)
|
||||
removeDuplicates(publicKey, messages)
|
||||
val newRawMessages = removeDuplicates(publicKey, messages)
|
||||
return parseEnvelopes(newRawMessages);
|
||||
} else {
|
||||
listOf<Map<*,*>>()
|
||||
listOf()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateLastMessageHashValueIfPossible(snode: Snode, publicKey: String, rawMessages: List<*>) {
|
||||
val lastMessageAsJSON = rawMessages.lastOrNull() as? Map<*, *>
|
||||
val hashValue = lastMessageAsJSON?.get("hash") as? String
|
||||
val expiration = lastMessageAsJSON?.get("expiration") as? Int
|
||||
if (hashValue != null) {
|
||||
database.setLastMessageHashValue(snode, publicKey, hashValue)
|
||||
} else if (rawMessages.isNotEmpty()) {
|
||||
@@ -278,6 +266,26 @@ object SnodeAPI {
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseEnvelopes(rawMessages: List<*>): List<SignalServiceProtos.Envelope> {
|
||||
return rawMessages.mapNotNull { rawMessage ->
|
||||
val rawMessageAsJSON = rawMessage as? Map<*, *>
|
||||
val base64EncodedData = rawMessageAsJSON?.get("data") as? String
|
||||
val data = base64EncodedData?.let { Base64.decode(it) }
|
||||
if (data != null) {
|
||||
try {
|
||||
MessageWrapper.unwrap(data)
|
||||
} catch (e: Exception) {
|
||||
Log.d("Loki", "Failed to unwrap data for message: ${rawMessage.prettifiedDescription()}.")
|
||||
null
|
||||
}
|
||||
} else {
|
||||
Log.d("Loki", "Failed to decode data for message: ${rawMessage?.prettifiedDescription()}.")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// Error Handling
|
||||
internal fun handleSnodeError(statusCode: Int, json: Map<*, *>?, snode: Snode, publicKey: String? = null): Exception? {
|
||||
fun handleBadSnode() {
|
||||
@@ -313,20 +321,6 @@ object SnodeAPI {
|
||||
Log.d("Loki", "Got a 421 without an associated public key.")
|
||||
}
|
||||
}
|
||||
432 -> {
|
||||
// The PoW difficulty is too low
|
||||
val powDifficulty = json?.get("difficulty") as? Int
|
||||
if (powDifficulty != null) {
|
||||
if (powDifficulty < 100) {
|
||||
Log.d("Loki", "Setting proof of work difficulty to $powDifficulty (snode: $snode).")
|
||||
SnodeAPI.powDifficulty = powDifficulty
|
||||
} else {
|
||||
handleBadSnode()
|
||||
}
|
||||
} else {
|
||||
Log.d("Loki", "Failed to update proof of work difficulty.")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
handleBadSnode()
|
||||
Log.d("Loki", "Unhandled response code: ${statusCode}.")
|
||||
@@ -335,11 +329,9 @@ object SnodeAPI {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Type Aliases
|
||||
typealias RawResponse = Map<*, *>
|
||||
typealias MessageListPromise = Promise<List<*>, Exception>
|
||||
typealias MessageListPromise = Promise<List<SignalServiceProtos.Envelope>, Exception>
|
||||
typealias RawResponsePromise = Promise<RawResponse, Exception>
|
||||
|
@@ -1,5 +1,7 @@
|
||||
package org.session.libsession.snode
|
||||
|
||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||
|
||||
data class SnodeMessage(
|
||||
// The hex encoded public key of the recipient.
|
||||
val recipient: String,
|
||||
@@ -8,16 +10,16 @@ data class SnodeMessage(
|
||||
// The time to live for the message in milliseconds.
|
||||
val ttl: Long,
|
||||
// When the proof of work was calculated.
|
||||
val timestamp: Long,
|
||||
// The base 64 encoded proof of work.
|
||||
val nonce: String
|
||||
val timestamp: Long
|
||||
) {
|
||||
|
||||
internal fun toJSON(): Map<String, String> {
|
||||
return mutableMapOf(
|
||||
"pubKey" to recipient,
|
||||
"data" to data,
|
||||
"ttl" to ttl.toString(),
|
||||
"timestamp" to timestamp.toString(),
|
||||
"nonce" to nonce)
|
||||
return mapOf(
|
||||
"pubKey" to if (SnodeAPI.useTestnet) recipient.removing05PrefixIfNeeded() else recipient,
|
||||
"data" to data,
|
||||
"ttl" to ttl.toString(),
|
||||
"timestamp" to timestamp.toString(),
|
||||
"nonce" to ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -3,13 +3,14 @@ package org.session.libsession.snode
|
||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
||||
import org.session.libsignal.service.loki.utilities.Broadcaster
|
||||
|
||||
class SnodeConfiguration(val storage: LokiAPIDatabaseProtocol, val broadcaster: Broadcaster) {
|
||||
class SnodeModule(val storage: LokiAPIDatabaseProtocol, val broadcaster: Broadcaster) {
|
||||
|
||||
companion object {
|
||||
lateinit var shared: SnodeConfiguration
|
||||
lateinit var shared: SnodeModule
|
||||
|
||||
fun configure(storage: LokiAPIDatabaseProtocol, broadcaster: Broadcaster) {
|
||||
if (Companion::shared.isInitialized) { return }
|
||||
shared = SnodeConfiguration(storage, broadcaster)
|
||||
shared = SnodeModule(storage, broadcaster)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,9 @@
|
||||
package org.session.libsession.snode
|
||||
|
||||
import org.session.libsignal.service.loki.Snode
|
||||
|
||||
interface SnodeStorageProtocol {
|
||||
|
||||
fun getSnodePool(): Set<Snode>
|
||||
fun setSnodePool(newValue: Set<Snode>)
|
||||
fun getOnionRequestPaths(): List<List<Snode>>
|
||||
|
@@ -0,0 +1,88 @@
|
||||
package org.session.libsession.utilities
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Request
|
||||
import org.session.libsession.messaging.file_server.FileServerAPI
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
import org.session.libsignal.service.api.messages.SignalServiceAttachment
|
||||
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException
|
||||
import org.session.libsignal.service.api.push.exceptions.PushNetworkException
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import java.io.*
|
||||
|
||||
object DownloadUtilities {
|
||||
|
||||
/**
|
||||
* Blocks the calling thread.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun downloadFile(destination: File, url: String, maxSize: Int, listener: SignalServiceAttachment.ProgressListener?) {
|
||||
val outputStream = FileOutputStream(destination) // Throws
|
||||
var remainingAttempts = 4
|
||||
var exception: Exception? = null
|
||||
while (remainingAttempts > 0) {
|
||||
remainingAttempts -= 1
|
||||
try {
|
||||
downloadFile(outputStream, url, maxSize, listener)
|
||||
exception = null
|
||||
break
|
||||
} catch (e: Exception) {
|
||||
exception = e
|
||||
}
|
||||
}
|
||||
if (exception != null) { throw exception }
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks the calling thread.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun downloadFile(outputStream: OutputStream, url: String, maxSize: Int, listener: SignalServiceAttachment.ProgressListener?) {
|
||||
// We need to throw a PushNetworkException or NonSuccessfulResponseCodeException
|
||||
// because the underlying Signal logic requires these to work correctly
|
||||
val oldPrefixedHost = "https://" + HttpUrl.get(url).host()
|
||||
var newPrefixedHost = oldPrefixedHost
|
||||
if (oldPrefixedHost.contains(FileServerAPI.fileStorageBucketURL)) {
|
||||
newPrefixedHost = FileServerAPI.shared.server
|
||||
}
|
||||
// Edge case that needs to work: https://file-static.lokinet.org/i1pNmpInq3w9gF3TP8TFCa1rSo38J6UM
|
||||
// → https://file.getsession.org/loki/v1/f/XLxogNXVEIWHk14NVCDeppzTujPHxu35
|
||||
val fileID = url.substringAfter(oldPrefixedHost).substringAfter("/f/")
|
||||
val sanitizedURL = "$newPrefixedHost/loki/v1/f/$fileID"
|
||||
val request = Request.Builder().url(sanitizedURL).get()
|
||||
try {
|
||||
val serverPublicKey = if (newPrefixedHost.contains(FileServerAPI.shared.server)) FileServerAPI.fileServerPublicKey
|
||||
else FileServerAPI.shared.getPublicKeyForOpenGroupServer(newPrefixedHost).get()
|
||||
val json = OnionRequestAPI.sendOnionRequest(request.build(), newPrefixedHost, serverPublicKey, isJSONRequired = false).get()
|
||||
val result = json["result"] as? String
|
||||
if (result == null) {
|
||||
Log.d("Loki", "Couldn't parse attachment from: $json.")
|
||||
throw PushNetworkException("Missing response body.")
|
||||
}
|
||||
val body = Base64.decode(result)
|
||||
if (body.size > maxSize) {
|
||||
Log.d("Loki", "Attachment size limit exceeded.")
|
||||
throw PushNetworkException("Max response size exceeded.")
|
||||
}
|
||||
body.inputStream().use { input ->
|
||||
val buffer = ByteArray(32768)
|
||||
var count = 0
|
||||
var bytes = input.read(buffer)
|
||||
while (bytes >= 0) {
|
||||
outputStream.write(buffer, 0, bytes)
|
||||
count += bytes
|
||||
if (count > maxSize) {
|
||||
Log.d("Loki", "Attachment size limit exceeded.")
|
||||
throw PushNetworkException("Max response size exceeded.")
|
||||
}
|
||||
listener?.onAttachmentProgress(body.size.toLong(), count.toLong())
|
||||
bytes = input.read(buffer)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.d("Loki", "Couldn't download attachment due to error: $e.")
|
||||
throw if (e is NonSuccessfulResponseCodeException) e else PushNetworkException(e)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
package org.session.libsession.utilities;
|
||||
|
||||
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
|
||||
public class LinkedBlockingLifoQueue<E> extends LinkedBlockingDeque<E> {
|
||||
|
@@ -3,7 +3,6 @@ package org.session.libsession.utilities
|
||||
import android.telephony.PhoneNumberUtils
|
||||
import android.util.Patterns
|
||||
|
||||
|
||||
object NumberUtil {
|
||||
private val emailPattern = Patterns.EMAIL_ADDRESS
|
||||
|
||||
|
@@ -14,6 +14,7 @@ class SSKEnvironment(
|
||||
val notificationManager: MessageNotifier,
|
||||
val messageExpirationManager: MessageExpirationManagerProtocol
|
||||
) {
|
||||
|
||||
interface TypingIndicatorsProtocol {
|
||||
fun didReceiveTypingStartedMessage(context: Context, threadId: Long, author: Address, device: Int)
|
||||
fun didReceiveTypingStoppedMessage(context: Context, threadId: Long, author: Address, device: Int, isReplacedByIncomingMessage: Boolean)
|
||||
|
@@ -5,6 +5,7 @@ import org.session.libsignal.utilities.concurrent.ListenableFuture.Listener;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public abstract class AssertedSuccessListener<T> implements Listener<T> {
|
||||
|
||||
@Override
|
||||
public void onFailure(ExecutionException e) {
|
||||
throw new AssertionError(e);
|
||||
|
@@ -30,5 +30,4 @@ public final class DynamicLanguageContextWrapper {
|
||||
copy.setLocale(locale);
|
||||
return copy;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -7,8 +7,7 @@ import java.util.Locale;
|
||||
|
||||
public final class LanguageString {
|
||||
|
||||
private LanguageString() {
|
||||
}
|
||||
private LanguageString() { }
|
||||
|
||||
/**
|
||||
* @param languageString String in format language_REGION, e.g. en_US
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package org.session.libsession.utilities.preferences;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
@@ -10,7 +10,6 @@ public abstract class SnackbarAsyncTask<Params>
|
||||
extends AsyncTask<Params, Void, Void>
|
||||
implements View.OnClickListener
|
||||
{
|
||||
|
||||
private final View view;
|
||||
private final String snackbarText;
|
||||
private final String snackbarActionText;
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package org.session.libsession.utilities.views;
|
||||
|
||||
|
||||
import android.view.ViewStub;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
Reference in New Issue
Block a user