Add basic CDSv2 database writes and unit tests.

This commit is contained in:
Greyson Parrelli
2022-05-16 12:15:11 -04:00
committed by Cody Henthorne
parent 307be5c75e
commit dda5ce4809
8 changed files with 324 additions and 27 deletions

View File

@@ -121,8 +121,8 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
const val TABLE_NAME = "recipient"
const val ID = "_id"
private const val SERVICE_ID = "uuid"
private const val PNI_COLUMN = "pni"
const val SERVICE_ID = "uuid"
const val PNI_COLUMN = "pni"
private const val USERNAME = "username"
const val PHONE = "phone"
const val EMAIL = "email"
@@ -403,6 +403,14 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
return getByColumn(SERVICE_ID, serviceId.toString())
}
/**
* Will return a recipient matching the PNI, but only in the explicit [PNI_COLUMN]. This should only be checked in conjunction with [getByServiceId] as a way
* to avoid creating a recipient we already merged.
*/
fun getByPni(pni: PNI): Optional<RecipientId> {
return getByColumn(PNI_COLUMN, pni.toString())
}
fun getByUsername(username: String): Optional<RecipientId> {
return getByColumn(USERNAME, username)
}
@@ -2131,7 +2139,11 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
}
/**
* A dumb implementation of processing CDSv2 results. Suitable only for testing and not for actual use.
* Processes CDSv2 results, merging recipients as necessary.
*
* Important: This is under active development and is not suitable for actual use.
*
* @return A set of [RecipientId]s that were updated/inserted.
*/
fun bulkProcessCdsV2Result(mapping: Map<String, CdsV2Result>): Set<RecipientId> {
val ids: MutableSet<RecipientId> = mutableSetOf()
@@ -2140,7 +2152,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
db.beginTransaction()
try {
for ((e164, result) in mapping) {
ids += getAndPossiblyMerge(result.bestServiceId(), e164, true)
ids += processCdsV2Result(e164, result.pni, result.aci)
}
db.setTransactionSuccessful()
@@ -2151,6 +2163,47 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
return ids
}
@VisibleForTesting
fun processCdsV2Result(e164: String, pni: PNI, aci: ACI?): RecipientId {
val byE164: RecipientId? = getByE164(e164).orElse(null)
val byPni: RecipientId? = getByServiceId(pni).orElse(null)
val byPniOnly: RecipientId? = getByPni(pni).orElse(null)
val byAci: RecipientId? = aci?.let { getByServiceId(it).orElse(null) }
val commonId: RecipientId? = listOf(byE164, byPni, byPniOnly, byAci).commonId()
val allRequiredDbFields: List<RecipientId?> = if (aci != null) listOf(byE164, byAci, byPniOnly) else listOf(byE164, byPni, byPniOnly)
val allRequiredDbFieldPopulated: Boolean = allRequiredDbFields.all { it != null }
// All ID's agree and the database is up-to-date
if (commonId != null && allRequiredDbFieldPopulated) {
return commonId
}
// All ID's agree but we need to update the database
if (commonId != null && !allRequiredDbFieldPopulated) {
writableDatabase
.update(TABLE_NAME)
.values(
PHONE to e164,
SERVICE_ID to (aci ?: pni).toString(),
PNI_COLUMN to pni.toString(),
REGISTERED to RegisteredState.REGISTERED.id,
STORAGE_SERVICE_ID to Base64.encodeBytes(StorageSyncHelper.generateKey())
)
.where("$ID = ?", commonId)
.run()
return commonId
}
// Nothing matches
if (byE164 == null && byPni == null && byAci == null) {
val id: Long = writableDatabase.insert(TABLE_NAME, null, buildContentValuesForCdsInsert(e164, pni, aci))
return RecipientId.from(id)
}
throw NotImplementedError("Handle cases where IDs map to different individuals")
}
fun getUninvitedRecipientsForInsights(): List<RecipientId> {
val results: MutableList<RecipientId> = LinkedList()
val args = arrayOf((System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31)).toString())
@@ -2865,6 +2918,18 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
return values
}
private fun buildContentValuesForCdsInsert(e164: String, pni: PNI, aci: ACI?): ContentValues {
val serviceId: ServiceId = aci ?: pni
return ContentValues().apply {
put(PHONE, e164)
put(SERVICE_ID, serviceId.toString())
put(PNI_COLUMN, pni.toString())
put(REGISTERED, RegisteredState.REGISTERED.id)
put(STORAGE_SERVICE_ID, Base64.encodeBytes(StorageSyncHelper.generateKey()))
put(AVATAR_COLOR, AvatarColor.random().serialize())
}
}
private fun getValuesForStorageContact(contact: SignalContactRecord, isInsert: Boolean): ContentValues {
return ContentValues().apply {
val profileName = ProfileName.fromParts(contact.givenName.orElse(null), contact.familyName.orElse(null))
@@ -2940,6 +3005,22 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
}
}
/**
* @return The common id if all non-null ids are equal, or null if all are null or at least one non-null pair doesn't match.
*/
private fun Collection<RecipientId?>.commonId(): RecipientId? {
val nonNull = this.filterNotNull()
if (nonNull.isEmpty()) {
return null
}
return if (nonNull.all { it.equals(nonNull[0]) }) {
nonNull[0]
} else {
null
}
}
/**
* Should only be used for debugging! A very destructive action that clears all known serviceIds.
*/