Merge remote-tracking branch 'upstream/dev' into open_groups_V2, working on compact poller implementation

# Conflicts:
#	app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java
#	app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
#	app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
#	app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt
#	app/src/main/java/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt
#	app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt
#	app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt
#	app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt
#	app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionManagerUtilities.kt
#	app/src/main/java/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt
#	app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt
#	app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt
#	libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt
#	libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt
#	libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt
#	libsession/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt
#	libsession/src/main/java/org/session/libsession/messaging/messages/Destination.kt
#	libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt
#	libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupMessageV2.kt
#	libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupV2.kt
#	libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt
#	libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt
#	libsession/src/main/java/org/session/libsession/messaging/utilities/DotNetAPI.kt
#	libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt
#	libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt
#	libsession/src/main/java/org/session/libsession/snode/SnodeMessage.kt
#	libsession/src/main/java/org/session/libsession/utilities/mentions/MentionsManager.kt
#	libsignal/src/main/java/org/session/libsignal/service/loki/api/SwarmAPI.kt
#	libsignal/src/main/java/org/session/libsignal/service/loki/api/opengroups/PublicChat.kt
#	libsignal/src/main/java/org/session/libsignal/service/loki/utilities/mentions/MentionsManager.kt
This commit is contained in:
jubb
2021-04-28 17:41:30 +10:00
158 changed files with 1112 additions and 16856 deletions

View File

@@ -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?
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -9,12 +9,12 @@ 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.opengroups.OpenGroupV2
import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.open_groups.OpenGroupV2
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
@@ -22,7 +22,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 {

View File

@@ -1,6 +1,5 @@
package org.session.libsession.messaging.avatars;
import android.content.Context;
import androidx.annotation.NonNull;

View File

@@ -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();
}

View File

@@ -7,5 +7,4 @@ public interface FallbackContactPhoto {
public Drawable asDrawable(Context context, int color);
public Drawable asDrawable(Context context, int color, boolean inverted);
}

View File

@@ -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}]+");

View File

