diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 02786c1943..a5b45a1580 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -10,7 +10,6 @@ import org.session.libsession.messaging.jobs.MessageSendJob import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.GroupUpdated import org.session.libsession.messaging.messages.visible.Attachment -import org.session.libsession.messaging.messages.visible.Profile import org.session.libsession.messaging.messages.visible.Reaction import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId @@ -38,7 +37,6 @@ interface StorageProtocol { fun getUserED25519KeyPair(): KeyPair? fun getUserX25519KeyPair(): KeyPair fun getUserBlindedAccountId(serverPublicKey: String): AccountId? - fun getUserProfile(): Profile // Jobs fun persistJob(job: Job) diff --git a/app/src/main/java/org/session/libsession/messaging/messages/Message.kt b/app/src/main/java/org/session/libsession/messaging/messages/Message.kt index 05e839341b..38d0c2eedc 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/Message.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/Message.kt @@ -1,6 +1,7 @@ package org.session.libsession.messaging.messages import network.loki.messenger.libsession_util.util.ExpiryMode +import org.session.libsession.database.MessageDataProvider import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.visible.VisibleMessage @@ -47,18 +48,32 @@ abstract class Message { && sender != null && recipient != null - abstract fun toProto(): SignalServiceProtos.Content? + protected abstract fun buildProto( + builder: SignalServiceProtos.Content.Builder, + messageDataProvider: MessageDataProvider + ) - abstract fun shouldDiscardIfBlocked(): Boolean - - fun SignalServiceProtos.Content.Builder.applyExpiryMode() = apply { - expirationTimerSeconds = expiryMode.expirySeconds.toInt() - expirationType = when (expiryMode) { + fun toProto( + builder: SignalServiceProtos.Content.Builder, + messageDataProvider: MessageDataProvider + ) { + // First apply common message data + // * Expiry mode + builder.expirationTimerSeconds = expiryMode.expirySeconds.toInt() + builder.expirationType = when (expiryMode) { is ExpiryMode.AfterSend -> ExpirationType.DELETE_AFTER_SEND is ExpiryMode.AfterRead -> ExpirationType.DELETE_AFTER_READ else -> ExpirationType.UNKNOWN } + + // * Timestamps + builder.setSigTimestampMs(sentTimestamp!!) + + // Then ask the subclasses to build their specific proto + buildProto(builder, messageDataProvider) } + + abstract fun shouldDiscardIfBlocked(): Boolean } inline fun M.copyExpiration(proto: SignalServiceProtos.Content): M = apply { @@ -71,20 +86,10 @@ inline fun M.copyExpiration(proto: SignalServiceProtos.Cont } } -fun SignalServiceProtos.Content.expiryMode(): ExpiryMode = - (takeIf { it.hasExpirationTimerSeconds() }?.expirationTimerSeconds ?: dataMessage?.expireTimerSeconds)?.let { duration -> - when (expirationType.takeIf { duration > 0 }) { - ExpirationType.DELETE_AFTER_SEND -> ExpiryMode.AfterSend(duration.toLong()) - ExpirationType.DELETE_AFTER_READ -> ExpiryMode.AfterRead(duration.toLong()) - else -> ExpiryMode.NONE - } - } ?: ExpiryMode.NONE - /** * Apply ExpiryMode from the current setting. */ inline fun M.applyExpiryMode(recipientAddress: Address): M = apply { expiryMode = MessagingModuleConfiguration.shared.recipientRepository.getRecipientSync(recipientAddress) - ?.expiryMode?.coerceSendToRead(coerceDisappearAfterSendToRead) - ?: ExpiryMode.NONE + .expiryMode.coerceSendToRead(coerceDisappearAfterSendToRead) } diff --git a/app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt b/app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt index 1ac62e171e..a377e9fc65 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt @@ -1,12 +1,13 @@ package org.session.libsession.messaging.messages +import com.google.protobuf.ByteString import network.loki.messenger.libsession_util.util.BaseCommunityInfo import network.loki.messenger.libsession_util.util.UserPic -import org.session.libsession.messaging.messages.visible.Profile import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.toAddress import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.updateContact +import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.BlindMappingRepository @@ -164,44 +165,69 @@ class ProfileUpdateHandler @Inject constructor( } companion object { - fun create( - name: String? = null, - picUrl: String?, - picKey: ByteArray?, - blocksCommunityMessageRequests: Boolean? = null, - proStatus: Boolean? = null, - profileUpdateTime: Instant? - ): Updates? { - val hasNameUpdate = !name.isNullOrBlank() - val pic = when { - picUrl == null -> null - picUrl.isBlank() || picKey == null || picKey.size !in VALID_PROFILE_KEY_LENGTH -> UserPic.DEFAULT - else -> UserPic(picUrl, picKey) + fun create(content: SignalServiceProtos.Content): Updates? { + val profile: SignalServiceProtos.DataMessage.LokiProfile + val profilePicKey: ByteString? + + when { + content.hasDataMessage() && content.dataMessage.hasProfile() -> { + profile = content.dataMessage.profile + profilePicKey = + if (content.dataMessage.hasProfileKey()) content.dataMessage.profileKey else null + } + + content.hasMessageRequestResponse() && content.messageRequestResponse.hasProfile() -> { + profile = content.messageRequestResponse.profile + profilePicKey = + if (content.messageRequestResponse.hasProfileKey()) content.messageRequestResponse.profileKey else null + } + + else -> { + // No profile found, not updating. + // This is different from having an empty profile, which is a valid update. + return null + } } - if (!hasNameUpdate && pic == null && blocksCommunityMessageRequests == null && proStatus == null) { + val pic = if (profile.hasProfilePicture()) { + if (!profile.profilePicture.isNullOrBlank() && profilePicKey != null && + profilePicKey.size() in VALID_PROFILE_KEY_LENGTH) { + UserPic( + url = profile.profilePicture, + key = profilePicKey.toByteArray() + ) + } else { + UserPic.DEFAULT // Clear the profile picture + } + } else { + null // No update to profile picture + } + + val name = if (profile.hasDisplayName()) profile.displayName else null + val blocksCommunityMessageRequests = if (content.hasDataMessage() && + content.dataMessage.hasBlocksCommunityMessageRequests()) { + content.dataMessage.blocksCommunityMessageRequests + } else { + null + } + + if (name == null && pic == null && blocksCommunityMessageRequests == null) { + // Nothing is updated.. return null } return Updates( - name = if (hasNameUpdate) name else null, + name = name, pic = pic, blocksCommunityMessageRequests = blocksCommunityMessageRequests, - profileUpdateTime = profileUpdateTime + profileUpdateTime = if (profile.hasLastProfileUpdateSeconds()) { + Instant.ofEpochSecond(profile.lastProfileUpdateSeconds) + } else { + null + } ) } - fun Profile.toUpdates( - blocksCommunityMessageRequests: Boolean? = null, - ): Updates? { - return create( - name = this.displayName, - picUrl = this.profilePictureURL, - picKey = this.profileKey, - blocksCommunityMessageRequests = blocksCommunityMessageRequests, - profileUpdateTime = this.profileUpdated - ) - } } } diff --git a/app/src/main/java/org/session/libsession/messaging/messages/control/CallMessage.kt b/app/src/main/java/org/session/libsession/messaging/messages/control/CallMessage.kt index 1d565acc62..f4c9781730 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/control/CallMessage.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/control/CallMessage.kt @@ -1,13 +1,12 @@ package org.session.libsession.messaging.messages.control -import org.session.libsession.messaging.messages.applyExpiryMode +import org.session.libsession.database.MessageDataProvider import org.session.libsession.messaging.messages.copyExpiration import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.ANSWER import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.END_CALL import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.OFFER import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.PRE_OFFER -import org.session.libsignal.utilities.Log import java.util.UUID class CallMessage(): ControlMessage() { @@ -77,23 +76,16 @@ class CallMessage(): ControlMessage() { } } - override fun toProto(): SignalServiceProtos.Content? { - val nonNullType = type ?: run { - Log.w(TAG,"Couldn't construct call message request proto from: $this") - return null - } - - val callMessage = SignalServiceProtos.CallMessage.newBuilder() - .setType(nonNullType) + protected override fun buildProto( + builder: SignalServiceProtos.Content.Builder, + messageDataProvider: MessageDataProvider + ) { + builder.callMessageBuilder + .setType(type!!) .addAllSdps(sdps) .addAllSdpMLineIndexes(sdpMLineIndexes) .addAllSdpMids(sdpMids) .setUuid(callId!!.toString()) - - return SignalServiceProtos.Content.newBuilder() - .applyExpiryMode() - .setCallMessage(callMessage) - .build() } override fun equals(other: Any?): Boolean { diff --git a/app/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt b/app/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt index 45c1b2fe3e..79872cc635 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt @@ -1,8 +1,8 @@ package org.session.libsession.messaging.messages.control +import org.session.libsession.database.MessageDataProvider import org.session.libsession.messaging.messages.copyExpiration import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.utilities.Log class DataExtractionNotification() : ControlMessage() { var kind: Kind? = null @@ -53,28 +53,17 @@ class DataExtractionNotification() : ControlMessage() { } } - override fun toProto(): SignalServiceProtos.Content? { - val kind = kind - if (kind == null) { - Log.w(TAG, "Couldn't construct data extraction notification proto from: $this") - return null - } - try { - val dataExtractionNotification = SignalServiceProtos.DataExtractionNotification.newBuilder() - when(kind) { - is Kind.Screenshot -> dataExtractionNotification.type = SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT - is Kind.MediaSaved -> { - dataExtractionNotification.type = SignalServiceProtos.DataExtractionNotification.Type.MEDIA_SAVED - dataExtractionNotification.timestampMs = kind.timestamp - } + protected override fun buildProto( + builder: SignalServiceProtos.Content.Builder, + messageDataProvider: MessageDataProvider + ) { + val dataExtractionNotification = builder.dataExtractionNotificationBuilder + when (val kind = kind!!) { + is Kind.Screenshot -> dataExtractionNotification.type = SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT + is Kind.MediaSaved -> { + dataExtractionNotification.type = SignalServiceProtos.DataExtractionNotification.Type.MEDIA_SAVED + dataExtractionNotification.timestampMs = kind.timestamp } - return SignalServiceProtos.Content.newBuilder() - .setDataExtractionNotification(dataExtractionNotification.build()) - .applyExpiryMode() - .build() - } catch (e: Exception) { - Log.w(TAG, "Couldn't construct data extraction notification proto from: $this") - return null } } } diff --git a/app/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt b/app/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt index 1e39b4d593..c7d1f21903 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt @@ -1,10 +1,10 @@ package org.session.libsession.messaging.messages.control +import org.session.libsession.database.MessageDataProvider import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.copyExpiration import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE -import org.session.libsignal.utilities.Log /** In the case of a sync message, the public key of the person the message was targeted at. * @@ -25,21 +25,16 @@ data class ExpirationTimerUpdate(var syncTarget: String? = null, val isGroup: Bo } } - override fun toProto(): SignalServiceProtos.Content? { - val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder().apply { - flags = EXPIRATION_TIMER_UPDATE_VALUE - expireTimerSeconds = expiryMode.expirySeconds.toInt() - } - // Sync target - syncTarget?.let { dataMessageProto.syncTarget = it } - return try { - SignalServiceProtos.Content.newBuilder() - .setDataMessage(dataMessageProto) - .applyExpiryMode() - .build() - } catch (e: Exception) { - Log.w(TAG, "Couldn't construct expiration timer update proto from: $this", e) - null - } + protected override fun buildProto( + builder: SignalServiceProtos.Content.Builder, + messageDataProvider: MessageDataProvider + ) { + builder.dataMessageBuilder + .setFlags(EXPIRATION_TIMER_UPDATE_VALUE) + .setExpireTimerSeconds(expiryMode.expirySeconds.toInt()) + .also { builder -> + // Sync target + syncTarget?.let { builder.syncTarget = it } + } } } diff --git a/app/src/main/java/org/session/libsession/messaging/messages/control/GroupUpdated.kt b/app/src/main/java/org/session/libsession/messaging/messages/control/GroupUpdated.kt index 28120be716..16fa0cba23 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/control/GroupUpdated.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/control/GroupUpdated.kt @@ -1,13 +1,11 @@ package org.session.libsession.messaging.messages.control -import org.session.libsession.messaging.messages.visible.Profile +import org.session.libsession.database.MessageDataProvider import org.session.libsignal.protos.SignalServiceProtos.Content -import org.session.libsignal.protos.SignalServiceProtos.DataMessage import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage class GroupUpdated @JvmOverloads constructor( val inner: GroupUpdateMessage = GroupUpdateMessage.getDefaultInstance(), - val profile: Profile? = null ): ControlMessage() { override fun isValid(): Boolean { @@ -26,18 +24,13 @@ class GroupUpdated @JvmOverloads constructor( if (message.hasDataMessage() && message.dataMessage.hasGroupUpdateMessage()) GroupUpdated( inner = message.dataMessage.groupUpdateMessage, - profile = Profile.fromProto(message.dataMessage) ) else null } - override fun toProto(): Content { - val dataMessage = DataMessage.newBuilder() + override fun buildProto(builder: Content.Builder, messageDataProvider: MessageDataProvider) { + builder.dataMessageBuilder .setGroupUpdateMessage(inner) .apply { profile?.let(this::setProfile) } - .build() - return Content.newBuilder() - .setDataMessage(dataMessage) - .build() } } \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt b/app/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt index fad9eba2b9..bb7057b17e 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt @@ -1,34 +1,21 @@ package org.session.libsession.messaging.messages.control -import com.google.protobuf.ByteString +import org.session.libsession.database.MessageDataProvider import org.session.libsession.messaging.messages.copyExpiration -import org.session.libsession.messaging.messages.visible.Profile import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.utilities.Log -class MessageRequestResponse(val isApproved: Boolean, var profile: Profile? = null) : ControlMessage() { +class MessageRequestResponse(val isApproved: Boolean) : ControlMessage() { override val isSelfSendValid: Boolean = true override fun shouldDiscardIfBlocked(): Boolean = true - override fun toProto(): SignalServiceProtos.Content? { - val profileProto = SignalServiceProtos.DataMessage.LokiProfile.newBuilder() - profile?.displayName?.let { profileProto.displayName = it } - profile?.profilePictureURL?.let { profileProto.profilePicture = it } - val messageRequestResponseProto = SignalServiceProtos.MessageRequestResponse.newBuilder() + override fun buildProto( + builder: SignalServiceProtos.Content.Builder, + messageDataProvider: MessageDataProvider + ) { + builder.messageRequestResponseBuilder .setIsApproved(isApproved) - .setProfile(profileProto.build()) - profile?.profileKey?.let { messageRequestResponseProto.profileKey = ByteString.copyFrom(it) } - return try { - SignalServiceProtos.Content.newBuilder() - .applyExpiryMode() - .setMessageRequestResponse(messageRequestResponseProto.build()) - .build() - } catch (e: Exception) { - Log.w(TAG, "Couldn't construct message request response proto from: $this") - null - } } companion object { @@ -37,13 +24,7 @@ class MessageRequestResponse(val isApproved: Boolean, var profile: Profile? = nu fun fromProto(proto: SignalServiceProtos.Content): MessageRequestResponse? { val messageRequestResponseProto = if (proto.hasMessageRequestResponse()) proto.messageRequestResponse else return null val isApproved = messageRequestResponseProto.isApproved - val profileProto = messageRequestResponseProto.profile - val profile = Profile().apply { - displayName = profileProto.displayName - profileKey = if (messageRequestResponseProto.hasProfileKey()) messageRequestResponseProto.profileKey.toByteArray() else null - profilePictureURL = profileProto.profilePicture - } - return MessageRequestResponse(isApproved, profile) + return MessageRequestResponse(isApproved) .copyExpiration(proto) } } diff --git a/app/src/main/java/org/session/libsession/messaging/messages/control/ReadReceipt.kt b/app/src/main/java/org/session/libsession/messaging/messages/control/ReadReceipt.kt index 876f47dd33..4c8021e416 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/control/ReadReceipt.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/control/ReadReceipt.kt @@ -1,8 +1,8 @@ package org.session.libsession.messaging.messages.control +import org.session.libsession.database.MessageDataProvider import org.session.libsession.messaging.messages.copyExpiration import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.utilities.Log class ReadReceipt() : ControlMessage() { var timestamps: List? = null @@ -33,24 +33,15 @@ class ReadReceipt() : ControlMessage() { this.timestamps = timestamps } - override fun toProto(): SignalServiceProtos.Content? { - val timestamps = timestamps - if (timestamps == null) { - Log.w(TAG, "Couldn't construct read receipt proto from: $this") - return null - } - - return try { - SignalServiceProtos.Content.newBuilder() - .setReceiptMessage( - SignalServiceProtos.ReceiptMessage.newBuilder() - .setType(SignalServiceProtos.ReceiptMessage.Type.READ) - .addAllTimestampMs(timestamps.asIterable()).build() - ).applyExpiryMode() - .build() - } catch (e: Exception) { - Log.w(TAG, "Couldn't construct read receipt proto from: $this") - null - } + protected override fun buildProto( + builder: SignalServiceProtos.Content.Builder, + messageDataProvider: MessageDataProvider + ) { + builder + .receiptMessageBuilder + .setType(SignalServiceProtos.ReceiptMessage.Type.READ) + .addAllTimestampMs(requireNotNull(timestamps) { + "Timestamps is null" + }) } } \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/messages/control/TypingIndicator.kt b/app/src/main/java/org/session/libsession/messaging/messages/control/TypingIndicator.kt index 92a172e9e6..ab2c17a1f2 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/control/TypingIndicator.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/control/TypingIndicator.kt @@ -1,8 +1,8 @@ package org.session.libsession.messaging.messages.control +import org.session.libsession.database.MessageDataProvider import org.session.libsession.messaging.messages.copyExpiration import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.utilities.Log class TypingIndicator() : ControlMessage() { var kind: Kind? = null @@ -51,21 +51,12 @@ class TypingIndicator() : ControlMessage() { this.kind = kind } - override fun toProto(): SignalServiceProtos.Content? { - val timestamp = sentTimestamp - val kind = kind - if (timestamp == null || kind == null) { - Log.w(TAG, "Couldn't construct typing indicator proto from: $this") - return null - } - return try { - SignalServiceProtos.Content.newBuilder() - .setTypingMessage(SignalServiceProtos.TypingMessage.newBuilder().setTimestampMs(timestamp).setAction(kind.toProto()).build()) - .applyExpiryMode() - .build() - } catch (e: Exception) { - Log.w(TAG, "Couldn't construct typing indicator proto from: $this") - null - } + protected override fun buildProto( + builder: SignalServiceProtos.Content.Builder, + messageDataProvider: MessageDataProvider + ) { + builder.typingMessageBuilder + .setTimestampMs(sentTimestamp!!) + .setAction(kind!!.toProto()) } } \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/messages/control/UnsendRequest.kt b/app/src/main/java/org/session/libsession/messaging/messages/control/UnsendRequest.kt index 6e04375eda..38f5d7d34b 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/control/UnsendRequest.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/control/UnsendRequest.kt @@ -1,8 +1,8 @@ package org.session.libsession.messaging.messages.control +import org.session.libsession.database.MessageDataProvider import org.session.libsession.messaging.messages.copyExpiration import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.utilities.Log class UnsendRequest(var timestamp: Long? = null, var author: String? = null): ControlMessage() { @@ -24,22 +24,13 @@ class UnsendRequest(var timestamp: Long? = null, var author: String? = null): Co proto.takeIf { it.hasUnsendRequest() }?.unsendRequest?.run { UnsendRequest(timestampMs, author) }?.copyExpiration(proto) } - override fun toProto(): SignalServiceProtos.Content? { - val timestamp = timestamp - val author = author - if (timestamp == null || author == null) { - Log.w(TAG, "Couldn't construct unsend request proto from: $this") - return null - } - return try { - SignalServiceProtos.Content.newBuilder() - .setUnsendRequest(SignalServiceProtos.UnsendRequest.newBuilder().setTimestampMs(timestamp).setAuthor(author).build()) - .applyExpiryMode() - .build() - } catch (e: Exception) { - Log.w(TAG, "Couldn't construct unsend request proto from: $this") - null - } + protected override fun buildProto( + builder: SignalServiceProtos.Content.Builder, + messageDataProvider: MessageDataProvider + ) { + builder.unsendRequestBuilder + .setTimestampMs(timestamp!!) + .setAuthor(author!!) } } \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.kt b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.kt index 459afdc644..988d65fea0 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.kt @@ -1,5 +1,6 @@ package org.session.libsession.messaging.messages.signal +import network.loki.messenger.libsession_util.protocol.ProFeatures import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview @@ -28,6 +29,7 @@ class OutgoingMediaMessage( val linkPreviews: List, val group: Address.GroupLike?, val isGroupUpdateMessage: Boolean, + val proFeatures: ProFeatures = ProFeatures.NONE, ) { init { check(!isGroupUpdateMessage || group != null) { diff --git a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.kt b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.kt index c15d4bb2d5..a81a094162 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.kt @@ -1,5 +1,6 @@ package org.session.libsession.messaging.messages.signal +import network.loki.messenger.libsession_util.protocol.ProFeatures import org.session.libsession.messaging.messages.visible.OpenGroupInvitation import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.utilities.UpdateMessageData @@ -13,6 +14,7 @@ data class OutgoingTextMessage( val subscriptionId: Int = -1, val sentTimestampMillis: Long, val isOpenGroupInvitation: Boolean, + val proFeatures: ProFeatures = ProFeatures.NONE, ) { constructor( message: VisibleMessage, @@ -28,6 +30,8 @@ data class OutgoingTextMessage( isOpenGroupInvitation = false, ) + val proFeaturesRawValue: Long get() = proFeatures.rawValue + companion object { fun fromOpenGroupInvitation( invitation: OpenGroupInvitation, diff --git a/app/src/main/java/org/session/libsession/messaging/messages/visible/Profile.kt b/app/src/main/java/org/session/libsession/messaging/messages/visible/Profile.kt deleted file mode 100644 index b5677282cb..0000000000 --- a/app/src/main/java/org/session/libsession/messaging/messages/visible/Profile.kt +++ /dev/null @@ -1,63 +0,0 @@ -package org.session.libsession.messaging.messages.visible - -import com.google.protobuf.ByteString -import org.session.libsignal.utilities.Log -import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile -import org.thoughtcrime.securesms.util.DateUtils.Companion.asEpochMillis -import org.thoughtcrime.securesms.util.DateUtils.Companion.asEpochSeconds -import org.thoughtcrime.securesms.util.DateUtils.Companion.millsToInstant -import org.thoughtcrime.securesms.util.DateUtils.Companion.secondsToInstant -import org.thoughtcrime.securesms.util.DateUtils.Companion.toEpochSeconds -import java.time.Instant -import java.time.ZonedDateTime - -class Profile( - var displayName: String? = null, - var profileKey: ByteArray? = null, - var profilePictureURL: String? = null, - var profileUpdated: Instant? = null -) { - - companion object { - const val TAG = "Profile" - - fun fromProto(proto: SignalServiceProtos.DataMessage): Profile? { - val profileProto = proto.profile ?: return null - val displayName = profileProto.displayName ?: return null - val profileKey = proto.profileKey - val profilePictureURL = profileProto.profilePicture - val profileUpdated = profileProto.lastProfileUpdateSeconds - .takeIf { profileProto.hasLastProfileUpdateSeconds() } - ?.secondsToInstant() - - if (profileKey != null && profilePictureURL != null) { - return Profile(displayName, profileKey.toByteArray(), profilePictureURL, profileUpdated = profileUpdated) - } else { - return Profile(displayName, profileUpdated = profileUpdated) - } - } - } - - fun toProto(): SignalServiceProtos.DataMessage? { - val displayName = displayName - if (displayName == null) { - Log.w(TAG, "Couldn't construct profile proto from: $this") - return null - } - val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder() - val profileProto = SignalServiceProtos.DataMessage.LokiProfile.newBuilder() - profileProto.displayName = displayName - profileKey?.let { dataMessageProto.profileKey = ByteString.copyFrom(it) } - profilePictureURL?.let { profileProto.profilePicture = it } - profileUpdated?.let { profileProto.lastProfileUpdateSeconds = it.toEpochSeconds() } - // Build - try { - dataMessageProto.profile = profileProto.build() - return dataMessageProto.build() - } catch (e: Exception) { - Log.w(TAG, "Couldn't construct profile proto from: $this") - return null - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt b/app/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt index e79ccdf0e0..5a0f65b108 100644 --- a/app/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt +++ b/app/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt @@ -3,7 +3,7 @@ package org.session.libsession.messaging.messages.visible import androidx.annotation.Keep import network.loki.messenger.BuildConfig import network.loki.messenger.libsession_util.protocol.ProFeatures -import org.session.libsession.messaging.MessagingModuleConfiguration +import org.session.libsession.database.MessageDataProvider import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.copyExpiration import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment @@ -22,7 +22,6 @@ data class VisibleMessage( val attachmentIDs: MutableList = mutableListOf(), var quote: Quote? = null, var linkPreview: LinkPreview? = null, - var profile: Profile? = null, var openGroupInvitation: OpenGroupInvitation? = null, var reaction: Reaction? = null, var hasMention: Boolean = false, @@ -61,18 +60,19 @@ data class VisibleMessage( if (it.hasQuote()) quote = Quote.fromProto(it.quote) linkPreview = it.previewList.firstOrNull()?.let(LinkPreview::fromProto) if (it.hasOpenGroupInvitation()) openGroupInvitation = it.openGroupInvitation?.let(OpenGroupInvitation::fromProto) - // TODO Contact - profile = Profile.fromProto(it) if (it.hasReaction()) reaction = it.reaction?.let(Reaction::fromProto) blocksMessageRequests = it.hasBlocksCommunityMessageRequests() && it.blocksCommunityMessageRequests }.copyExpiration(proto) } } - override fun toProto(): SignalServiceProtos.Content? { - val proto = SignalServiceProtos.Content.newBuilder() - // Profile - val dataMessage = profile?.toProto()?.toBuilder() ?: SignalServiceProtos.DataMessage.newBuilder() + protected override fun buildProto( + builder: SignalServiceProtos.Content.Builder, + messageDataProvider: MessageDataProvider + ) { + val dataMessage = builder.dataMessageBuilder + + // Text if (text != null) { dataMessage.body = text } // Quote @@ -96,8 +96,7 @@ data class VisibleMessage( dataMessage.openGroupInvitation = openGroupInvitationProto } // Attachments - val database = MessagingModuleConfiguration.shared.messageDataProvider - val attachments = attachmentIDs.mapNotNull { database.getSignalAttachmentPointer(it) } + val attachments = attachmentIDs.mapNotNull { messageDataProvider.getSignalAttachmentPointer(it) } if (attachments.any { it.url.isNullOrEmpty() }) { if (BuildConfig.DEBUG) { Log.w(TAG, "Sending a message before all associated attachments have been uploaded.") @@ -105,9 +104,6 @@ data class VisibleMessage( } val pointers = attachments.mapNotNull { Attachment.createAttachmentPointer(it) } dataMessage.addAllAttachments(pointers) - // TODO: Contact - // Expiration timer on the message - proto.applyExpiryMode() // Community blocked message requests flag dataMessage.blocksCommunityMessageRequests = blocksMessageRequests @@ -115,13 +111,10 @@ data class VisibleMessage( if (syncTarget != null) { dataMessage.syncTarget = syncTarget } - // Build - return try { - proto.dataMessage = dataMessage.build() - proto.build() - } catch (e: Exception) { - Log.w(TAG, "Couldn't construct visible message proto from: $this") - null + + // Pro features + if (proFeatures != ProFeatures.NONE) { + builder.proMessageBuilder.setFeatures(proFeatures.rawValue) } } // endregion diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/GroupMessageHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/GroupMessageHandler.kt index abeb569713..a16f8d24e8 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/GroupMessageHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/GroupMessageHandler.kt @@ -11,6 +11,7 @@ import org.session.libsession.messaging.utilities.MessageAuthentication.buildDel import org.session.libsession.messaging.utilities.MessageAuthentication.buildGroupInviteSignature import org.session.libsession.messaging.utilities.MessageAuthentication.buildInfoChangeSignature import org.session.libsession.messaging.utilities.MessageAuthentication.buildMemberChangeSignature +import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log @@ -26,7 +27,7 @@ class GroupMessageHandler @Inject constructor( private val groupManagerV2: GroupManagerV2, @param:ManagerScope private val scope: CoroutineScope, ) { - fun handleGroupUpdated(message: GroupUpdated, groupId: AccountId?) { + fun handleGroupUpdated(message: GroupUpdated, groupId: AccountId?, proto: SignalServiceProtos.Content) { val inner = message.inner if (groupId == null && !inner.hasInviteMessage() && !inner.hasPromoteMessage()) { @@ -34,14 +35,7 @@ class GroupMessageHandler @Inject constructor( } // Update profile if needed - ProfileUpdateHandler.Updates.create( - name = message.profile?.displayName, - picUrl = message.profile?.profilePictureURL, - picKey = message.profile?.profileKey, - blocksCommunityMessageRequests = null, - proStatus = null, - profileUpdateTime = null - )?.let { updates -> + ProfileUpdateHandler.Updates.create(proto)?.let { updates -> profileUpdateHandler.handleProfileUpdate( senderId = AccountId(message.sender!!), updates = updates, @@ -50,9 +44,9 @@ class GroupMessageHandler @Inject constructor( } when { - inner.hasInviteMessage() -> handleNewLibSessionClosedGroupMessage(message) + inner.hasInviteMessage() -> handleNewLibSessionClosedGroupMessage(message, proto) inner.hasInviteResponse() -> handleInviteResponse(message, groupId!!) - inner.hasPromoteMessage() -> handlePromotionMessage(message) + inner.hasPromoteMessage() -> handlePromotionMessage(message, proto) inner.hasInfoChangeMessage() -> handleGroupInfoChange(message, groupId!!) inner.hasMemberChangeMessage() -> handleMemberChange(message, groupId!!) inner.hasMemberLeftMessage() -> handleMemberLeft(message, groupId!!) @@ -61,7 +55,7 @@ class GroupMessageHandler @Inject constructor( } } - private fun handleNewLibSessionClosedGroupMessage(message: GroupUpdated) { + private fun handleNewLibSessionClosedGroupMessage(message: GroupUpdated, proto: SignalServiceProtos.Content) { val storage = storage val ourUserId = storage.getUserPublicKey()!! val invite = message.inner.inviteMessage @@ -82,7 +76,9 @@ class GroupMessageHandler @Inject constructor( groupName = invite.name, authData = invite.memberAuthData.toByteArray(), inviter = adminId, - inviterName = message.profile?.displayName, + inviterName = if (proto.hasDataMessage() && proto.dataMessage.hasProfile() && proto.dataMessage.profile.hasDisplayName()) + proto.dataMessage.profile.displayName + else null, inviteMessageHash = message.serverHash!!, inviteMessageTimestamp = message.sentTimestamp!!, ) @@ -121,7 +117,7 @@ class GroupMessageHandler @Inject constructor( } - private fun handlePromotionMessage(message: GroupUpdated) { + private fun handlePromotionMessage(message: GroupUpdated, proto: SignalServiceProtos.Content) { val promotion = message.inner.promoteMessage val seed = promotion.groupIdentitySeed.toByteArray() val sender = message.sender!! @@ -134,7 +130,9 @@ class GroupMessageHandler @Inject constructor( groupName = promotion.name, adminKeySeed = seed, promoter = adminId, - promoterName = message.profile?.displayName, + promoterName = if (proto.hasDataMessage() && proto.dataMessage.hasProfile() && proto.dataMessage.profile.hasDisplayName()) + proto.dataMessage.profile.displayName + else null, promoteMessageHash = message.serverHash!!, promoteMessageTimestamp = message.sentTimestamp!!, ) diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageEncrypter.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageEncrypter.kt index cc09b12a37..d026b4fb26 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageEncrypter.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageEncrypter.kt @@ -4,7 +4,6 @@ import network.loki.messenger.libsession_util.SessionEncrypt import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.sending_receiving.MessageSender.Error import org.session.libsignal.utilities.Hex -import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.removingIdPrefixIfNeeded @@ -20,7 +19,7 @@ object MessageEncrypter { * @return the encrypted message. */ internal fun encrypt(plaintext: ByteArray, recipientHexEncodedX25519PublicKey: String): ByteArray { - val userED25519KeyPair = MessagingModuleConfiguration.shared.storage.getUserED25519KeyPair() ?: throw Error.NoUserED25519KeyPair + val userED25519KeyPair = MessagingModuleConfiguration.shared.storage.getUserED25519KeyPair() ?: throw Error.NoUserED25519KeyPair() val recipientX25519PublicKey = Hex.fromStringCondensed(recipientHexEncodedX25519PublicKey.removingIdPrefixIfNeeded()) try { @@ -31,26 +30,8 @@ object MessageEncrypter { ).data } catch (exception: Exception) { Log.d("Loki", "Couldn't encrypt message due to error: $exception.") - throw Error.EncryptionFailed + throw Error.EncryptionFailed() } } - internal fun encryptBlinded( - plaintext: ByteArray, - recipientBlindedId: String, - serverPublicKey: String - ): ByteArray { - if (IdPrefix.fromValue(recipientBlindedId) != IdPrefix.BLINDED) throw Error.SigningFailed - val userEdKeyPair = - MessagingModuleConfiguration.shared.storage.getUserED25519KeyPair() ?: throw Error.NoUserED25519KeyPair - val recipientBlindedPublicKey = Hex.fromStringCondensed(recipientBlindedId.removingIdPrefixIfNeeded()) - - return SessionEncrypt.encryptForBlindedRecipient( - message = plaintext, - myEd25519Privkey = userEdKeyPair.secretKey.data, - serverPubKey = Hex.fromStringCondensed(serverPublicKey), - recipientBlindId = byteArrayOf(0x15) + recipientBlindedPublicKey - ).data - } - } \ No newline at end of file diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageRequestResponseHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageRequestResponseHandler.kt index 1d303ea872..46be6917ba 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageRequestResponseHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageRequestResponseHandler.kt @@ -3,7 +3,6 @@ package org.session.libsession.messaging.sending_receiving import network.loki.messenger.libsession_util.protocol.ProFeatures import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.ProfileUpdateHandler -import org.session.libsession.messaging.messages.ProfileUpdateHandler.Updates.Companion.toUpdates import org.session.libsession.messaging.messages.control.MessageRequestResponse import org.session.libsession.messaging.messages.signal.IncomingMediaMessage import org.session.libsession.messaging.messages.visible.VisibleMessage @@ -13,6 +12,7 @@ import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.updateContact import org.session.libsession.utilities.upsertContact +import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.BlindMappingRepository import org.thoughtcrime.securesms.database.MmsDatabase @@ -68,7 +68,8 @@ class MessageRequestResponseHandler @Inject constructor( fun handleExplicitRequestResponseMessage( ctx: ReceivedMessageProcessor.MessageProcessingContext?, - message: MessageRequestResponse + message: MessageRequestResponse, + proto: SignalServiceProtos.Content, ) { val (sender, receiver) = fetchSenderAndReceiver(message) ?: return // Always handle explicit request response @@ -81,7 +82,7 @@ class MessageRequestResponseHandler @Inject constructor( // Always process the profile update if any. We don't need // to process profile for other kind of messages as they should be handled elsewhere - message.profile?.toUpdates()?.let { updates -> + ProfileUpdateHandler.Updates.create(proto)?.let { updates -> profileUpdateHandler.get().handleProfileUpdate( senderId = (sender.address as Address.Standard).accountId, updates = updates, diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index a35bc1fa95..67cae5d7b8 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -1,6 +1,7 @@ package org.session.libsession.messaging.sending_receiving -import kotlinx.coroutines.GlobalScope +import com.google.protobuf.ByteString +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.launch @@ -8,6 +9,7 @@ import kotlinx.coroutines.supervisorScope import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.Namespace +import network.loki.messenger.libsession_util.ReadableUserProfile import network.loki.messenger.libsession_util.protocol.SessionProtocol import network.loki.messenger.libsession_util.util.BlindKeyAPI import network.loki.messenger.libsession_util.util.ExpiryMode @@ -29,16 +31,20 @@ import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.open_groups.OpenGroupApi.Capability import org.session.libsession.messaging.open_groups.OpenGroupMessage import org.session.libsession.snode.SnodeAPI -import org.session.libsession.snode.SnodeAPI.nowWithOffset +import org.session.libsession.snode.SnodeClock import org.session.libsession.snode.SnodeMessage import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigFactoryProtocol +import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.RecipientRepository +import org.thoughtcrime.securesms.dependencies.ManagerScope +import org.thoughtcrime.securesms.pro.copyFromLibSession +import org.thoughtcrime.securesms.pro.db.ProDatabase import org.thoughtcrime.securesms.service.ExpiringMessageManager import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -55,28 +61,53 @@ class MessageSender @Inject constructor( private val messageDataProvider: MessageDataProvider, private val messageSendJobFactory: MessageSendJob.Factory, private val messageExpirationManager: ExpiringMessageManager, + private val proDatabase: ProDatabase, + private val snodeClock: SnodeClock, + @param:ManagerScope private val scope: CoroutineScope, ) { // Error - sealed class Error(val description: String) : Exception(description) { - object InvalidMessage : Error("Invalid message.") - object ProtoConversionFailed : Error("Couldn't convert message to proto.") - object NoUserED25519KeyPair : Error("Couldn't find user ED25519 key pair.") - object SigningFailed : Error("Couldn't sign message.") - object EncryptionFailed : Error("Couldn't encrypt message.") - data class InvalidDestination(val destination: Destination): Error("Can't send this way to $destination") + sealed class Error(val description: String, cause: Throwable? = null) : Exception(description, cause) { + class InvalidMessage : Error("Invalid message.") + class ProtoConversionFailed(cause: Throwable) : Error("Couldn't convert message to proto.", cause) + class NoUserED25519KeyPair : Error("Couldn't find user ED25519 key pair.") + class SigningFailed : Error("Couldn't sign message.") + class EncryptionFailed : Error("Couldn't encrypt message.") // Closed groups - object NoThread : Error("Couldn't find a thread associated with the given group public key.") - object NoKeyPair: Error("Couldn't find a private key associated with the given group public key.") - object InvalidClosedGroupUpdate : Error("Invalid group update.") + class InvalidClosedGroupUpdate : Error("Invalid group update.") internal val isRetryable: Boolean = when (this) { - is InvalidMessage, ProtoConversionFailed, InvalidClosedGroupUpdate -> false + is InvalidMessage, is ProtoConversionFailed, is InvalidClosedGroupUpdate -> false else -> true } } + + private fun SignalServiceProtos.DataMessage.Builder.copyProfileFromConfig() { + configFactory.withUserConfigs { + val pic = it.userProfile.getPic() + + profileBuilder.setDisplayName(it.userProfile.getName().orEmpty()) + .setProfilePicture(pic.url) + .setLastProfileUpdateSeconds(it.userProfile.getProfileUpdatedSeconds()) + + setProfileKey(ByteString.copyFrom(pic.keyAsByteArray)) + } + } + + private fun SignalServiceProtos.MessageRequestResponse.Builder.copyProfileFromConfig() { + configFactory.withUserConfigs { + val pic = it.userProfile.getPic() + + profileBuilder.setDisplayName(it.userProfile.getName().orEmpty()) + .setProfilePicture(pic.url) + .setLastProfileUpdateSeconds(it.userProfile.getProfileUpdatedSeconds()) + + setProfileKey(ByteString.copyFrom(pic.keyAsByteArray)) + } + } + // Convenience suspend fun sendNonDurably(message: Message, destination: Destination, isSyncMessage: Boolean) { return if (destination is Destination.OpenGroup || destination is Destination.OpenGroupInbox) { @@ -86,15 +117,42 @@ class MessageSender @Inject constructor( } } + private fun buildProto(msg: Message): SignalServiceProtos.Content { + try { + val builder = SignalServiceProtos.Content.newBuilder() + + msg.toProto(builder, messageDataProvider) + + // Attach pro proof + proDatabase.getCurrentProProof()?.let { proof -> + builder.proMessageBuilder.proofBuilder.copyFromLibSession(proof) + } + + // Attach the user's profile if needed + when { + builder.hasDataMessage() && !builder.dataMessageBuilder.hasProfile() -> { + builder.dataMessageBuilder.copyProfileFromConfig() + } + + builder.hasMessageRequestResponse() && !builder.messageRequestResponseBuilder.hasProfile() -> { + builder.messageRequestResponseBuilder.copyProfileFromConfig() + } + } + + return builder.build() + } catch (e: Exception) { + throw Error.ProtoConversionFailed(e) + } + } + // One-on-One Chats & Closed Groups - @Throws(Exception::class) fun buildWrappedMessageToSnode(destination: Destination, message: Message, isSyncMessage: Boolean): SnodeMessage { val userPublicKey = storage.getUserPublicKey() val userEd25519PrivKey = requireNotNull(storage.getUserED25519KeyPair()?.secretKey?.data) { "Missing user key" } // Set the timestamp, sender and recipient - val messageSendTime = nowWithOffset + val messageSendTime = snodeClock.currentTimeMills() if (message.sentTimestamp == null) { message.sentTimestamp = messageSendTime // Visible messages will already have their sent timestamp set @@ -112,7 +170,7 @@ class MessageSender @Inject constructor( val isSelfSend = (message.recipient == userPublicKey) // Validate the message if (!message.isValid()) { - throw Error.InvalidMessage + throw Error.InvalidMessage() } // Stop here if this is a self-send, unless it's: // • a configuration message @@ -122,30 +180,13 @@ class MessageSender @Inject constructor( && !isSyncMessage && message !is UnsendRequest ) { - throw Error.InvalidMessage - } - // Attach the user's profile if needed - if (message is VisibleMessage) { - message.profile = storage.getUserProfile() - } - if (message is MessageRequestResponse) { - message.profile = storage.getUserProfile() - } - // Convert it to protobuf - val proto = message.toProto()?.toBuilder() ?: throw Error.ProtoConversionFailed - if (message is GroupUpdated) { - if (message.profile != null) { - proto.mergeDataMessage(message.profile.toProto()) - } + throw Error.InvalidMessage() } - // Set the timestamp on the content so it can be verified against envelope timestamp - proto.setSigTimestampMs(message.sentTimestamp!!) - val messageContent = when (destination) { is Destination.Contact -> { SessionProtocol.encodeFor1o1( - plaintext = proto.build().toByteArray(), + plaintext = buildProto(message).toByteArray(), myEd25519PrivKey = userEd25519PrivKey, timestampMs = message.sentTimestamp!!, recipientPubKey = Hex.fromStringCondensed(destination.publicKey), @@ -155,7 +196,7 @@ class MessageSender @Inject constructor( is Destination.ClosedGroup -> { SessionProtocol.encodeForGroup( - plaintext = proto.build().toByteArray(), + plaintext = buildProto(message).toByteArray(), myEd25519PrivKey = userEd25519PrivKey, timestampMs = message.sentTimestamp!!, groupEd25519PublicKey = Hex.fromStringCondensed(destination.publicKey), @@ -254,7 +295,7 @@ class MessageSender @Inject constructor( // Open Groups private suspend fun sendToOpenGroupDestination(destination: Destination, message: Message) { if (message.sentTimestamp == null) { - message.sentTimestamp = nowWithOffset + message.sentTimestamp = snodeClock.currentTimeMills() } // Attach the blocks message requests info configFactory.withUserConfigs { configs -> @@ -263,7 +304,7 @@ class MessageSender @Inject constructor( } } val userEdKeyPair = storage.getUserED25519KeyPair()!! - var serverCapabilities = listOf() + var serverCapabilities: List var blindedPublicKey: ByteArray? = null when (destination) { is Destination.OpenGroup -> { @@ -294,21 +335,15 @@ class MessageSender @Inject constructor( message.sender = messageSender try { - // Attach the user's profile if needed - if (message is VisibleMessage) { - message.profile = storage.getUserProfile() - } - val content = message.toProto()!!.toBuilder() - .setSigTimestampMs(message.sentTimestamp!!) - .build() + val content = buildProto(message) when (destination) { is Destination.OpenGroup -> { - val whisperMods = if (destination.whisperTo.isNullOrEmpty() && destination.whisperMods) "mods" else null + val whisperMods = if (destination.whisperTo.isEmpty() && destination.whisperMods) "mods" else null message.recipient = "${destination.server}.${destination.roomToken}.${destination.whisperTo}.$whisperMods" // Validate the message if (message !is VisibleMessage || !message.isValid()) { - throw Error.InvalidMessage + throw Error.InvalidMessage() } val plaintext = SessionProtocol.encodeForCommunity( plaintext = content.toByteArray(), @@ -338,7 +373,7 @@ class MessageSender @Inject constructor( message.recipient = destination.blindedPublicKey // Validate the message if (message !is VisibleMessage || !message.isValid()) { - throw Error.InvalidMessage + throw Error.InvalidMessage() } val ciphertext = SessionProtocol.encodeForCommunityInbox( plaintext = content.toByteArray(), @@ -423,7 +458,7 @@ class MessageSender @Inject constructor( if (message is ExpirationTimerUpdate) message.syncTarget = destination.publicKey message.id?.let(storage::markAsSyncing) - GlobalScope.launch { + scope.launch { try { sendToSnodeDestination(Destination.Contact(userPublicKey), message, true) } catch (ec: Exception) { diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 624693ca98..3c9058502d 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -117,7 +117,11 @@ class ReceivedMessageHandler @Inject constructor( when (message) { is ReadReceipt -> handleReadReceipt(message) is TypingIndicator -> handleTypingIndicator(message) - is GroupUpdated -> handleGroupUpdated(message, (threadAddress as? Address.Group)?.accountId) + is GroupUpdated -> handleGroupUpdated( + message = message, + closedGroup = (threadAddress as? Address.Group)?.accountId, + proto = proto + ) is ExpirationTimerUpdate -> { // For groupsv2, there are dedicated mechanisms for handling expiration timers, and // we want to avoid the 1-to-1 message format which is unauthenticated in a group settings. @@ -132,7 +136,7 @@ class ReceivedMessageHandler @Inject constructor( } is DataExtractionNotification -> handleDataExtractionNotification(message) is UnsendRequest -> handleUnsendRequest(message) - is MessageRequestResponse -> messageRequestResponseHandler.get().handleExplicitRequestResponseMessage(null, message) + is MessageRequestResponse -> messageRequestResponseHandler.get().handleExplicitRequestResponseMessage(null, message, proto) is VisibleMessage -> handleVisibleMessage( message = message, proto = proto, @@ -443,14 +447,7 @@ class ReceivedMessageHandler @Inject constructor( // - must be done after the message is persisted) // - must be done after neccessary contact is created if (runProfileUpdate && senderAddress is Address.WithAccountId) { - val updates = ProfileUpdateHandler.Updates.create( - name = message.profile?.displayName, - picUrl = message.profile?.profilePictureURL, - picKey = message.profile?.profileKey, - blocksCommunityMessageRequests = message.blocksMessageRequests, - proStatus = null, - profileUpdateTime = message.profile?.profileUpdated, - ) + val updates = ProfileUpdateHandler.Updates.create(proto) if (updates != null) { profileUpdateHandler.get().handleProfileUpdate( @@ -489,7 +486,7 @@ class ReceivedMessageHandler @Inject constructor( return null } - private fun handleGroupUpdated(message: GroupUpdated, closedGroup: AccountId?) { + private fun handleGroupUpdated(message: GroupUpdated, closedGroup: AccountId?, proto: SignalServiceProtos.Content) { val inner = message.inner if (closedGroup == null && !inner.hasInviteMessage() && !inner.hasPromoteMessage()) { @@ -497,14 +494,7 @@ class ReceivedMessageHandler @Inject constructor( } // Update profile if needed - ProfileUpdateHandler.Updates.create( - name = message.profile?.displayName, - picUrl = message.profile?.profilePictureURL, - picKey = message.profile?.profileKey, - blocksCommunityMessageRequests = null, - proStatus = null, - profileUpdateTime = null - )?.let { updates -> + ProfileUpdateHandler.Updates.create(proto)?.let { updates -> profileUpdateHandler.get().handleProfileUpdate( senderId = AccountId(message.sender!!), updates = updates, @@ -513,9 +503,9 @@ class ReceivedMessageHandler @Inject constructor( } when { - inner.hasInviteMessage() -> handleNewLibSessionClosedGroupMessage(message) + inner.hasInviteMessage() -> handleNewLibSessionClosedGroupMessage(message, proto) inner.hasInviteResponse() -> handleInviteResponse(message, closedGroup!!) - inner.hasPromoteMessage() -> handlePromotionMessage(message) + inner.hasPromoteMessage() -> handlePromotionMessage(message, proto) inner.hasInfoChangeMessage() -> handleGroupInfoChange(message, closedGroup!!) inner.hasMemberChangeMessage() -> handleMemberChange(message, closedGroup!!) inner.hasMemberLeftMessage() -> handleMemberLeft(message, closedGroup!!) @@ -594,7 +584,7 @@ class ReceivedMessageHandler @Inject constructor( groupManagerV2.handleGroupInfoChange(message, closedGroup) } - private fun handlePromotionMessage(message: GroupUpdated) { + private fun handlePromotionMessage(message: GroupUpdated, proto: SignalServiceProtos.Content) { val promotion = message.inner.promoteMessage val seed = promotion.groupIdentitySeed.toByteArray() val sender = message.sender!! @@ -607,7 +597,9 @@ class ReceivedMessageHandler @Inject constructor( groupName = promotion.name, adminKeySeed = seed, promoter = adminId, - promoterName = message.profile?.displayName, + promoterName = if (proto.hasDataMessage() && proto.dataMessage.hasProfile() && proto.dataMessage.profile.hasDisplayName()) + proto.dataMessage.profile.displayName + else null, promoteMessageHash = message.serverHash!!, promoteMessageTimestamp = message.sentTimestamp!!, ) @@ -630,7 +622,7 @@ class ReceivedMessageHandler @Inject constructor( } } - private fun handleNewLibSessionClosedGroupMessage(message: GroupUpdated) { + private fun handleNewLibSessionClosedGroupMessage(message: GroupUpdated, proto: SignalServiceProtos.Content) { val storage = storage val ourUserId = storage.getUserPublicKey()!! val invite = message.inner.inviteMessage @@ -651,7 +643,9 @@ class ReceivedMessageHandler @Inject constructor( groupName = invite.name, authData = invite.memberAuthData.toByteArray(), inviter = adminId, - inviterName = message.profile?.displayName, + inviterName = if (proto.hasDataMessage() && proto.dataMessage.hasProfile() && proto.dataMessage.profile.hasDisplayName()) + proto.dataMessage.profile.displayName + else null, inviteMessageHash = message.serverHash!!, inviteMessageTimestamp = message.sentTimestamp!!, ) diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageProcessor.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageProcessor.kt index 9dc0ce1eb5..93e3614bb7 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageProcessor.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageProcessor.kt @@ -165,7 +165,8 @@ class ReceivedMessageProcessor @Inject constructor( is TypingIndicator -> handleTypingIndicator(message) is GroupUpdated -> groupMessageHandler.get().handleGroupUpdated( message = message, - groupId = (threadAddress as? Address.Group)?.accountId + groupId = (threadAddress as? Address.Group)?.accountId, + proto = proto ) is ExpirationTimerUpdate -> { @@ -184,7 +185,7 @@ class ReceivedMessageProcessor @Inject constructor( is DataExtractionNotification -> handleDataExtractionNotification(message) is UnsendRequest -> handleUnsendRequest(message) is MessageRequestResponse -> messageRequestResponseHandler.get() - .handleExplicitRequestResponseMessage(context, message) + .handleExplicitRequestResponseMessage(context, message, proto) is VisibleMessage -> { if (message.isSenderSelf && diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/VisibleMessageHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/VisibleMessageHandler.kt index a5e1332f5e..672e46a318 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/VisibleMessageHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/VisibleMessageHandler.kt @@ -202,14 +202,7 @@ class VisibleMessageHandler @Inject constructor( // - must be done after the message is persisted) // - must be done after neccessary contact is created if (runProfileUpdate && senderAddress is Address.WithAccountId) { - val updates = ProfileUpdateHandler.Updates.create( - name = message.profile?.displayName, - picUrl = message.profile?.profilePictureURL, - picKey = message.profile?.profileKey, - blocksCommunityMessageRequests = message.blocksMessageRequests, - proStatus = null, - profileUpdateTime = message.profile?.profileUpdated, - ) + val updates = ProfileUpdateHandler.Updates.create(proto) if (updates != null) { profileUpdateHandler.get().handleProfileUpdate( diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index b631653285..e336673488 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -611,6 +611,7 @@ class MmsDatabase @Inject constructor( contentValues.put(EXPIRES_IN, message.expiresInMillis) contentValues.put(EXPIRE_STARTED, message.expireStartedAtMillis) contentValues.put(ADDRESS, message.recipient.toString()) + contentValues.put(PRO_FEATURES, message.proFeatures.rawValue) contentValues.put( DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values).mapToLong { obj: Long -> obj } @@ -1067,32 +1068,26 @@ class MmsDatabase @Inject constructor( return getMediaMmsMessageRecord(cursor!!, getQuote) } - private fun getMediaMmsMessageRecord(cursor: Cursor, getQuote: Boolean): MediaMmsMessageRecord { + private fun getMediaMmsMessageRecord(cursor: Cursor, getQuote: Boolean): MmsMessageRecord { val id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)) val dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT)) val dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_RECEIVED)) val box = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX)) val threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)) val address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)) - val addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(ADDRESS_DEVICE_ID)) val deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(DELIVERY_RECEIPT_COUNT)) var readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(READ_RECEIPT_COUNT)) val body = cursor.getString(cursor.getColumnIndexOrThrow(BODY)) - val partCount = cursor.getInt(cursor.getColumnIndexOrThrow(PART_COUNT)) - val mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(MISMATCHED_IDENTITIES)) - val networkDocument = cursor.getString(cursor.getColumnIndexOrThrow(NETWORK_FAILURE)) - val subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID)) val expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN)) val expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED)) val hasMention = cursor.getInt(cursor.getColumnIndexOrThrow(HAS_MENTION)) == 1 val messageContentJson = cursor.getString(cursor.getColumnIndexOrThrow(MESSAGE_CONTENT)) + val proFeatures = cursor.getLong(cursor.getColumnIndexOrThrow(PRO_FEATURES)) if (!isReadReceiptsEnabled(context)) { readReceiptCount = 0 } val recipient = getRecipientFor(address) - val mismatches = getMismatchedIdentities(mismatchDocument) - val networkFailures = getFailures(networkDocument) val attachments = attachmentDatabase.getAttachment( cursor ) @@ -1117,12 +1112,25 @@ class MmsDatabase @Inject constructor( }.getOrNull() return MediaMmsMessageRecord( - id, recipient, recipient, - addressDeviceId, dateSent, dateReceived, deliveryReceiptCount, - threadId, body, slideDeck!!, partCount, box, mismatches, - networkFailures, subscriptionId, expiresIn, expireStarted, - readReceiptCount, quote, contacts, previews, reactions, hasMention, - messageContent + /* id = */ id, + /* conversationRecipient = */ recipient, + /* individualRecipient = */ recipient, + /* dateSent = */ dateSent, + /* dateReceived = */ dateReceived, + /* deliveryReceiptCount = */ deliveryReceiptCount, + /* threadId = */ threadId, + /* body = */ body, + /* slideDeck = */ slideDeck!!, + /* mailbox = */ box, + /* expiresIn = */ expiresIn, + /* expireStarted = */ expireStarted, + /* readReceiptCount = */ readReceiptCount, + /* quote = */ quote, + /* linkPreviews = */ previews, + /* reactions = */ reactions, + /* hasMention = */ hasMention, + /* messageContent = */ messageContent, + /* proFeaturesRawValue = */ proFeatures ) } @@ -1130,28 +1138,6 @@ class MmsDatabase @Inject constructor( return recipientRepository.getRecipientSync(serialized.toAddress()) } - private fun getMismatchedIdentities(document: String?): List? { - if (!document.isNullOrEmpty()) { - try { - return JsonUtil.fromJson(document, IdentityKeyMismatchList::class.java).list - } catch (e: IOException) { - Log.w(TAG, e) - } - } - return LinkedList() - } - - private fun getFailures(document: String?): List? { - if (!document.isNullOrEmpty()) { - try { - return JsonUtil.fromJson(document, NetworkFailureList::class.java).list - } catch (ioe: IOException) { - Log.w(TAG, ioe) - } - } - return LinkedList() - } - private fun getSlideDeck(attachments: List): SlideDeck? { val messageAttachments: List? = Stream.of(attachments) .filterNot { obj: DatabaseAttachment? -> obj!!.isQuote } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 1da6bb14cb..559768c9ad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -96,7 +96,8 @@ public class MmsSmsDatabase extends Database { MmsDatabase.LINK_PREVIEWS, ReactionDatabase.REACTION_JSON_ALIAS, MmsSmsColumns.HAS_MENTION, - MmsSmsColumns.SERVER_HASH + MmsSmsColumns.SERVER_HASH, + MmsSmsColumns.PRO_FEATURES }; private final LoginStateRepository loginStateRepository; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabaseSQL.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabaseSQL.kt index 59ab96dc09..25c243f008 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabaseSQL.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabaseSQL.kt @@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.database.model.MessageId * ```sqlite * SELECT sms_fields, * (query reaction table) AS reactions, + * NULL AS attachments, * (query hash table) AS server_hash * FROM sms * @@ -119,7 +120,8 @@ fun buildMmsSmsCombinedQuery( NULL AS ${MmsDatabase.SHARED_CONTACTS}, NULL AS ${MmsDatabase.LINK_PREVIEWS}, ${MmsSmsColumns.HAS_MENTION}, - ($smsHashQuery) AS ${MmsSmsColumns.SERVER_HASH} + ($smsHashQuery) AS ${MmsSmsColumns.SERVER_HASH}, + ${MmsSmsColumns.PRO_FEATURES} FROM ${SmsDatabase.TABLE_NAME} $whereStatement """ @@ -217,7 +219,8 @@ fun buildMmsSmsCombinedQuery( ${MmsDatabase.SHARED_CONTACTS}, ${MmsDatabase.LINK_PREVIEWS}, ${MmsSmsColumns.HAS_MENTION}, - ($mmsHashQuery) AS ${MmsSmsColumns.SERVER_HASH} + ($mmsHashQuery) AS ${MmsSmsColumns.SERVER_HASH}, + ${MmsSmsColumns.PRO_FEATURES} FROM ${MmsDatabase.TABLE_NAME} $whereStatement """ diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 041fc108e5..fee95babea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -36,11 +36,8 @@ import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; import org.session.libsession.snode.SnodeAPI; import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.IdentityKeyMismatch; -import org.session.libsession.utilities.IdentityKeyMismatchList; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsignal.utilities.JsonUtil; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.guava.Optional; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; @@ -49,7 +46,6 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import java.io.Closeable; -import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -548,6 +544,7 @@ public long insertMessageOutbox(long threadId, OutgoingTextMessage message, contentValues.put(EXPIRE_STARTED, message.getExpireStartedAtMillis()); contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(Long::longValue).sum()); contentValues.put(READ_RECEIPT_COUNT, Stream.of(earlyReadReceipts.values()).mapToLong(Long::longValue).sum()); + contentValues.put(PRO_FEATURES, message.getProFeaturesRawValue()); if (isDuplicate(message, threadId)) { Log.w(TAG, "Duplicate message (" + message.getSentTimestampMillis() + "), ignoring..."); @@ -724,7 +721,6 @@ public int getCount() { public SmsMessageRecord getCurrent() { long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID)); Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS))); - int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS_DEVICE_ID)); long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE)); long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_RECEIVED)); long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_SENT)); @@ -732,39 +728,38 @@ public SmsMessageRecord getCurrent() { int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS)); int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.DELIVERY_RECEIPT_COUNT)); int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.READ_RECEIPT_COUNT)); - String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.MISMATCHED_IDENTITIES)); - int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.SUBSCRIPTION_ID)); long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRES_IN)); long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRE_STARTED)); String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY)); boolean hasMention = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.HAS_MENTION)) == 1; + long proFeatures = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.PRO_FEATURES)); if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { readReceiptCount = 0; } - List mismatches = getMismatches(mismatchDocument); Recipient recipient = recipientRepository.getRecipientSync(address); List reactions = reactionDatabase.get().getReactions(cursor); - return new SmsMessageRecord(messageId, body, recipient, - recipient, - dateSent, dateReceived, deliveryReceiptCount, type, - threadId, status, mismatches, - expiresIn, expireStarted, readReceiptCount, reactions, hasMention); + return new SmsMessageRecord( + messageId, + body, + recipient, + recipient, + dateSent, + dateReceived, + deliveryReceiptCount, + type, + threadId, + status, + expiresIn, + expireStarted, + readReceiptCount, + reactions, + hasMention, + proFeatures); } - private List getMismatches(String document) { - try { - if (!TextUtils.isEmpty(document)) { - return JsonUtil.fromJson(document, IdentityKeyMismatchList.class).getList(); - } - } catch (IOException e) { - Log.w(TAG, e); - } - - return new LinkedList<>(); - } @Override public void close() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 32c94a5eb4..35ccd55c86 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -29,7 +29,6 @@ import org.session.libsession.messaging.messages.signal.IncomingTextMessage import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage import org.session.libsession.messaging.messages.visible.Attachment -import org.session.libsession.messaging.messages.visible.Profile import org.session.libsession.messaging.messages.visible.Reaction import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId @@ -68,7 +67,6 @@ import org.thoughtcrime.securesms.database.model.ReactionRecord import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.mms.PartAuthority -import org.thoughtcrime.securesms.util.DateUtils.Companion.secondsToInstant import org.thoughtcrime.securesms.util.FilenameUtils import org.thoughtcrime.securesms.util.SessionMetaProtocol import java.time.Instant @@ -89,7 +87,6 @@ open class Storage @Inject constructor( private val threadDatabase: ThreadDatabase, private val recipientDatabase: RecipientSettingsDatabase, private val attachmentDatabase: AttachmentDatabase, - private val draftDatabase: DraftDatabase, private val lokiAPIDatabase: LokiAPIDatabase, private val groupDatabase: GroupDatabase, private val lokiMessageDatabase: LokiMessageDatabase, @@ -120,18 +117,6 @@ open class Storage @Inject constructor( return AccountId(BlindKeyAPI.blind15Ids(myId, serverPublicKey).first()) } - override fun getUserProfile(): Profile { - return configFactory.withUserConfigs { configs -> - val pic = configs.userProfile.getPic() - Profile( - displayName = configs.userProfile.getName(), - profilePictureURL = pic.url.takeIf { it.isNotBlank() }, - profileKey = pic.key.data.takeIf { pic.url.isNotBlank() }, - profileUpdated = configs.userProfile.getProfileUpdatedSeconds().secondsToInstant(), - ) - } - } - override fun getAttachmentsForMessage(mmsMessageId: Long): List { return attachmentDatabase.getAttachmentsForMessage(mmsMessageId) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java index 7fbcf9669e..37d56e2007 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java @@ -16,15 +16,10 @@ */ package org.thoughtcrime.securesms.database.model; -import android.content.Context; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; -import org.session.libsession.utilities.Contact; -import org.session.libsession.utilities.IdentityKeyMismatch; -import org.session.libsession.utilities.NetworkFailure; import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.SmsDatabase.Status; import org.thoughtcrime.securesms.database.model.content.MessageContent; @@ -41,40 +36,29 @@ */ public class MediaMmsMessageRecord extends MmsMessageRecord { - private final int partCount; public MediaMmsMessageRecord(long id, Recipient conversationRecipient, - Recipient individualRecipient, int recipientDeviceId, + Recipient individualRecipient, long dateSent, long dateReceived, int deliveryReceiptCount, long threadId, String body, @NonNull SlideDeck slideDeck, - int partCount, long mailbox, - List mismatches, - List failures, int subscriptionId, + long mailbox, long expiresIn, long expireStarted, int readReceiptCount, - @Nullable Quote quote, @NonNull List contacts, + @Nullable Quote quote, @NonNull List linkPreviews, @NonNull List reactions, boolean hasMention, - @Nullable MessageContent messageContent) + @Nullable MessageContent messageContent, + long proFeaturesRawValue) { super(id, body, conversationRecipient, individualRecipient, dateSent, - dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures, - expiresIn, expireStarted, slideDeck, readReceiptCount, quote, contacts, - linkPreviews, reactions, hasMention, messageContent); - this.partCount = partCount; - } - - public int getPartCount() { - return partCount; + dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, + expiresIn, expireStarted, slideDeck, readReceiptCount, quote, + linkPreviews, reactions, hasMention, messageContent, proFeaturesRawValue); } - @Override + @Override public boolean isMmsNotification() { return false; } - @Override - public CharSequence getDisplayBody(@NonNull Context context) { - return super.getDisplayBody(context); - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index 65afa14e86..3d3f775263 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -20,8 +20,6 @@ import android.text.Spannable; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; -import android.text.style.RelativeSizeSpan; -import android.text.style.StyleSpan; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -57,8 +55,6 @@ */ public abstract class MessageRecord extends DisplayRecord { private final Recipient individualRecipient; - private final List mismatches; - private final List networkFailures; private final long expiresIn; private final long expireStarted; public final long id; @@ -67,6 +63,7 @@ public abstract class MessageRecord extends DisplayRecord { @Nullable private UpdateMessageData groupUpdateMessage; + final long proFeaturesRawValue; public abstract boolean isMms(); public abstract boolean isMmsNotification(); @@ -79,22 +76,20 @@ public final MessageId getMessageId() { Recipient individualRecipient, long dateSent, long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, long type, - List mismatches, - List networkFailures, long expiresIn, long expireStarted, int readReceiptCount, List reactions, boolean hasMention, - @Nullable MessageContent messageContent) + @Nullable MessageContent messageContent, + long proFeaturesRawValue) { super(body, conversationRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, readReceiptCount, messageContent); this.id = id; this.individualRecipient = individualRecipient; - this.mismatches = mismatches; - this.networkFailures = networkFailures; this.expiresIn = expiresIn; this.expireStarted = expireStarted; this.reactions = reactions; this.hasMention = hasMention; + this.proFeaturesRawValue = proFeaturesRawValue; } public long getId() { @@ -109,9 +104,7 @@ public Recipient getIndividualRecipient() { public long getType() { return type; } - public List getNetworkFailures() { - return networkFailures; - } + public long getExpiresIn() { return expiresIn; } @@ -197,15 +190,7 @@ public boolean isGroupExpirationTimerUpdate() { return updateMessageData != null && updateMessageData.getKind() instanceof UpdateMessageData.Kind.GroupExpirationUpdated; } - protected SpannableString emphasisAdded(String sequence) { - SpannableString spannable = new SpannableString(sequence); - spannable.setSpan(new RelativeSizeSpan(0.9f), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - spannable.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - - return spannable; - } - - @Override + @Override public boolean equals(Object other) { return other instanceof MessageRecord && ((MessageRecord) other).getId() == getId() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecords.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecords.kt new file mode 100644 index 0000000000..9d899a7516 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecords.kt @@ -0,0 +1,5 @@ +package org.thoughtcrime.securesms.database.model + +import network.loki.messenger.libsession_util.protocol.ProFeatures + +val MessageRecord.proFeatures: ProFeatures get() = ProFeatures(proFeaturesRawValue) \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java index d0cc28a597..5dd4e8206f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java @@ -4,84 +4,83 @@ import androidx.annotation.Nullable; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; -import org.session.libsession.utilities.Contact; -import org.session.libsession.utilities.IdentityKeyMismatch; -import org.session.libsession.utilities.NetworkFailure; import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.model.content.MessageContent; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; public abstract class MmsMessageRecord extends MessageRecord { - private final @NonNull SlideDeck slideDeck; - private final @Nullable Quote quote; - private final @NonNull List contacts = new LinkedList<>(); - private final @NonNull List linkPreviews = new LinkedList<>(); - - MmsMessageRecord(long id, String body, Recipient conversationRecipient, - Recipient individualRecipient, long dateSent, - long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, - long type, List mismatches, - List networkFailures, long expiresIn, - long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount, - @Nullable Quote quote, @NonNull List contacts, - @NonNull List linkPreviews, List reactions, boolean hasMention, - @Nullable MessageContent messageContent) - { - super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, expiresIn, expireStarted, readReceiptCount, reactions, hasMention, messageContent); - this.slideDeck = slideDeck; - this.quote = quote; - this.contacts.addAll(contacts); - this.linkPreviews.addAll(linkPreviews); - } - - @Override - public boolean isMms() { - return true; - } - - @NonNull - public SlideDeck getSlideDeck() { - return slideDeck; - } - - @Override - public boolean isMediaPending() { - for (Slide slide : getSlideDeck().getSlides()) { - if (slide.isInProgress() || slide.isPendingDownload()) { + private final @NonNull SlideDeck slideDeck; + private final @Nullable Quote quote; + private final @NonNull List linkPreviews = new ArrayList<>(); + + MmsMessageRecord(long id, String body, Recipient conversationRecipient, + Recipient individualRecipient, long dateSent, + long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, + long type, + long expiresIn, + long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount, + @Nullable Quote quote, + @NonNull List linkPreviews, List reactions, boolean hasMention, + @Nullable MessageContent messageContent, + long proFeaturesRawValue) { + super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, expiresIn, expireStarted, readReceiptCount, reactions, hasMention, messageContent, proFeaturesRawValue); + this.slideDeck = slideDeck; + this.quote = quote; + this.linkPreviews.addAll(linkPreviews); + } + + @Override + public boolean isMms() { return true; - } } - return false; - } - - public boolean containsMediaSlide() { - return slideDeck.containsMediaSlide(); - } - public @Nullable Quote getQuote() { - return quote; - } - public @NonNull List getSharedContacts() { - return contacts; - } - public @NonNull List getLinkPreviews() { - return linkPreviews; - } - - public boolean hasAttachmentUri() { - boolean hasData = false; - - for (Slide slide : slideDeck.getSlides()) { - if (slide.getUri() != null || slide.getThumbnailUri() != null) { - hasData = true; - break; - } + @Override + public boolean isMmsNotification() { + return false; + } + + @NonNull + public SlideDeck getSlideDeck() { + return slideDeck; } - return hasData; - } + @Override + public boolean isMediaPending() { + for (Slide slide : getSlideDeck().getSlides()) { + if (slide.isInProgress() || slide.isPendingDownload()) { + return true; + } + } + + return false; + } + + public boolean containsMediaSlide() { + return slideDeck.containsMediaSlide(); + } + + public @Nullable Quote getQuote() { + return quote; + } + + public @NonNull List getLinkPreviews() { + return linkPreviews; + } + + public boolean hasAttachmentUri() { + boolean hasData = false; + + for (Slide slide : slideDeck.getSlides()) { + if (slide.getUri() != null || slide.getThumbnailUri() != null) { + hasData = true; + break; + } + } + + return hasData; + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java index 46aab667fe..5b4acdce6d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java @@ -21,10 +21,8 @@ import androidx.annotation.NonNull; -import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.recipients.Recipient; -import java.util.LinkedList; import java.util.List; /** @@ -35,38 +33,38 @@ */ public class SmsMessageRecord extends MessageRecord { - public SmsMessageRecord(long id, - String body, Recipient recipient, - Recipient individualRecipient, - long dateSent, long dateReceived, - int deliveryReceiptCount, - long type, long threadId, - int status, List mismatches, - long expiresIn, long expireStarted, - int readReceiptCount, List reactions, boolean hasMention) - { - super(id, body, recipient, individualRecipient, - dateSent, dateReceived, threadId, status, deliveryReceiptCount, type, - mismatches, new LinkedList<>(), - expiresIn, expireStarted, readReceiptCount, reactions, hasMention, null); - } + public SmsMessageRecord(long id, + String body, Recipient recipient, + Recipient individualRecipient, + long dateSent, long dateReceived, + int deliveryReceiptCount, + long type, long threadId, + int status, + long expiresIn, long expireStarted, + int readReceiptCount, List reactions, boolean hasMention, + long proFeaturesRawValue) { + super(id, body, recipient, individualRecipient, + dateSent, dateReceived, threadId, status, deliveryReceiptCount, type, + expiresIn, expireStarted, readReceiptCount, reactions, hasMention, null, + proFeaturesRawValue); + } - public long getType() { - return type; - } + public long getType() { + return type; + } - @Override - public CharSequence getDisplayBody(@NonNull Context context) { - return super.getDisplayBody(context); - } + @Override + public CharSequence getDisplayBody(@NonNull Context context) { + return super.getDisplayBody(context); + } - @Override - public boolean isMms() { - return false; - } + @Override + public boolean isMms() { + return false; + } - @Override - public boolean isMmsNotification() { - return false; - } + @Override + public boolean isMmsNotification() { + return false; + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt index 89efd74846..5e700a6db0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt @@ -32,7 +32,6 @@ import org.session.libsession.messaging.jobs.InviteContactsJob import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.control.GroupUpdated -import org.session.libsession.messaging.messages.visible.Profile import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.utilities.MessageAuthentication.buildDeleteMemberContentSignature import org.session.libsession.messaging.utilities.MessageAuthentication.buildInfoChangeSignature @@ -121,7 +120,6 @@ class GroupManagerV2Impl @Inject constructor( ): Recipient = withContext(dispatcher) { val ourAccountId = requireNotNull(storage.getUserPublicKey()) { "Our account ID is not available" } - val ourProfile = storage.getUserProfile() val groupCreationTimestamp = clock.currentTimeMills() @@ -161,10 +159,14 @@ class GroupManagerV2Impl @Inject constructor( } // Add ourselves as admin + val (ourName, ourPic) = configFactory.withUserConfigs { configs -> + configs.userProfile.getName().orEmpty() to configs.userProfile.getPic() + } + newGroupConfigs.groupMembers.set( newGroupConfigs.groupMembers.getOrConstruct(ourAccountId).apply { - setName(ourProfile.displayName.orEmpty()) - setProfilePic(ourProfile.profilePicture ?: UserPic.DEFAULT) + setName(ourName) + setProfilePic(ourPic) setPromotionAccepted() } ) @@ -201,7 +203,7 @@ class GroupManagerV2Impl @Inject constructor( "Failed to create a thread for the group" } - val recipient = recipientRepository.getRecipient(Address.fromSerialized(groupId.hexString))!! + val recipient = recipientRepository.getRecipient(Address.fromSerialized(groupId.hexString)) // Invite members JobQueue.shared.add( @@ -660,7 +662,7 @@ class GroupManagerV2Impl @Inject constructor( .setIsApproved(true) val responseData = GroupUpdateMessage.newBuilder() .setInviteResponse(inviteResponse) - val responseMessage = GroupUpdated(responseData.build(), profile = storage.getUserProfile()) + val responseMessage = GroupUpdated(responseData.build()) // this will fail the first couple of times :) runCatching { messageSender.sendNonDurably( @@ -1196,15 +1198,4 @@ class GroupManagerV2Impl @Inject constructor( val firstError = this.results.firstOrNull { it.code != 200 } require(firstError == null) { "$errorMessage: ${firstError!!.body}" } } - - private val Profile.profilePicture: UserPic? - get() { - val url = this.profilePictureURL - val key = this.profileKey - return if (url != null && key != null) { - UserPic(url, key) - } else { - null - } - } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt index f3271a4629..f9b44b4cb1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/QRCodeActivity.kt @@ -35,12 +35,14 @@ import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions import org.thoughtcrime.securesms.ui.theme.LocalType import org.thoughtcrime.securesms.util.applySafeInsetsPaddings +import javax.inject.Inject private val TITLES = listOf(R.string.view, R.string.scan) @AndroidEntryPoint class QRCodeActivity : ScreenLockActionBarActivity() { + @Inject lateinit var loginStateRepository: LoginStateRepository override val applyDefaultWindowInsets: Boolean diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProProofs.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProProofs.kt new file mode 100644 index 0000000000..1d13b218f4 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProProofs.kt @@ -0,0 +1,16 @@ +package org.thoughtcrime.securesms.pro + +import com.google.protobuf.ByteString +import network.loki.messenger.libsession_util.pro.ProProof +import org.session.libsignal.protos.SignalServiceProtos + +/** + * Copies values from a libsession ProProof into a protobuf-based ProProof. + */ +fun SignalServiceProtos.ProProof.Builder.copyFromLibSession( + proProof: ProProof +): SignalServiceProtos.ProProof.Builder = setVersion(proProof.version) + .setExpiryUnixTs(proProof.expiryMs) + .setGenIndexHash(ByteString.copyFrom(proProof.genIndexHashHex.hexToByteArray())) + .setRotatingPublicKey(ByteString.copyFrom(proProof.rotatingPubKeyHex.hexToByteArray())) + .setSig(ByteString.copyFrom(proProof.signatureHex.hexToByteArray())) diff --git a/app/src/main/proto/SignalService.proto b/app/src/main/proto/SignalService.proto index 72e8bb739b..558e51dffe 100644 --- a/app/src/main/proto/SignalService.proto +++ b/app/src/main/proto/SignalService.proto @@ -59,6 +59,7 @@ message Content { optional ExpirationType expirationType = 12; optional uint32 expirationTimerSeconds = 13; optional uint64 sigTimestampMs = 15; + optional ProMessage proMessage = 16; reserved 14; reserved 11; // Used to be a "sharedConfigMessage" but no longer used @@ -313,4 +314,17 @@ message AttachmentPointer { optional uint32 height = 10; optional string caption = 11; optional string url = 101; +} + +message ProMessage { + optional ProProof proof = 1; + optional uint64 features = 2; // this will need to be set by what we get from pro_features_for_utf* +} + +message ProProof { + optional uint32 version = 1; + optional bytes genIndexHash = 2; + optional bytes rotatingPublicKey = 3; + optional uint64 expiryUnixTs = 4; // Epoch timestamp in milliseconds + optional bytes sig = 5; } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ec3c3d5217..d3239698e1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ kotlinVersion = "2.2.20" kryoVersion = "5.6.2" kspVersion = "2.3.0" legacySupportV13Version = "1.0.0" -libsessionUtilAndroidVersion = "1.0.9-13-g49bee28" +libsessionUtilAndroidVersion = "1.0.9-14-g2c548d4" media3ExoplayerVersion = "1.8.0" mockitoCoreVersion = "5.20.0" navVersion = "2.9.5"