@ -2,16 +2,17 @@
package org.session.libsession.messaging.sending_receiving
import android.content.Context
import android.util.Log
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.deferred
import org.session.libsession.messaging.Configuration
import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsession.messaging.messages.control.ClosedGroupUpdate
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI
import org.session.libsession.utilities.LKGroupUtilities
import org.session.libsession.messaging.sending_receiving.MessageSender.Error
import org.session.libsession.messaging.threads.Address
import org.session.libsession.utilities.GroupUtil
import org.session.libsignal.libsignal.ecc.Curve
import org.session.libsignal.libsignal.util.Hex
@ -26,8 +27,9 @@ import java.util.*
fun MessageSender . createClosedGroup ( name : String , members : Collection < String > ) : Promise < String , Exception > {
val deferred = deferred < String , Exception > ( )
// Prepare
val storage = MessagingConfiguration . shared . storage
val members = members
val userPublicKey = Configuration . shared . storage . getUserPublicKey ( ) !!
val userPublicKey = storage . getUserPublicKey ( ) !!
// Generate a key pair for the group
val groupKeyPair = Curve . generateKeyPair ( )
val groupPublicKey = groupKeyPair . hexEncodedPublicKey // Includes the "05" prefix
@ -41,12 +43,9 @@ fun MessageSender.createClosedGroup(name: String, members: Collection<String>):
// Create the group
val admins = setOf ( userPublicKey )
val adminsAsData = admins . map { Hex . fromStringCondensed ( it ) }
val groupID = LKGroupUtilities . getEncodedClosedGroupIDAsData ( groupPublicKey )
/ * TODO :
DatabaseFactory . getGroupDatabase ( context ) . create ( groupID , name , LinkedList < Address > ( members . map { Address . fromSerialized ( it ) } ) ,
null , null , LinkedList < Address > ( admins . map { Address . fromSerialized ( it ) } ) )
DatabaseFactory . getRecipientDatabase ( context ) . setProfileSharing ( Recipient . from ( context , Address . fromSerialized ( groupID ) , false ) , true )
* /
val groupID = GroupUtil . getEncodedClosedGroupID ( groupPublicKey )
storage . createGroup ( groupID , name , LinkedList ( members . map { Address . fromSerialized ( it ) } ) , null , null , LinkedList ( admins . map { Address . fromSerialized ( it ) } ) )
storage . setProfileSharing ( Address . fromSerialized ( groupID ) , true )
// Send a closed group update message to all members using established channels
val promises = mutableListOf < Promise < Unit , Exception > > ( )
for ( member in members ) {
@ -55,16 +54,17 @@ fun MessageSender.createClosedGroup(name: String, members: Collection<String>):
senderKeys , membersAsData , adminsAsData )
val closedGroupUpdate = ClosedGroupUpdate ( )
closedGroupUpdate . kind = closedGroupUpdateKind
val promise = MessageSender . sendNonDurably ( closedGroupUpdate , threadID )
val address = Address . fromSerialized ( member )
val promise = MessageSender . sendNonDurably ( closedGroupUpdate , address )
promises . add ( promise )
}
// Add the group to the user's set of public keys to poll for
Configuration. shared . sskDatabase . setClosedGroupPrivateKey ( groupPublicKey , groupKeyPair . hexEncodedPrivateKey )
Messaging Configuration. shared . sskDatabase . setClosedGroupPrivateKey ( groupPublicKey , groupKeyPair . hexEncodedPrivateKey )
// Notify the PN server
PushNotificationAPI . performOperation ( PushNotificationAPI . ClosedGroupOperation . Subscribe , groupPublicKey , userPublicKey )
// Notify the user
val threadID = storage . getOrCreateThreadIdFor ( Address . fromSerialized ( groupID ) )
/ * TODO
val threadID = DatabaseFactory . getThreadDatabase ( context ) . getOrCreateThreadIdFor ( Recipient . from ( context , Address . fromSerialized ( groupID ) , false ) )
insertOutgoingInfoMessage ( context , groupID , GroupContext . Type . UPDATE , name , members , admins , threadID )
* /
// Fulfill the promise
@ -75,46 +75,47 @@ fun MessageSender.createClosedGroup(name: String, members: Collection<String>):
fun MessageSender . update ( groupPublicKey : String , members : Collection < String > , name : String ) : Promise < Unit , Exception > {
val deferred = deferred < Unit , Exception > ( )
val userPublicKey = Configuration. shared . storage . getUserPublicKey ( ) !!
val sskDatabase = Configuration . shared . sskDatabase
val groupDB = DatabaseFactory . getGroupDatabase ( context )
val groupID = LK GroupUtilities . getEncodedClosedGroupID AsData ( groupPublicKey )
val group = groupDB . getGroup ( groupID ) . orNull ( )
val storage = Messaging Configuration. shared . storage
val userPublicKey = storage . getUserPublicKey ( ) !!
val sskDatabase = MessagingConfiguration . shared . sskDatabase
val groupID = GroupUtil. getEncodedClosedGroupID ( groupPublicKey )
val group = storage . getGroup ( groupID )
if ( group == null ) {
Log . d ( " Loki " , " Can't update nonexistent closed group. " )
return deferred . reject ( Error . NoThread )
deferred . reject ( Error . NoThread )
return deferred . promise
}
val oldMembers = group . members . map { it . serialize ( ) } . toSet ( )
val newMembers = members . minus ( oldMembers )
val membersAsData = members . map { Hex . fromStringCondensed ( it ) }
val admins = group . admins . map { it . serialize ( ) }
val adminsAsData = admins . map { Hex . fromStringCondensed ( it ) }
val groupPrivateKey = DatabaseFactory . getSSKDatabase ( context ) . getClosedGroupPrivateKey ( groupPublicKey )
val groupPrivateKey = sskDatabase . getClosedGroupPrivateKey ( groupPublicKey )
if ( groupPrivateKey == null ) {
Log . d ( " Loki " , " Couldn't get private key for closed group. " )
return @Thread deferred . reject ( Error . NoPrivateKey )
deferred . reject ( Error . NoPrivateKey )
return deferred . promise
}
val wasAnyUserRemoved = members . toSet ( ) . intersect ( oldMembers ) != oldMembers . toSet ( )
val removedMembers = oldMembers . minus ( members )
val isUserLeaving = removedMembers . contains ( userPublicKey )
va r newSenderKeys = listOf < ClosedGroupSenderKey > ( )
va l newSenderKeys : List < ClosedGroupSenderKey >
if ( wasAnyUserRemoved ) {
if ( isUserLeaving && removedMembers . count ( ) != 1 ) {
Log . d ( " Loki " , " Can't remove self and others simultaneously. " )
return @Thread deferred . reject ( Error . InvalidUpdate )
deferred . reject ( Error . InvalidClosedGroupUpdate )
return deferred . promise
}
// Establish sessions if needed
establishSessionsWithMembersIfNeeded ( context , members )
// Send the update to the existing members using established channels (don't include new ratchets as everyone should regenerate new ratchets individually)
for ( member in oldMembers ) {
@Suppress ( " NAME_SHADOWING " )
val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob . Kind . Info ( Hex . fromStringCondensed ( groupPublicKey ) ,
val promises = oldMembers . map { member ->
val closedGroupUpdateKind = ClosedGroupUpdate . Kind . Info ( Hex . fromStringCondensed ( groupPublicKey ) ,
name , setOf ( ) , membersAsData , adminsAsData )
@Suppress ( " NAME_SHADOWING " )
val job = ClosedGroupUpdateMessageSendJob ( member , closedGroupUpdateKind )
job . setContext ( context )
job . onRun ( ) // Run the job immediately
val closedGroupUpdate = ClosedGroupUpdate ( )
closedGroupUpdate . kind = closedGroupUpdateKind
val address = Address . fromSerialized ( member )
MessageSender . sendNonDurably ( closedGroupUpdate , address ) . get ( )
}
val allOldRatchets = sskDatabase . getAllClosedGroupRatchets ( groupPublicKey , ClosedGroupRatchetCollectionType . Current )
for ( pair in allOldRatchets ) {
val senderPublicKey = pair . first
@ -128,30 +129,30 @@ fun MessageSender.update(groupPublicKey: String, members: Collection<String>, na
// send it out to all members (minus the removed ones) using established channels.
if ( isUserLeaving ) {
sskDatabase . removeClosedGroupPrivateKey ( groupPublicKey )
groupDB . setActive ( groupID , false )
groupDB . removeMember ( groupID , Address . fromSerialized ( userPublicKey ) )
storage . setActive ( groupID , false )
storage . removeMember ( groupID , Address . fromSerialized ( userPublicKey ) )
// Notify the PN server
LokiPushNotificationManager. performOperation ( context , ClosedGroupOperation . Unsubscribe , groupPublicKey , userPublicKey )
PushNotificationAPI. performOperation ( PushNotificationAPI . ClosedGroupOperation . Unsubscribe , groupPublicKey , userPublicKey )
} else {
// Send closed group update messages to any new members using established channels
for ( member in newMembers ) {
@Suppress ( " NAME_SHADOWING " )
val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob . Kind . New ( Hex . fromStringCondensed ( groupPublicKey ) , name ,
val closedGroupUpdateKind = ClosedGroupUpdate . Kind . New ( Hex . fromStringCondensed ( groupPublicKey ) , name ,
Hex . fromStringCondensed ( groupPrivateKey ) , listOf ( ) , membersAsData , adminsAsData )
@Suppress ( " NAME_SHADOWING " )
val job = ClosedGroupUpdateMessageSendJob ( member , closedGroupUpdateKind )
ApplicationContext . getInstance ( context ) . jobManager . add ( job )
val closedGroupUpdate = ClosedGroupUpdate ( )
closedGroupUpdate . kind = closedGroupUpdateKind
val address = Address . fromSerialized ( member )
MessageSender . sendNonDurably ( closedGroupUpdate , address )
}
// Send out the user's new ratchet to all members (minus the removed ones) using established channels
val userRatchet = SharedSenderKeysImplementation . shared . generateRatchet ( groupPublicKey , userPublicKey )
val userSenderKey = ClosedGroupSenderKey ( Hex . fromStringCondensed ( userRatchet . chainKey ) , userRatchet . keyIndex , Hex . fromStringCondensed ( userPublicKey ) )
for ( member in members ) {
if ( member == userPublicKey ) { continue }
@Suppress ( " NAME_SHADOWING " )
val closedGroupUpdate Kind = ClosedGroupUpdateMessageSendJob . Kind . SenderKey ( Hex . fromStringCondensed ( groupPublicKey ) , userSenderKey )
@Suppress ( " NAME_SHADOWING " )
val job = ClosedGroupUpdateMessageSendJob ( member , closedGroupUpdateKind )
ApplicationContext. getInstance ( context ) . jobManager . add ( job )
val closedGroupUpdateKind = ClosedGroupUpdate . Kind . SenderKey ( Hex . fromStringCondensed ( groupPublicKey ) , userSenderKey )
val closedGroupUpdate = ClosedGroupUpdate ( )
closedGroupUpdate . kind = closedGroupUpdateKind
val address = Address . fromSerialized ( member )
MessageSender. sendNonDurably ( closedGroupUpdate , address )
}
}
} else if ( newMembers . isNotEmpty ( ) ) {
@ -161,49 +162,68 @@ fun MessageSender.update(groupPublicKey: String, members: Collection<String>, na
ClosedGroupSenderKey ( Hex . fromStringCondensed ( ratchet . chainKey ) , ratchet . keyIndex , Hex . fromStringCondensed ( publicKey ) )
}
// Send a closed group update message to the existing members with the new members' ratchets (this message is aimed at the group)
val closedGroupUpdateKind = ClosedGroupUpdate MessageSendJob . Kind . Info ( Hex . fromStringCondensed ( groupPublicKey ) , name ,
val closedGroupUpdateKind = ClosedGroupUpdate . Kind . Info ( Hex . fromStringCondensed ( groupPublicKey ) , name ,
newSenderKeys , membersAsData , adminsAsData )
val job = ClosedGroupUpdateMessageSendJob ( groupPublicKey , closedGroupUpdateKind )
ApplicationContext . getInstance ( context ) . jobManager . add ( job )
// Establish sessions if needed
establishSessionsWithMembersIfNeeded ( context , newMember s)
val closedGroupUpdate = ClosedGroupUpdate ( )
closedGroupUpdate . kind = closedGroupUpdateKind
val address = Address . fromSerialized ( groupID )
MessageSender . send ( closedGroupUpdate , addres s)
// Send closed group update messages to the new members using established channels
var allSenderKeys = sskDatabase . getAllClosedGroupSenderKeys ( groupPublicKey , ClosedGroupRatchetCollectionType . Current )
allSenderKeys = allSenderKeys . union ( newSenderKeys )
for ( member in newMembers ) {
@Suppress ( " NAME_SHADOWING " )
val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob . Kind . New ( Hex . fromStringCondensed ( groupPublicKey ) , name ,
val closedGroupUpdateKind = ClosedGroupUpdate . Kind . New ( Hex . fromStringCondensed ( groupPublicKey ) , name ,
Hex . fromStringCondensed ( groupPrivateKey ) , allSenderKeys , membersAsData , adminsAsData )
@Suppress ( " NAME_SHADOWING " )
val job = ClosedGroupUpdateMessageSendJob ( member , closedGroupUpdateKind )
ApplicationContext . getInstance ( context ) . jobManager . add ( job )
val closedGroupUpdate = ClosedGroupUpdate ( )
closedGroupUpdate . kind = closedGroupUpdateKind
val address = Address . fromSerialized ( member )
MessageSender . send ( closedGroupUpdate , address )
}
} else {
val allSenderKeys = sskDatabase . getAllClosedGroupSenderKeys ( groupPublicKey , ClosedGroupRatchetCollectionType . Current )
val closedGroupUpdateKind = ClosedGroupUpdate MessageSendJob . Kind . Info ( Hex . fromStringCondensed ( groupPublicKey ) , name ,
val closedGroupUpdateKind = ClosedGroupUpdate . Kind . Info ( Hex . fromStringCondensed ( groupPublicKey ) , name ,
allSenderKeys , membersAsData , adminsAsData )
val job = ClosedGroupUpdateMessageSendJob ( groupPublicKey , closedGroupUpdateKind )
ApplicationContext . getInstance ( context ) . jobManager . add ( job )
val closedGroupUpdate = ClosedGroupUpdate ( )
closedGroupUpdate . kind = closedGroupUpdateKind
val address = Address . fromSerialized ( groupID )
MessageSender . send ( closedGroupUpdate , address )
}
// Update the group
groupDB . updateTitle ( groupID , name )
storage . updateTitle ( groupID , name )
if ( !is UserLeaving ) {
// The call below sets isActive to true, so if the user is leaving we have to use groupDB.remove(...) instead
groupDB . updateMembers ( groupID , members . map { Address . fromSerialized ( it ) } )
storage . updateMembers ( groupID , members . map { Address . fromSerialized ( it ) } )
}
// Notify the user
val infoType = if ( isUserLeaving ) SignalServiceProtos . GroupContext . Type . QUIT else SignalServiceProtos . GroupContext . Type . UPDATE
val threadID = DatabaseFactory . getThreadDatabase ( context ) . getOrCreateThreadIdFor ( Recipient . from ( context , Address . fromSerialized ( groupID ) , false ) )
val threadID = storage . getOrCreateThreadIdFor ( Address . fromSerialized ( groupID ) )
/ * TODO
insertOutgoingInfoMessage ( context , groupID , infoType , name , members , admins , threadID )
* /
deferred . resolve ( Unit )
return deferred . promise
}
fun MessageSender . leave ( groupPublicKey : String ) {
val storage = MessagingConfiguration . shared . storage
val userPublicKey = storage . getUserPublicKey ( ) !!
val groupID = GroupUtil . getEncodedClosedGroupID ( groupPublicKey )
val group = storage . getGroup ( groupID )
if ( group == null ) {
Log . d ( " Loki " , " Can't leave nonexistent closed group. " )
return
}
val name = group . title
val oldMembers = group . members . map { it . serialize ( ) } . toSet ( )
val newMembers = oldMembers . minus ( userPublicKey )
return update ( groupPublicKey , newMembers , name ) . get ( )
}
fun MessageSender . requestSenderKey ( groupPublicKey : String , senderPublicKey : String ) {
Log . d ( " Loki " , " Requesting sender key for group public key: $groupPublicKey , sender public key: $senderPublicKey . " )
// Send the request
val address = Address . fromSerialized ( senderPublicKey )
val closedGroupUpdateKind = ClosedGroupUpdate . Kind . SenderKeyRequest ( Hex . fromStringCondensed ( groupPublicKey ) )
val closedGroupUpdate = ClosedGroupUpdate ( )
closedGroupUpdate . kind = closedGroupUpdateKind
MessageSender . send ( closedGroupUpdate , Destination . ClosedGroup ( groupPublicKey ) )
MessageSender . send ( closedGroupUpdate , address )
}