@@ -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) {

View File

@@ -1,6 +1,5 @@
package org.session.libsession.messaging.avatars;
import android.content.Context;
import android.net.Uri;

View File

@@ -1,6 +1,5 @@
package org.session.libsession.messaging.avatars;
import android.content.Context;
import android.net.Uri;

View File

@@ -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() {

View File

@@ -1,9 +1,9 @@
package org.session.libsession.messaging.jobs
import okhttp3.HttpUrl
import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsession.messaging.fileserver.FileServerAPI
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.file_server.FileServerAPI
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState
import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsignal.service.api.crypto.AttachmentCipherInputStream
@@ -13,13 +13,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.")
@@ -31,39 +28,39 @@ 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()
val threadId = MessagingConfiguration.shared.storage.getThreadIdForMms(databaseMessageID)
val openGroupV2 = MessagingConfiguration.shared.storage.getV2OpenGroup(threadId.toString())
val threadId = MessagingModuleConfiguration.shared.storage.getThreadIdForMms(databaseMessageID)
val openGroupV2 = MessagingModuleConfiguration.shared.storage.getV2OpenGroup(threadId.toString())
val stream = if (openGroupV2 == null) {
FileServerAPI.shared.downloadFile(tempFile, attachment.url, MAX_ATTACHMENT_SIZE, null)
FileServerAPI.shared.downloadFile(tempFile, attachment.url, null)
// DECRYPTION
// Assume we're retrieving an attachment for an open group server if the digest is not set
if (attachment.digest?.size ?: 0 == 0 || attachment.key.isNullOrEmpty()) FileInputStream(tempFile)
@@ -98,17 +95,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 {

View File

@@ -3,10 +3,10 @@ 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.opengroups.OpenGroupAPIV2
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsignal.service.api.crypto.AttachmentCipherOutputStream
@@ -19,7 +19,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
@@ -35,9 +34,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"
@@ -46,22 +43,18 @@ 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)
val openGroupV2 = MessagingConfiguration.shared.storage.getV2OpenGroup(threadID)
openGroup?.let {
server = it.server
shouldEncrypt = false
}
openGroupV2?.let {
server = it.server
shouldEncrypt = false
}
val openGroupV2 = MessagingModuleConfiguration.shared.storage.getV2OpenGroup(threadID)
val openGroup = MessagingModuleConfiguration.shared.storage.getOpenGroup(threadID)
val server = openGroup?.let {
it.server
} ?: openGroupV2?.let {
it.server
} ?: FileServerAPI.shared.server
val shouldEncrypt = (openGroup == null && openGroupV2 == 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
@@ -77,9 +70,8 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
DotNetAPI.UploadResult(result, "${openGroupV2.server}/files/$result", byteArrayOf())
}
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)
@@ -87,33 +79,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) {
@@ -121,10 +112,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)
@@ -132,10 +120,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 {
@@ -143,9 +131,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)

View File

@@ -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 {

View File

@@ -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
/**

View File

@@ -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)

View File

@@ -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,20 +14,18 @@ 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 multiDispatcher = Executors.newFixedThreadPool(2).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 ->
@@ -64,14 +62,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() {
@@ -82,21 +80,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) {
@@ -113,7 +111,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)
}

View File

@@ -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))
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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 {

View File

@@ -1,7 +1,5 @@
package org.session.libsession.messaging.jobs
import java.util.*
class SessionJobManagerFactories {
companion object {

View File

@@ -1,20 +1,20 @@
package org.session.libsession.utilities.mentions
package org.session.libsession.messaging.mentions
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.utilities.mentions.Mention
import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsignal.service.loki.database.LokiThreadDatabaseProtocol
import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol
class MentionsManager(private val userPublicKey: String, private val threadDatabase: LokiThreadDatabaseProtocol,
private val userDatabase: 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 {
lateinit var shared: MentionsManager
public lateinit var shared: MentionsManager
fun configureIfNeeded(userPublicKey: String, threadDatabase: LokiThreadDatabaseProtocol, userDatabase: LokiUserDatabaseProtocol) {
public fun configureIfNeeded(userPublicKey: String, userDatabase: LokiUserDatabaseProtocol) {
if (::shared.isInitialized) { return; }
shared = MentionsManager(userPublicKey, threadDatabase, userDatabase)
shared = MentionsManager(userPublicKey, userDatabase)
}
}
@@ -31,15 +31,11 @@ class MentionsManager(private val userPublicKey: String, private val threadDatab
// Prepare
val cache = userPublicKeyCache[threadID] ?: return listOf()
// Gather candidates
val storage = MessagingConfiguration.shared.storage
val publicChat = threadDatabase.getPublicChat(threadID)
val openGroupV2 = storage.getV2OpenGroup(threadID.toString())
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 if (openGroupV2 != null) {
displayName = userDatabase.getServerDisplayName(openGroupV2.id, publicKey)
} else {
displayName = userDatabase.getDisplayName(publicKey)
}

View File

@@ -1,12 +1,14 @@
package org.session.libsession.messaging.messages
import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.open_groups.OpenGroupV2
import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.threads.Address
import org.session.libsession.utilities.GroupUtil
import org.session.libsignal.service.loki.utilities.toHexString
typealias OpenGroupModel = org.session.libsession.messaging.opengroups.OpenGroup
typealias OpenGroupV2Model = org.session.libsession.messaging.opengroups.OpenGroupV2
typealias OpenGroupModel = OpenGroup
typealias OpenGroupV2Model = OpenGroupV2
sealed class Destination {
@@ -35,7 +37,7 @@ sealed class Destination {
ClosedGroup(groupPublicKey)
}
address.isOpenGroup -> {
val storage = MessagingConfiguration.shared.storage
val storage = MessagingModuleConfiguration.shared.storage
val threadID = storage.getThreadID(address.contactIdentifier())!!
when (val openGroup = storage.getOpenGroup(threadID) ?: storage.getV2OpenGroup(threadID)) {
is OpenGroupModel -> OpenGroup(openGroup.channel, openGroup.server)

View File

@@ -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

View File

@@ -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,11 +143,6 @@ 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
@@ -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) {

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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)

View File

@@ -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")
}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"
}

View File

@@ -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

View File

@@ -1,10 +1,9 @@
package org.session.libsession.messaging.opengroups
package org.session.libsession.messaging.open_groups
import com.fasterxml.jackson.databind.PropertyNamingStrategy
import com.fasterxml.jackson.databind.annotation.JsonNaming
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import nl.komponents.kovenant.Kovenant
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.bind
@@ -13,14 +12,12 @@ import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.MediaType
import okhttp3.RequestBody
import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsession.messaging.fileserver.FileServerAPI
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2.Error
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2.Error
import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.utilities.AESGCM
import org.session.libsignal.service.loki.api.utilities.HTTP
import org.session.libsignal.service.loki.api.utilities.HTTP.Verb.*
import org.session.libsignal.service.loki.utilities.DownloadUtilities
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.service.loki.utilities.toHexString
import org.session.libsignal.utilities.Base64.*
@@ -29,7 +26,6 @@ import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.utilities.createContext
import org.session.libsignal.utilities.logging.Log
import org.whispersystems.curve25519.Curve25519
import java.io.ByteArrayOutputStream
import java.util.*
object OpenGroupAPIV2 {
@@ -130,12 +126,12 @@ object OpenGroupAPIV2 {
}
if (request.useOnionRouting) {
val publicKey = MessagingConfiguration.shared.storage.getOpenGroupPublicKey(request.server)
val publicKey = MessagingModuleConfiguration.shared.storage.getOpenGroupPublicKey(request.server)
?: return Promise.ofFail(Error.NO_PUBLIC_KEY)
return OnionRequestAPI.sendOnionRequest(requestBuilder.build(), request.server, publicKey)
.fail { e ->
if (e is OnionRequestAPI.HTTPRequestFailedAtDestinationException && e.statusCode == 401) {
val storage = MessagingConfiguration.shared.storage
val storage = MessagingModuleConfiguration.shared.storage
if (request.room != null) {
storage.removeAuthToken("${request.server}.${request.room}")
} else {
@@ -163,7 +159,7 @@ object OpenGroupAPIV2 {
}
fun getAuthToken(room: String, server: String): Promise<String, Exception> {
val storage = MessagingConfiguration.shared.storage
val storage = MessagingModuleConfiguration.shared.storage
return storage.getAuthToken(room, server)?.let {
Promise.of(it)
} ?: run {
@@ -176,7 +172,7 @@ object OpenGroupAPIV2 {
}
fun requestNewAuthToken(room: String, server: String): Promise<String, Exception> {
val (publicKey, privateKey) = MessagingConfiguration.shared.storage.getUserKeyPair()
val (publicKey, privateKey) = MessagingModuleConfiguration.shared.storage.getUserKeyPair()
?: return Promise.ofFail(Error.GENERIC)
val queryParameters = mutableMapOf("public_key" to publicKey)
val request = Request(GET, room, server, "auth_token_challenge", queryParameters, isAuthRequired = false, parameters = null)
@@ -199,7 +195,7 @@ object OpenGroupAPIV2 {
}
fun claimAuthToken(authToken: String, room: String, server: String): Promise<String, Exception> {
val parameters = mapOf("public_key" to MessagingConfiguration.shared.storage.getUserPublicKey()!!)
val parameters = mapOf("public_key" to MessagingModuleConfiguration.shared.storage.getUserPublicKey()!!)
val headers = mapOf("Authorization" to authToken)
val request = Request(verb = POST, room = room, server = server, endpoint = "claim_auth_token",
parameters = parameters, headers = headers, isAuthRequired = false)
@@ -209,7 +205,7 @@ object OpenGroupAPIV2 {
fun deleteAuthToken(room: String, server: String): Promise<Unit, Exception> {
val request = Request(verb = DELETE, room = room, server = server, endpoint = "auth_token")
return send(request).map(sharedContext) {
MessagingConfiguration.shared.storage.removeAuthToken(room, server)
MessagingModuleConfiguration.shared.storage.removeAuthToken(room, server)
}
}
@@ -245,7 +241,7 @@ object OpenGroupAPIV2 {
// region Messages
fun getMessages(room: String, server: String): Promise<List<OpenGroupMessageV2>, Exception> {
val storage = MessagingConfiguration.shared.storage
val storage = MessagingModuleConfiguration.shared.storage
val queryParameters = mutableMapOf<String, String>()
storage.getLastMessageServerId(room, server)?.let { lastId ->
queryParameters += "from_server_id" to lastId.toString()
@@ -294,7 +290,7 @@ object OpenGroupAPIV2 {
}
fun getDeletedMessages(room: String, server: String): Promise<List<Long>, Exception> {
val storage = MessagingConfiguration.shared.storage
val storage = MessagingModuleConfiguration.shared.storage
val queryParameters = mutableMapOf<String, String>()
storage.getLastDeletionServerId(room, server)?.let { last ->
queryParameters["from_server_id"] = last.toString()
@@ -347,15 +343,33 @@ object OpenGroupAPIV2 {
// endregion
// region General
// fun getCompactPoll(rooms: List<String>, server: String): Promise<Map<String,CompactPollResult>, Exception> {
// val request = Request(verb = POST, room = null, server = server, endpoint = "compact_poll", isAuthRequired = false)
//
// // build a request for all rooms
//
// }
fun getCompactPoll(rooms: List<String>, server: String): Promise<Map<String,CompactPollResult>, Exception> {
val requestAuths = rooms.associateWith { room -> getAuthToken(room,server) }
val storage = MessagingModuleConfiguration.shared.storage
val requests = rooms.mapNotNull { room ->
val authToken = try {
requestAuths[room]?.get()
} catch (e: Exception) {
Log.e("Loki", "Failed to get auth token for $room",e)
null
} ?: return@mapNotNull null
CompactPollRequest(roomId = room,
authToken = authToken,
fromDeletionServerId = storage.getLastDeletionServerId(room, server),
fromMessageServerId = storage.getLastMessageServerId(room, server)
)
}
val request = Request(verb = POST, room = null, server = server, endpoint = "compact_poll", isAuthRequired = false, parameters = mapOf("requests" to requests))
// build a request for all rooms
return send(request = request).map(sharedContext) { json ->
val results = json["results"] as? Map<*,*> ?: throw Error.PARSING_FAILED
TODO()
}
}
fun getDefaultRoomsIfNeeded(): Promise<List<DefaultGroup>, Exception> {
val storage = MessagingConfiguration.shared.storage
val storage = MessagingModuleConfiguration.shared.storage
storage.setOpenGroupPublicKey(DEFAULT_SERVER, DEFAULT_SERVER_PUBLIC_KEY)
return getAllRooms(DEFAULT_SERVER).map { groups ->
val earlyGroups = groups.map { group ->
@@ -414,7 +428,7 @@ object OpenGroupAPIV2 {
val request = Request(verb = GET, room = room, server = server, endpoint = "member_count")
return send(request).map(sharedContext) { json ->
val memberCount = json["member_count"] as? Long ?: throw Error.PARSING_FAILED
val storage = MessagingConfiguration.shared.storage
val storage = MessagingModuleConfiguration.shared.storage
storage.setUserCount(room, server, memberCount)
memberCount
}

View File

@@ -1,4 +1,4 @@
package org.session.libsession.messaging.opengroups
package org.session.libsession.messaging.open_groups
data class OpenGroupInfo (
val displayName: String,

View File

@@ -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,

View File

@@ -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.libsignal.service.internal.push.PushTransportDetails
import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.utilities.Base64
@@ -40,7 +40,7 @@ data class OpenGroupMessageV2(
fun sign(): OpenGroupMessageV2? {
if (base64EncodedData.isEmpty()) return null
val (publicKey, privateKey) = MessagingConfiguration.shared.storage.getUserKeyPair() ?: return null
val (publicKey, privateKey) = MessagingModuleConfiguration.shared.storage.getUserKeyPair() ?: return null
if (sender != publicKey) return null // only sign our own messages?

View File

@@ -1,4 +1,4 @@
package org.session.libsession.messaging.opengroups
package org.session.libsession.messaging.open_groups
import org.session.libsignal.utilities.JsonUtil
import java.util.*

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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,15 +12,15 @@ 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.OpenGroupAPIV2
import org.session.libsession.messaging.opengroups.OpenGroupMessage
import org.session.libsession.messaging.opengroups.OpenGroupMessageV2
import org.session.libsession.messaging.open_groups.OpenGroupAPI
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
import org.session.libsession.messaging.open_groups.OpenGroupMessage
import org.session.libsession.messaging.open_groups.OpenGroupMessageV2
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
@@ -29,10 +29,9 @@ 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
@@ -75,7 +74,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 */
@@ -85,7 +84,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)
}
@@ -129,7 +128,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,
@@ -140,27 +139,26 @@ 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,
is Destination.OpenGroupV2 -> throw Error.PreconditionFailure("Destination should not be open groups!")
}
val wrappedMessage = MessageWrapper.wrap(kind, message.sentTimestamp!!, senderPublicKey, ciphertext)
// Calculate proof of work
// Send the result
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
SnodeConfiguration.shared.broadcaster.broadcast("calculatingPoW", message.sentTimestamp!!)
SnodeModule.shared.broadcaster.broadcast("calculatingPoW", message.sentTimestamp!!)
}
val recipient = message.recipient!!
val base64EncodedData = Base64.encodeBytes(wrappedMessage)
// Send the result
val snodeMessage = SnodeMessage(recipient, base64EncodedData, message.ttl, message.sentTimestamp!!)
val snodeMessage = SnodeMessage(message.recipient!!, base64EncodedData, message.ttl, message.sentTimestamp!!)
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
SnodeConfiguration.shared.broadcaster.broadcast("sendingMessage", message.sentTimestamp!!)
SnodeModule.shared.broadcaster.broadcast("sendingMessage", message.sentTimestamp!!)
}
SnodeAPI.sendMessage(snodeMessage).success { promises: Set<RawResponsePromise> ->
var isSuccess = false
@@ -171,7 +169,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)
@@ -203,7 +201,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)
@@ -286,7 +284,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
@@ -314,7 +312,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)
}
@@ -322,7 +320,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)
@@ -340,7 +338,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)
@@ -348,13 +346,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)

