@ -14,21 +14,29 @@ import PromiseKit
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				public  final  class  ClosedGroupsProtocol  :  NSObject  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				    public  static  let  isSharedSenderKeysEnabled  =  true 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				    // /   -   N o t e :   I t ' s   r e c o m m e n d e d   t o   b a t c h   f e t c h   t h e   d e v i c e   l i n k s   f o r   t h e   g i v e n   s e t   o f   m e m b e r s   b e f o r e   i n v o k i n g   t h i s ,   t o   a v o i d 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				    // /   th e   m e s s a g e   s e n d i n g   p i p e l i n e    ma k i n g   a   r e q u e s t   f o r   e a c h   m e m b e r . 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				    // /   -   N o t e :   I t ' s   r e c o m m e n d e d   t o   b a t c h   f e t c h   t h e   d e v i c e   l i n k s   f o r   t h e   g i v e n   s e t   o f   m e m b e r s   b e f o r e   i n v o k i n g   t h i s ,   t o   a v o i d   t h e   m e s s a g e   s e n d i n g   p i p e l i n e 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				    // /    ma k i n g   a   r e q u e s t   f o r   e a c h   m e m b e r . 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				    public  static  func  createClosedGroup ( name :  String ,  members  membersAsSet :  Set < String > ,  transaction :  YapDatabaseReadWriteTransaction )  ->  TSGroupThread  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   P r e p a r e 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        var  membersAsSet  =  membersAsSet 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  messageSenderJobQueue  =  SSKEnvironment . shared . messageSenderJobQueue 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  userPublicKey  =  getUserHexEncodedPublicKey ( ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   G e n e r a t e   a   k e y   p a i r   f o r   t h e   g r o u p 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  groupKeyPair  =  Curve25519 . generateKeyPair ( ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  groupPublicKey  =  groupKeyPair . hexEncodedPublicKey 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   E n s u r e   t h e   c u r r e n t   u s e r ' s   m a s t e r   d e v i c e   i s    in c l u d e d   i n   t h e   m e m b e r   l i s t 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  groupPublicKey  =  groupKeyPair . hexEncodedPublicKey  //   I n c l u d e s   t h e   " 0 5 "   p r e f i x  
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   E n s u r e   t h e   c u r r e n t   u s e r ' s   m a s t e r   d e v i c e   i s   th e   o n e   t h a t ' s    in c l u d e d   i n   t h e   m e m b e r   l i s t 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        membersAsSet . remove ( userPublicKey ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        membersAsSet . insert ( UserDefaults . standard [ . masterHexEncodedPublicKey ]  ? ?  userPublicKey ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   C r e a t e   r a t c h e t s   f o r   a l l   u s e r s   i n v o l v e d 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  members  =  [ String ] ( membersAsSet )  //   O n   t h e   r e c e i v i n g   s i d e   i t ' s   a s s u m e d   t h a t   t h e   m e m b e r   l i s t   a n d   c h a i n   k e y   l i s t   a r e   o r d e r e d   t h e   s a m e 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  ratchets  =  members . map  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   C r e a t e   r a t c h e t s   f o r   a l l   m e m b e r s   ( a n d   t h e i r   l i n k e d   d e v i c e s ) .   T h e   s o r t i n g   t h a t   h a p p e n s   i s   n e e d e d   b e c a u s e   t h e   r e c e i v i n g   e n d   a s s u m e s   t h a t   t h e   m e m b e r 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   l i s t   a n d   s e n d e r   k e y   l i s t   a r e   o r d e r e d   t h e   s a m e . 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        var  membersAndLinkedDevicesAsSet :  Set < String >  =  [ ] 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        for  member  in  membersAsSet  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            let  deviceLinks  =  OWSPrimaryStorage . shared ( ) . getDeviceLinks ( for :  member ,  in :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            membersAndLinkedDevicesAsSet . formUnion ( deviceLinks . flatMap  {  [  $0 . master . hexEncodedPublicKey ,  $0 . slave . hexEncodedPublicKey  ]  } ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        } 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  members  =  [ String ] ( membersAsSet ) . sorted ( ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  membersAndLinkedDevices  =  [ String ] ( membersAndLinkedDevicesAsSet ) . sorted ( ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  ratchets  =  membersAndLinkedDevices . map  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            SharedSenderKeysImplementation . shared . generateRatchet ( for :  groupPublicKey ,  senderPublicKey :  $0 ,  using :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        } 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   C r e a t e   t h e   g r o u p 
 
			
		 
		
	
	
		
			
				
					
						
						
						
							
								 
							 
						
					 
				
			
			 
			 
			
				@ -39,15 +47,15 @@ public final class ClosedGroupsProtocol : NSObject {
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        thread . usesSharedSenderKeys  =  true 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        thread . save ( with :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        SSKEnvironment . shared . profileManager . addThread ( toProfileWhitelist :  thread ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   E s t a b l i s h   s e s s i o n s   i f   n e e d e d   ( s h o u l d n ' t   b e   n e c e s s a r y   u n d e r   n o r m a l   c i r c u m s t a n c e s   a s 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   t h e   u s e r   c a n   o n l y   p i c k   f r o m   e x i s t i n g   c o n t a c t s ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        establishSessionsIfNeeded ( with :  members ,  using :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   S e n d   a   c l o s e d   g r o u p   u p d a t e   m e s s a g e   t o   a l l   m e m b e r s   i n v o l v e d   u s i n g   e s t a b l i s h e d   c h a n n e l s 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   E s t a b l i s h   s e s s i o n s   i f   n e e d e d 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        establishSessionsIfNeeded ( with :  members ,  using :  transaction )  //   N o t   ` m e m b e r s A n d L i n k e d D e v i c e s `   a s   t h i s   i n t e r n a l l y   t a k e s   c a r e   o f   m u l t i   d e v i c e   a l r e a d y 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   S e n d   a   c l o s e d   g r o u p   u p d a t e   m e s s a g e   t o   a l l   m e m b e r s   ( a n d   t h e i r   l i n k e d   d e v i c e s )   u s i n g   e s t a b l i s h e d   c h a n n e l s 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  senderKeys  =  ratchets . map  {  ClosedGroupSenderKey ( chainKey :  Data ( hex :  $0 . chainKey ) ,  keyIndex :  $0 . keyIndex )  } 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        for  member  in  members  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        for  member  in  members  {  //   N o t   ` m e m b e r s A n d L i n k e d D e v i c e s `   a s   t h i s   i n t e r n a l l y   t a k e s   c a r e   o f   m u l t i   d e v i c e   a l r e a d y  
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            let  thread  =  TSContactThread . getOrCreateThread ( withContactId :  member ,  transaction :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            thread . save ( with :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            let  closedGroupUpdateMessageKind  =  ClosedGroupUpdateMessage . Kind . new ( groupPublicKey :  Data ( hex :  groupPublicKey ) ,  name :  name ,  groupPrivateKey :  groupKeyPair . privateKey ,  senderKeys :  senderKeys ,  members :  members ,  admins :  admins ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            let  closedGroupUpdateMessageKind  =  ClosedGroupUpdateMessage . Kind . new ( groupPublicKey :  Data ( hex :  groupPublicKey ) ,  name :  name , 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				                groupPrivateKey :  groupKeyPair . privateKey ,  senderKeys :  senderKeys ,  members :  members ,  admins :  admins ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            let  closedGroupUpdateMessage  =  ClosedGroupUpdateMessage ( thread :  thread ,  kind :  closedGroupUpdateMessageKind ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            messageSenderJobQueue . add ( message :  closedGroupUpdateMessage ,  transaction :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        } 
 
			
		 
		
	
	
		
			
				
					
						
							
								 
							 
						
						
							
								 
							 
						
						
					 
				
			
			 
			 
			
				@ -76,28 +84,35 @@ public final class ClosedGroupsProtocol : NSObject {
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   A d d   t h e   m e m b e r s   t o   t h e   m e m b e r   l i s t 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        var  members  =  group . groupMemberIds 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        members . append ( contentsOf :  newMembersAsSet ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   E s t a b l i s h   s e s s i o n s   i f   n e e d e d   ( s h o u l d n ' t   b e   n e c e s s a r y   u n d e r   n o r m a l   c i r c u m s t a n c e s   a s 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   t h e   u s e r   c a n   o n l y   p i c k   f r o m   e x i s t i n g   c o n t a c t s ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        establishSessionsIfNeeded ( with :  members ,  using :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   G e n e r a t e   r a t c h e t s   f o r   t h e   n e w   m e m b e r s 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  newMembers  =  [ String ] ( newMembersAsSet )  //   O n   t h e   r e c e i v i n g   s i d e   i t ' s   a s s u m e d   t h a t   t h e   m e m b e r   l i s t   a n d   c h a i n   k e y   l i s t   a r e   o r d e r e d   t h e   s a m e 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  ratchets  =  newMembers . map  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   G e n e r a t e   r a t c h e t s   f o r   t h e   n e w   m e m b e r s   ( a n d   t h e i r   l i n k e d   d e v i c e s ) .   T h e   s o r t i n g   t h a t   h a p p e n s   i s   n e e d e d   b e c a u s e   t h e   r e c e i v i n g   e n d   a s s u m e s   t h a t   t h e   m e m b e r 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   l i s t   a n d   s e n d e r   k e y   l i s t   a r e   o r d e r e d   t h e   s a m e . 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        var  newMembersAndLinkedDevicesAsSet :  Set < String >  =  [ ] 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        for  member  in  newMembersAsSet  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            let  deviceLinks  =  OWSPrimaryStorage . shared ( ) . getDeviceLinks ( for :  member ,  in :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            newMembersAndLinkedDevicesAsSet . formUnion ( deviceLinks . flatMap  {  [  $0 . master . hexEncodedPublicKey ,  $0 . slave . hexEncodedPublicKey  ]  } ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        } 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        members  =  members . sorted ( ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  newMembersAndLinkedDevices  =  [ String ] ( newMembersAndLinkedDevicesAsSet ) . sorted ( ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  ratchets  =  newMembersAndLinkedDevices . map  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            SharedSenderKeysImplementation . shared . generateRatchet ( for :  groupPublicKey ,  senderPublicKey :  $0 ,  using :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        } 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   S e n d   a   c l o s e d   g r o u p   u p d a t e   m e s s a g e   t o   t h e   e x i s t i n g   m e m b e r s   w i t h   t h e   n e w   m e m b e r s '   r a t c h e t s   ( t h i s   m e s s a g e   i s 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   a i m e d   a t   t h e   g r o u p ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   S e n d   a   c l o s e d   g r o u p   u p d a t e   m e s s a g e   t o   t h e   e x i s t i n g   m e m b e r s   w i t h   t h e   n e w   m e m b e r s '   r a t c h e t s   ( t h i s   m e s s a g e   i s   a i m e d   a t   t h e   g r o u p ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  senderKeys  =  ratchets . map  {  ClosedGroupSenderKey ( chainKey :  Data ( hex :  $0 . chainKey ) ,  keyIndex :  $0 . keyIndex )  } 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  closedGroupUpdateMessageKind  =  ClosedGroupUpdateMessage . Kind . info ( groupPublicKey :  Data ( hex :  groupPublicKey ) ,  name :  name ,  senderKeys :  senderKeys ,  members :  members ,  admins :  admins ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  closedGroupUpdateMessageKind  =  ClosedGroupUpdateMessage . Kind . info ( groupPublicKey :  Data ( hex :  groupPublicKey ) ,  name :  name ,  senderKeys :  senderKeys , 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            members :  members ,  admins :  admins ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  closedGroupUpdateMessage  =  ClosedGroupUpdateMessage ( thread :  thread ,  kind :  closedGroupUpdateMessageKind ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        messageSenderJobQueue . add ( message :  closedGroupUpdateMessage ,  transaction :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   S e n d   c l o s e d   g r o u p   u p d a t e   m e s s a g e s   t o   t h e   n e w   m e m b e r s   u s i n g   e s t a b l i s h e d   c h a n n e l s 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   E s t a b l i s h   s e s s i o n s   i f   n e e d e d 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        establishSessionsIfNeeded ( with :  [ String ] ( newMembersAsSet ) ,  using :  transaction )  //   N o t   ` n e w M e m b e r s A n d L i n k e d D e v i c e s `   a s   t h i s   i n t e r n a l l y   t a k e s   c a r e   o f   m u l t i   d e v i c e   a l r e a d y 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   S e n d   c l o s e d   g r o u p   u p d a t e   m e s s a g e s   t o   t h e   n e w   m e m b e r s   ( a n d   t h e i r   l i n k e d   d e v i c e s )   u s i n g   e s t a b l i s h e d   c h a n n e l s 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  allSenderKeys  =  Storage . getAllClosedGroupRatchets ( for :  groupPublicKey ) . map  {  //   T h i s   i n c l u d e s   t h e   n e w l y   g e n e r a t e d   r a t c h e t s 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            ClosedGroupSenderKey ( chainKey :  Data ( hex :  $0 . chainKey ) ,  keyIndex :  $0 . keyIndex ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        } 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        for  member  in  newMembers  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        for  member  in  newMembers AsSet  {  //   N o t   ` n e w M e m b e r s A n d L i n k e d D e v i c e s `   a s   t h i s   i n t e r n a l l y   t a k e s   c a r e   o f   m u l t i   d e v i c e   a l r e a d y  
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            let  thread  =  TSContactThread . getOrCreateThread ( contactId :  member ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            thread . save ( with :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            let  closedGroupUpdateMessageKind  =  ClosedGroupUpdateMessage . Kind . new ( groupPublicKey :  Data ( hex :  groupPublicKey ) ,  name :  name ,  groupPrivateKey :  Data ( hex :  groupPrivateKey ) ,  senderKeys :  allSenderKeys ,  members :  members ,  admins :  admins ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            let  closedGroupUpdateMessageKind  =  ClosedGroupUpdateMessage . Kind . new ( groupPublicKey :  Data ( hex :  groupPublicKey ) ,  name :  name , 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				                groupPrivateKey :  Data ( hex :  groupPrivateKey ) ,  senderKeys :  allSenderKeys ,  members :  members ,  admins :  admins ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            let  closedGroupUpdateMessage  =  ClosedGroupUpdateMessage ( thread :  thread ,  kind :  closedGroupUpdateMessageKind ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            messageSenderJobQueue . add ( message :  closedGroupUpdateMessage ,  transaction :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        } 
 
			
		 
		
	
	
		
			
				
					
						
						
						
							
								 
							 
						
					 
				
			
			 
			 
			
				@ -115,12 +130,12 @@ public final class ClosedGroupsProtocol : NSObject {
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				    } 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				    public  static  func  removeMembers ( _  membersToRemove :  Set < String > ,  from  groupPublicKey :  String ,  using  transaction :  YapDatabaseReadWriteTransaction )  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   P r e p a r e 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  userPublicKey  =  getUserHexEncodedPublicKey ( ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  isUserLeaving  =  membersToRemove . contains ( userPublicKey ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        guard  ! isUserLeaving  ||  membersToRemove . count  = =  1  else  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            return  print ( " [Loki] Can't remove self and others simultaneously. " ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        } 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   P r e p a r e 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  messageSenderJobQueue  =  SSKEnvironment . shared . messageSenderJobQueue 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  groupID  =  LKGroupUtilities . getEncodedClosedGroupID ( groupPublicKey ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        guard  let  thread  =  TSGroupThread . fetch ( uniqueId :  groupID ,  transaction :  transaction )  else  { 
 
			
		 
		
	
	
		
			
				
					
						
						
						
							
								 
							 
						
					 
				
			
			 
			 
			
				@ -137,26 +152,26 @@ public final class ClosedGroupsProtocol : NSObject {
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        } 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        indexes . forEach  {  members . remove ( at :  $0 )  } 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   S e n d   t h e   u p d a t e   t o   t h e   g r o u p   ( d o n ' t   i n c l u d e   n e w   r a t c h e t s   a s   e v e r y o n e   s h o u l d   g e n e r a t e   n e w   r a t c h e t s   i n d i v i d u a l l y ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  closedGroupUpdateMessageKind  =  ClosedGroupUpdateMessage . Kind . info ( groupPublicKey :  Data ( hex :  groupPublicKey ) ,  name :  name ,  senderKeys :  [ ] ,  members :  members ,  admins :  admins ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  closedGroupUpdateMessageKind  =  ClosedGroupUpdateMessage . Kind . info ( groupPublicKey :  Data ( hex :  groupPublicKey ) ,  name :  name ,  senderKeys :  [ ] , 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            members :  members ,  admins :  admins ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  closedGroupUpdateMessage  =  ClosedGroupUpdateMessage ( thread :  thread ,  kind :  closedGroupUpdateMessageKind ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        messageSenderJobQueue . add ( message :  closedGroupUpdateMessage ,  transaction :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   D e l e t e   a l l   r a t c h e t s   ( i t ' s   i m p o r t a n t   t h a t   t h i s   h a p p e n s   a f t e r   s e n d i n g   o u t   t h e   u p d a t e ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        Storage . removeAllClosedGroupRatchets ( for :  groupPublicKey ,  using :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   R e m o v e   t h e   g r o u p   f r o m   t h e   u s e r ' s   s e t   o f   p u b l i c   k e y s   t o   p o l l   f o r   i f   t h e   u s e r   i s   l e a v i n g .   O t h e r w i s e   g e n e r a t e 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   a  n e w   r a t c h e t   a n d   s e n d   i t   o u t   t o   a l l    me m b e r s   ( m i n u s   t h e   r e m o v e d   o n e s )   u s i n g   e s t a b l i s h e d   c h a n n e l s . 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   R e m o v e   t h e   g r o u p   f r o m   t h e   u s e r ' s   s e t   o f   p u b l i c   k e y s   t o   p o l l   f o r   i f   t h e   u s e r   i s   l e a v i n g .   O t h e r w i s e   g e n e r a t e   a   n e w   r a t c h e t   a n d   s e n d   i t   o u t   t o   a l l 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //    me m b e r s   ( m i n u s   t h e   r e m o v e d   o n e s )   a n d   t h e i r   l i n k e d   d e v i c e s   u s i n g   e s t a b l i s h e d   c h a n n e l s . 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        if  isUserLeaving  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            Storage . removeClosedGroupPrivateKey ( for :  groupPublicKey ,  using :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        }  else  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            //   E s t a b l i s h   s e s s i o n s   i f   n e e d e d   ( s h o u l d n ' t   b e   n e c e s s a r y   u n d e r   n o r m a l   c i r c u m s t a n c e s   a s 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            //   s e s s i o n s   w o u l d ' v e   a l r e a d y   b e e n   e s t a b l i s h e d   p r e v i o u s l y ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            establishSessionsIfNeeded ( with :  members ,  using :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            //   S e n d   o u t   t h e   u s e r ' s   n e w   r a t c h e t   t o   a l l   m e m b e r s   ( m i n u s   t h e   r e m o v e d   o n e s )   u s i n g   e s t a b l i s h e d   c h a n n e l s 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            let  newRatchet  =  SharedSenderKeysImplementation . shared . generateRatchet ( for :  groupPublicKey ,  senderPublicKey :  userPublicKey ,  using :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            let  newSenderKey  =  ClosedGroupSenderKey ( chainKey :  Data ( hex :  newRatchet . chainKey ) ,  keyIndex :  newRatchet . keyIndex ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            for  member  in  members  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            //   E s t a b l i s h   s e s s i o n s   i f   n e e d e d 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            establishSessionsIfNeeded ( with :  members ,  using :  transaction )  //   T h i s   i n t e r n a l l y   t a k e s   c a r e   o f   m u l t i   d e v i c e 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            //   S e n d   o u t   t h e   u s e r ' s   n e w   r a t c h e t   t o   a l l   m e m b e r s   ( m i n u s   t h e   r e m o v e d   o n e s )   a n d   t h e i r   l i n k e d   d e v i c e s   u s i n g   e s t a b l i s h e d   c h a n n e l s 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            let  userRatchet  =  SharedSenderKeysImplementation . shared . generateRatchet ( for :  groupPublicKey ,  senderPublicKey :  userPublicKey ,  using :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            let  userSenderKey  =  ClosedGroupSenderKey ( chainKey :  Data ( hex :  userRatchet . chainKey ) ,  keyIndex :  userRatchet . keyIndex ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            for  member  in  members  {  //   T h i s   i n t e r n a l l y   t a k e s   c a r e   o f   m u l t i   d e v i c e 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				                let  thread  =  TSContactThread . getOrCreateThread ( withContactId :  member ,  transaction :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				                thread . save ( with :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				                let  closedGroupUpdateMessageKind  =  ClosedGroupUpdateMessage . Kind . senderKey ( groupPublicKey :  Data ( hex :  groupPublicKey ) ,  senderKey :  new SenderKey) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				                let  closedGroupUpdateMessageKind  =  ClosedGroupUpdateMessage . Kind . senderKey ( groupPublicKey :  Data ( hex :  groupPublicKey ) ,  senderKey :  user SenderKey) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				                let  closedGroupUpdateMessage  =  ClosedGroupUpdateMessage ( thread :  thread ,  kind :  closedGroupUpdateMessageKind ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				                messageSenderJobQueue . add ( message :  closedGroupUpdateMessage ,  transaction :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            } 
 
			
		 
		
	
	
		
			
				
					
						
							
								 
							 
						
						
							
								 
							 
						
						
					 
				
			
			 
			 
			
				@ -210,12 +225,13 @@ public final class ClosedGroupsProtocol : NSObject {
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  infoMessage  =  TSInfoMessage ( timestamp :  NSDate . ows_millisecondTimeStamp ( ) ,  in :  thread ,  messageType :  . typeGroupUpdate ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        infoMessage . save ( with :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   E s t a b l i s h   s e s s i o n s   i f   n e e d e d 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        establishSessionsIfNeeded ( with :  members ,  using :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        establishSessionsIfNeeded ( with :  members ,  using :  transaction )  //   T h i s   i n t e r n a l l y   t a k e s   c a r e   o f   m u l t i   d e v i c e  
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				    } 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				    // /   I n v o k e d   u p o n   r e c e i v i n g   a   g r o u p   u p d a t e .   A   g r o u p   u p d a t e   i s   s e n t   o u t   w h e n   a   g r o u p ' s   n a m e   i s   c h a n g e d ,   w h e n   n e w   u s e r s 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				    // /   a r e   a d d e d ,   w h e n   u s e r s   l e a v e   o r   a r e   k i c k e d ,   o r   i f   t h e   g r o u p   a d m i n s   a r e   c h a n g e d . 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				    private  static  func  handleInfoMessage ( _  closedGroupUpdate :  SSKProtoDataMessageClosedGroupUpdate ,  from  senderPublicKey :  String ,  using  transaction :  YapDatabaseReadWriteTransaction )  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				    // /   I n v o k e d   u p o n   r e c e i v i n g   a   g r o u p   u p d a t e .   A   g r o u p   u p d a t e   i s   s e n t   o u t   w h e n   a   g r o u p ' s   n a m e   i s   c h a n g e d ,   w h e n   n e w   u s e r s   a r e   a d d e d ,   w h e n   u s e r s   l e a v e   o r   a r e 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				    // /   k i c k e d ,   o r   i f   t h e   g r o u p   a d m i n s   a r e   c h a n g e d . 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				    private  static  func  handleInfoMessage ( _  closedGroupUpdate :  SSKProtoDataMessageClosedGroupUpdate ,  from  senderPublicKey :  String , 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				         using  transaction :  YapDatabaseReadWriteTransaction )  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   U n w r a p   t h e   m e s s a g e 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  groupPublicKey  =  closedGroupUpdate . groupPublicKey . toHexString ( ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  name  =  closedGroupUpdate . name 
 
			
		 
		
	
	
		
			
				
					
						
						
						
							
								 
							 
						
					 
				
			
			 
			 
			
				@ -237,19 +253,15 @@ public final class ClosedGroupsProtocol : NSObject {
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            return  print ( " [Loki] Ignoring closed group update from non-admin. " ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        } 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   E s t a b l i s h   s e s s i o n s   i f   n e e d e d   ( i t ' s   i m p o r t a n t   t h a t   t h i s   h a p p e n s   b e f o r e   t h e   c o d e   b e l o w ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        establishSessionsIfNeeded ( with :  members ,  using :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   P a r s e   o u t   a n y   n e w   m e m b e r s   a n d   s t o r e   t h e i r   r a t c h e t s   ( i t ' s   i m p o r t a n t   t h a t 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   t h i s   h a p p e n s   b e f o r e   t h e   c o d e   b e l o w ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        establishSessionsIfNeeded ( with :  members ,  using :  transaction )  //   T h i s   i n t e r n a l l y   t a k e s   c a r e   o f   m u l t i   d e v i c e 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   P a r s e   o u t   a n y   n e w   m e m b e r s   a n d   s t o r e   t h e i r   r a t c h e t s   ( i t ' s   i m p o r t a n t   t h a t   t h i s   h a p p e n s   b e f o r e   t h e   c o d e   b e l o w ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  oldMembers  =  group . groupMemberIds 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        let  newMembers  =  members . filter  {  ! oldMembers . contains ( $0 )  } 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        if  newMembers . count  = =  senderKeys . count  {  //   I f   s o m e o n e   l e f t   o r   w a s   k i c k e d   t h e   m e s s a g e   w o n ' t   h a v e   a n y   s e n d e r   k e y s 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        zip ( newMembers ,  senderKeys ) . forEach  {  ( member ,  senderKey )  in 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            let  ratchet  =  ClosedGroupRatchet ( chainKey :  senderKey . chainKey . toHexString ( ) ,  keyIndex :  UInt ( senderKey . keyIndex ) ,  messageKeys :  [ ] ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            Storage . setClosedGroupRatchet ( for :  groupPublicKey ,  senderPublicKey :  member ,  ratchet :  ratchet ,  using :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        } 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        } 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   D e l e t e   a l l   r a t c h e t s   a n d   s e n d   o u t   t h e   u s e r ' s   n e w   r a t c h e t   u s i n g   e s t a b l i s h e d 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   c h a n n e l s   i f   a n y   m e m b e r   o f   t h e   g r o u p   l e f t   o r   w a s   r e m o v e d 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        //   D e l e t e   a l l   r a t c h e t s   a n d   s e n d   o u t   t h e   u s e r ' s   n e w   r a t c h e t   u s i n g   e s t a b l i s h e d   c h a n n e l s   i f   a n y   m e m b e r   o f   t h e   g r o u p   l e f t   o r   w a s   r e m o v e d 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				        if  Set ( members ) . intersection ( oldMembers )  !=  Set ( oldMembers )  { 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            Storage . removeAllClosedGroupRatchets ( for :  groupPublicKey ,  using :  transaction ) 
 
			
		 
		
	
		
			
				 
				 
			
			 
			 
			
				            let  userPublicKey  =  getUserHexEncodedPublicKey ( )