pull/503/head
Niels Andriesse 3 years ago
parent bc66c45bca
commit 676c307412

@ -30,7 +30,6 @@ interface MessageDataProvider {
fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult)
fun updateAttachmentAfterUploadFailed(attachmentId: Long) fun updateAttachmentAfterUploadFailed(attachmentId: Long)
// Quotes
fun getMessageForQuote(timestamp: Long, author: Address): Pair<Long, Boolean>? fun getMessageForQuote(timestamp: Long, author: Address): Pair<Long, Boolean>?
fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List<Attachment> fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List<Attachment>
fun getMessageBodyFor(timestamp: Long, author: String): String fun getMessageBodyFor(timestamp: Long, author: String): String

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

@ -1,6 +1,5 @@
package org.session.libsession.messaging.avatars; package org.session.libsession.messaging.avatars;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
@ -19,5 +18,4 @@ public interface ContactPhoto extends Key {
@Nullable Uri getUri(@NonNull Context context); @Nullable Uri getUri(@NonNull Context context);
boolean isProfilePhoto(); boolean isProfilePhoto();
} }

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

@ -19,7 +19,6 @@ import org.session.libsession.utilities.ViewUtil;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class GeneratedContactPhoto implements FallbackContactPhoto { public class GeneratedContactPhoto implements FallbackContactPhoto {
private static final Pattern PATTERN = Pattern.compile("[^\\p{L}\\p{Nd}\\p{S}]+"); private static final Pattern PATTERN = Pattern.compile("[^\\p{L}\\p{Nd}\\p{S}]+");

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

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

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

@ -11,13 +11,10 @@ import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long): Job { class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long): Job {
override var delegate: JobDelegate? = null override var delegate: JobDelegate? = null
override var id: String? = null override var id: String? = null
override var failureCount: Int = 0 override var failureCount: Int = 0
private val MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024
// Error // Error
internal sealed class Error(val description: String) : Exception(description) { internal sealed class Error(val description: String) : Exception(description) {
object NoAttachment : Error("No such attachment.") object NoAttachment : Error("No such attachment.")
@ -28,17 +25,17 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
companion object { companion object {
val KEY: String = "AttachmentDownloadJob" 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_ATTACHMENT_ID = "attachment_id"
private val KEY_TS_INCOMING_MESSAGE_ID = "tsIncoming_message_id" private val KEY_TS_INCOMING_MESSAGE_ID = "tsIncoming_message_id"
} }
override fun execute() { override fun execute() {
val handleFailure: (java.lang.Exception) -> Unit = { exception -> val handleFailure: (java.lang.Exception) -> Unit = { exception ->
if(exception is Error && exception == Error.NoAttachment) { if (exception == Error.NoAttachment) {
MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID)
this.handlePermanentFailure(exception) 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) // No need to retry if the response is invalid. Most likely this means we (incorrectly)
// got a "Cannot GET ..." error from the file server. // got a "Cannot GET ..." error from the file server.
MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID)
@ -53,9 +50,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachmentID, this.databaseMessageID) messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachmentID, this.databaseMessageID)
val tempFile = createTempFile() val tempFile = createTempFile()
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 // Assume we're retrieving an attachment for an open group server if the digest is not set
val stream = if (attachment.digest?.size ?: 0 == 0 || attachment.key.isNullOrEmpty()) FileInputStream(tempFile) val stream = if (attachment.digest?.size ?: 0 == 0 || attachment.key.isNullOrEmpty()) FileInputStream(tempFile)
@ -89,12 +84,10 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
return file return file
} }
//database functions
override fun serialize(): Data { override fun serialize(): Data {
return Data.Builder().putLong(KEY_ATTACHMENT_ID, attachmentID) return Data.Builder().putLong(KEY_ATTACHMENT_ID, attachmentID)
.putLong(KEY_TS_INCOMING_MESSAGE_ID, databaseMessageID) .putLong(KEY_TS_INCOMING_MESSAGE_ID, databaseMessageID)
.build(); .build();
} }
override fun getFactoryKey(): String { override fun getFactoryKey(): String {
@ -102,6 +95,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
} }
class Factory: Job.Factory<AttachmentDownloadJob> { class Factory: Job.Factory<AttachmentDownloadJob> {
override fun create(data: Data): AttachmentDownloadJob { override fun create(data: Data): AttachmentDownloadJob {
return AttachmentDownloadJob(data.getLong(KEY_ATTACHMENT_ID), data.getLong(KEY_TS_INCOMING_MESSAGE_ID)) return AttachmentDownloadJob(data.getLong(KEY_ATTACHMENT_ID), data.getLong(KEY_TS_INCOMING_MESSAGE_ID))
} }

@ -18,7 +18,6 @@ import org.session.libsignal.service.loki.utilities.PlaintextOutputStreamFactory
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val message: Message, val messageSendJobID: String) : Job { class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val message: Message, val messageSendJobID: String) : Job {
override var delegate: JobDelegate? = null override var delegate: JobDelegate? = null
override var id: String? = null override var id: String? = null
override var failureCount: Int = 0 override var failureCount: Int = 0
@ -34,9 +33,7 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
val TAG = AttachmentUploadJob::class.simpleName val TAG = AttachmentUploadJob::class.simpleName
val KEY: String = "AttachmentUploadJob" val KEY: String = "AttachmentUploadJob"
val maxFailureCount: Int = 20 // Keys used for database storage
//keys used for database storage purpose
private val KEY_ATTACHMENT_ID = "attachment_id" private val KEY_ATTACHMENT_ID = "attachment_id"
private val KEY_THREAD_ID = "thread_id" private val KEY_THREAD_ID = "thread_id"
private val KEY_MESSAGE = "message" private val KEY_MESSAGE = "message"
@ -46,16 +43,12 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
override fun execute() { override fun execute() {
try { try {
val attachment = MessagingConfiguration.shared.messageDataProvider.getScaledSignalAttachmentStream(attachmentID) val attachment = MessagingConfiguration.shared.messageDataProvider.getScaledSignalAttachmentStream(attachmentID)
?: return handleFailure(Error.NoAttachment) ?: return handleFailure(Error.NoAttachment)
var server = FileServerAPI.shared.server
var shouldEncrypt = true
val usePadding = false val usePadding = false
val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID) val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID)
openGroup?.let { val server = if (openGroup != null) openGroup.server else FileServerAPI.shared.server
server = it.server val shouldEncrypt = (openGroup == null) // Encrypt if this isn't an open group
shouldEncrypt = false
}
val attachmentKey = Util.getSecretBytes(64) val attachmentKey = Util.getSecretBytes(64)
val paddedLength = if (usePadding) PaddingInputStream.getPaddedSize(attachment.length) else attachment.length val paddedLength = if (usePadding) PaddingInputStream.getPaddedSize(attachment.length) else attachment.length
@ -67,9 +60,8 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
val uploadResult = FileServerAPI.shared.uploadAttachment(server, attachmentData) val uploadResult = FileServerAPI.shared.uploadAttachment(server, attachmentData)
handleSuccess(attachment, attachmentKey, uploadResult) handleSuccess(attachment, attachmentKey, uploadResult)
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
if (e is Error && e == Error.NoAttachment) { if (e == Error.NoAttachment) {
this.handlePermanentFailure(e) this.handlePermanentFailure(e)
} else if (e is DotNetAPI.Error && !e.isRetryable) { } else if (e is DotNetAPI.Error && !e.isRetryable) {
this.handlePermanentFailure(e) this.handlePermanentFailure(e)
@ -77,7 +69,6 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
this.handleFailure(e) this.handleFailure(e)
} }
} }
} }
private fun handleSuccess(attachment: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) { private fun handleSuccess(attachment: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) {
@ -97,7 +88,7 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
private fun handleFailure(e: Exception) { private fun handleFailure(e: Exception) {
Log.w(TAG, "Attachment upload failed due to error: $this.") Log.w(TAG, "Attachment upload failed due to error: $this.")
delegate?.handleJobFailed(this, e) delegate?.handleJobFailed(this, e)
if (failureCount + 1 == AttachmentUploadJob.maxFailureCount) { if (failureCount + 1 == maxFailureCount) {
failAssociatedMessageSendJob(e) failAssociatedMessageSendJob(e)
} }
} }
@ -111,10 +102,7 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
} }
} }
//database functions
override fun serialize(): Data { override fun serialize(): Data {
//serialize Message property
val kryo = Kryo() val kryo = Kryo()
kryo.isRegistrationRequired = false kryo.isRegistrationRequired = false
val serializedMessage = ByteArray(4096) val serializedMessage = ByteArray(4096)
@ -122,10 +110,10 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
kryo.writeObject(output, message) kryo.writeObject(output, message)
output.close() output.close()
return Data.Builder().putLong(KEY_ATTACHMENT_ID, attachmentID) return Data.Builder().putLong(KEY_ATTACHMENT_ID, attachmentID)
.putString(KEY_THREAD_ID, threadID) .putString(KEY_THREAD_ID, threadID)
.putByteArray(KEY_MESSAGE, serializedMessage) .putByteArray(KEY_MESSAGE, serializedMessage)
.putString(KEY_MESSAGE_SEND_JOB_ID, messageSendJobID) .putString(KEY_MESSAGE_SEND_JOB_ID, messageSendJobID)
.build(); .build();
} }
override fun getFactoryKey(): String { override fun getFactoryKey(): String {
@ -133,9 +121,9 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
} }
class Factory: Job.Factory<AttachmentUploadJob> { class Factory: Job.Factory<AttachmentUploadJob> {
override fun create(data: Data): AttachmentUploadJob { override fun create(data: Data): AttachmentUploadJob {
val serializedMessage = data.getByteArray(KEY_MESSAGE) val serializedMessage = data.getByteArray(KEY_MESSAGE)
//deserialize Message property
val kryo = Kryo() val kryo = Kryo()
val input = Input(serializedMessage) val input = Input(serializedMessage)
val message: Message = kryo.readObject(input, Message::class.java) val message: Message = kryo.readObject(input, Message::class.java)

@ -12,7 +12,6 @@ import org.session.libsession.utilities.ParcelableUtil;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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. // Introduce a dedicated Map<String, byte[]> field specifically for parcelable needs.
public class Data { public class Data {

@ -8,15 +8,13 @@ interface Job {
val maxFailureCount: Int val maxFailureCount: Int
companion object { companion object {
//keys used for database storage purpose // Keys used for database storage
private val KEY_ID = "id" private val KEY_ID = "id"
private val KEY_FAILURE_COUNT = "failure_count" private val KEY_FAILURE_COUNT = "failure_count"
} }
fun execute() fun execute()
//database functions
fun serialize(): Data fun serialize(): Data
/** /**

@ -1,6 +1,7 @@
package org.session.libsession.messaging.jobs package org.session.libsession.messaging.jobs
interface JobDelegate { interface JobDelegate {
fun handleJobSucceeded(job: Job) fun handleJobSucceeded(job: Job)
fun handleJobFailed(job: Job, error: Exception) fun handleJobFailed(job: Job, error: Exception)
fun handleJobFailedPermanently(job: Job, error: Exception) fun handleJobFailedPermanently(job: Job, error: Exception)

@ -14,19 +14,17 @@ import kotlin.math.min
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.roundToLong import kotlin.math.roundToLong
class JobQueue : JobDelegate { class JobQueue : JobDelegate {
private var hasResumedPendingJobs = false // Just for debugging private var hasResumedPendingJobs = false // Just for debugging
private val jobTimestampMap = ConcurrentHashMap<Long, AtomicInteger>() private val jobTimestampMap = ConcurrentHashMap<Long, AtomicInteger>()
private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
private val scope = GlobalScope + SupervisorJob() private val scope = GlobalScope + SupervisorJob()
private val queue = Channel<Job>(UNLIMITED) private val queue = Channel<Job>(UNLIMITED)
val timer = Timer() val timer = Timer()
init { init {
// process jobs // Process jobs
scope.launch(dispatcher) { scope.launch(dispatcher) {
while (isActive) { while (isActive) {
queue.receive().let { job -> queue.receive().let { job ->
@ -49,7 +47,7 @@ class JobQueue : JobDelegate {
private fun addWithoutExecuting(job: Job) { private fun addWithoutExecuting(job: Job) {
// When adding multiple jobs in rapid succession, timestamps might not be good enough as a unique ID. To // 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 // 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. // of the order in which the jobs were added.
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
@ -70,7 +68,7 @@ class JobQueue : JobDelegate {
val allPendingJobs = MessagingConfiguration.shared.storage.getAllPendingJobs(type) val allPendingJobs = MessagingConfiguration.shared.storage.getAllPendingJobs(type)
allPendingJobs.sortedBy { it.id }.forEach { job -> allPendingJobs.sortedBy { it.id }.forEach { job ->
Log.i("Jobs", "Resuming pending job of type: ${job::class.simpleName}.") 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
} }
} }
} }

@ -7,7 +7,6 @@ import org.session.libsession.messaging.sending_receiving.handle
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val openGroupMessageServerID: Long? = null, val openGroupID: String? = null) : Job { class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val openGroupMessageServerID: Long? = null, val openGroupID: String? = null) : Job {
override var delegate: JobDelegate? = null override var delegate: JobDelegate? = null
override var id: String? = null override var id: String? = null
override var failureCount: Int = 0 override var failureCount: Int = 0
@ -20,7 +19,7 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val
private val RECEIVE_LOCK = Object() private val RECEIVE_LOCK = Object()
//keys used for database storage purpose // Keys used for database storage
private val KEY_DATA = "data" private val KEY_DATA = "data"
private val KEY_IS_BACKGROUND_POLL = "is_background_poll" private val KEY_IS_BACKGROUND_POLL = "is_background_poll"
private val KEY_OPEN_GROUP_MESSAGE_SERVER_ID = "openGroupMessageServerID" 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) delegate?.handleJobFailed(this, e)
} }
//database functions
override fun serialize(): Data { override fun serialize(): Data {
val builder = Data.Builder().putByteArray(KEY_DATA, data) val builder = Data.Builder().putByteArray(KEY_DATA, data)
.putBoolean(KEY_IS_BACKGROUND_POLL, isBackgroundPoll) .putBoolean(KEY_IS_BACKGROUND_POLL, isBackgroundPoll)
@ -83,6 +80,7 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val
} }
class Factory: Job.Factory<MessageReceiveJob> { class Factory: Job.Factory<MessageReceiveJob> {
override fun create(data: Data): 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)) 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))
} }

@ -11,7 +11,6 @@ import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
class MessageSendJob(val message: Message, val destination: Destination) : Job { class MessageSendJob(val message: Message, val destination: Destination) : Job {
override var delegate: JobDelegate? = null override var delegate: JobDelegate? = null
override var id: String? = null override var id: String? = null
override var failureCount: Int = 0 override var failureCount: Int = 0
@ -22,7 +21,7 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
val TAG = MessageSendJob::class.simpleName val TAG = MessageSendJob::class.simpleName
val KEY: String = "MessageSendJob" val KEY: String = "MessageSendJob"
//keys used for database storage purpose // Keys used for database storage
private val KEY_MESSAGE = "message" private val KEY_MESSAGE = "message"
private val KEY_DESTINATION = "destination" private val KEY_DESTINATION = "destination"
} }
@ -77,10 +76,7 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
delegate?.handleJobFailed(this, error) delegate?.handleJobFailed(this, error)
} }
//database functions
override fun serialize(): Data { override fun serialize(): Data {
//serialize Message and Destination properties
val kryo = Kryo() val kryo = Kryo()
kryo.isRegistrationRequired = false kryo.isRegistrationRequired = false
val output = Output(ByteArray(4096), -1) // maxBufferSize '-1' will dynamically grow internally if we run out of room serializing the message 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() output.close()
val serializedDestination = output.toBytes() val serializedDestination = output.toBytes()
return Data.Builder().putByteArray(KEY_MESSAGE, serializedMessage) return Data.Builder().putByteArray(KEY_MESSAGE, serializedMessage)
.putByteArray(KEY_DESTINATION, serializedDestination) .putByteArray(KEY_DESTINATION, serializedDestination)
.build(); .build();
} }
override fun getFactoryKey(): String { override fun getFactoryKey(): String {
@ -101,10 +97,10 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
} }
class Factory: Job.Factory<MessageSendJob> { class Factory: Job.Factory<MessageSendJob> {
override fun create(data: Data): MessageSendJob { override fun create(data: Data): MessageSendJob {
val serializedMessage = data.getByteArray(KEY_MESSAGE) val serializedMessage = data.getByteArray(KEY_MESSAGE)
val serializedDestination = data.getByteArray(KEY_DESTINATION) val serializedDestination = data.getByteArray(KEY_DESTINATION)
//deserialize Message and Destination properties
val kryo = Kryo() val kryo = Kryo()
var input = Input(serializedMessage) var input = Input(serializedMessage)
val message = kryo.readClassAndObject(input) as Message val message = kryo.readClassAndObject(input) as Message

@ -26,7 +26,7 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job {
companion object { companion object {
val KEY: String = "NotifyPNServerJob" val KEY: String = "NotifyPNServerJob"
//keys used for database storage purpose // Keys used for database storage
private val KEY_MESSAGE = "message" private val KEY_MESSAGE = "message"
} }
@ -61,18 +61,14 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job {
delegate?.handleJobFailed(this, error) delegate?.handleJobFailed(this, error)
} }
//database functions
override fun serialize(): Data { override fun serialize(): Data {
//serialize SnodeMessage property
val kryo = Kryo() val kryo = Kryo()
kryo.isRegistrationRequired = false kryo.isRegistrationRequired = false
val serializedMessage = ByteArray(4096) val serializedMessage = ByteArray(4096)
val output = Output(serializedMessage) val output = Output(serializedMessage)
kryo.writeObject(output, message) kryo.writeObject(output, message)
output.close() output.close()
return Data.Builder().putByteArray(KEY_MESSAGE, serializedMessage) return Data.Builder().putByteArray(KEY_MESSAGE, serializedMessage).build();
.build();
} }
override fun getFactoryKey(): String { override fun getFactoryKey(): String {
@ -80,9 +76,9 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job {
} }
class Factory: Job.Factory<NotifyPNServerJob> { class Factory: Job.Factory<NotifyPNServerJob> {
override fun create(data: Data): NotifyPNServerJob { override fun create(data: Data): NotifyPNServerJob {
val serializedMessage = data.getByteArray(KEY_MESSAGE) val serializedMessage = data.getByteArray(KEY_MESSAGE)
//deserialize SnodeMessage property
val kryo = Kryo() val kryo = Kryo()
val input = Input(serializedMessage) val input = Input(serializedMessage)
val message: SnodeMessage = kryo.readObject(input, SnodeMessage::class.java) val message: SnodeMessage = kryo.readObject(input, SnodeMessage::class.java)

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

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

@ -28,15 +28,10 @@ class ClosedGroupControlMessage() : ControlMessage() {
var kind: Kind? = null var kind: Kind? = null
// Kind enum
sealed class Kind { sealed class Kind {
class New(var publicKey: ByteString, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List<ByteString>, var admins: List<ByteString>) : 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()) 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. /// 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). /// - 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()) internal constructor(): this(listOf())
} }
class MemberLeft() : Kind() class MemberLeft() : Kind()
class EncryptionKeyPairRequest(): Kind()
val description: String = val description: String =
when(this) { when(this) {
is New -> "new" is New -> "new"
is Update -> "update"
is EncryptionKeyPair -> "encryptionKeyPair" is EncryptionKeyPair -> "encryptionKeyPair"
is NameChange -> "nameChange" is NameChange -> "nameChange"
is MembersAdded -> "membersAdded" is MembersAdded -> "membersAdded"
is MembersRemoved -> "membersRemoved" is MembersRemoved -> "membersRemoved"
is MemberLeft -> "memberLeft" is MemberLeft -> "memberLeft"
is EncryptionKeyPairRequest -> "encryptionKeyPairRequest"
} }
} }
@ -75,12 +67,11 @@ class ClosedGroupControlMessage() : ControlMessage() {
if (!proto.hasDataMessage() || !proto.dataMessage.hasClosedGroupControlMessage()) return null if (!proto.hasDataMessage() || !proto.dataMessage.hasClosedGroupControlMessage()) return null
val closedGroupControlMessageProto = proto.dataMessage?.closedGroupControlMessage!! val closedGroupControlMessageProto = proto.dataMessage?.closedGroupControlMessage!!
val kind: Kind val kind: Kind
when(closedGroupControlMessageProto.type) { when (closedGroupControlMessageProto.type) {
DataMessage.ClosedGroupControlMessage.Type.NEW -> { DataMessage.ClosedGroupControlMessage.Type.NEW -> {
val publicKey = closedGroupControlMessageProto.publicKey ?: return null val publicKey = closedGroupControlMessageProto.publicKey ?: return null
val name = closedGroupControlMessageProto.name ?: return null val name = closedGroupControlMessageProto.name ?: return null
val encryptionKeyPairAsProto = closedGroupControlMessageProto.encryptionKeyPair ?: return null val encryptionKeyPairAsProto = closedGroupControlMessageProto.encryptionKeyPair ?: return null
try { try {
val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairAsProto.publicKey.toByteArray()), DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray())) val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairAsProto.publicKey.toByteArray()), DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray()))
kind = Kind.New(publicKey, name, encryptionKeyPair, closedGroupControlMessageProto.membersList, closedGroupControlMessageProto.adminsList) kind = Kind.New(publicKey, name, encryptionKeyPair, closedGroupControlMessageProto.membersList, closedGroupControlMessageProto.adminsList)
@ -89,10 +80,6 @@ class ClosedGroupControlMessage() : ControlMessage() {
return null return null
} }
} }
DataMessage.ClosedGroupControlMessage.Type.UPDATE -> {
val name = closedGroupControlMessageProto.name ?: return null
kind = Kind.Update(name, closedGroupControlMessageProto.membersList)
}
DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> { DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> {
val publicKey = closedGroupControlMessageProto.publicKey val publicKey = closedGroupControlMessageProto.publicKey
val wrappers = closedGroupControlMessageProto.wrappersList.mapNotNull { KeyPairWrapper.fromProto(it) } val wrappers = closedGroupControlMessageProto.wrappersList.mapNotNull { KeyPairWrapper.fromProto(it) }
@ -111,35 +98,28 @@ class ClosedGroupControlMessage() : ControlMessage() {
DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> { DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> {
kind = Kind.MemberLeft() kind = Kind.MemberLeft()
} }
DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR_REQUEST -> {
kind = Kind.EncryptionKeyPairRequest()
}
} }
return ClosedGroupControlMessage(kind) return ClosedGroupControlMessage(kind)
} }
} }
// constructor
internal constructor(kind: Kind?) : this() { internal constructor(kind: Kind?) : this() {
this.kind = kind this.kind = kind
} }
// validation
override fun isValid(): Boolean { override fun isValid(): Boolean {
if (!super.isValid()) return false if (!super.isValid()) return false
val kind = kind ?: return false val kind = kind ?: return false
return when(kind) { return when(kind) {
is Kind.New -> { is Kind.New -> {
!kind.publicKey.isEmpty && kind.name.isNotEmpty() && kind.encryptionKeyPair!!.publicKey != null !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.EncryptionKeyPair -> true
is Kind.NameChange -> kind.name.isNotEmpty() is Kind.NameChange -> kind.name.isNotEmpty()
is Kind.MembersAdded -> kind.members.isNotEmpty() is Kind.MembersAdded -> kind.members.isNotEmpty()
is Kind.MembersRemoved -> kind.members.isNotEmpty() is Kind.MembersRemoved -> kind.members.isNotEmpty()
is Kind.MemberLeft -> true is Kind.MemberLeft -> true
is Kind.EncryptionKeyPairRequest -> true
} }
} }
@ -163,11 +143,6 @@ class ClosedGroupControlMessage() : ControlMessage() {
closedGroupControlMessage.addAllMembers(kind.members) closedGroupControlMessage.addAllMembers(kind.members)
closedGroupControlMessage.addAllAdmins(kind.admins) closedGroupControlMessage.addAllAdmins(kind.admins)
} }
is Kind.Update -> {
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.UPDATE
closedGroupControlMessage.name = kind.name
closedGroupControlMessage.addAllMembers(kind.members)
}
is Kind.EncryptionKeyPair -> { is Kind.EncryptionKeyPair -> {
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR
closedGroupControlMessage.publicKey = kind.publicKey closedGroupControlMessage.publicKey = kind.publicKey
@ -188,9 +163,6 @@ class ClosedGroupControlMessage() : ControlMessage() {
is Kind.MemberLeft -> { is Kind.MemberLeft -> {
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT 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 contentProto = SignalServiceProtos.Content.newBuilder()
val dataMessageProto = DataMessage.newBuilder() val dataMessageProto = DataMessage.newBuilder()
@ -199,7 +171,7 @@ class ClosedGroupControlMessage() : ControlMessage() {
setGroupContext(dataMessageProto) setGroupContext(dataMessageProto)
// Expiration timer // Expiration timer
// TODO: We * want * expiration timer updates to be explicit. But currently Android will disable the expiration timer for a conversation // 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... // 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 dataMessageProto.expireTimer = Recipient.from(MessagingConfiguration.shared.context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(recipient!!)), false).expireMessages
contentProto.dataMessage = dataMessageProto.build() contentProto.dataMessage = dataMessageProto.build()
return contentProto.build() return contentProto.build()

@ -185,14 +185,14 @@ open class DotNetAPI {
/** /**
* Blocks the calling thread. * 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 val outputStream = FileOutputStream(destination) // Throws
var remainingAttempts = 4 var remainingAttempts = 4
var exception: Exception? = null var exception: Exception? = null
while (remainingAttempts > 0) { while (remainingAttempts > 0) {
remainingAttempts -= 1 remainingAttempts -= 1
try { try {
downloadFile(outputStream, url, maxSize, listener) downloadFile(outputStream, url, listener)
exception = null exception = null
break break
} catch (e: Exception) { } catch (e: Exception) {
@ -205,7 +205,7 @@ open class DotNetAPI {
/** /**
* Blocks the calling thread. * 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 // We need to throw a PushNetworkException or NonSuccessfulResponseCodeException
// because the underlying Signal logic requires these to work correctly // because the underlying Signal logic requires these to work correctly
val oldPrefixedHost = "https://" + HttpUrl.get(url).host() val oldPrefixedHost = "https://" + HttpUrl.get(url).host()
@ -228,7 +228,7 @@ open class DotNetAPI {
throw PushNetworkException("Missing response body.") throw PushNetworkException("Missing response body.")
} }
val body = Base64.decode(result) val body = Base64.decode(result)
if (body.size > maxSize) { if (body.size > FileServerAPI.maxFileSize) {
Log.d("Loki", "Attachment size limit exceeded.") Log.d("Loki", "Attachment size limit exceeded.")
throw PushNetworkException("Max response size exceeded.") throw PushNetworkException("Max response size exceeded.")
} }
@ -239,7 +239,7 @@ open class DotNetAPI {
while (bytes >= 0) { while (bytes >= 0) {
outputStream.write(buffer, 0, bytes) outputStream.write(buffer, 0, bytes)
count += bytes count += bytes
if (count > maxSize) { if (count > FileServerAPI.maxFileSize) {
Log.d("Loki", "Attachment size limit exceeded.") Log.d("Loki", "Attachment size limit exceeded.")
throw PushNetworkException("Max response size exceeded.") throw PushNetworkException("Max response size exceeded.")
} }

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

@ -3,7 +3,6 @@ package org.session.libsession.utilities
import android.telephony.PhoneNumberUtils import android.telephony.PhoneNumberUtils
import android.util.Patterns import android.util.Patterns
object NumberUtil { object NumberUtil {
private val emailPattern = Patterns.EMAIL_ADDRESS private val emailPattern = Patterns.EMAIL_ADDRESS

@ -14,6 +14,7 @@ class SSKEnvironment(
val notificationManager: MessageNotifier, val notificationManager: MessageNotifier,
val messageExpirationManager: MessageExpirationManagerProtocol val messageExpirationManager: MessageExpirationManagerProtocol
) { ) {
interface TypingIndicatorsProtocol { interface TypingIndicatorsProtocol {
fun didReceiveTypingStartedMessage(context: Context, threadId: Long, author: Address, device: Int) fun didReceiveTypingStartedMessage(context: Context, threadId: Long, author: Address, device: Int)
fun didReceiveTypingStoppedMessage(context: Context, threadId: Long, author: Address, device: Int, isReplacedByIncomingMessage: Boolean) fun didReceiveTypingStoppedMessage(context: Context, threadId: Long, author: Address, device: Int, isReplacedByIncomingMessage: Boolean)

@ -5,6 +5,7 @@ import org.session.libsignal.utilities.concurrent.ListenableFuture.Listener;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
public abstract class AssertedSuccessListener<T> implements Listener<T> { public abstract class AssertedSuccessListener<T> implements Listener<T> {
@Override @Override
public void onFailure(ExecutionException e) { public void onFailure(ExecutionException e) {
throw new AssertionError(e); throw new AssertionError(e);

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

@ -7,8 +7,7 @@ import java.util.Locale;
public final class LanguageString { public final class LanguageString {
private LanguageString() { private LanguageString() { }
}
/** /**
* @param languageString String in format language_REGION, e.g. en_US * @param languageString String in format language_REGION, e.g. en_US

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

@ -10,7 +10,6 @@ public abstract class SnackbarAsyncTask<Params>
extends AsyncTask<Params, Void, Void> extends AsyncTask<Params, Void, Void>
implements View.OnClickListener implements View.OnClickListener
{ {
private final View view; private final View view;
private final String snackbarText; private final String snackbarText;
private final String snackbarActionText; private final String snackbarActionText;

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

Loading…
Cancel
Save