Merge pull request #462 from hjubb/config_contacts_sync_message

Config contacts sync message
This commit is contained in:
Niels Andriesse 2021-02-25 14:45:55 +11:00 committed by GitHub
commit f2208f40b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 1382 additions and 52 deletions

View File

@ -16,6 +16,7 @@ import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
import org.thoughtcrime.securesms.loki.utilities.ContactUtilities
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
import java.util.* import java.util.*
@ -28,7 +29,12 @@ object MultiDeviceProtocol {
val lastSyncTime = TextSecurePreferences.getLastConfigurationSyncTime(context) val lastSyncTime = TextSecurePreferences.getLastConfigurationSyncTime(context)
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
if (now - lastSyncTime < 2 * 24 * 60 * 60 * 1000) return if (now - lastSyncTime < 2 * 24 * 60 * 60 * 1000) return
val configurationMessage = ConfigurationMessage.getCurrent() val contacts = ContactUtilities.getAllContacts(context).filter { recipient ->
!recipient.isBlocked && !recipient.name.isNullOrEmpty() && !recipient.isLocalNumber && recipient.address.serialize().isNotEmpty()
}.map { recipient ->
ConfigurationMessage.Contact(recipient.address.serialize(), recipient.name!!, recipient.profileAvatar, recipient.profileKey)
}
val configurationMessage = ConfigurationMessage.getCurrent(contacts)
val serializedMessage = configurationMessage.toProto()!!.toByteArray() val serializedMessage = configurationMessage.toProto()!!.toByteArray()
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender() val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
val address = SignalServiceAddress(userPublicKey) val address = SignalServiceAddress(userPublicKey)
@ -47,7 +53,12 @@ object MultiDeviceProtocol {
// TODO: refactor this to use new message sending job // TODO: refactor this to use new message sending job
fun forceSyncConfigurationNowIfNeeded(context: Context) { fun forceSyncConfigurationNowIfNeeded(context: Context) {
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return
val configurationMessage = ConfigurationMessage.getCurrent() val contacts = ContactUtilities.getAllContacts(context).filter { recipient ->
!recipient.isGroupRecipient && !recipient.isBlocked && !recipient.name.isNullOrEmpty() && !recipient.isLocalNumber && recipient.address.serialize().isNotEmpty()
}.map { recipient ->
ConfigurationMessage.Contact(recipient.address.serialize(), recipient.name!!, recipient.profileAvatar, recipient.profileKey)
}
val configurationMessage = ConfigurationMessage.getCurrent(contacts)
val serializedMessage = configurationMessage.toProto()!!.toByteArray() val serializedMessage = configurationMessage.toProto()!!.toByteArray()
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender() val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
val address = SignalServiceAddress(userPublicKey) val address = SignalServiceAddress(userPublicKey)

View File

@ -12,12 +12,12 @@ object ContactUtilities {
val cursor = threadDatabase.conversationList val cursor = threadDatabase.conversationList
val result = mutableSetOf<Recipient>() val result = mutableSetOf<Recipient>()
threadDatabase.readerFor(cursor).use { reader -> threadDatabase.readerFor(cursor).use { reader ->
while (reader.next != null) { while (reader.next != null) {
val thread = reader.current val thread = reader.current
val recipient = thread.recipient val recipient = thread.recipient
result.add(recipient) result.add(recipient)
}
} }
return result
} }
return result
}
} }

View File

@ -14,7 +14,7 @@ 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(val closedGroups: List<ClosedGroup>, val openGroups: List<String>, val displayName: String, val profilePicture: String?, val profileKey: ByteArray): ControlMessage() { class ConfigurationMessage(val closedGroups: List<ClosedGroup>, val openGroups: List<String>, val contacts: List<Contact>, val displayName: String, val profilePicture: String?, val profileKey: ByteArray): ControlMessage() {
class ClosedGroup(val publicKey: String, val name: String, val encryptionKeyPair: ECKeyPair, val members: List<String>, val admins: List<String>) { class ClosedGroup(val publicKey: String, val name: String, val encryptionKeyPair: ECKeyPair, val members: List<String>, val admins: List<String>) {
val isValid: Boolean get() = members.isNotEmpty() && admins.isNotEmpty() val isValid: Boolean get() = members.isNotEmpty() && admins.isNotEmpty()
@ -51,12 +51,39 @@ class ConfigurationMessage(val closedGroups: List<ClosedGroup>, val openGroups:
} }
} }
class Contact(val publicKey: String, val name: String, val profilePicture: String?, val profileKey: ByteArray?) {
companion object {
fun fromProto(proto: SignalServiceProtos.ConfigurationMessage.Contact): Contact? {
if (!proto.hasName() || !proto.hasProfileKey()) return null
val publicKey = proto.publicKey.toByteArray().toHexString()
val name = proto.name
val profilePicture = if (proto.hasProfilePicture()) proto.profilePicture else null
val profileKey = if (proto.hasProfileKey()) proto.profileKey.toByteArray() else null
return Contact(publicKey,name,profilePicture,profileKey)
}
}
fun toProto(): SignalServiceProtos.ConfigurationMessage.Contact? {
val result = SignalServiceProtos.ConfigurationMessage.Contact.newBuilder()
result.name = this.name
result.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(publicKey))
if (!this.profilePicture.isNullOrEmpty()) {
result.profilePicture = this.profilePicture
}
if (this.profileKey != null) {
result.profileKey = ByteString.copyFrom(this.profileKey)
}
return result.build()
}
}
override val ttl: Long = 4 * 24 * 60 * 60 * 1000 override val ttl: Long = 4 * 24 * 60 * 60 * 1000
override val isSelfSendValid: Boolean = true override val isSelfSendValid: Boolean = true
companion object { companion object {
fun getCurrent(): ConfigurationMessage { fun getCurrent(contacts: List<Contact>): ConfigurationMessage {
val closedGroups = mutableListOf<ClosedGroup>() val closedGroups = mutableListOf<ClosedGroup>()
val openGroups = mutableListOf<String>() val openGroups = mutableListOf<String>()
val sharedConfig = MessagingConfiguration.shared val sharedConfig = MessagingConfiguration.shared
@ -69,8 +96,7 @@ class ConfigurationMessage(val closedGroups: List<ClosedGroup>, val openGroups:
for (groupRecord in groups) { for (groupRecord in groups) {
if (groupRecord.isClosedGroup) { if (groupRecord.isClosedGroup) {
if (!groupRecord.members.contains(Address.fromSerialized(storage.getUserPublicKey()!!))) continue if (!groupRecord.members.contains(Address.fromSerialized(storage.getUserPublicKey()!!))) continue
val groupPublicKey = GroupUtil.getDecodedGroupIDAsData(groupRecord.encodedId).toHexString() val groupPublicKey = GroupUtil.doubleDecodeGroupID(groupRecord.encodedId).toHexString()
if (!storage.isClosedGroup(groupPublicKey)) continue
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, groupRecord.title, encryptionKeyPair, groupRecord.members.map { it.serialize() }, groupRecord.admins.map { it.serialize() })
closedGroups.add(closedGroup) closedGroups.add(closedGroup)
@ -82,7 +108,7 @@ class ConfigurationMessage(val closedGroups: List<ClosedGroup>, val openGroups:
} }
} }
return ConfigurationMessage(closedGroups, openGroups, displayName, profilePicture, profileKey) return ConfigurationMessage(closedGroups, openGroups, contacts, displayName, profilePicture, profileKey)
} }
fun fromProto(proto: SignalServiceProtos.Content): ConfigurationMessage? { fun fromProto(proto: SignalServiceProtos.Content): ConfigurationMessage? {
@ -93,7 +119,7 @@ class ConfigurationMessage(val closedGroups: List<ClosedGroup>, val openGroups:
val displayName = configurationProto.displayName val displayName = configurationProto.displayName
val profilePicture = configurationProto.profilePicture val profilePicture = configurationProto.profilePicture
val profileKey = configurationProto.profileKey val profileKey = configurationProto.profileKey
return ConfigurationMessage(closedGroups, openGroups, displayName, profilePicture, profileKey.toByteArray()) return ConfigurationMessage(closedGroups, openGroups, listOf(), displayName, profilePicture, profileKey.toByteArray())
} }
} }
@ -101,8 +127,11 @@ class ConfigurationMessage(val closedGroups: List<ClosedGroup>, val openGroups:
val configurationProto = SignalServiceProtos.ConfigurationMessage.newBuilder() val configurationProto = SignalServiceProtos.ConfigurationMessage.newBuilder()
configurationProto.addAllClosedGroups(closedGroups.mapNotNull { it.toProto() }) configurationProto.addAllClosedGroups(closedGroups.mapNotNull { it.toProto() })
configurationProto.addAllOpenGroups(openGroups) configurationProto.addAllOpenGroups(openGroups)
configurationProto.addAllContacts(this.contacts.mapNotNull { it.toProto() })
configurationProto.displayName = displayName configurationProto.displayName = displayName
configurationProto.profilePicture = profilePicture.orEmpty() if (!profilePicture.isNullOrEmpty()) {
configurationProto.profilePicture = profilePicture
}
configurationProto.profileKey = ByteString.copyFrom(profileKey) configurationProto.profileKey = ByteString.copyFrom(profileKey)
val contentProto = SignalServiceProtos.Content.newBuilder() val contentProto = SignalServiceProtos.Content.newBuilder()
contentProto.configurationMessage = configurationProto.build() contentProto.configurationMessage = configurationProto.build()

View File

@ -223,11 +223,21 @@ message ConfigurationMessage {
repeated bytes admins = 5; repeated bytes admins = 5;
} }
message Contact {
// @required
required bytes publicKey = 1;
// @required
required string name = 2;
optional string profilePicture = 3;
optional bytes profileKey = 4;
}
repeated ClosedGroup closedGroups = 1; repeated ClosedGroup closedGroups = 1;
repeated string openGroups = 2; repeated string openGroups = 2;
optional string displayName = 3; optional string displayName = 3;
optional string profilePicture = 4; optional string profilePicture = 4;
optional bytes profileKey = 5; optional bytes profileKey = 5;
repeated Contact contacts = 6;
} }
message ReceiptMessage { message ReceiptMessage {