View File

@@ -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 {
@@ -198,8 +198,8 @@ fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List<St
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 +230,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 +278,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.")

View File

@@ -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())

View File

@@ -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
@@ -148,8 +148,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
@@ -180,10 +180,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))
}
@@ -233,13 +233,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)
}
}
@@ -253,8 +251,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) {
@@ -283,62 +281,9 @@ 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
@@ -376,8 +321,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
@@ -412,8 +357,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
@@ -459,8 +404,8 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo
}
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
@@ -518,8 +463,8 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup
}
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
@@ -565,31 +510,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 {
@@ -608,7 +528,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)

View File

@@ -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;
}

View File

@@ -1,4 +1,4 @@
package org.session.libsession.messaging.sending_receiving.dataextraction
package org.session.libsession.messaging.sending_receiving.data_extraction
class DataExtractionNotificationInfoMessage {

View File

@@ -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;

View File

@@ -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)
}

View File

@@ -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
}
@@ -75,7 +75,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)
}
}

View File

@@ -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>()
@@ -78,7 +78,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()
@@ -163,7 +163,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())
@@ -202,7 +202,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)
@@ -211,9 +211,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}.")

View File

@@ -2,11 +2,11 @@ package org.session.libsession.messaging.sending_receiving.pollers
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.OpenGroupAPIV2
import org.session.libsession.messaging.opengroups.OpenGroupV2
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
import org.session.libsession.messaging.open_groups.OpenGroupV2
import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.utilities.successBackground
@@ -72,7 +72,7 @@ class OpenGroupV2Poller(private val openGroup: OpenGroupV2, private val executor
content.dataMessage = dataMessageProto
// Envelope
val builder = SignalServiceProtos.Envelope.newBuilder()
builder.type = SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER
builder.type = SignalServiceProtos.Envelope.Type.SESSION_MESSAGE
builder.source = senderPublicKey
builder.sourceDevice = 1
builder.content = content.build().toByteString()
@@ -102,9 +102,9 @@ class OpenGroupV2Poller(private val openGroup: OpenGroupV2, private val executor
private fun pollForDeletedMessages() {
OpenGroupAPIV2.getDeletedMessages(openGroup.room, 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.room} on server: ${openGroup.server}.")

View File

@@ -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)

View File

@@ -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>?)

View File

@@ -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)

