mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
sync dev
This commit is contained in:
parent
1e93d4651c
commit
0a952bcb85
@ -30,7 +30,6 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.BindableConversationItem;
|
import org.thoughtcrime.securesms.BindableConversationItem;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationAdapter.HeaderViewHolder;
|
import org.thoughtcrime.securesms.conversation.ConversationAdapter.HeaderViewHolder;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
@ -16,6 +16,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import kotlinx.android.synthetic.main.activity_settings.*
|
import kotlinx.android.synthetic.main.activity_settings.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
|
import nl.komponents.kovenant.task
|
||||||
import nl.komponents.kovenant.ui.failUi
|
import nl.komponents.kovenant.ui.failUi
|
||||||
import nl.komponents.kovenant.ui.successUi
|
import nl.komponents.kovenant.ui.successUi
|
||||||
import org.session.libsignal.service.loki.utilities.toHexString
|
import org.session.libsignal.service.loki.utilities.toHexString
|
||||||
@ -239,6 +240,9 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
val members = this.members.map {
|
val members = this.members.map {
|
||||||
Recipient.from(this, Address.fromSerialized(it), false)
|
Recipient.from(this, Address.fromSerialized(it), false)
|
||||||
}.toSet()
|
}.toSet()
|
||||||
|
val originalMembers = this.originalMembers.map {
|
||||||
|
Recipient.from(this, Address.fromSerialized(it), false)
|
||||||
|
}.toSet()
|
||||||
|
|
||||||
val admins = members.toSet() //TODO For now, consider all the users to be admins.
|
val admins = members.toSet() //TODO For now, consider all the users to be admins.
|
||||||
|
|
||||||
@ -272,11 +276,25 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
if (isSSKBasedClosedGroup) {
|
if (isSSKBasedClosedGroup) {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
loaderContainer.fadeIn()
|
loaderContainer.fadeIn()
|
||||||
val promise: Promise<Unit, Exception>
|
val promise: Promise<Any, Exception> = if (!members.contains(Recipient.from(this, Address.fromSerialized(userPublicKey), false))) {
|
||||||
if (!members.contains(Recipient.from(this, Address.fromSerialized(userPublicKey), false))) {
|
ClosedGroupsProtocolV2.leave(this, groupPublicKey!!)
|
||||||
promise = ClosedGroupsProtocolV2.leave(this, groupPublicKey!!)
|
|
||||||
} else {
|
} else {
|
||||||
promise = ClosedGroupsProtocolV2.update(this, groupPublicKey!!, members.map { it.address.serialize() }, name)
|
// TODO: uncomment when we switch to sending new explicit updates after clients update
|
||||||
|
// task {
|
||||||
|
// val name =
|
||||||
|
// if (hasNameChanged) ClosedGroupsProtocolV2.explicitNameChange(this@EditClosedGroupActivity,groupPublicKey!!,name)
|
||||||
|
// else Promise.of(Unit)
|
||||||
|
// name.get()
|
||||||
|
// members.filterNot { it in originalMembers }.let { adds ->
|
||||||
|
// if (adds.isNotEmpty()) ClosedGroupsProtocolV2.explicitAddMembers(this@EditClosedGroupActivity, groupPublicKey!!, adds.map { it.address.serialize() })
|
||||||
|
// else Promise.of(Unit)
|
||||||
|
// }.get()
|
||||||
|
// originalMembers.filterNot { it in members }.let { removes ->
|
||||||
|
// if (removes.isNotEmpty()) ClosedGroupsProtocolV2.explicitRemoveMembers(this@EditClosedGroupActivity, groupPublicKey!!, removes.map { it.address.serialize() })
|
||||||
|
// else Promise.of(Unit)
|
||||||
|
// }.get()
|
||||||
|
// }
|
||||||
|
ClosedGroupsProtocolV2.update(this, groupPublicKey!!, members.map { it.address.serialize() }, name)
|
||||||
}
|
}
|
||||||
promise.successUi {
|
promise.successUi {
|
||||||
loaderContainer.fadeOut()
|
loaderContainer.fadeOut()
|
||||||
|
@ -31,6 +31,10 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete
|
|||||||
sealed class Kind {
|
sealed class Kind {
|
||||||
class New(val publicKey: ByteArray, val name: String, val encryptionKeyPair: ECKeyPair, val members: Collection<ByteArray>, val admins: Collection<ByteArray>) : Kind()
|
class New(val publicKey: ByteArray, val name: String, val encryptionKeyPair: ECKeyPair, val members: Collection<ByteArray>, val admins: Collection<ByteArray>) : Kind()
|
||||||
class Update(val name: String, val members: Collection<ByteArray>) : Kind()
|
class Update(val name: String, val members: Collection<ByteArray>) : Kind()
|
||||||
|
object Leave : Kind()
|
||||||
|
class RemoveMembers(val members: Collection<ByteArray>) : Kind()
|
||||||
|
class AddMembers(val members: Collection<ByteArray>) : Kind()
|
||||||
|
class NameChange(val name: String) : Kind()
|
||||||
class EncryptionKeyPair(val wrappers: Collection<KeyPairWrapper>) : Kind() // The new encryption key pair encrypted for each member individually
|
class EncryptionKeyPair(val wrappers: Collection<KeyPairWrapper>) : Kind() // The new encryption key pair encrypted for each member individually
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +92,23 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete
|
|||||||
val members = kind.members.joinToString(" - ") { it.toHexString() }
|
val members = kind.members.joinToString(" - ") { it.toHexString() }
|
||||||
builder.putString("members", members)
|
builder.putString("members", members)
|
||||||
}
|
}
|
||||||
|
is Kind.RemoveMembers -> {
|
||||||
|
builder.putString("kind", "RemoveMembers")
|
||||||
|
val members = kind.members.joinToString(" - ") { it.toHexString() }
|
||||||
|
builder.putString("members", members)
|
||||||
|
}
|
||||||
|
Kind.Leave -> {
|
||||||
|
builder.putString("kind", "Leave")
|
||||||
|
}
|
||||||
|
is Kind.AddMembers -> {
|
||||||
|
builder.putString("kind", "AddMembers")
|
||||||
|
val members = kind.members.joinToString(" - ") { it.toHexString() }
|
||||||
|
builder.putString("members", members)
|
||||||
|
}
|
||||||
|
is Kind.NameChange -> {
|
||||||
|
builder.putString("kind", "NameChange")
|
||||||
|
builder.putString("name", kind.name)
|
||||||
|
}
|
||||||
is Kind.EncryptionKeyPair -> {
|
is Kind.EncryptionKeyPair -> {
|
||||||
builder.putString("kind", "EncryptionKeyPair")
|
builder.putString("kind", "EncryptionKeyPair")
|
||||||
val wrappers = kind.wrappers.joinToString(" - ") { Json.encodeToString(it) }
|
val wrappers = kind.wrappers.joinToString(" - ") { Json.encodeToString(it) }
|
||||||
@ -123,6 +144,21 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete
|
|||||||
val wrappers: Collection<KeyPairWrapper> = data.getString("wrappers").split(" - ").map { Json.decodeFromString(it) }
|
val wrappers: Collection<KeyPairWrapper> = data.getString("wrappers").split(" - ").map { Json.decodeFromString(it) }
|
||||||
kind = Kind.EncryptionKeyPair(wrappers)
|
kind = Kind.EncryptionKeyPair(wrappers)
|
||||||
}
|
}
|
||||||
|
"RemoveMembers" -> {
|
||||||
|
val members = data.getString("members").split(" - ").map { Hex.fromStringCondensed(it) }
|
||||||
|
kind = Kind.RemoveMembers(members)
|
||||||
|
}
|
||||||
|
"AddMembers" -> {
|
||||||
|
val members = data.getString("members").split(" - ").map { Hex.fromStringCondensed(it) }
|
||||||
|
kind = Kind.AddMembers(members)
|
||||||
|
}
|
||||||
|
"NameChange" -> {
|
||||||
|
val name = data.getString("name")
|
||||||
|
kind = Kind.NameChange(name)
|
||||||
|
}
|
||||||
|
"Leave" -> {
|
||||||
|
kind = Kind.Leave
|
||||||
|
}
|
||||||
else -> throw Exception("Invalid closed group update message kind: $rawKind.")
|
else -> throw Exception("Invalid closed group update message kind: $rawKind.")
|
||||||
}
|
}
|
||||||
return ClosedGroupUpdateMessageSendJobV2(parameters, destination, kind)
|
return ClosedGroupUpdateMessageSendJobV2(parameters, destination, kind)
|
||||||
@ -154,6 +190,21 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete
|
|||||||
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR
|
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR
|
||||||
closedGroupUpdate.addAllWrappers(kind.wrappers.map { it.toProto() })
|
closedGroupUpdate.addAllWrappers(kind.wrappers.map { it.toProto() })
|
||||||
}
|
}
|
||||||
|
Kind.Leave -> {
|
||||||
|
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBER_LEFT
|
||||||
|
}
|
||||||
|
is Kind.RemoveMembers -> {
|
||||||
|
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_REMOVED
|
||||||
|
closedGroupUpdate.addAllMembers(kind.members.map { ByteString.copyFrom(it) })
|
||||||
|
}
|
||||||
|
is Kind.AddMembers -> {
|
||||||
|
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_ADDED
|
||||||
|
closedGroupUpdate.addAllMembers(kind.members.map { ByteString.copyFrom(it) })
|
||||||
|
}
|
||||||
|
is Kind.NameChange -> {
|
||||||
|
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.NAME_CHANGE
|
||||||
|
closedGroupUpdate.name = kind.name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dataMessage.closedGroupUpdateV2 = closedGroupUpdate.build()
|
dataMessage.closedGroupUpdateV2 = closedGroupUpdate.build()
|
||||||
contentMessage.dataMessage = dataMessage.build()
|
contentMessage.dataMessage = dataMessage.build()
|
||||||
@ -162,10 +213,9 @@ class ClosedGroupUpdateMessageSendJobV2 private constructor(parameters: Paramete
|
|||||||
val address = SignalServiceAddress(destination)
|
val address = SignalServiceAddress(destination)
|
||||||
val recipient = recipient(context, destination)
|
val recipient = recipient(context, destination)
|
||||||
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient)
|
val udAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient)
|
||||||
val ttl: Int
|
val ttl = when (kind) {
|
||||||
when (kind) {
|
is Kind.EncryptionKeyPair -> 4 * 24 * 60 * 60 * 1000
|
||||||
is Kind.EncryptionKeyPair -> ttl = 4 * 24 * 60 * 60 * 1000
|
else -> TTLUtilities.getTTL(TTLUtilities.MessageType.ClosedGroupUpdate)
|
||||||
else -> ttl = TTLUtilities.getTTL(TTLUtilities.MessageType.ClosedGroupUpdate)
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// isClosedGroup can always be false as it's only used in the context of legacy closed groups
|
// isClosedGroup can always be false as it's only used in the context of legacy closed groups
|
||||||
|
@ -5,6 +5,7 @@ import android.util.Log
|
|||||||
import com.google.protobuf.ByteString
|
import com.google.protobuf.ByteString
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.deferred
|
import nl.komponents.kovenant.deferred
|
||||||
|
import nl.komponents.kovenant.task
|
||||||
import org.session.libsignal.libsignal.ecc.Curve
|
import org.session.libsignal.libsignal.ecc.Curve
|
||||||
import org.session.libsignal.libsignal.ecc.DjbECPrivateKey
|
import org.session.libsignal.libsignal.ecc.DjbECPrivateKey
|
||||||
import org.session.libsignal.libsignal.ecc.DjbECPublicKey
|
import org.session.libsignal.libsignal.ecc.DjbECPublicKey
|
||||||
@ -12,21 +13,25 @@ import org.session.libsignal.libsignal.ecc.ECKeyPair
|
|||||||
import org.session.libsignal.libsignal.util.guava.Optional
|
import org.session.libsignal.libsignal.util.guava.Optional
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceGroup
|
import org.session.libsignal.service.api.messages.SignalServiceGroup
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||||
|
import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext
|
||||||
import org.session.libsignal.utilities.ThreadUtils
|
import org.session.libsignal.utilities.ThreadUtils
|
||||||
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
|
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
|
||||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
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.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.database.GroupDatabase
|
||||||
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager
|
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager
|
||||||
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager.ClosedGroupOperation
|
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager.ClosedGroupOperation
|
||||||
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl
|
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl
|
||||||
|
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage
|
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage
|
||||||
import org.thoughtcrime.securesms.sms.IncomingGroupMessage
|
import org.thoughtcrime.securesms.sms.IncomingGroupMessage
|
||||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage
|
import org.thoughtcrime.securesms.sms.IncomingTextMessage
|
||||||
import org.session.libsignal.utilities.Hex
|
import org.session.libsignal.utilities.Hex
|
||||||
|
|
||||||
import org.session.libsession.messaging.threads.Address
|
import org.session.libsession.messaging.threads.Address
|
||||||
|
import org.session.libsession.messaging.threads.GroupRecord
|
||||||
import org.session.libsession.messaging.threads.recipients.Recipient
|
import org.session.libsession.messaging.threads.recipients.Recipient
|
||||||
import org.session.libsession.utilities.GroupUtil
|
import org.session.libsession.utilities.GroupUtil
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
@ -36,7 +41,7 @@ import java.util.*
|
|||||||
import kotlin.jvm.Throws
|
import kotlin.jvm.Throws
|
||||||
|
|
||||||
object ClosedGroupsProtocolV2 {
|
object ClosedGroupsProtocolV2 {
|
||||||
val groupSizeLimit = 100
|
const val groupSizeLimit = 100
|
||||||
|
|
||||||
sealed class Error(val description: String) : Exception() {
|
sealed class Error(val description: String) : Exception() {
|
||||||
object NoThread : Error("Couldn't find a thread associated with the given group public key")
|
object NoThread : Error("Couldn't find a thread associated with the given group public key")
|
||||||
@ -44,7 +49,7 @@ object ClosedGroupsProtocolV2 {
|
|||||||
object InvalidUpdate : Error("Invalid group update.")
|
object InvalidUpdate : Error("Invalid group update.")
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun createClosedGroup(context: Context, name: String, members: Collection<String>): Promise<String, Exception> {
|
fun createClosedGroup(context: Context, name: String, members: Collection<String>): Promise<String, Exception> {
|
||||||
val deferred = deferred<String, Exception>()
|
val deferred = deferred<String, Exception>()
|
||||||
ThreadUtils.queue {
|
ThreadUtils.queue {
|
||||||
// Prepare
|
// Prepare
|
||||||
@ -60,7 +65,7 @@ object ClosedGroupsProtocolV2 {
|
|||||||
val admins = setOf( userPublicKey )
|
val admins = setOf( userPublicKey )
|
||||||
val adminsAsData = admins.map { Hex.fromStringCondensed(it) }
|
val adminsAsData = admins.map { Hex.fromStringCondensed(it) }
|
||||||
DatabaseFactory.getGroupDatabase(context).create(groupID, name, LinkedList(members.map { Address.fromSerialized(it) }),
|
DatabaseFactory.getGroupDatabase(context).create(groupID, name, LinkedList(members.map { Address.fromSerialized(it) }),
|
||||||
null, null, LinkedList(admins.map { Address.fromSerialized(it) }))
|
null, null, LinkedList(admins.map { Address.fromSerialized(it!!) }))
|
||||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.from(context, Address.fromSerialized(groupID), false), true)
|
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.from(context, Address.fromSerialized(groupID), false), true)
|
||||||
// Send a closed group update message to all members individually
|
// Send a closed group update message to all members individually
|
||||||
val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, encryptionKeyPair, membersAsData, adminsAsData)
|
val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, encryptionKeyPair, membersAsData, adminsAsData)
|
||||||
@ -76,7 +81,7 @@ object ClosedGroupsProtocolV2 {
|
|||||||
apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey)
|
apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey)
|
||||||
// Notify the user
|
// Notify the user
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
|
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
|
||||||
insertOutgoingInfoMessage(context, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, name, members, admins, threadID)
|
insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID)
|
||||||
// Notify the PN server
|
// Notify the PN server
|
||||||
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
||||||
// Fulfill the promise
|
// Fulfill the promise
|
||||||
@ -87,7 +92,155 @@ object ClosedGroupsProtocolV2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun leave(context: Context, groupPublicKey: String): Promise<Unit, Exception> {
|
fun explicitLeave(context: Context, groupPublicKey: String): Promise<Unit, Exception> {
|
||||||
|
val deferred = deferred<Unit, Exception>()
|
||||||
|
ThreadUtils.queue {
|
||||||
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||||
|
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
|
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||||
|
val groupID = doubleEncodeGroupID(groupPublicKey)
|
||||||
|
val group = groupDB.getGroup(groupID).orNull()
|
||||||
|
val updatedMembers = group.members.map { it.serialize() }.toSet() - userPublicKey
|
||||||
|
val admins = group.admins.map { it.serialize() }
|
||||||
|
val name = group.title
|
||||||
|
if (group == null) {
|
||||||
|
Log.d("Loki", "Can't leave nonexistent closed group.")
|
||||||
|
return@queue deferred.reject(Error.NoThread)
|
||||||
|
}
|
||||||
|
// Send the update to the group
|
||||||
|
@Suppress("NAME_SHADOWING")
|
||||||
|
val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, ClosedGroupUpdateMessageSendJobV2.Kind.Leave)
|
||||||
|
job.setContext(context)
|
||||||
|
job.onRun() // Run the job immediately
|
||||||
|
// Remove the group private key and unsubscribe from PNs
|
||||||
|
disableLocalGroupAndUnsubscribe(context, apiDB, groupPublicKey, groupDB, groupID, userPublicKey)
|
||||||
|
// Notify the user
|
||||||
|
val infoType = GroupContext.Type.QUIT
|
||||||
|
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
|
||||||
|
insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID)
|
||||||
|
deferred.resolve(Unit)
|
||||||
|
}
|
||||||
|
return deferred.promise
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun explicitAddMembers(context: Context, groupPublicKey: String, membersToAdd: List<String>): Promise<Any, java.lang.Exception> {
|
||||||
|
return task {
|
||||||
|
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
|
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||||
|
val groupID = doubleEncodeGroupID(groupPublicKey)
|
||||||
|
val group = groupDB.getGroup(groupID).orNull()
|
||||||
|
if (group == null) {
|
||||||
|
Log.d("Loki", "Can't leave nonexistent closed group.")
|
||||||
|
return@task Error.NoThread
|
||||||
|
}
|
||||||
|
val updatedMembers = group.members.map { it.serialize() }.toSet() + membersToAdd
|
||||||
|
// Save the new group members
|
||||||
|
groupDB.updateMembers(groupID, updatedMembers.map { Address.fromSerialized(it) })
|
||||||
|
val membersAsData = updatedMembers.map { Hex.fromStringCondensed(it) }
|
||||||
|
val newMembersAsData = membersToAdd.map { Hex.fromStringCondensed(it) }
|
||||||
|
val admins = group.admins.map { it.serialize() }
|
||||||
|
val adminsAsData = admins.map { Hex.fromStringCondensed(it) }
|
||||||
|
val encryptionKeyPair = apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey)
|
||||||
|
if (encryptionKeyPair == null) {
|
||||||
|
Log.d("Loki", "Couldn't get encryption key pair for closed group.")
|
||||||
|
return@task Error.NoKeyPair
|
||||||
|
}
|
||||||
|
val name = group.title
|
||||||
|
// Send the update to the group
|
||||||
|
val memberUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.AddMembers(newMembersAsData)
|
||||||
|
val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, memberUpdateKind)
|
||||||
|
job.setContext(context)
|
||||||
|
job.onRun() // Run the job immediately
|
||||||
|
// Send closed group update messages to any new members individually
|
||||||
|
for (member in membersToAdd) {
|
||||||
|
@Suppress("NAME_SHADOWING")
|
||||||
|
val closedGroupNewKind = ClosedGroupUpdateMessageSendJobV2.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, encryptionKeyPair, membersAsData, adminsAsData)
|
||||||
|
@Suppress("NAME_SHADOWING")
|
||||||
|
val newMemberJob = ClosedGroupUpdateMessageSendJobV2(member, closedGroupNewKind)
|
||||||
|
ApplicationContext.getInstance(context).jobManager.add(newMemberJob)
|
||||||
|
}
|
||||||
|
// Notify the user
|
||||||
|
val infoType = GroupContext.Type.UPDATE
|
||||||
|
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
|
||||||
|
insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun explicitRemoveMembers(context: Context, groupPublicKey: String, membersToRemove: List<String>): Promise<Any, Exception> {
|
||||||
|
return task {
|
||||||
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
|
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
|
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||||
|
val groupID = doubleEncodeGroupID(groupPublicKey)
|
||||||
|
val group = groupDB.getGroup(groupID).orNull()
|
||||||
|
if (group == null) {
|
||||||
|
Log.d("Loki", "Can't leave nonexistent closed group.")
|
||||||
|
return@task Error.NoThread
|
||||||
|
}
|
||||||
|
val updatedMembers = group.members.map { it.serialize() }.toSet() - membersToRemove
|
||||||
|
// Save the new group members
|
||||||
|
groupDB.updateMembers(groupID, updatedMembers.map { Address.fromSerialized(it) })
|
||||||
|
val removeMembersAsData = membersToRemove.map { Hex.fromStringCondensed(it) }
|
||||||
|
val admins = group.admins.map { it.serialize() }
|
||||||
|
val encryptionKeyPair = apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey)
|
||||||
|
if (encryptionKeyPair == null) {
|
||||||
|
Log.d("Loki", "Couldn't get encryption key pair for closed group.")
|
||||||
|
return@task Error.NoKeyPair
|
||||||
|
}
|
||||||
|
if (membersToRemove.any { it in admins } && updatedMembers.isNotEmpty()) {
|
||||||
|
Log.d("Loki", "Can't remove admin from closed group unless the group is destroyed entirely.")
|
||||||
|
return@task Error.InvalidUpdate
|
||||||
|
}
|
||||||
|
val name = group.title
|
||||||
|
// Send the update to the group
|
||||||
|
val memberUpdateKind = ClosedGroupUpdateMessageSendJobV2.Kind.RemoveMembers(removeMembersAsData)
|
||||||
|
val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, memberUpdateKind)
|
||||||
|
job.setContext(context)
|
||||||
|
job.onRun() // Run the job immediately
|
||||||
|
val isCurrentUserAdmin = admins.contains(userPublicKey)
|
||||||
|
if (isCurrentUserAdmin) {
|
||||||
|
generateAndSendNewEncryptionKeyPair(context, groupPublicKey, updatedMembers)
|
||||||
|
}
|
||||||
|
// Notify the user
|
||||||
|
val infoType = GroupContext.Type.UPDATE
|
||||||
|
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
|
||||||
|
insertOutgoingInfoMessage(context, groupID, infoType, name, updatedMembers, admins, threadID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun explicitNameChange(context: Context, groupPublicKey: String, newName: String): Promise<Unit, Exception> {
|
||||||
|
val deferred = deferred<Unit, Exception>()
|
||||||
|
ThreadUtils.queue {
|
||||||
|
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||||
|
val groupID = doubleEncodeGroupID(groupPublicKey)
|
||||||
|
val group = groupDB.getGroup(groupID).orNull()
|
||||||
|
val members = group.members.map { it.serialize() }.toSet()
|
||||||
|
val admins = group.admins.map { it.serialize() }
|
||||||
|
if (group == null) {
|
||||||
|
Log.d("Loki", "Can't leave nonexistent closed group.")
|
||||||
|
return@queue deferred.reject(Error.NoThread)
|
||||||
|
}
|
||||||
|
// Send the update to the group
|
||||||
|
val kind = ClosedGroupUpdateMessageSendJobV2.Kind.NameChange(newName)
|
||||||
|
val job = ClosedGroupUpdateMessageSendJobV2(groupPublicKey, kind)
|
||||||
|
job.setContext(context)
|
||||||
|
job.onRun() // Run the job immediately
|
||||||
|
// Update the group
|
||||||
|
groupDB.updateTitle(groupID, newName)
|
||||||
|
// Notify the user
|
||||||
|
val infoType = GroupContext.Type.UPDATE
|
||||||
|
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
|
||||||
|
insertOutgoingInfoMessage(context, groupID, infoType, newName, members, admins, threadID)
|
||||||
|
deferred.resolve(Unit)
|
||||||
|
}
|
||||||
|
return deferred.promise
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun leave(context: Context, groupPublicKey: String): Promise<Unit, Exception> {
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||||
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||||
val groupID = doubleEncodeGroupID(groupPublicKey)
|
val groupID = doubleEncodeGroupID(groupPublicKey)
|
||||||
@ -108,7 +261,7 @@ object ClosedGroupsProtocolV2 {
|
|||||||
return update(context, groupPublicKey, newMembers, name)
|
return update(context, groupPublicKey, newMembers, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun update(context: Context, groupPublicKey: String, members: Collection<String>, name: String): Promise<Unit, Exception> {
|
fun update(context: Context, groupPublicKey: String, members: Collection<String>, name: String): Promise<Unit, Exception> {
|
||||||
val deferred = deferred<Unit, Exception>()
|
val deferred = deferred<Unit, Exception>()
|
||||||
ThreadUtils.queue {
|
ThreadUtils.queue {
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||||
@ -181,7 +334,7 @@ object ClosedGroupsProtocolV2 {
|
|||||||
groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) })
|
groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) })
|
||||||
}
|
}
|
||||||
// Notify the user
|
// Notify the user
|
||||||
val infoType = if (isUserLeaving) SignalServiceProtos.GroupContext.Type.QUIT else SignalServiceProtos.GroupContext.Type.UPDATE
|
val infoType = if (isUserLeaving) GroupContext.Type.QUIT else GroupContext.Type.UPDATE
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
|
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
|
||||||
insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID)
|
insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID)
|
||||||
deferred.resolve(Unit)
|
deferred.resolve(Unit)
|
||||||
@ -223,29 +376,41 @@ object ClosedGroupsProtocolV2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun handleMessage(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
|
fun handleMessage(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
|
||||||
if (!isValid(closedGroupUpdate)) { return; }
|
if (!isValid(closedGroupUpdate, senderPublicKey)) { return }
|
||||||
when (closedGroupUpdate.type) {
|
when (closedGroupUpdate.type) {
|
||||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.NEW -> handleNewClosedGroup(context, closedGroupUpdate, senderPublicKey)
|
SignalServiceProtos.ClosedGroupUpdateV2.Type.NEW -> handleNewClosedGroup(context, closedGroupUpdate, senderPublicKey)
|
||||||
|
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_REMOVED -> handleClosedGroupMembersRemoved(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||||
|
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_ADDED -> handleClosedGroupMembersAdded(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||||
|
SignalServiceProtos.ClosedGroupUpdateV2.Type.NAME_CHANGE -> handleClosedGroupNameChange(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||||
|
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBER_LEFT -> handleClosedGroupMemberLeft(context, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.UPDATE -> handleClosedGroupUpdate(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
|
SignalServiceProtos.ClosedGroupUpdateV2.Type.UPDATE -> handleClosedGroupUpdate(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR -> handleGroupEncryptionKeyPair(context, closedGroupUpdate, groupPublicKey, senderPublicKey)
|
SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR -> handleGroupEncryptionKeyPair(context, closedGroupUpdate, groupPublicKey, senderPublicKey)
|
||||||
else -> {
|
else -> {
|
||||||
// Do nothing
|
Log.d("Loki","Can't handle closed group update of unknown type: ${closedGroupUpdate.type}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isValid(closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2): Boolean {
|
private fun isValid(closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, senderPublicKey: String): Boolean {
|
||||||
when (closedGroupUpdate.type) {
|
return when (closedGroupUpdate.type) {
|
||||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.NEW -> {
|
SignalServiceProtos.ClosedGroupUpdateV2.Type.NEW -> {
|
||||||
return !closedGroupUpdate.publicKey.isEmpty && !closedGroupUpdate.name.isNullOrEmpty() && !(closedGroupUpdate.encryptionKeyPair.privateKey ?: ByteString.copyFrom(ByteArray(0))).isEmpty
|
(!closedGroupUpdate.publicKey.isEmpty && !closedGroupUpdate.name.isNullOrEmpty() && !(closedGroupUpdate.encryptionKeyPair.privateKey ?: ByteString.copyFrom(ByteArray(0))).isEmpty
|
||||||
&& !(closedGroupUpdate.encryptionKeyPair.publicKey ?: ByteString.copyFrom(ByteArray(0))).isEmpty && closedGroupUpdate.membersCount > 0 && closedGroupUpdate.adminsCount > 0
|
&& !(closedGroupUpdate.encryptionKeyPair.publicKey ?: ByteString.copyFrom(ByteArray(0))).isEmpty && closedGroupUpdate.membersCount > 0 && closedGroupUpdate.adminsCount > 0)
|
||||||
}
|
}
|
||||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.UPDATE -> {
|
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_ADDED,
|
||||||
return !closedGroupUpdate.name.isNullOrEmpty()
|
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_REMOVED -> {
|
||||||
|
closedGroupUpdate.membersCount > 0
|
||||||
}
|
}
|
||||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR -> return true
|
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBER_LEFT -> {
|
||||||
else -> return false
|
senderPublicKey.isNotEmpty()
|
||||||
|
}
|
||||||
|
SignalServiceProtos.ClosedGroupUpdateV2.Type.UPDATE,
|
||||||
|
SignalServiceProtos.ClosedGroupUpdateV2.Type.NAME_CHANGE -> {
|
||||||
|
!closedGroupUpdate.name.isNullOrEmpty()
|
||||||
|
}
|
||||||
|
SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR -> true
|
||||||
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,12 +442,144 @@ object ClosedGroupsProtocolV2 {
|
|||||||
val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairAsProto.publicKey.toByteArray().removing05PrefixIfNeeded()), DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray()))
|
val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairAsProto.publicKey.toByteArray().removing05PrefixIfNeeded()), DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray()))
|
||||||
apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey)
|
apiDB.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey)
|
||||||
// Notify the user
|
// Notify the user
|
||||||
insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins)
|
insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins)
|
||||||
// Notify the PN server
|
// Notify the PN server
|
||||||
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun handleClosedGroupUpdate(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
|
fun handleClosedGroupMembersRemoved(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
|
||||||
|
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
|
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||||
|
val groupID = doubleEncodeGroupID(groupPublicKey)
|
||||||
|
val group = groupDB.getGroup(groupID).orNull()
|
||||||
|
if (group == null) {
|
||||||
|
Log.d("Loki", "Ignoring closed group info message for nonexistent group.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||||
|
val name = group.title
|
||||||
|
// Check common group update logic
|
||||||
|
val members = group.members.map { it.serialize() }
|
||||||
|
val admins = group.admins.map { it.toString() }
|
||||||
|
|
||||||
|
// Users that are part of this remove update
|
||||||
|
val updateMembers = closedGroupUpdate.membersList.map { it.toByteArray().toHexString() }
|
||||||
|
|
||||||
|
if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If admin leaves the group is disbanded
|
||||||
|
val didAdminLeave = admins.any { it in updateMembers }
|
||||||
|
// newMembers to save is old members minus removed members
|
||||||
|
val newMembers = members - updateMembers
|
||||||
|
// user should be posting MEMBERS_LEFT so this should not be encountered
|
||||||
|
val senderLeft = senderPublicKey in updateMembers
|
||||||
|
if (senderLeft) {
|
||||||
|
Log.d("Loki", "Received a MEMBERS_REMOVED instead of a MEMBERS_LEFT from sender $senderPublicKey")
|
||||||
|
}
|
||||||
|
val wasCurrentUserRemoved = userPublicKey in updateMembers
|
||||||
|
|
||||||
|
// admin should send a MEMBERS_LEFT message but handled here in case
|
||||||
|
if (didAdminLeave || wasCurrentUserRemoved) {
|
||||||
|
disableLocalGroupAndUnsubscribe(context, apiDB, groupPublicKey, groupDB, groupID, userPublicKey)
|
||||||
|
} else {
|
||||||
|
val isCurrentUserAdmin = admins.contains(userPublicKey)
|
||||||
|
groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
|
||||||
|
if (isCurrentUserAdmin) {
|
||||||
|
generateAndSendNewEncryptionKeyPair(context, groupPublicKey, newMembers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val (contextType, signalType) =
|
||||||
|
if (senderLeft) GroupContext.Type.QUIT to SignalServiceGroup.Type.QUIT
|
||||||
|
else GroupContext.Type.UPDATE to SignalServiceGroup.Type.UPDATE
|
||||||
|
|
||||||
|
insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, members, admins)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleClosedGroupMembersAdded(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
|
||||||
|
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||||
|
val groupID = doubleEncodeGroupID(groupPublicKey)
|
||||||
|
val group = groupDB.getGroup(groupID).orNull()
|
||||||
|
if (group == null) {
|
||||||
|
Log.d("Loki", "Ignoring closed group info message for nonexistent group.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val name = group.title
|
||||||
|
// Check common group update logic
|
||||||
|
val members = group.members.map { it.serialize() }
|
||||||
|
val admins = group.admins.map { it.serialize() }
|
||||||
|
|
||||||
|
// Users that are part of this remove update
|
||||||
|
val updateMembers = closedGroupUpdate.membersList.map { it.toByteArray().toHexString() }
|
||||||
|
// newMembers to save is old members minus removed members
|
||||||
|
val newMembers = members + updateMembers
|
||||||
|
groupDB.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
|
||||||
|
|
||||||
|
insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleClosedGroupNameChange(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
|
||||||
|
// Check that the sender is a member of the group (before the update)
|
||||||
|
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||||
|
val groupID = doubleEncodeGroupID(groupPublicKey)
|
||||||
|
val group = groupDB.getGroup(groupID).orNull()
|
||||||
|
if (group == null) {
|
||||||
|
Log.d("Loki", "Ignoring closed group info message for nonexistent group.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Check common group update logic
|
||||||
|
if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val members = group.members.map { it.serialize() }
|
||||||
|
val admins = group.admins.map { it.serialize() }
|
||||||
|
val name = closedGroupUpdate.name
|
||||||
|
groupDB.updateTitle(groupID, name)
|
||||||
|
|
||||||
|
insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleClosedGroupMemberLeft(context: Context, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
|
||||||
|
// Check the user leaving isn't us, will already be handled
|
||||||
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||||
|
if (senderPublicKey == userPublicKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
|
val groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||||
|
val groupID = doubleEncodeGroupID(groupPublicKey)
|
||||||
|
val group = groupDB.getGroup(groupID).orNull()
|
||||||
|
if (group == null) {
|
||||||
|
Log.d("Loki", "Ignoring closed group info message for nonexistent group.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val name = group.title
|
||||||
|
// Check common group update logic
|
||||||
|
val members = group.members.map { it.serialize() }
|
||||||
|
val admins = group.admins.map { it.toString() }
|
||||||
|
if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If admin leaves the group is disbanded
|
||||||
|
val didAdminLeave = admins.contains(senderPublicKey)
|
||||||
|
val updatedMemberList = members - senderPublicKey
|
||||||
|
|
||||||
|
if (didAdminLeave) {
|
||||||
|
disableLocalGroupAndUnsubscribe(context, apiDB, groupPublicKey, groupDB, groupID, userPublicKey)
|
||||||
|
} else {
|
||||||
|
val isCurrentUserAdmin = admins.contains(userPublicKey)
|
||||||
|
groupDB.updateMembers(groupID, updatedMemberList.map { Address.fromSerialized(it) })
|
||||||
|
if (isCurrentUserAdmin) {
|
||||||
|
generateAndSendNewEncryptionKeyPair(context, groupPublicKey, updatedMemberList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.QUIT, SignalServiceGroup.Type.QUIT, name, members, admins)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleClosedGroupUpdate(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
|
||||||
// Prepare
|
// Prepare
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
@ -297,15 +594,8 @@ object ClosedGroupsProtocolV2 {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
val oldMembers = group.members.map { it.serialize() }
|
val oldMembers = group.members.map { it.serialize() }
|
||||||
val newMembers = members.toSet().minus(oldMembers)
|
// Check common group update logic
|
||||||
// Check that the message isn't from before the group was created
|
if (!isValidGroupUpdate(group, sentTimestamp, senderPublicKey)) {
|
||||||
if (group.createAt > sentTimestamp) {
|
|
||||||
Log.d("Loki", "Ignoring closed group update from before thread was created.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Check that the sender is a member of the group (before the update)
|
|
||||||
if (!oldMembers.contains(senderPublicKey)) {
|
|
||||||
Log.d("Loki", "Ignoring closed group info message from non-member.")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Check that the admin wasn't removed unless the group was destroyed entirely
|
// Check that the admin wasn't removed unless the group was destroyed entirely
|
||||||
@ -316,20 +606,13 @@ object ClosedGroupsProtocolV2 {
|
|||||||
// Remove the group from the user's set of public keys to poll for if the current user was removed
|
// Remove the group from the user's set of public keys to poll for if the current user was removed
|
||||||
val wasCurrentUserRemoved = !members.contains(userPublicKey)
|
val wasCurrentUserRemoved = !members.contains(userPublicKey)
|
||||||
if (wasCurrentUserRemoved) {
|
if (wasCurrentUserRemoved) {
|
||||||
apiDB.removeClosedGroupPublicKey(groupPublicKey)
|
disableLocalGroupAndUnsubscribe(context, apiDB, groupPublicKey, groupDB, groupID, userPublicKey)
|
||||||
// Remove the key pairs
|
|
||||||
apiDB.removeAllClosedGroupEncryptionKeyPairs(groupPublicKey)
|
|
||||||
// Mark the group as inactive
|
|
||||||
groupDB.setActive(groupID, false)
|
|
||||||
groupDB.removeMember(groupID, Address.fromSerialized(userPublicKey))
|
|
||||||
// Notify the PN server
|
|
||||||
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey)
|
|
||||||
}
|
}
|
||||||
// Generate and distribute a new encryption key pair if needed
|
// Generate and distribute a new encryption key pair if needed
|
||||||
val wasAnyUserRemoved = (members.toSet().intersect(oldMembers) != oldMembers.toSet())
|
val wasAnyUserRemoved = (members.toSet().intersect(oldMembers) != oldMembers.toSet())
|
||||||
val isCurrentUserAdmin = group.admins.map { it.toString() }.contains(userPublicKey)
|
val isCurrentUserAdmin = group.admins.map { it.toString() }.contains(userPublicKey)
|
||||||
if (wasAnyUserRemoved && isCurrentUserAdmin) {
|
if (wasAnyUserRemoved && isCurrentUserAdmin) {
|
||||||
generateAndSendNewEncryptionKeyPair(context, groupPublicKey, members.minus(newMembers))
|
generateAndSendNewEncryptionKeyPair(context, groupPublicKey, members)
|
||||||
}
|
}
|
||||||
// Update the group
|
// Update the group
|
||||||
groupDB.updateTitle(groupID, name)
|
groupDB.updateTitle(groupID, name)
|
||||||
@ -339,11 +622,39 @@ object ClosedGroupsProtocolV2 {
|
|||||||
}
|
}
|
||||||
// Notify the user
|
// Notify the user
|
||||||
val wasSenderRemoved = !members.contains(senderPublicKey)
|
val wasSenderRemoved = !members.contains(senderPublicKey)
|
||||||
val type0 = if (wasSenderRemoved) SignalServiceProtos.GroupContext.Type.QUIT else SignalServiceProtos.GroupContext.Type.UPDATE
|
val type0 = if (wasSenderRemoved) GroupContext.Type.QUIT else GroupContext.Type.UPDATE
|
||||||
val type1 = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE
|
val type1 = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE
|
||||||
insertIncomingInfoMessage(context, senderPublicKey, groupID, type0, type1, name, members, group.admins.map { it.toString() })
|
insertIncomingInfoMessage(context, senderPublicKey, groupID, type0, type1, name, members, group.admins.map { it.toString() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun disableLocalGroupAndUnsubscribe(context: Context, apiDB: LokiAPIDatabase, groupPublicKey: String, groupDB: GroupDatabase, groupID: String, userPublicKey: String) {
|
||||||
|
apiDB.removeClosedGroupPublicKey(groupPublicKey)
|
||||||
|
// Remove the key pairs
|
||||||
|
apiDB.removeAllClosedGroupEncryptionKeyPairs(groupPublicKey)
|
||||||
|
// Mark the group as inactive
|
||||||
|
groupDB.setActive(groupID, false)
|
||||||
|
groupDB.removeMember(groupID, Address.fromSerialized(userPublicKey))
|
||||||
|
// Notify the PN server
|
||||||
|
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isValidGroupUpdate(group: GroupRecord,
|
||||||
|
sentTimestamp: Long,
|
||||||
|
senderPublicKey: String): Boolean {
|
||||||
|
val oldMembers = group.members.map { it.serialize() }
|
||||||
|
// Check that the message isn't from before the group was created
|
||||||
|
if (group.createdAt > sentTimestamp) {
|
||||||
|
Log.d("Loki", "Ignoring closed group update from before thread was created.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Check that the sender is a member of the group (before the update)
|
||||||
|
if (senderPublicKey !in oldMembers) {
|
||||||
|
Log.d("Loki", "Ignoring closed group info message from non-member.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleGroupEncryptionKeyPair(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, groupPublicKey: String, senderPublicKey: String) {
|
private fun handleGroupEncryptionKeyPair(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, groupPublicKey: String, senderPublicKey: String) {
|
||||||
// Prepare
|
// Prepare
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
@ -373,9 +684,9 @@ object ClosedGroupsProtocolV2 {
|
|||||||
Log.d("Loki", "Received a new closed group encryption key pair")
|
Log.d("Loki", "Received a new closed group encryption key pair")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type0: SignalServiceProtos.GroupContext.Type, type1: SignalServiceGroup.Type,
|
private fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type0: GroupContext.Type, type1: SignalServiceGroup.Type,
|
||||||
name: String, members: Collection<String>, admins: Collection<String>) {
|
name: String, members: Collection<String>, admins: Collection<String>) {
|
||||||
val groupContextBuilder = SignalServiceProtos.GroupContext.newBuilder()
|
val groupContextBuilder = GroupContext.newBuilder()
|
||||||
.setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID)))
|
.setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID)))
|
||||||
.setType(type0)
|
.setType(type0)
|
||||||
.setName(name)
|
.setName(name)
|
||||||
@ -388,10 +699,10 @@ object ClosedGroupsProtocolV2 {
|
|||||||
smsDB.insertMessageInbox(infoMessage)
|
smsDB.insertMessageInbox(infoMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceProtos.GroupContext.Type, name: String,
|
private fun insertOutgoingInfoMessage(context: Context, groupID: String, type: GroupContext.Type, name: String,
|
||||||
members: Collection<String>, admins: Collection<String>, threadID: Long) {
|
members: Collection<String>, admins: Collection<String>, threadID: Long) {
|
||||||
val recipient = Recipient.from(context, Address.fromSerialized(groupID), false)
|
val recipient = Recipient.from(context, Address.fromSerialized(groupID), false)
|
||||||
val groupContextBuilder = SignalServiceProtos.GroupContext.newBuilder()
|
val groupContextBuilder = GroupContext.newBuilder()
|
||||||
.setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID)))
|
.setId(ByteString.copyFrom(GroupUtil.getDecodedGroupIDAsData(groupID)))
|
||||||
.setType(type)
|
.setType(type)
|
||||||
.setName(name)
|
.setName(name)
|
||||||
|
@ -9,7 +9,7 @@ class GroupRecord(
|
|||||||
val encodedId: String, val title: String, members: String?, val avatar: ByteArray?,
|
val encodedId: String, val title: String, members: String?, val avatar: ByteArray?,
|
||||||
val avatarId: Long?, val avatarKey: ByteArray?, val avatarContentType: String?,
|
val avatarId: Long?, val avatarKey: ByteArray?, val avatarContentType: String?,
|
||||||
val relay: String?, val isActive: Boolean, val avatarDigest: ByteArray?, val isMms: Boolean,
|
val relay: String?, val isActive: Boolean, val avatarDigest: ByteArray?, val isMms: Boolean,
|
||||||
val url: String?, admins: String?, val createAt: Long
|
val url: String?, admins: String?, val createdAt: Long
|
||||||
) {
|
) {
|
||||||
var members: List<Address> = LinkedList<Address>()
|
var members: List<Address> = LinkedList<Address>()
|
||||||
var admins: List<Address> = LinkedList<Address>()
|
var admins: List<Address> = LinkedList<Address>()
|
||||||
|
@ -230,6 +230,10 @@ message ClosedGroupUpdateV2 {
|
|||||||
NEW = 1; // publicKey, name, encryptionKeyPair, members, admins
|
NEW = 1; // publicKey, name, encryptionKeyPair, members, admins
|
||||||
UPDATE = 2; // name, members
|
UPDATE = 2; // name, members
|
||||||
ENCRYPTION_KEY_PAIR = 3; // wrappers
|
ENCRYPTION_KEY_PAIR = 3; // wrappers
|
||||||
|
NAME_CHANGE = 4; // name
|
||||||
|
MEMBERS_ADDED = 5; // members
|
||||||
|
MEMBERS_REMOVED = 6; // members
|
||||||
|
MEMBER_LEFT = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message KeyPair {
|
message KeyPair {
|
||||||
|
@ -24711,9 +24711,6 @@ public final class SignalServiceProtos {
|
|||||||
MEMBERS_REMOVED(5, 6),
|
MEMBERS_REMOVED(5, 6),
|
||||||
/**
|
/**
|
||||||
* <code>MEMBER_LEFT = 7;</code>
|
* <code>MEMBER_LEFT = 7;</code>
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* </pre>
|
|
||||||
*/
|
*/
|
||||||
MEMBER_LEFT(6, 7),
|
MEMBER_LEFT(6, 7),
|
||||||
;
|
;
|
||||||
@ -24768,14 +24765,10 @@ public final class SignalServiceProtos {
|
|||||||
public static final int MEMBERS_REMOVED_VALUE = 6;
|
public static final int MEMBERS_REMOVED_VALUE = 6;
|
||||||
/**
|
/**
|
||||||
* <code>MEMBER_LEFT = 7;</code>
|
* <code>MEMBER_LEFT = 7;</code>
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* </pre>
|
|
||||||
*/
|
*/
|
||||||
public static final int MEMBER_LEFT_VALUE = 7;
|
public static final int MEMBER_LEFT_VALUE = 7;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public final int getNumber() { return value; }
|
public final int getNumber() { return value; }
|
||||||
|
|
||||||
public static Type valueOf(int value) {
|
public static Type valueOf(int value) {
|
||||||
@ -49481,7 +49474,7 @@ public final class SignalServiceProtos {
|
|||||||
"\022\026\n\022PROFILE_KEY_UPDATE\020\004\022\035\n\030DEVICE_UNLIN" +
|
"\022\026\n\022PROFILE_KEY_UPDATE\020\004\022\035\n\030DEVICE_UNLIN" +
|
||||||
"KING_REQUEST\020\200\001\"A\n\017LokiUserProfile\022\023\n\013di" +
|
"KING_REQUEST\020\200\001\"A\n\017LokiUserProfile\022\023\n\013di" +
|
||||||
"splayName\030\001 \001(\t\022\031\n\021profilePictureURL\030\002 \001",
|
"splayName\030\001 \001(\t\022\031\n\021profilePictureURL\030\002 \001",
|
||||||
"(\t\"\301\003\n\023ClosedGroupUpdateV2\0225\n\004type\030\001 \002(\016" +
|
"(\t\"\213\004\n\023ClosedGroupUpdateV2\0225\n\004type\030\001 \002(\016" +
|
||||||
"2\'.signalservice.ClosedGroupUpdateV2.Typ" +
|
"2\'.signalservice.ClosedGroupUpdateV2.Typ" +
|
||||||
"e\022\021\n\tpublicKey\030\002 \001(\014\022\014\n\004name\030\003 \001(\t\022E\n\021en" +
|
"e\022\021\n\tpublicKey\030\002 \001(\014\022\014\n\004name\030\003 \001(\t\022E\n\021en" +
|
||||||
"cryptionKeyPair\030\004 \001(\0132*.signalservice.Cl" +
|
"cryptionKeyPair\030\004 \001(\0132*.signalservice.Cl" +
|
||||||
@ -49491,98 +49484,100 @@ public final class SignalServiceProtos {
|
|||||||
"Wrapper\0320\n\007KeyPair\022\021\n\tpublicKey\030\001 \002(\014\022\022\n" +
|
"Wrapper\0320\n\007KeyPair\022\021\n\tpublicKey\030\001 \002(\014\022\022\n" +
|
||||||
"\nprivateKey\030\002 \002(\014\032=\n\016KeyPairWrapper\022\021\n\tp" +
|
"\nprivateKey\030\002 \002(\014\032=\n\016KeyPairWrapper\022\021\n\tp" +
|
||||||
"ublicKey\030\001 \002(\014\022\030\n\020encryptedKeyPair\030\002 \002(\014",
|
"ublicKey\030\001 \002(\014\022\030\n\020encryptedKeyPair\030\002 \002(\014",
|
||||||
"\"4\n\004Type\022\007\n\003NEW\020\001\022\n\n\006UPDATE\020\002\022\027\n\023ENCRYPT" +
|
"\"~\n\004Type\022\007\n\003NEW\020\001\022\n\n\006UPDATE\020\002\022\027\n\023ENCRYPT" +
|
||||||
"ION_KEY_PAIR\020\003\"\357\002\n\021ClosedGroupUpdate\022\014\n\004" +
|
"ION_KEY_PAIR\020\003\022\017\n\013NAME_CHANGE\020\004\022\021\n\rMEMBE" +
|
||||||
"name\030\001 \001(\t\022\026\n\016groupPublicKey\030\002 \001(\014\022\027\n\017gr" +
|
"RS_ADDED\020\005\022\023\n\017MEMBERS_REMOVED\020\006\022\017\n\013MEMBE" +
|
||||||
"oupPrivateKey\030\003 \001(\014\022>\n\nsenderKeys\030\004 \003(\0132" +
|
"R_LEFT\020\007\"\357\002\n\021ClosedGroupUpdate\022\014\n\004name\030\001" +
|
||||||
"*.signalservice.ClosedGroupUpdate.Sender" +
|
" \001(\t\022\026\n\016groupPublicKey\030\002 \001(\014\022\027\n\017groupPri" +
|
||||||
"Key\022\017\n\007members\030\005 \003(\014\022\016\n\006admins\030\006 \003(\014\0223\n\004" +
|
"vateKey\030\003 \001(\014\022>\n\nsenderKeys\030\004 \003(\0132*.sign" +
|
||||||
"type\030\007 \001(\0162%.signalservice.ClosedGroupUp" +
|
"alservice.ClosedGroupUpdate.SenderKey\022\017\n" +
|
||||||
"date.Type\032B\n\tSenderKey\022\020\n\010chainKey\030\001 \001(\014" +
|
"\007members\030\005 \003(\014\022\016\n\006admins\030\006 \003(\014\0223\n\004type\030\007" +
|
||||||
"\022\020\n\010keyIndex\030\002 \001(\r\022\021\n\tpublicKey\030\003 \001(\014\"A\n" +
|
" \001(\0162%.signalservice.ClosedGroupUpdate.T" +
|
||||||
"\004Type\022\007\n\003NEW\020\000\022\010\n\004INFO\020\001\022\026\n\022SENDER_KEY_R",
|
"ype\032B\n\tSenderKey\022\020\n\010chainKey\030\001 \001(\014\022\020\n\010ke",
|
||||||
"EQUEST\020\002\022\016\n\nSENDER_KEY\020\003\"\036\n\013NullMessage\022" +
|
"yIndex\030\002 \001(\r\022\021\n\tpublicKey\030\003 \001(\014\"A\n\004Type\022" +
|
||||||
"\017\n\007padding\030\001 \001(\014\"u\n\016ReceiptMessage\0220\n\004ty" +
|
"\007\n\003NEW\020\000\022\010\n\004INFO\020\001\022\026\n\022SENDER_KEY_REQUEST" +
|
||||||
"pe\030\001 \001(\0162\".signalservice.ReceiptMessage." +
|
"\020\002\022\016\n\nSENDER_KEY\020\003\"\036\n\013NullMessage\022\017\n\007pad" +
|
||||||
"Type\022\021\n\ttimestamp\030\002 \003(\004\"\036\n\004Type\022\014\n\010DELIV" +
|
"ding\030\001 \001(\014\"u\n\016ReceiptMessage\0220\n\004type\030\001 \001" +
|
||||||
"ERY\020\000\022\010\n\004READ\020\001\"\214\001\n\rTypingMessage\022\021\n\ttim" +
|
"(\0162\".signalservice.ReceiptMessage.Type\022\021" +
|
||||||
"estamp\030\001 \001(\004\0223\n\006action\030\002 \001(\0162#.signalser" +
|
"\n\ttimestamp\030\002 \003(\004\"\036\n\004Type\022\014\n\010DELIVERY\020\000\022" +
|
||||||
"vice.TypingMessage.Action\022\017\n\007groupId\030\003 \001" +
|
"\010\n\004READ\020\001\"\214\001\n\rTypingMessage\022\021\n\ttimestamp" +
|
||||||
"(\014\"\"\n\006Action\022\013\n\007STARTED\020\000\022\013\n\007STOPPED\020\001\"\253" +
|
"\030\001 \001(\004\0223\n\006action\030\002 \001(\0162#.signalservice.T" +
|
||||||
"\001\n\010Verified\022\023\n\013destination\030\001 \001(\t\022\023\n\013iden" +
|
"ypingMessage.Action\022\017\n\007groupId\030\003 \001(\014\"\"\n\006" +
|
||||||
"tityKey\030\002 \001(\014\022,\n\005state\030\003 \001(\0162\035.signalser",
|
"Action\022\013\n\007STARTED\020\000\022\013\n\007STOPPED\020\001\"\253\001\n\010Ver",
|
||||||
"vice.Verified.State\022\023\n\013nullMessage\030\004 \001(\014" +
|
"ified\022\023\n\013destination\030\001 \001(\t\022\023\n\013identityKe" +
|
||||||
"\"2\n\005State\022\013\n\007DEFAULT\020\000\022\014\n\010VERIFIED\020\001\022\016\n\n" +
|
"y\030\002 \001(\014\022,\n\005state\030\003 \001(\0162\035.signalservice.V" +
|
||||||
"UNVERIFIED\020\002\"\325\014\n\013SyncMessage\022-\n\004sent\030\001 \001" +
|
"erified.State\022\023\n\013nullMessage\030\004 \001(\014\"2\n\005St" +
|
||||||
"(\0132\037.signalservice.SyncMessage.Sent\0225\n\010c" +
|
"ate\022\013\n\007DEFAULT\020\000\022\014\n\010VERIFIED\020\001\022\016\n\nUNVERI" +
|
||||||
"ontacts\030\002 \001(\0132#.signalservice.SyncMessag" +
|
"FIED\020\002\"\325\014\n\013SyncMessage\022-\n\004sent\030\001 \001(\0132\037.s" +
|
||||||
"e.Contacts\0221\n\006groups\030\003 \001(\0132!.signalservi" +
|
"ignalservice.SyncMessage.Sent\0225\n\010contact" +
|
||||||
"ce.SyncMessage.Groups\0223\n\007request\030\004 \001(\0132\"" +
|
"s\030\002 \001(\0132#.signalservice.SyncMessage.Cont" +
|
||||||
".signalservice.SyncMessage.Request\022-\n\004re" +
|
"acts\0221\n\006groups\030\003 \001(\0132!.signalservice.Syn" +
|
||||||
"ad\030\005 \003(\0132\037.signalservice.SyncMessage.Rea" +
|
"cMessage.Groups\0223\n\007request\030\004 \001(\0132\".signa" +
|
||||||
"d\0223\n\007blocked\030\006 \001(\0132\".signalservice.SyncM",
|
"lservice.SyncMessage.Request\022-\n\004read\030\005 \003",
|
||||||
"essage.Blocked\022)\n\010verified\030\007 \001(\0132\027.signa" +
|
"(\0132\037.signalservice.SyncMessage.Read\0223\n\007b" +
|
||||||
"lservice.Verified\022?\n\rconfiguration\030\t \001(\013" +
|
"locked\030\006 \001(\0132\".signalservice.SyncMessage" +
|
||||||
"2(.signalservice.SyncMessage.Configurati" +
|
".Blocked\022)\n\010verified\030\007 \001(\0132\027.signalservi" +
|
||||||
"on\022\017\n\007padding\030\010 \001(\014\022M\n\024stickerPackOperat" +
|
"ce.Verified\022?\n\rconfiguration\030\t \001(\0132(.sig" +
|
||||||
"ion\030\n \003(\0132/.signalservice.SyncMessage.St" +
|
"nalservice.SyncMessage.Configuration\022\017\n\007" +
|
||||||
"ickerPackOperation\022?\n\nopenGroups\030d \003(\0132+" +
|
"padding\030\010 \001(\014\022M\n\024stickerPackOperation\030\n " +
|
||||||
".signalservice.SyncMessage.OpenGroupDeta" +
|
"\003(\0132/.signalservice.SyncMessage.StickerP" +
|
||||||
"ils\032\236\002\n\004Sent\022\023\n\013destination\030\001 \001(\t\022\021\n\ttim" +
|
"ackOperation\022?\n\nopenGroups\030d \003(\0132+.signa" +
|
||||||
"estamp\030\002 \001(\004\022+\n\007message\030\003 \001(\0132\032.signalse" +
|
"lservice.SyncMessage.OpenGroupDetails\032\236\002" +
|
||||||
"rvice.DataMessage\022 \n\030expirationStartTime",
|
"\n\004Sent\022\023\n\013destination\030\001 \001(\t\022\021\n\ttimestamp",
|
||||||
"stamp\030\004 \001(\004\022V\n\022unidentifiedStatus\030\005 \003(\0132" +
|
"\030\002 \001(\004\022+\n\007message\030\003 \001(\0132\032.signalservice." +
|
||||||
":.signalservice.SyncMessage.Sent.Unident" +
|
"DataMessage\022 \n\030expirationStartTimestamp\030" +
|
||||||
"ifiedDeliveryStatus\032G\n\032UnidentifiedDeliv" +
|
"\004 \001(\004\022V\n\022unidentifiedStatus\030\005 \003(\0132:.sign" +
|
||||||
"eryStatus\022\023\n\013destination\030\001 \001(\t\022\024\n\014uniden" +
|
"alservice.SyncMessage.Sent.UnidentifiedD" +
|
||||||
"tified\030\002 \001(\010\032a\n\010Contacts\022.\n\004blob\030\001 \001(\0132 " +
|
"eliveryStatus\032G\n\032UnidentifiedDeliverySta" +
|
||||||
".signalservice.AttachmentPointer\022\027\n\010comp" +
|
"tus\022\023\n\013destination\030\001 \001(\t\022\024\n\014unidentified" +
|
||||||
"lete\030\002 \001(\010:\005false\022\014\n\004data\030e \001(\014\032F\n\006Group" +
|
"\030\002 \001(\010\032a\n\010Contacts\022.\n\004blob\030\001 \001(\0132 .signa" +
|
||||||
"s\022.\n\004blob\030\001 \001(\0132 .signalservice.Attachme" +
|
"lservice.AttachmentPointer\022\027\n\010complete\030\002" +
|
||||||
"ntPointer\022\014\n\004data\030e \001(\014\032,\n\007Blocked\022\017\n\007nu" +
|
" \001(\010:\005false\022\014\n\004data\030e \001(\014\032F\n\006Groups\022.\n\004b" +
|
||||||
"mbers\030\001 \003(\t\022\020\n\010groupIds\030\002 \003(\014\032\217\001\n\007Reques",
|
"lob\030\001 \001(\0132 .signalservice.AttachmentPoin",
|
||||||
"t\0225\n\004type\030\001 \001(\0162\'.signalservice.SyncMess" +
|
"ter\022\014\n\004data\030e \001(\014\032,\n\007Blocked\022\017\n\007numbers\030" +
|
||||||
"age.Request.Type\"M\n\004Type\022\013\n\007UNKNOWN\020\000\022\014\n" +
|
"\001 \003(\t\022\020\n\010groupIds\030\002 \003(\014\032\217\001\n\007Request\0225\n\004t" +
|
||||||
"\010CONTACTS\020\001\022\n\n\006GROUPS\020\002\022\013\n\007BLOCKED\020\003\022\021\n\r" +
|
"ype\030\001 \001(\0162\'.signalservice.SyncMessage.Re" +
|
||||||
"CONFIGURATION\020\004\032)\n\004Read\022\016\n\006sender\030\001 \001(\t\022" +
|
"quest.Type\"M\n\004Type\022\013\n\007UNKNOWN\020\000\022\014\n\010CONTA" +
|
||||||
"\021\n\ttimestamp\030\002 \001(\004\032}\n\rConfiguration\022\024\n\014r" +
|
"CTS\020\001\022\n\n\006GROUPS\020\002\022\013\n\007BLOCKED\020\003\022\021\n\rCONFIG" +
|
||||||
"eadReceipts\030\001 \001(\010\022&\n\036unidentifiedDeliver" +
|
"URATION\020\004\032)\n\004Read\022\016\n\006sender\030\001 \001(\t\022\021\n\ttim" +
|
||||||
"yIndicators\030\002 \001(\010\022\030\n\020typingIndicators\030\003 " +
|
"estamp\030\002 \001(\004\032}\n\rConfiguration\022\024\n\014readRec" +
|
||||||
"\001(\010\022\024\n\014linkPreviews\030\004 \001(\010\032\234\001\n\024StickerPac" +
|
"eipts\030\001 \001(\010\022&\n\036unidentifiedDeliveryIndic" +
|
||||||
"kOperation\022\016\n\006packId\030\001 \001(\014\022\017\n\007packKey\030\002 " +
|
"ators\030\002 \001(\010\022\030\n\020typingIndicators\030\003 \001(\010\022\024\n" +
|
||||||
"\001(\014\022B\n\004type\030\003 \001(\01624.signalservice.SyncMe",
|
"\014linkPreviews\030\004 \001(\010\032\234\001\n\024StickerPackOpera",
|
||||||
"ssage.StickerPackOperation.Type\"\037\n\004Type\022" +
|
"tion\022\016\n\006packId\030\001 \001(\014\022\017\n\007packKey\030\002 \001(\014\022B\n" +
|
||||||
"\013\n\007INSTALL\020\000\022\n\n\006REMOVE\020\001\0322\n\020OpenGroupDet" +
|
"\004type\030\003 \001(\01624.signalservice.SyncMessage." +
|
||||||
"ails\022\013\n\003url\030\001 \001(\t\022\021\n\tchannelID\030\002 \001(\r\"\354\001\n" +
|
"StickerPackOperation.Type\"\037\n\004Type\022\013\n\007INS" +
|
||||||
"\021AttachmentPointer\022\n\n\002id\030\001 \001(\006\022\023\n\013conten" +
|
"TALL\020\000\022\n\n\006REMOVE\020\001\0322\n\020OpenGroupDetails\022\013" +
|
||||||
"tType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\022\014\n\004size\030\004 \001(\r\022\021" +
|
"\n\003url\030\001 \001(\t\022\021\n\tchannelID\030\002 \001(\r\"\354\001\n\021Attac" +
|
||||||
"\n\tthumbnail\030\005 \001(\014\022\016\n\006digest\030\006 \001(\014\022\020\n\010fil" +
|
"hmentPointer\022\n\n\002id\030\001 \001(\006\022\023\n\013contentType\030" +
|
||||||
"eName\030\007 \001(\t\022\r\n\005flags\030\010 \001(\r\022\r\n\005width\030\t \001(" +
|
"\002 \001(\t\022\013\n\003key\030\003 \001(\014\022\014\n\004size\030\004 \001(\r\022\021\n\tthum" +
|
||||||
"\r\022\016\n\006height\030\n \001(\r\022\017\n\007caption\030\013 \001(\t\022\013\n\003ur" +
|
"bnail\030\005 \001(\014\022\016\n\006digest\030\006 \001(\014\022\020\n\010fileName\030" +
|
||||||
"l\030e \001(\t\"\032\n\005Flags\022\021\n\rVOICE_MESSAGE\020\001\"\243\002\n\014" +
|
"\007 \001(\t\022\r\n\005flags\030\010 \001(\r\022\r\n\005width\030\t \001(\r\022\016\n\006h" +
|
||||||
"GroupContext\022\n\n\002id\030\001 \001(\014\022.\n\004type\030\002 \001(\0162 ",
|
"eight\030\n \001(\r\022\017\n\007caption\030\013 \001(\t\022\013\n\003url\030e \001(",
|
||||||
".signalservice.GroupContext.Type\022\014\n\004name" +
|
"\t\"\032\n\005Flags\022\021\n\rVOICE_MESSAGE\020\001\"\243\002\n\014GroupC" +
|
||||||
"\030\003 \001(\t\022\017\n\007members\030\004 \003(\t\0220\n\006avatar\030\005 \001(\0132" +
|
"ontext\022\n\n\002id\030\001 \001(\014\022.\n\004type\030\002 \001(\0162 .signa" +
|
||||||
" .signalservice.AttachmentPointer\022\016\n\006adm" +
|
"lservice.GroupContext.Type\022\014\n\004name\030\003 \001(\t" +
|
||||||
"ins\030\006 \003(\t\022\023\n\nnewMembers\030\346\007 \003(\t\022\027\n\016remove" +
|
"\022\017\n\007members\030\004 \003(\t\0220\n\006avatar\030\005 \001(\0132 .sign" +
|
||||||
"dMembers\030\347\007 \003(\t\"H\n\004Type\022\013\n\007UNKNOWN\020\000\022\n\n\006" +
|
"alservice.AttachmentPointer\022\016\n\006admins\030\006 " +
|
||||||
"UPDATE\020\001\022\013\n\007DELIVER\020\002\022\010\n\004QUIT\020\003\022\020\n\014REQUE" +
|
"\003(\t\022\023\n\nnewMembers\030\346\007 \003(\t\022\027\n\016removedMembe" +
|
||||||
"ST_INFO\020\004\"\231\002\n\016ContactDetails\022\016\n\006number\030\001" +
|
"rs\030\347\007 \003(\t\"H\n\004Type\022\013\n\007UNKNOWN\020\000\022\n\n\006UPDATE" +
|
||||||
" \001(\t\022\014\n\004name\030\002 \001(\t\0224\n\006avatar\030\003 \001(\0132$.sig" +
|
"\020\001\022\013\n\007DELIVER\020\002\022\010\n\004QUIT\020\003\022\020\n\014REQUEST_INF" +
|
||||||
"nalservice.ContactDetails.Avatar\022\r\n\005colo" +
|
"O\020\004\"\231\002\n\016ContactDetails\022\016\n\006number\030\001 \001(\t\022\014" +
|
||||||
"r\030\004 \001(\t\022)\n\010verified\030\005 \001(\0132\027.signalservic",
|
"\n\004name\030\002 \001(\t\0224\n\006avatar\030\003 \001(\0132$.signalser",
|
||||||
"e.Verified\022\022\n\nprofileKey\030\006 \001(\014\022\017\n\007blocke" +
|
"vice.ContactDetails.Avatar\022\r\n\005color\030\004 \001(" +
|
||||||
"d\030\007 \001(\010\022\023\n\013expireTimer\030\010 \001(\r\022\020\n\010nickname" +
|
"\t\022)\n\010verified\030\005 \001(\0132\027.signalservice.Veri" +
|
||||||
"\030e \001(\t\032-\n\006Avatar\022\023\n\013contentType\030\001 \001(\t\022\016\n" +
|
"fied\022\022\n\nprofileKey\030\006 \001(\014\022\017\n\007blocked\030\007 \001(" +
|
||||||
"\006length\030\002 \001(\r\"\367\001\n\014GroupDetails\022\n\n\002id\030\001 \001" +
|
"\010\022\023\n\013expireTimer\030\010 \001(\r\022\020\n\010nickname\030e \001(\t" +
|
||||||
"(\014\022\014\n\004name\030\002 \001(\t\022\017\n\007members\030\003 \003(\t\0222\n\006ava" +
|
"\032-\n\006Avatar\022\023\n\013contentType\030\001 \001(\t\022\016\n\006lengt" +
|
||||||
"tar\030\004 \001(\0132\".signalservice.GroupDetails.A" +
|
"h\030\002 \001(\r\"\367\001\n\014GroupDetails\022\n\n\002id\030\001 \001(\014\022\014\n\004" +
|
||||||
"vatar\022\024\n\006active\030\005 \001(\010:\004true\022\023\n\013expireTim" +
|
"name\030\002 \001(\t\022\017\n\007members\030\003 \003(\t\0222\n\006avatar\030\004 " +
|
||||||
"er\030\006 \001(\r\022\r\n\005color\030\007 \001(\t\022\017\n\007blocked\030\010 \001(\010" +
|
"\001(\0132\".signalservice.GroupDetails.Avatar\022" +
|
||||||
"\022\016\n\006admins\030\t \003(\t\032-\n\006Avatar\022\023\n\013contentTyp" +
|
"\024\n\006active\030\005 \001(\010:\004true\022\023\n\013expireTimer\030\006 \001" +
|
||||||
"e\030\001 \001(\t\022\016\n\006length\030\002 \001(\rBB\n+org.session.l",
|
"(\r\022\r\n\005color\030\007 \001(\t\022\017\n\007blocked\030\010 \001(\010\022\016\n\006ad",
|
||||||
"ibsignal.service.internal.pushB\023SignalSe" +
|
"mins\030\t \003(\t\032-\n\006Avatar\022\023\n\013contentType\030\001 \001(" +
|
||||||
"rviceProtos"
|
"\t\022\016\n\006length\030\002 \001(\rBB\n+org.session.libsign" +
|
||||||
|
"al.service.internal.pushB\023SignalServiceP" +
|
||||||
|
"rotos"
|
||||||
};
|
};
|
||||||
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
||||||
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
||||||
|
Loading…
Reference in New Issue
Block a user