2021-10-15 19:39:53 +00:00
package org.thoughtcrime.securesms.database
import android.app.Application
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
2021-12-08 18:22:36 +00:00
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers
2021-10-15 19:39:53 +00:00
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
2022-03-24 17:23:23 +00:00
import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.SignalProtocolAddress
import org.signal.libsignal.protocol.state.SessionRecord
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
2021-10-15 19:39:53 +00:00
import org.signal.storageservice.protos.groups.local.DecryptedGroup
import org.signal.storageservice.protos.groups.local.DecryptedMember
2021-12-08 18:22:36 +00:00
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
2022-02-24 17:40:28 +00:00
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.DistributionListRecord
2021-10-15 19:39:53 +00:00
import org.thoughtcrime.securesms.database.model.Mention
2021-11-11 18:12:51 +00:00
import org.thoughtcrime.securesms.database.model.MessageId
2021-10-15 19:39:53 +00:00
import org.thoughtcrime.securesms.database.model.MessageRecord
2021-11-11 18:12:51 +00:00
import org.thoughtcrime.securesms.database.model.ReactionRecord
2021-10-15 19:39:53 +00:00
import org.thoughtcrime.securesms.groups.GroupId
2022-02-02 23:35:16 +00:00
import org.thoughtcrime.securesms.keyvalue.SignalStore
2021-10-15 19:39:53 +00:00
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
2021-12-08 18:22:36 +00:00
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
2021-10-15 19:39:53 +00:00
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.sms.IncomingTextMessage
import org.thoughtcrime.securesms.util.CursorUtil
2021-10-28 19:39:36 +00:00
import org.whispersystems.signalservice.api.push.ACI
2022-02-02 23:35:16 +00:00
import org.whispersystems.signalservice.api.push.PNI
2021-10-15 19:39:53 +00:00
import org.whispersystems.signalservice.api.util.UuidUtil
2022-03-15 00:49:40 +00:00
import java.util.Optional
2021-10-15 19:39:53 +00:00
import java.util.UUID
@RunWith ( AndroidJUnit4 :: class )
class RecipientDatabaseTest _merges {
private lateinit var recipientDatabase : RecipientDatabase
private lateinit var identityDatabase : IdentityDatabase
private lateinit var groupReceiptDatabase : GroupReceiptDatabase
private lateinit var groupDatabase : GroupDatabase
private lateinit var threadDatabase : ThreadDatabase
private lateinit var smsDatabase : MessageDatabase
private lateinit var mmsDatabase : MessageDatabase
private lateinit var sessionDatabase : SessionDatabase
private lateinit var mentionDatabase : MentionDatabase
2021-11-11 18:12:51 +00:00
private lateinit var reactionDatabase : ReactionDatabase
2021-12-08 18:22:36 +00:00
private lateinit var notificationProfileDatabase : NotificationProfileDatabase
2022-02-24 17:40:28 +00:00
private lateinit var distributionListDatabase : DistributionListDatabase
2021-10-15 19:39:53 +00:00
2022-02-17 20:55:54 +00:00
private val localAci = ACI . from ( UUID . randomUUID ( ) )
private val localPni = PNI . from ( UUID . randomUUID ( ) )
2022-02-02 23:35:16 +00:00
2021-10-15 19:39:53 +00:00
@Before
fun setup ( ) {
2021-11-18 17:36:52 +00:00
recipientDatabase = SignalDatabase . recipients
identityDatabase = SignalDatabase . identities
groupReceiptDatabase = SignalDatabase . groupReceipts
groupDatabase = SignalDatabase . groups
threadDatabase = SignalDatabase . threads
smsDatabase = SignalDatabase . sms
mmsDatabase = SignalDatabase . mms
sessionDatabase = SignalDatabase . sessions
mentionDatabase = SignalDatabase . mentions
reactionDatabase = SignalDatabase . reactions
2021-12-08 18:22:36 +00:00
notificationProfileDatabase = SignalDatabase . notificationProfiles
2022-02-24 17:40:28 +00:00
distributionListDatabase = SignalDatabase . distributionLists
2021-10-15 19:39:53 +00:00
2022-02-02 23:35:16 +00:00
SignalStore . account ( ) . setAci ( localAci )
SignalStore . account ( ) . setPni ( localPni )
2021-10-15 19:39:53 +00:00
}
/** High trust lets you merge two different users into one. You should prefer the ACI user. Not shown: merging threads, dropping e164 sessions, etc. */
@Test
fun getAndPossiblyMerge _general ( ) {
// Setup
2022-02-17 20:55:54 +00:00
val recipientIdAci : RecipientId = recipientDatabase . getOrInsertFromServiceId ( ACI _A )
2021-10-15 19:39:53 +00:00
val recipientIdE164 : RecipientId = recipientDatabase . getOrInsertFromE164 ( E164 _A )
2022-02-17 20:55:54 +00:00
val recipientIdAciB : RecipientId = recipientDatabase . getOrInsertFromServiceId ( ACI _B )
2021-10-15 19:39:53 +00:00
val smsId1 : Long = smsDatabase . insertMessageInbox ( smsMessage ( sender = recipientIdAci , time = 0 , body = " 0 " ) ) . get ( ) . messageId
val smsId2 : Long = smsDatabase . insertMessageInbox ( smsMessage ( sender = recipientIdE164 , time = 1 , body = " 1 " ) ) . get ( ) . messageId
val smsId3 : Long = smsDatabase . insertMessageInbox ( smsMessage ( sender = recipientIdAci , time = 2 , body = " 2 " ) ) . get ( ) . messageId
val mmsId1 : Long = mmsDatabase . insertSecureDecryptedMessageInbox ( mmsMessage ( sender = recipientIdAci , time = 3 , body = " 3 " ) , - 1 ) . get ( ) . messageId
val mmsId2 : Long = mmsDatabase . insertSecureDecryptedMessageInbox ( mmsMessage ( sender = recipientIdE164 , time = 4 , body = " 4 " ) , - 1 ) . get ( ) . messageId
val mmsId3 : Long = mmsDatabase . insertSecureDecryptedMessageInbox ( mmsMessage ( sender = recipientIdAci , time = 5 , body = " 5 " ) , - 1 ) . get ( ) . messageId
val threadIdAci : Long = threadDatabase . getThreadIdFor ( recipientIdAci ) !!
val threadIdE164 : Long = threadDatabase . getThreadIdFor ( recipientIdE164 ) !!
assertNotEquals ( threadIdAci , threadIdE164 )
mentionDatabase . insert ( threadIdAci , mmsId1 , listOf ( Mention ( recipientIdE164 , 0 , 1 ) ) )
mentionDatabase . insert ( threadIdE164 , mmsId2 , listOf ( Mention ( recipientIdAci , 0 , 1 ) ) )
groupReceiptDatabase . insert ( listOf ( recipientIdAci , recipientIdE164 ) , mmsId1 , 0 , 3 )
val identityKeyAci : IdentityKey = identityKey ( 1 )
val identityKeyE164 : IdentityKey = identityKey ( 2 )
identityDatabase . saveIdentity ( ACI_A . toString ( ) , recipientIdAci , identityKeyAci , IdentityDatabase . VerifiedStatus . VERIFIED , false , 0 , false )
identityDatabase . saveIdentity ( E164 _A , recipientIdE164 , identityKeyE164 , IdentityDatabase . VerifiedStatus . VERIFIED , false , 0 , false )
2022-02-02 23:35:16 +00:00
sessionDatabase . store ( localAci , SignalProtocolAddress ( ACI_A . toString ( ) , 1 ) , SessionRecord ( ) )
2021-10-15 19:39:53 +00:00
2021-11-11 18:12:51 +00:00
reactionDatabase . addReaction ( MessageId ( smsId1 , false ) , ReactionRecord ( " a " , recipientIdAci , 1 , 1 ) )
reactionDatabase . addReaction ( MessageId ( mmsId1 , true ) , ReactionRecord ( " b " , recipientIdE164 , 1 , 1 ) )
2021-12-08 18:22:36 +00:00
val profile1 : NotificationProfile = notificationProfile ( name = " Test " )
val profile2 : NotificationProfile = notificationProfile ( name = " Test2 " )
notificationProfileDatabase . addAllowedRecipient ( profileId = profile1 . id , recipientId = recipientIdAci )
notificationProfileDatabase . addAllowedRecipient ( profileId = profile1 . id , recipientId = recipientIdE164 )
notificationProfileDatabase . addAllowedRecipient ( profileId = profile2 . id , recipientId = recipientIdE164 )
notificationProfileDatabase . addAllowedRecipient ( profileId = profile2 . id , recipientId = recipientIdAciB )
2022-02-24 17:40:28 +00:00
val distributionListId : DistributionListId = distributionListDatabase . createList ( " testlist " , listOf ( recipientIdE164 , recipientIdAciB ) ) !!
2021-10-15 19:39:53 +00:00
// Merge
val retrievedId : RecipientId = recipientDatabase . getAndPossiblyMerge ( ACI _A , E164 _A , true )
val retrievedThreadId : Long = threadDatabase . getThreadIdFor ( retrievedId ) !!
assertEquals ( recipientIdAci , retrievedId )
// Recipient validation
val retrievedRecipient = Recipient . resolved ( retrievedId )
2022-02-17 20:55:54 +00:00
assertEquals ( ACI _A , retrievedRecipient . requireServiceId ( ) )
2021-10-15 19:39:53 +00:00
assertEquals ( E164 _A , retrievedRecipient . requireE164 ( ) )
val existingE164Recipient = Recipient . resolved ( recipientIdE164 )
assertEquals ( retrievedId , existingE164Recipient . id )
// Thread validation
assertEquals ( threadIdAci , retrievedThreadId )
assertNull ( threadDatabase . getThreadIdFor ( recipientIdE164 ) )
assertNull ( threadDatabase . getThreadRecord ( threadIdE164 ) )
// SMS validation
val sms1 : MessageRecord = smsDatabase . getMessageRecord ( smsId1 ) !!
val sms2 : MessageRecord = smsDatabase . getMessageRecord ( smsId2 ) !!
val sms3 : MessageRecord = smsDatabase . getMessageRecord ( smsId3 ) !!
assertEquals ( retrievedId , sms1 . recipient . id )
assertEquals ( retrievedId , sms2 . recipient . id )
assertEquals ( retrievedId , sms3 . recipient . id )
assertEquals ( retrievedThreadId , sms1 . threadId )
assertEquals ( retrievedThreadId , sms2 . threadId )
assertEquals ( retrievedThreadId , sms3 . threadId )
// MMS validation
val mms1 : MessageRecord = mmsDatabase . getMessageRecord ( mmsId1 ) !!
val mms2 : MessageRecord = mmsDatabase . getMessageRecord ( mmsId2 ) !!
val mms3 : MessageRecord = mmsDatabase . getMessageRecord ( mmsId3 ) !!
assertEquals ( retrievedId , mms1 . recipient . id )
assertEquals ( retrievedId , mms2 . recipient . id )
assertEquals ( retrievedId , mms3 . recipient . id )
assertEquals ( retrievedThreadId , mms1 . threadId )
assertEquals ( retrievedThreadId , mms2 . threadId )
assertEquals ( retrievedThreadId , mms3 . threadId )
// Mention validation
val mention1 : MentionModel = getMention ( mmsId1 )
assertEquals ( retrievedId , mention1 . recipientId )
assertEquals ( retrievedThreadId , mention1 . threadId )
val mention2 : MentionModel = getMention ( mmsId2 )
assertEquals ( retrievedId , mention2 . recipientId )
assertEquals ( retrievedThreadId , mention2 . threadId )
// Group receipt validation
val groupReceipts : List < GroupReceiptDatabase . GroupReceiptInfo > = groupReceiptDatabase . getGroupReceiptInfo ( mmsId1 )
assertEquals ( retrievedId , groupReceipts [ 0 ] . recipientId )
assertEquals ( retrievedId , groupReceipts [ 1 ] . recipientId )
// Identity validation
assertEquals ( identityKeyAci , identityDatabase . getIdentityStoreRecord ( ACI_A . toString ( ) ) !! . identityKey )
assertNull ( identityDatabase . getIdentityStoreRecord ( E164 _A ) )
// Session validation
2022-02-02 23:35:16 +00:00
assertNotNull ( sessionDatabase . load ( localAci , SignalProtocolAddress ( ACI_A . toString ( ) , 1 ) ) )
2021-11-11 18:12:51 +00:00
// Reaction validation
val reactionsSms : List < ReactionRecord > = reactionDatabase . getReactions ( MessageId ( smsId1 , false ) )
val reactionsMms : List < ReactionRecord > = reactionDatabase . getReactions ( MessageId ( mmsId1 , true ) )
assertEquals ( 1 , reactionsSms . size )
assertEquals ( ReactionRecord ( " a " , recipientIdAci , 1 , 1 ) , reactionsSms [ 0 ] )
assertEquals ( 1 , reactionsMms . size )
assertEquals ( ReactionRecord ( " b " , recipientIdAci , 1 , 1 ) , reactionsMms [ 0 ] )
2021-12-08 18:22:36 +00:00
// Notification Profile validation
val updatedProfile1 : NotificationProfile = notificationProfileDatabase . getProfile ( profile1 . id ) !!
val updatedProfile2 : NotificationProfile = notificationProfileDatabase . getProfile ( profile2 . id ) !!
assertThat ( " Notification Profile 1 should now only contain ACI $recipientIdAci " , updatedProfile1 . allowedMembers , Matchers . containsInAnyOrder ( recipientIdAci ) )
assertThat ( " Notification Profile 2 should now contain ACI A ( $recipientIdAci ) and ACI B ( $recipientIdAciB ) " , updatedProfile2 . allowedMembers , Matchers . containsInAnyOrder ( recipientIdAci , recipientIdAciB ) )
2022-02-24 17:40:28 +00:00
// Distribution List validation
val updatedList : DistributionListRecord = distributionListDatabase . getList ( distributionListId ) !!
assertThat ( " Distribution list should have updated $recipientIdE164 to $recipientIdAci " , updatedList . members , Matchers . containsInAnyOrder ( recipientIdAci , recipientIdAciB ) )
2021-10-15 19:39:53 +00:00
}
private val context : Application
get ( ) = InstrumentationRegistry . getInstrumentation ( ) . targetContext . applicationContext as Application
2022-03-14 19:49:46 +00:00
private fun smsMessage ( sender : RecipientId , time : Long = 0 , body : String = " " , groupId : Optional < GroupId > = Optional . empty ( ) ) : IncomingTextMessage {
2021-10-15 19:39:53 +00:00
return IncomingTextMessage ( sender , 1 , time , time , time , body , groupId , 0 , true , null )
}
2022-03-14 19:49:46 +00:00
private fun mmsMessage ( sender : RecipientId , time : Long = 0 , body : String = " " , groupId : Optional < GroupId > = Optional . empty ( ) ) : IncomingMediaMessage {
return IncomingMediaMessage ( sender , groupId , body , time , time , time , emptyList ( ) , 0 , 0 , false , false , true , Optional . empty ( ) )
2021-10-15 19:39:53 +00:00
}
private fun identityKey ( value : Byte ) : IdentityKey {
val bytes = ByteArray ( 33 )
bytes [ 0 ] = 0x05
bytes [ 1 ] = value
return IdentityKey ( bytes )
}
private fun groupMasterKey ( value : Byte ) : GroupMasterKey {
val bytes = ByteArray ( 32 )
bytes [ 0 ] = value
return GroupMasterKey ( bytes )
}
private fun decryptedGroup ( members : Collection < UUID > ) : DecryptedGroup {
return DecryptedGroup . newBuilder ( )
. addAllMembers ( members . map { DecryptedMember . newBuilder ( ) . setUuid ( UuidUtil . toByteString ( it ) ) . build ( ) } )
. build ( )
}
private fun getMention ( messageId : Long ) : MentionModel {
2021-11-18 17:36:52 +00:00
SignalDatabase . rawDatabase . rawQuery ( " SELECT * FROM ${MentionDatabase.TABLE_NAME} WHERE ${MentionDatabase.MESSAGE_ID} = $messageId " ) . use { cursor ->
2021-10-15 19:39:53 +00:00
cursor . moveToFirst ( )
return MentionModel (
recipientId = RecipientId . from ( CursorUtil . requireLong ( cursor , MentionDatabase . RECIPIENT _ID ) ) ,
threadId = CursorUtil . requireLong ( cursor , MentionDatabase . THREAD _ID )
)
}
}
2021-12-08 18:22:36 +00:00
private fun notificationProfile ( name : String ) : NotificationProfile {
return ( notificationProfileDatabase . createProfile ( name = name , emoji = " " , color = AvatarColor . A210 , System . currentTimeMillis ( ) ) as NotificationProfileDatabase . NotificationProfileChangeResult . Success ) . notificationProfile
}
2021-10-15 19:39:53 +00:00
/** The normal mention model doesn't have a threadId, so we need to do it ourselves for the test */
data class MentionModel (
val recipientId : RecipientId ,
val threadId : Long
)
companion object {
2021-10-28 19:39:36 +00:00
val ACI _A = ACI . from ( UUID . fromString ( " 3436efbe-5a76-47fa-a98a-7e72c948a82e " ) )
val ACI _B = ACI . from ( UUID . fromString ( " 8de7f691-0b60-4a68-9cd9-ed2f8453f9ed " ) )
2021-10-15 19:39:53 +00:00
val E164 _A = " +12221234567 "
val E164 _B = " +13331234567 "
}
}