View File

@@ -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()) {

View File

@@ -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()) {

View File

@@ -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)

View File

@@ -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

View File

@@ -5,10 +5,12 @@ import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.functional.map
import nl.komponents.kovenant.then
import okhttp3.*
import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsession.messaging.fileserver.FileServerAPI
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.messaging.file_server.FileServerAPI
import org.session.libsignal.utilities.DiffieHellman
import org.session.libsignal.service.api.crypto.ProfileCipherOutputStream
import org.session.libsignal.service.api.messages.SignalServiceAttachment
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException
@@ -57,7 +59,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
@@ -76,9 +78,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)
@@ -102,7 +104,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 }
}
@@ -141,7 +143,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
}
}
@@ -180,14 +182,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) {
@@ -200,7 +202,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()
@@ -223,7 +225,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.")
}
@@ -234,7 +236,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.")
}
@@ -330,7 +332,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}.")
}

View File

@@ -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

View File

@@ -6,16 +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.Snode
import org.session.libsignal.service.loki.api.utilities.*
import org.session.libsession.utilities.AESGCM.EncryptionResult
import org.session.libsession.utilities.getBodyForOnionRequest
import org.session.libsession.utilities.getHeadersForOnionRequest
import org.session.libsignal.service.loki.api.Snode
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI
import org.session.libsignal.service.loki.api.utilities.*
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.utilities.*
import org.session.libsignal.utilities.*
import org.session.libsignal.utilities.logging.Log
private typealias Path = List<Snode>
@@ -23,16 +25,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)
}
}
@@ -49,16 +56,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<*, *>)
@@ -72,7 +78,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()
}
@@ -111,7 +117,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() }
@@ -136,7 +142,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
@@ -151,10 +157,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)
@@ -171,7 +177,7 @@ object OnionRequestAPI {
}
}.map { paths ->
OnionRequestAPI.paths = paths + reusablePaths
SnodeAPI.broadcaster.broadcast("pathsBuilt")
broadcaster.broadcast("pathsBuilt")
paths
}
}
@@ -205,12 +211,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)
}
}
@@ -261,10 +267,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
@@ -276,7 +282,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()
@@ -285,7 +291,7 @@ object OnionRequestAPI {
}
addLayer()
}
}.map(SnodeAPI.sharedContext) { OnionBuildingResult(guardSnode, encryptionResult, destinationSymmetricKey) }
}.map { OnionBuildingResult(guardSnode, encryptionResult, destinationSymmetricKey) }
}
/**

View File

@@ -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" }
}

View File

@@ -6,8 +6,10 @@ 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
@@ -19,12 +21,10 @@ 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>
@@ -33,16 +33,15 @@ 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
// 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:$seedPort", "https://storage.seed3.loki.network:$seedPort", "https://public.loki.foundation:$seedPort" )
setOf( "https://storage.seed1.loki.network:$seedNodePort ", "https://storage.seed3.loki.network:$seedNodePort ", "https://public.loki.foundation:$seedNodePort" )
}
}
private val snodeFailureThreshold = 4
@@ -50,13 +49,11 @@ object SnodeAPI {
private val useOnionRequests = true
internal val useTestnet = true
internal var powDifficulty = 1
// 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
@@ -94,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)
@@ -170,7 +167,7 @@ object SnodeAPI {
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)
@@ -186,8 +183,8 @@ object SnodeAPI {
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) }
}
}
}
@@ -199,19 +196,7 @@ object SnodeAPI {
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()
}
@@ -243,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()) {
@@ -281,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() {
@@ -316,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}.")
@@ -338,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>

View File

@@ -12,12 +12,14 @@ data class SnodeMessage(
// When the proof of work was calculated.
val timestamp: Long
) {
internal fun toJSON(): Map<String, String> {
return mutableMapOf(
"pubKey" to if (SnodeAPI.useTestnet) recipient.removing05PrefixIfNeeded() else recipient,
return mapOf(
"pubKey" to if (SnodeAPI.useTestnet) recipient.removing05PrefixIfNeeded() else recipient,
"data" to data,
"ttl" to ttl.toString(),
"timestamp" to timestamp.toString(),
"nonce" to "")
"nonce" to ""
)
}
}

View File

@@ -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)
}
}
}

View File

@@ -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>>

View File

@@ -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)
}
}
}

View File

@@ -1,6 +1,5 @@
package org.session.libsession.utilities;
import java.util.concurrent.LinkedBlockingDeque;
public class LinkedBlockingLifoQueue<E> extends LinkedBlockingDeque<E> {

View File

@@ -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

View File

@@ -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)

View File

@@ -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);

View File

@@ -30,5 +30,4 @@ public final class DynamicLanguageContextWrapper {
copy.setLocale(locale);
return copy;
}
}

View File

@@ -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

View File

@@ -1,6 +1,5 @@
package org.session.libsession.utilities.preferences;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

View File

@@ -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;

View File

@@ -1,6 +1,5 @@
package org.session.libsession.utilities.views;
import android.view.ViewStub;
import androidx.annotation.NonNull;