Minor message type refactoring

This commit is contained in:
nielsandriesse 2021-05-12 14:01:57 +10:00
parent 11a89c0a76
commit fa5edcefd5
13 changed files with 204 additions and 210 deletions

View File

@ -7,9 +7,6 @@ import org.session.libsession.messaging.threads.Address
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString
typealias OpenGroupModel = OpenGroup
typealias OpenGroupV2Model = OpenGroupV2
sealed class Destination { sealed class Destination {
class Contact(var publicKey: String) : Destination() { class Contact(var publicKey: String) : Destination() {
@ -21,11 +18,12 @@ sealed class Destination {
class OpenGroup(var channel: Long, var server: String) : Destination() { class OpenGroup(var channel: Long, var server: String) : Destination() {
internal constructor(): this(0, "") internal constructor(): this(0, "")
} }
class OpenGroupV2(var room: String, var server: String): Destination() { class OpenGroupV2(var room: String, var server: String) : Destination() {
internal constructor(): this("", "") internal constructor(): this("", "")
} }
companion object { companion object {
fun from(address: Address): Destination { fun from(address: Address): Destination {
return when { return when {
address.isContact -> { address.isContact -> {
@ -39,10 +37,12 @@ sealed class Destination {
address.isOpenGroup -> { address.isOpenGroup -> {
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val threadID = storage.getThreadID(address.contactIdentifier())!! val threadID = storage.getThreadID(address.contactIdentifier())!!
when (val openGroup = storage.getOpenGroup(threadID) ?: storage.getV2OpenGroup(threadID)) { when (val openGroup = storage.getV2OpenGroup(threadID) ?: storage.getOpenGroup(threadID)) {
is OpenGroupModel -> OpenGroup(openGroup.channel, openGroup.server) is org.session.libsession.messaging.open_groups.OpenGroup
is OpenGroupV2Model -> OpenGroupV2(openGroup.room, openGroup.server) -> Destination.OpenGroup(openGroup.channel, openGroup.server)
else -> throw Exception("Invalid OpenGroup $openGroup") is org.session.libsession.messaging.open_groups.OpenGroupV2
-> Destination.OpenGroupV2(openGroup.room, openGroup.server)
else -> throw Exception("Missing open group for thread with ID: $threadID.")
} }
} }
else -> { else -> {

View File

@ -18,12 +18,10 @@ abstract class Message {
open val isSelfSendValid: Boolean = false open val isSelfSendValid: Boolean = false
open fun isValid(): Boolean { open fun isValid(): Boolean {
sentTimestamp?.let { val sentTimestamp = sentTimestamp
if (it <= 0) return false if (sentTimestamp != null && sentTimestamp <= 0) { return false }
} val receivedTimestamp = receivedTimestamp
receivedTimestamp?.let { if (receivedTimestamp != null && receivedTimestamp <= 0) { return false }
if (it <= 0) return false
}
return sender != null && recipient != null return sender != null && recipient != null
} }

View File

@ -16,9 +16,10 @@ import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
class ClosedGroupControlMessage() : ControlMessage() { class ClosedGroupControlMessage() : ControlMessage() {
var kind: Kind? = null
override val ttl: Long = run { override val ttl: Long get() {
when (kind) { return when (kind) {
is Kind.EncryptionKeyPair -> 14 * 24 * 60 * 60 * 1000 is Kind.EncryptionKeyPair -> 14 * 24 * 60 * 60 * 1000
else -> 14 * 24 * 60 * 60 * 1000 else -> 14 * 24 * 60 * 60 * 1000
} }
@ -26,31 +27,46 @@ class ClosedGroupControlMessage() : ControlMessage() {
override val isSelfSendValid: Boolean = true override val isSelfSendValid: Boolean = true
var kind: Kind? = null override fun isValid(): Boolean {
val kind = kind
if (!super.isValid() || kind == null) return false
return when (kind) {
is Kind.New -> {
!kind.publicKey.isEmpty && kind.name.isNotEmpty() && kind.encryptionKeyPair?.publicKey != null
&& kind.encryptionKeyPair?.privateKey != null && kind.members.isNotEmpty() && kind.admins.isNotEmpty()
}
is Kind.EncryptionKeyPair -> true
is Kind.NameChange -> kind.name.isNotEmpty()
is Kind.MembersAdded -> kind.members.isNotEmpty()
is Kind.MembersRemoved -> kind.members.isNotEmpty()
is Kind.MemberLeft -> true
}
}
sealed class Kind { sealed class Kind {
class New(var publicKey: ByteString, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List<ByteString>, var admins: List<ByteString>) : Kind() { class New(var publicKey: ByteString, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List<ByteString>, var admins: List<ByteString>) : Kind() {
internal constructor(): this(ByteString.EMPTY, "", null, listOf(), listOf()) internal constructor() : this(ByteString.EMPTY, "", null, listOf(), listOf())
} }
/// An encryption key pair encrypted for each member individually. /** An encryption key pair encrypted for each member individually.
/// *
/// - Note: `publicKey` is only set when an encryption key pair is sent in a one-to-one context (i.e. not in a group). * **Note:** `publicKey` is only set when an encryption key pair is sent in a one-to-one context (i.e. not in a group).
*/
class EncryptionKeyPair(var publicKey: ByteString?, var wrappers: Collection<KeyPairWrapper>) : Kind() { class EncryptionKeyPair(var publicKey: ByteString?, var wrappers: Collection<KeyPairWrapper>) : Kind() {
internal constructor(): this(null, listOf()) internal constructor() : this(null, listOf())
} }
class NameChange(var name: String) : Kind() { class NameChange(var name: String) : Kind() {
internal constructor(): this("") internal constructor() : this("")
} }
class MembersAdded(var members: List<ByteString>) : Kind() { class MembersAdded(var members: List<ByteString>) : Kind() {
internal constructor(): this(listOf()) internal constructor() : this(listOf())
} }
class MembersRemoved(var members: List<ByteString>) : Kind() { class MembersRemoved(var members: List<ByteString>) : Kind() {
internal constructor(): this(listOf()) internal constructor() : this(listOf())
} }
class MemberLeft() : Kind() class MemberLeft() : Kind()
val description: String = val description: String =
when(this) { when (this) {
is New -> "new" is New -> "new"
is EncryptionKeyPair -> "encryptionKeyPair" is EncryptionKeyPair -> "encryptionKeyPair"
is NameChange -> "nameChange" is NameChange -> "nameChange"
@ -65,18 +81,19 @@ class ClosedGroupControlMessage() : ControlMessage() {
fun fromProto(proto: SignalServiceProtos.Content): ClosedGroupControlMessage? { fun fromProto(proto: SignalServiceProtos.Content): ClosedGroupControlMessage? {
if (!proto.hasDataMessage() || !proto.dataMessage.hasClosedGroupControlMessage()) return null if (!proto.hasDataMessage() || !proto.dataMessage.hasClosedGroupControlMessage()) return null
val closedGroupControlMessageProto = proto.dataMessage?.closedGroupControlMessage!! val closedGroupControlMessageProto = proto.dataMessage!!.closedGroupControlMessage!!
val kind: Kind val kind: Kind
when (closedGroupControlMessageProto.type) { when (closedGroupControlMessageProto.type!!) {
DataMessage.ClosedGroupControlMessage.Type.NEW -> { DataMessage.ClosedGroupControlMessage.Type.NEW -> {
val publicKey = closedGroupControlMessageProto.publicKey ?: return null val publicKey = closedGroupControlMessageProto.publicKey ?: return null
val name = closedGroupControlMessageProto.name ?: return null val name = closedGroupControlMessageProto.name ?: return null
val encryptionKeyPairAsProto = closedGroupControlMessageProto.encryptionKeyPair ?: return null val encryptionKeyPairAsProto = closedGroupControlMessageProto.encryptionKeyPair ?: return null
try { try {
val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairAsProto.publicKey.toByteArray()), DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray())) val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairAsProto.publicKey.toByteArray()),
DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray()))
kind = Kind.New(publicKey, name, encryptionKeyPair, closedGroupControlMessageProto.membersList, closedGroupControlMessageProto.adminsList) kind = Kind.New(publicKey, name, encryptionKeyPair, closedGroupControlMessageProto.membersList, closedGroupControlMessageProto.adminsList)
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, "Couldn't parse key pair") Log.w(TAG, "Couldn't parse key pair from proto: $encryptionKeyPairAsProto.")
return null return null
} }
} }
@ -107,26 +124,10 @@ class ClosedGroupControlMessage() : ControlMessage() {
this.kind = kind this.kind = kind
} }
override fun isValid(): Boolean {
if (!super.isValid()) return false
val kind = kind ?: return false
return when(kind) {
is Kind.New -> {
!kind.publicKey.isEmpty && kind.name.isNotEmpty() && kind.encryptionKeyPair!!.publicKey != null
&& kind.encryptionKeyPair!!.privateKey != null && kind.members.isNotEmpty() && kind.admins.isNotEmpty()
}
is Kind.EncryptionKeyPair -> true
is Kind.NameChange -> kind.name.isNotEmpty()
is Kind.MembersAdded -> kind.members.isNotEmpty()
is Kind.MembersRemoved -> kind.members.isNotEmpty()
is Kind.MemberLeft -> true
}
}
override fun toProto(): SignalServiceProtos.Content? { override fun toProto(): SignalServiceProtos.Content? {
val kind = kind val kind = kind
if (kind == null) { if (kind == null) {
Log.w(TAG, "Couldn't construct closed group update proto from: $this") Log.w(TAG, "Couldn't construct closed group control message proto from: $this.")
return null return null
} }
try { try {
@ -176,7 +177,7 @@ class ClosedGroupControlMessage() : ControlMessage() {
contentProto.dataMessage = dataMessageProto.build() contentProto.dataMessage = dataMessageProto.build()
return contentProto.build() return contentProto.build()
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, "Couldn't construct closed group update proto from: $this") Log.w(TAG, "Couldn't construct closed group control message proto from: $this.")
return null return null
} }
} }
@ -188,6 +189,7 @@ class ClosedGroupControlMessage() : ControlMessage() {
} }
companion object { companion object {
fun fromProto(proto: DataMessage.ClosedGroupControlMessage.KeyPairWrapper): KeyPairWrapper { fun fromProto(proto: DataMessage.ClosedGroupControlMessage.KeyPairWrapper): KeyPairWrapper {
return KeyPairWrapper(proto.publicKey.toByteArray().toHexString(), proto.encryptedKeyPair) return KeyPairWrapper(proto.publicKey.toByteArray().toHexString(), proto.encryptedKeyPair)
} }
@ -199,7 +201,6 @@ class ClosedGroupControlMessage() : ControlMessage() {
val result = DataMessage.ClosedGroupControlMessage.KeyPairWrapper.newBuilder() val result = DataMessage.ClosedGroupControlMessage.KeyPairWrapper.newBuilder()
result.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(publicKey)) result.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(publicKey))
result.encryptedKeyPair = encryptedKeyPair result.encryptedKeyPair = encryptedKeyPair
return try { return try {
result.build() result.build()
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -14,12 +14,15 @@ import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups: List<String>, var contacts: List<Contact>, var displayName: String, var profilePicture: String?, var profileKey: ByteArray): ControlMessage() { class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups: List<String>, var contacts: List<Contact>,
var displayName: String, var profilePicture: String?, var profileKey: ByteArray) : ControlMessage() {
override val isSelfSendValid: Boolean = true
class ClosedGroup(var publicKey: String, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List<String>, var admins: List<String>) { class ClosedGroup(var publicKey: String, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List<String>, var admins: List<String>) {
val isValid: Boolean get() = members.isNotEmpty() && admins.isNotEmpty() val isValid: Boolean get() = members.isNotEmpty() && admins.isNotEmpty()
internal constructor(): this("", "", null, listOf(), listOf()) internal constructor() : this("", "", null, listOf(), listOf())
override fun toString(): String { override fun toString(): String {
return name return name
@ -56,7 +59,7 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
class Contact(var publicKey: String, var name: String, var profilePicture: String?, var profileKey: ByteArray?) { class Contact(var publicKey: String, var name: String, var profilePicture: String?, var profileKey: ByteArray?) {
internal constructor(): this("", "", null, null) internal constructor() : this("", "", null, null)
companion object { companion object {
@ -66,8 +69,7 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
val name = proto.name val name = proto.name
val profilePicture = if (proto.hasProfilePicture()) proto.profilePicture else null val profilePicture = if (proto.hasProfilePicture()) proto.profilePicture else null
val profileKey = if (proto.hasProfileKey()) proto.profileKey.toByteArray() else null val profileKey = if (proto.hasProfileKey()) proto.profileKey.toByteArray() else null
return Contact(publicKey, name, profilePicture, profileKey)
return Contact(publicKey,name,profilePicture,profileKey)
} }
} }
@ -79,18 +81,18 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
} catch (e: Exception) { } catch (e: Exception) {
return null return null
} }
if (!this.profilePicture.isNullOrEmpty()) { val profilePicture = profilePicture
result.profilePicture = this.profilePicture if (!profilePicture.isNullOrEmpty()) {
result.profilePicture = profilePicture
} }
if (this.profileKey != null) { val profileKey = profileKey
result.profileKey = ByteString.copyFrom(this.profileKey) if (profileKey != null) {
result.profileKey = ByteString.copyFrom(profileKey)
} }
return result.build() return result.build()
} }
} }
override val isSelfSendValid: Boolean = true
companion object { companion object {
fun getCurrent(contacts: List<Contact>): ConfigurationMessage? { fun getCurrent(contacts: List<Contact>): ConfigurationMessage? {
@ -103,24 +105,22 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
val profilePicture = TextSecurePreferences.getProfilePictureURL(context) val profilePicture = TextSecurePreferences.getProfilePictureURL(context)
val profileKey = ProfileKeyUtil.getProfileKey(context) val profileKey = ProfileKeyUtil.getProfileKey(context)
val groups = storage.getAllGroups() val groups = storage.getAllGroups()
for (groupRecord in groups) { for (group in groups) {
if (groupRecord.isClosedGroup) { if (group.isClosedGroup) {
if (!groupRecord.members.contains(Address.fromSerialized(storage.getUserPublicKey()!!))) continue if (!group.members.contains(Address.fromSerialized(storage.getUserPublicKey()!!))) continue
val groupPublicKey = GroupUtil.doubleDecodeGroupID(groupRecord.encodedId).toHexString() val groupPublicKey = GroupUtil.doubleDecodeGroupID(group.encodedId).toHexString()
val encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: continue val encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: continue
val closedGroup = ClosedGroup(groupPublicKey, groupRecord.title, encryptionKeyPair, groupRecord.members.map { it.serialize() }, groupRecord.admins.map { it.serialize() }) val closedGroup = ClosedGroup(groupPublicKey, group.title, encryptionKeyPair, group.members.map { it.serialize() }, group.admins.map { it.serialize() })
closedGroups.add(closedGroup) closedGroups.add(closedGroup)
} }
if (groupRecord.isOpenGroup) { if (group.isOpenGroup) {
val threadID = storage.getThreadID(groupRecord.encodedId) ?: continue val threadID = storage.getThreadID(group.encodedId) ?: continue
val openGroup = storage.getOpenGroup(threadID) val openGroup = storage.getOpenGroup(threadID)
val openGroupV2 = storage.getV2OpenGroup(threadID) val openGroupV2 = storage.getV2OpenGroup(threadID)
val shareUrl = openGroup?.server ?: openGroupV2?.toJoinUrl() ?: continue val shareUrl = openGroup?.server ?: openGroupV2?.toJoinUrl() ?: continue
openGroups.add(shareUrl) openGroups.add(shareUrl)
} }
} }
return ConfigurationMessage(closedGroups, openGroups, contacts, displayName, profilePicture, profileKey) return ConfigurationMessage(closedGroups, openGroups, contacts, displayName, profilePicture, profileKey)
} }
@ -145,6 +145,7 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
configurationProto.addAllOpenGroups(openGroups) configurationProto.addAllOpenGroups(openGroups)
configurationProto.addAllContacts(this.contacts.mapNotNull { it.toProto() }) configurationProto.addAllContacts(this.contacts.mapNotNull { it.toProto() })
configurationProto.displayName = displayName configurationProto.displayName = displayName
val profilePicture = profilePicture
if (!profilePicture.isNullOrEmpty()) { if (!profilePicture.isNullOrEmpty()) {
configurationProto.profilePicture = profilePicture configurationProto.profilePicture = profilePicture
} }
@ -157,10 +158,10 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
override fun toString(): String { override fun toString(): String {
return """ return """
ConfigurationMessage( ConfigurationMessage(
closedGroups: ${(closedGroups)} closedGroups: ${(closedGroups)},
openGroups: ${(openGroups)} openGroups: ${(openGroups)},
displayName: $displayName displayName: $displayName,
profilePicture: $profilePicture profilePicture: $profilePicture,
profileKey: $profileKey profileKey: $profileKey
) )
""".trimIndent() """.trimIndent()

View File

@ -2,5 +2,4 @@ package org.session.libsession.messaging.messages.control
import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.Message
abstract class ControlMessage : Message() { abstract class ControlMessage : Message()
}

View File

@ -3,7 +3,7 @@ package org.session.libsession.messaging.messages.control
import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
class DataExtractionNotification(): ControlMessage() { class DataExtractionNotification() : ControlMessage() {
var kind: Kind? = null var kind: Kind? = null
sealed class Kind { sealed class Kind {
@ -39,8 +39,8 @@ class DataExtractionNotification(): ControlMessage() {
} }
override fun isValid(): Boolean { override fun isValid(): Boolean {
if (!super.isValid()) return false val kind = kind
val kind = kind ?: return false if (!super.isValid() || kind == null) return false
return when(kind) { return when(kind) {
is Kind.Screenshot -> true is Kind.Screenshot -> true
is Kind.MediaSaved -> kind.timestamp > 0 is Kind.MediaSaved -> kind.timestamp > 0

View File

@ -6,13 +6,20 @@ import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos
class ExpirationTimerUpdate() : ControlMessage() { class ExpirationTimerUpdate() : ControlMessage() {
/// In the case of a sync message, the public key of the person the message was targeted at. /** In the case of a sync message, the public key of the person the message was targeted at.
/// - Note: `nil` if this isn't a sync message. *
* **Note:** `nil` if this isn't a sync message.
*/
var syncTarget: String? = null var syncTarget: String? = null
var duration: Int? = 0 var duration: Int? = 0
override val isSelfSendValid: Boolean = true override val isSelfSendValid: Boolean = true
override fun isValid(): Boolean {
if (!super.isValid()) return false
return duration != null
}
companion object { companion object {
const val TAG = "ExpirationTimerUpdate" const val TAG = "ExpirationTimerUpdate"
@ -26,21 +33,11 @@ class ExpirationTimerUpdate() : ControlMessage() {
} }
} }
internal constructor(syncTarget: String?, duration: Int) : this() { internal constructor(syncTarget: String? = null, duration: Int) : this() {
this.syncTarget = syncTarget this.syncTarget = syncTarget
this.duration = duration this.duration = duration
} }
internal constructor(duration: Int) : this() {
this.syncTarget = null
this.duration = duration
}
override fun isValid(): Boolean {
if (!super.isValid()) return false
return duration != null
}
override fun toProto(): SignalServiceProtos.Content? { override fun toProto(): SignalServiceProtos.Content? {
val duration = duration val duration = duration
if (duration == null) { if (duration == null) {

View File

@ -6,6 +6,13 @@ import org.session.libsignal.utilities.logging.Log
class ReadReceipt() : ControlMessage() { class ReadReceipt() : ControlMessage() {
var timestamps: List<Long>? = null var timestamps: List<Long>? = null
override fun isValid(): Boolean {
if (!super.isValid()) return false
val timestamps = timestamps ?: return false
if (timestamps.isNotEmpty()) { return true }
return false
}
companion object { companion object {
const val TAG = "ReadReceipt" const val TAG = "ReadReceipt"
@ -22,13 +29,6 @@ class ReadReceipt() : ControlMessage() {
this.timestamps = timestamps this.timestamps = timestamps
} }
override fun isValid(): Boolean {
if (!super.isValid()) return false
val timestamps = timestamps ?: return false
if (timestamps.isNotEmpty()) { return true }
return false
}
override fun toProto(): SignalServiceProtos.Content? { override fun toProto(): SignalServiceProtos.Content? {
val timestamps = timestamps val timestamps = timestamps
if (timestamps == null) { if (timestamps == null) {

View File

@ -4,9 +4,15 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
class TypingIndicator() : ControlMessage() { class TypingIndicator() : ControlMessage() {
override val ttl: Long = 30 * 1000
var kind: Kind? = null var kind: Kind? = null
override val ttl: Long = 20 * 1000
override fun isValid(): Boolean {
if (!super.isValid()) return false
return kind != null
}
companion object { companion object {
const val TAG = "TypingIndicator" const val TAG = "TypingIndicator"
@ -41,11 +47,6 @@ class TypingIndicator() : ControlMessage() {
this.kind = kind this.kind = kind
} }
override fun isValid(): Boolean {
if (!super.isValid()) return false
return kind != null
}
override fun toProto(): SignalServiceProtos.Content? { override fun toProto(): SignalServiceProtos.Content? {
val timestamp = sentTimestamp val timestamp = sentTimestamp
val kind = kind val kind = kind

View File

@ -10,6 +10,10 @@ class LinkPreview() {
var url: String? = null var url: String? = null
var attachmentID: Long? = 0 var attachmentID: Long? = 0
fun isValid(): Boolean {
return (title != null && url != null && attachmentID != null)
}
companion object { companion object {
const val TAG = "LinkPreview" const val TAG = "LinkPreview"
@ -20,11 +24,8 @@ class LinkPreview() {
} }
fun from(signalLinkPreview: SignalLinkPreiview?): LinkPreview? { fun from(signalLinkPreview: SignalLinkPreiview?): LinkPreview? {
return if (signalLinkPreview == null) { if (signalLinkPreview == null) { return null }
null return LinkPreview(signalLinkPreview.title, signalLinkPreview.url, signalLinkPreview.attachmentId?.rowId)
} else {
LinkPreview(signalLinkPreview.title, signalLinkPreview.url, signalLinkPreview.attachmentId?.rowId)
}
} }
} }
@ -34,10 +35,6 @@ class LinkPreview() {
this.attachmentID = attachmentID this.attachmentID = attachmentID
} }
fun isValid(): Boolean {
return (title != null && url != null && attachmentID != null)
}
fun toProto(): SignalServiceProtos.DataMessage.Preview? { fun toProto(): SignalServiceProtos.DataMessage.Preview? {
val url = url val url = url
if (url == null) { if (url == null) {
@ -46,10 +43,10 @@ class LinkPreview() {
} }
val linkPreviewProto = SignalServiceProtos.DataMessage.Preview.newBuilder() val linkPreviewProto = SignalServiceProtos.DataMessage.Preview.newBuilder()
linkPreviewProto.url = url linkPreviewProto.url = url
title?.let { linkPreviewProto.title = title } title?.let { linkPreviewProto.title = it }
val attachmentID = attachmentID val database = MessagingModuleConfiguration.shared.messageDataProvider
attachmentID?.let { attachmentID?.let {
MessagingModuleConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(attachmentID)?.let { database.getSignalAttachmentPointer(it)?.let {
val attachmentProto = Attachment.createAttachmentPointer(it) val attachmentProto = Attachment.createAttachmentPointer(it)
linkPreviewProto.image = attachmentProto linkPreviewProto.image = attachmentProto
} }

View File

@ -17,14 +17,13 @@ class Profile() {
val displayName = profileProto.displayName ?: return null val displayName = profileProto.displayName ?: return null
val profileKey = proto.profileKey val profileKey = proto.profileKey
val profilePictureURL = profileProto.profilePicture val profilePictureURL = profileProto.profilePicture
profileKey?.let { if (profileKey != null && profilePictureURL != null) {
profilePictureURL?.let { return Profile(displayName, profileKey.toByteArray(), profilePictureURL)
return Profile(displayName = displayName, profileKey = profileKey.toByteArray(), profilePictureURL = profilePictureURL) } else {
}
}
return Profile(displayName) return Profile(displayName)
} }
} }
}
internal constructor(displayName: String, profileKey: ByteArray? = null, profilePictureURL: String? = null) : this() { internal constructor(displayName: String, profileKey: ByteArray? = null, profilePictureURL: String? = null) : this() {
this.displayName = displayName this.displayName = displayName
@ -35,16 +34,14 @@ class Profile() {
fun toProto(): SignalServiceProtos.DataMessage? { fun toProto(): SignalServiceProtos.DataMessage? {
val displayName = displayName val displayName = displayName
if (displayName == null) { if (displayName == null) {
Log.w(TAG, "Couldn't construct link preview proto from: $this") Log.w(TAG, "Couldn't construct profile proto from: $this")
return null return null
} }
val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder() val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder()
val profileProto = SignalServiceProtos.DataMessage.LokiProfile.newBuilder() val profileProto = SignalServiceProtos.DataMessage.LokiProfile.newBuilder()
profileProto.displayName = displayName profileProto.displayName = displayName
val profileKey = profileKey profileKey?.let { dataMessageProto.profileKey = ByteString.copyFrom(it) }
profileKey?.let { dataMessageProto.profileKey = ByteString.copyFrom(profileKey) } profilePictureURL?.let { profileProto.profilePicture = it }
val profilePictureURL = profilePictureURL
profilePictureURL?.let { profileProto.profilePicture = profilePictureURL }
// Build // Build
try { try {
dataMessageProto.profile = profileProto.build() dataMessageProto.profile = profileProto.build()

View File

@ -13,6 +13,10 @@ class Quote() {
var text: String? = null var text: String? = null
var attachmentID: Long? = null var attachmentID: Long? = null
fun isValid(): Boolean {
return (timestamp != null && publicKey != null)
}
companion object { companion object {
const val TAG = "Quote" const val TAG = "Quote"
@ -24,12 +28,9 @@ class Quote() {
} }
fun from(signalQuote: SignalQuote?): Quote? { fun from(signalQuote: SignalQuote?): Quote? {
return if (signalQuote == null) { if (signalQuote == null) { return null }
null
} else {
val attachmentID = (signalQuote.attachments?.firstOrNull() as? DatabaseAttachment)?.attachmentId?.rowId val attachmentID = (signalQuote.attachments?.firstOrNull() as? DatabaseAttachment)?.attachmentId?.rowId
Quote(signalQuote.id, signalQuote.author.serialize(), signalQuote.text, attachmentID) return Quote(signalQuote.id, signalQuote.author.serialize(), signalQuote.text, attachmentID)
}
} }
} }
@ -40,10 +41,6 @@ class Quote() {
this.attachmentID = attachmentID this.attachmentID = attachmentID
} }
fun isValid(): Boolean {
return (timestamp != null && publicKey != null)
}
fun toProto(): SignalServiceProtos.DataMessage.Quote? { fun toProto(): SignalServiceProtos.DataMessage.Quote? {
val timestamp = timestamp val timestamp = timestamp
val publicKey = publicKey val publicKey = publicKey
@ -54,7 +51,7 @@ class Quote() {
val quoteProto = SignalServiceProtos.DataMessage.Quote.newBuilder() val quoteProto = SignalServiceProtos.DataMessage.Quote.newBuilder()
quoteProto.id = timestamp quoteProto.id = timestamp
quoteProto.author = publicKey quoteProto.author = publicKey
text?.let { quoteProto.text = text } text?.let { quoteProto.text = it }
addAttachmentsIfNeeded(quoteProto) addAttachmentsIfNeeded(quoteProto)
// Build // Build
try { try {
@ -66,23 +63,23 @@ class Quote() {
} }
private fun addAttachmentsIfNeeded(quoteProto: SignalServiceProtos.DataMessage.Quote.Builder) { private fun addAttachmentsIfNeeded(quoteProto: SignalServiceProtos.DataMessage.Quote.Builder) {
if (attachmentID == null) return val attachmentID = attachmentID ?: return
val attachment = MessagingModuleConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(attachmentID!!) val database = MessagingModuleConfiguration.shared.messageDataProvider
if (attachment == null) { val pointer = database.getSignalAttachmentPointer(attachmentID)
if (pointer == null) {
Log.w(TAG, "Ignoring invalid attachment for quoted message.") Log.w(TAG, "Ignoring invalid attachment for quoted message.")
return return
} }
if (attachment.url.isNullOrEmpty()) { if (pointer.url.isNullOrEmpty()) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
//TODO equivalent to iOS's preconditionFailure Log.w(TAG,"Sending a message before all associated attachments have been uploaded.")
Log.d(TAG,"Sending a message before all associated attachments have been uploaded.")
return return
} }
} }
val quotedAttachmentProto = SignalServiceProtos.DataMessage.Quote.QuotedAttachment.newBuilder() val quotedAttachmentProto = SignalServiceProtos.DataMessage.Quote.QuotedAttachment.newBuilder()
quotedAttachmentProto.contentType = attachment.contentType quotedAttachmentProto.contentType = pointer.contentType
if (attachment.fileName.isPresent) quotedAttachmentProto.fileName = attachment.fileName.get() if (pointer.fileName.isPresent) { quotedAttachmentProto.fileName = pointer.fileName.get() }
quotedAttachmentProto.thumbnail = Attachment.createAttachmentPointer(attachment) quotedAttachmentProto.thumbnail = Attachment.createAttachmentPointer(pointer)
try { try {
quoteProto.addAttachments(quotedAttachmentProto.build()) quoteProto.addAttachments(quotedAttachmentProto.build())
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -12,6 +12,10 @@ import org.session.libsignal.utilities.logging.Log
import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment
class VisibleMessage : Message() { class VisibleMessage : Message() {
/** In the case of a sync message, the public key of the person the message was targeted at.
*
* **Note:** `nil` if this isn't a sync message.
*/
var syncTarget: String? = null var syncTarget: String? = null
var text: String? = null var text: String? = null
val attachmentIDs: MutableList<Long> = mutableListOf() val attachmentIDs: MutableList<Long> = mutableListOf()
@ -21,46 +25,7 @@ class VisibleMessage : Message() {
override val isSelfSendValid: Boolean = true override val isSelfSendValid: Boolean = true
companion object { // region Validation
const val TAG = "VisibleMessage"
fun fromProto(proto: SignalServiceProtos.Content): VisibleMessage? {
val dataMessage = if (proto.hasDataMessage()) proto.dataMessage else return null
val result = VisibleMessage()
if (dataMessage.hasSyncTarget()) {
result.syncTarget = dataMessage.syncTarget
}
result.text = dataMessage.body
// Attachments are handled in MessageReceiver
val quoteProto = if (dataMessage.hasQuote()) dataMessage.quote else null
quoteProto?.let {
val quote = Quote.fromProto(quoteProto)
quote?.let { result.quote = quote }
}
val linkPreviewProto = dataMessage.previewList.firstOrNull()
linkPreviewProto?.let {
val linkPreview = LinkPreview.fromProto(linkPreviewProto)
linkPreview?.let { result.linkPreview = linkPreview }
}
// TODO Contact
val profile = Profile.fromProto(dataMessage)
profile?.let { result.profile = profile }
return result
}
}
fun addSignalAttachments(signalAttachments: List<SignalAttachment>) {
val attachmentIDs = signalAttachments.map {
val databaseAttachment = it as DatabaseAttachment
databaseAttachment.attachmentId.rowId
}
this.attachmentIDs.addAll(attachmentIDs)
}
fun isMediaMessage(): Boolean {
return attachmentIDs.isNotEmpty() || quote != null || linkPreview != null
}
override fun isValid(): Boolean { override fun isValid(): Boolean {
if (!super.isValid()) return false if (!super.isValid()) return false
if (attachmentIDs.isNotEmpty()) return true if (attachmentIDs.isNotEmpty()) return true
@ -68,56 +33,84 @@ class VisibleMessage : Message() {
if (text.isNotEmpty()) return true if (text.isNotEmpty()) return true
return false return false
} }
// endregion
// region Proto Conversion
companion object {
const val TAG = "VisibleMessage"
fun fromProto(proto: SignalServiceProtos.Content): VisibleMessage? {
val dataMessage = proto.dataMessage ?: return null
val result = VisibleMessage()
if (dataMessage.hasSyncTarget()) { result.syncTarget = dataMessage.syncTarget }
result.text = dataMessage.body
// Attachments are handled in MessageReceiver
val quoteProto = if (dataMessage.hasQuote()) dataMessage.quote else null
if (quoteProto != null) {
val quote = Quote.fromProto(quoteProto)
result.quote = quote
}
val linkPreviewProto = dataMessage.previewList.firstOrNull()
if (linkPreviewProto != null) {
val linkPreview = LinkPreview.fromProto(linkPreviewProto)
result.linkPreview = linkPreview
}
// TODO: Contact
val profile = Profile.fromProto(dataMessage)
if (profile != null) { result.profile = profile }
return result
}
}
override fun toProto(): SignalServiceProtos.Content? { override fun toProto(): SignalServiceProtos.Content? {
val proto = SignalServiceProtos.Content.newBuilder() val proto = SignalServiceProtos.Content.newBuilder()
val dataMessage: SignalServiceProtos.DataMessage.Builder val dataMessage: SignalServiceProtos.DataMessage.Builder
// Profile // Profile
val profile = profile val profileProto = profile?.let { it.toProto() }
val profileProto = profile?.toProto()
if (profileProto != null) { if (profileProto != null) {
dataMessage = profileProto.toBuilder() dataMessage = profileProto.toBuilder()
} else { } else {
dataMessage = SignalServiceProtos.DataMessage.newBuilder() dataMessage = SignalServiceProtos.DataMessage.newBuilder()
} }
// Text // Text
text?.let { dataMessage.body = text } if (text != null) { dataMessage.body = text }
// Quote // Quote
quote?.let { val quoteProto = quote?.let { it.toProto() }
val quoteProto = it.toProto() if (quoteProto != null) {
if (quoteProto != null) dataMessage.quote = quoteProto dataMessage.quote = quoteProto
} }
//Link preview // Link preview
linkPreview?.let { val linkPreviewProto = linkPreview?.let { it.toProto() }
val linkPreviewProto = it.toProto() if (linkPreviewProto != null) {
linkPreviewProto?.let {
dataMessage.addAllPreview(listOf(linkPreviewProto)) dataMessage.addAllPreview(listOf(linkPreviewProto))
} }
} // Attachments
//Attachments val database = MessagingModuleConfiguration.shared.messageDataProvider
val attachments = attachmentIDs.mapNotNull { MessagingModuleConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(it) } val attachments = attachmentIDs.mapNotNull { database.getSignalAttachmentPointer(it) }
if (!attachments.all { !it.url.isNullOrEmpty() }) { if (attachments.any { it.url.isNullOrEmpty() }) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
//TODO equivalent to iOS's preconditionFailure Log.w(TAG, "Sending a message before all associated attachments have been uploaded.")
Log.d(TAG, "Sending a message before all associated attachments have been uploaded.")
} }
} }
val attachmentPointers = attachments.mapNotNull { Attachment.createAttachmentPointer(it) } val pointers = attachments.mapNotNull { Attachment.createAttachmentPointer(it) }
dataMessage.addAllAttachments(attachmentPointers) dataMessage.addAllAttachments(pointers)
// TODO Contact // TODO: Contact
// Expiration timer // Expiration timer
// TODO: We * want * expiration timer updates to be explicit. But currently Android will disable the expiration timer for a conversation // TODO: We * want * expiration timer updates to be explicit. But currently Android will disable the expiration timer for a conversation
// if it receives a message without the current expiration timer value attached to it... // if it receives a message without the current expiration timer value attached to it...
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val context = MessagingModuleConfiguration.shared.context val context = MessagingModuleConfiguration.shared.context
val expiration = if (storage.isClosedGroup(recipient!!)) Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(recipient!!)), false).expireMessages val expiration = if (storage.isClosedGroup(recipient!!)) {
else Recipient.from(context, Address.fromSerialized(recipient!!), false).expireMessages Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(recipient!!)), false).expireMessages
} else {
Recipient.from(context, Address.fromSerialized(recipient!!), false).expireMessages
}
dataMessage.expireTimer = expiration dataMessage.expireTimer = expiration
// Group context // Group context
if (storage.isClosedGroup(recipient!!)) { if (storage.isClosedGroup(recipient!!)) {
try { try {
setGroupContext(dataMessage) setGroupContext(dataMessage)
} catch(e: Exception) { } catch (e: Exception) {
Log.w(TAG, "Couldn't construct visible message proto from: $this") Log.w(TAG, "Couldn't construct visible message proto from: $this")
return null return null
} }
@ -135,4 +128,17 @@ class VisibleMessage : Message() {
return null return null
} }
} }
// endregion
fun addSignalAttachments(signalAttachments: List<SignalAttachment>) {
val attachmentIDs = signalAttachments.map {
val databaseAttachment = it as DatabaseAttachment
databaseAttachment.attachmentId.rowId
}
this.attachmentIDs.addAll(attachmentIDs)
}
fun isMediaMessage(): Boolean {
return attachmentIDs.isNotEmpty() || quote != null || linkPreview != null
}
} }