kopia lustrzana https://github.com/ryukoposting/Signal-Android
Add support for doing normal CDS queries on CDSv2.
rodzic
9b17e7a7e2
commit
2eba9a8d72
|
@ -80,7 +80,9 @@ object ContactDiscovery {
|
|||
descriptor = "refresh-all",
|
||||
refresh = {
|
||||
if (FeatureFlags.phoneNumberPrivacy()) {
|
||||
ContactDiscoveryRefreshV2.refreshAll(context)
|
||||
ContactDiscoveryRefreshV2.refreshAll(context, useCompat = false)
|
||||
} else if (FeatureFlags.cdsV2Compat()) {
|
||||
ContactDiscoveryRefreshV2.refreshAll(context, useCompat = true)
|
||||
} else if (FeatureFlags.cdsV2LoadTesting()) {
|
||||
loadTestRefreshAll(context)
|
||||
} else {
|
||||
|
@ -103,7 +105,9 @@ object ContactDiscovery {
|
|||
descriptor = "refresh-multiple",
|
||||
refresh = {
|
||||
if (FeatureFlags.phoneNumberPrivacy()) {
|
||||
ContactDiscoveryRefreshV2.refresh(context, recipients)
|
||||
ContactDiscoveryRefreshV2.refresh(context, recipients, useCompat = false)
|
||||
} else if (FeatureFlags.cdsV2Compat()) {
|
||||
ContactDiscoveryRefreshV2.refresh(context, recipients, useCompat = true)
|
||||
} else if (FeatureFlags.cdsV2LoadTesting()) {
|
||||
loadTestRefresh(context, recipients)
|
||||
} else {
|
||||
|
@ -124,7 +128,9 @@ object ContactDiscovery {
|
|||
descriptor = "refresh-single",
|
||||
refresh = {
|
||||
if (FeatureFlags.phoneNumberPrivacy()) {
|
||||
ContactDiscoveryRefreshV2.refresh(context, listOf(recipient))
|
||||
ContactDiscoveryRefreshV2.refresh(context, listOf(recipient), useCompat = false)
|
||||
} else if (FeatureFlags.cdsV2Compat()) {
|
||||
ContactDiscoveryRefreshV2.refresh(context, listOf(recipient), useCompat = true)
|
||||
} else if (FeatureFlags.cdsV2LoadTesting()) {
|
||||
loadTestRefresh(context, listOf(recipient))
|
||||
} else {
|
||||
|
@ -381,14 +387,14 @@ object ContactDiscovery {
|
|||
private fun loadTestRefreshAll(context: Context): RefreshResult {
|
||||
return loadTestOperation(
|
||||
{ ContactDiscoveryRefreshV1.refreshAll(context) },
|
||||
{ ContactDiscoveryRefreshV2.refreshAll(context, ignoreResults = true) }
|
||||
{ ContactDiscoveryRefreshV2.refreshAll(context, useCompat = false, ignoreResults = true) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadTestRefresh(context: Context, recipients: List<Recipient>): RefreshResult {
|
||||
return loadTestOperation(
|
||||
{ ContactDiscoveryRefreshV1.refresh(context, recipients) },
|
||||
{ ContactDiscoveryRefreshV2.refresh(context, recipients, ignoreResults = true) }
|
||||
{ ContactDiscoveryRefreshV2.refresh(context, recipients, useCompat = false, ignoreResults = true) }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -122,7 +122,7 @@ class ContactDiscoveryRefreshV1 {
|
|||
|
||||
if (result.getNumberRewrites().size() > 0) {
|
||||
Log.i(TAG, "[getDirectoryResult] Need to rewrite some numbers.");
|
||||
recipientDatabase.updatePhoneNumbers(result.getNumberRewrites());
|
||||
recipientDatabase.rewritePhoneNumbers(result.getNumberRewrites());
|
||||
}
|
||||
|
||||
Map<RecipientId, ACI> aciMap = recipientDatabase.bulkProcessCdsResult(result.getRegisteredNumbers());
|
||||
|
@ -250,8 +250,8 @@ class ContactDiscoveryRefreshV1 {
|
|||
KeyStore iasKeyStore = getIasKeyStore(context);
|
||||
|
||||
try {
|
||||
Map<String, ACI> results = accountManager.getRegisteredUsers(iasKeyStore, sanitizedNumbers, BuildConfig.CDS_MRENCLAVE);
|
||||
FuzzyPhoneNumberHelper.OutputResult outputResult = FuzzyPhoneNumberHelper.generateOutput(results, inputResult);
|
||||
Map<String, ACI> results = accountManager.getRegisteredUsers(iasKeyStore, sanitizedNumbers, BuildConfig.CDS_MRENCLAVE);
|
||||
FuzzyPhoneNumberHelper.OutputResult<ACI> outputResult = FuzzyPhoneNumberHelper.generateOutput(results, inputResult);
|
||||
|
||||
return new ContactIntersection(outputResult.getNumbers(), outputResult.getRewrites(), ignoredNumbers);
|
||||
} catch (SignatureException | UnauthenticatedQuoteException | UnauthenticatedResponseException | Quote.InvalidQuoteFormatException | InvalidKeyException e) {
|
||||
|
|
|
@ -4,20 +4,25 @@ import android.content.Context
|
|||
import androidx.annotation.WorkerThread
|
||||
import org.signal.contacts.SystemContactsRepository
|
||||
import org.signal.core.util.Stopwatch
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase
|
||||
import org.thoughtcrime.securesms.contacts.sync.FuzzyPhoneNumberHelper.InputResult
|
||||
import org.thoughtcrime.securesms.contacts.sync.FuzzyPhoneNumberHelper.OutputResult
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.CdsV2Result
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.push.ACI
|
||||
import org.whispersystems.signalservice.api.services.CdsiV2Service
|
||||
import java.io.IOException
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.Future
|
||||
|
||||
/**
|
||||
* Performs the CDS refresh using the V2 interface (either CDSH or CDSI) that returns both PNIs and ACIs.
|
||||
|
@ -39,149 +44,190 @@ object ContactDiscoveryRefreshV2 {
|
|||
@WorkerThread
|
||||
@Synchronized
|
||||
@JvmStatic
|
||||
fun refreshAll(context: Context, ignoreResults: Boolean = false): ContactDiscovery.RefreshResult {
|
||||
val stopwatch = Stopwatch("refresh-all")
|
||||
|
||||
val previousE164s: Set<String> = if (SignalStore.misc().cdsToken != null) {
|
||||
SignalDatabase.cds.getAllE164s()
|
||||
} else {
|
||||
Log.w(TAG, "No token set! Cannot provide previousE164s.")
|
||||
emptySet()
|
||||
}
|
||||
stopwatch.split("previous")
|
||||
|
||||
fun refreshAll(context: Context, useCompat: Boolean, ignoreResults: Boolean = false): ContactDiscovery.RefreshResult {
|
||||
val recipientE164s: Set<String> = SignalDatabase.recipients.getAllE164s().sanitize()
|
||||
val newRecipientE164s: Set<String> = recipientE164s - previousE164s
|
||||
stopwatch.split("recipient")
|
||||
|
||||
val systemE164s: Set<String> = SystemContactsRepository.getAllDisplayNumbers(context).toE164s(context).sanitize()
|
||||
val newSystemE164s: Set<String> = systemE164s - previousE164s
|
||||
stopwatch.split("system")
|
||||
|
||||
val newE164s: Set<String> = newRecipientE164s + newSystemE164s
|
||||
|
||||
if (newE164s.isEmpty() && previousE164s.isEmpty()) {
|
||||
return ContactDiscovery.RefreshResult(emptySet(), emptyMap())
|
||||
}
|
||||
|
||||
val tokenToUse: ByteArray? = if (previousE164s.isNotEmpty()) {
|
||||
SignalStore.misc().cdsToken
|
||||
} else {
|
||||
if (SignalStore.misc().cdsToken != null) {
|
||||
Log.w(TAG, "We have a token, but our previousE164 list is empty! We cannot provide a token.")
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
val response: CdsiV2Service.Response = makeRequest(
|
||||
previousE164s = previousE164s,
|
||||
newE164s = newE164s,
|
||||
serviceIds = SignalDatabase.recipients.getAllServiceIdProfileKeyPairs(),
|
||||
token = tokenToUse,
|
||||
return refreshInternal(
|
||||
recipientE164s = recipientE164s,
|
||||
systemE164s = systemE164s,
|
||||
inputPreviousE164s = SignalDatabase.cds.getAllE164s(),
|
||||
saveToken = true,
|
||||
tag = "refresh-all"
|
||||
useCompat = useCompat,
|
||||
ignoreResults = ignoreResults
|
||||
)
|
||||
stopwatch.split("network")
|
||||
|
||||
SignalDatabase.cds.updateAfterCdsQuery(newE164s, recipientE164s + systemE164s)
|
||||
stopwatch.split("cds-db")
|
||||
|
||||
var registeredIds: Set<RecipientId> = emptySet()
|
||||
|
||||
if (ignoreResults) {
|
||||
Log.w(TAG, "[refresh-all] Ignoring CDSv2 results.")
|
||||
} else {
|
||||
registeredIds = SignalDatabase.recipients.bulkProcessCdsV2Result(
|
||||
response.results
|
||||
.mapValues { entry -> RecipientDatabase.CdsV2Result(entry.value.pni, entry.value.aci.orElse(null)) }
|
||||
)
|
||||
stopwatch.split("recipient-db")
|
||||
|
||||
SignalDatabase.recipients.bulkUpdatedRegisteredStatus(registeredIds.associateWith { null }, emptyList())
|
||||
stopwatch.split("update-registered")
|
||||
}
|
||||
|
||||
stopwatch.stop(TAG)
|
||||
Log.d(TAG, "[refresh-all] Used ${response.quotaUsedDebugOnly} units of our quota.")
|
||||
|
||||
return ContactDiscovery.RefreshResult(registeredIds, emptyMap())
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
@WorkerThread
|
||||
@Synchronized
|
||||
@JvmStatic
|
||||
fun refresh(context: Context, inputRecipients: List<Recipient>, ignoreResults: Boolean = false): ContactDiscovery.RefreshResult {
|
||||
val stopwatch = Stopwatch("refresh-some")
|
||||
|
||||
val recipients = inputRecipients.map { it.resolve() }
|
||||
stopwatch.split("resolve")
|
||||
|
||||
val inputIds: Set<RecipientId> = recipients.map { it.id }.toSet()
|
||||
fun refresh(context: Context, inputRecipients: List<Recipient>, useCompat: Boolean, ignoreResults: Boolean = false): ContactDiscovery.RefreshResult {
|
||||
val recipients: List<Recipient> = inputRecipients.map { it.resolve() }
|
||||
val inputE164s: Set<String> = recipients.mapNotNull { it.e164.orElse(null) }.toSet()
|
||||
|
||||
if (inputE164s.size > MAXIMUM_ONE_OFF_REQUEST_SIZE) {
|
||||
return if (inputE164s.size > MAXIMUM_ONE_OFF_REQUEST_SIZE) {
|
||||
Log.i(TAG, "List of specific recipients to refresh is too large! (Size: ${recipients.size}). Doing a full refresh instead.")
|
||||
val fullResult: ContactDiscovery.RefreshResult = refreshAll(context, ignoreResults)
|
||||
|
||||
return ContactDiscovery.RefreshResult(
|
||||
val fullResult: ContactDiscovery.RefreshResult = refreshAll(context, ignoreResults)
|
||||
val inputIds: Set<RecipientId> = recipients.map { it.id }.toSet()
|
||||
|
||||
ContactDiscovery.RefreshResult(
|
||||
registeredIds = fullResult.registeredIds.intersect(inputIds),
|
||||
rewrites = fullResult.rewrites.filterKeys { inputE164s.contains(it) }
|
||||
)
|
||||
}
|
||||
|
||||
if (inputE164s.isEmpty()) {
|
||||
Log.w(TAG, "No numbers to refresh!")
|
||||
return ContactDiscovery.RefreshResult(emptySet(), emptyMap())
|
||||
} else {
|
||||
Log.i(TAG, "Doing a one-off request for ${inputE164s.size} recipients.")
|
||||
}
|
||||
|
||||
val response: CdsiV2Service.Response = makeRequest(
|
||||
previousE164s = emptySet(),
|
||||
newE164s = inputE164s,
|
||||
serviceIds = SignalDatabase.recipients.getAllServiceIdProfileKeyPairs(),
|
||||
token = null,
|
||||
saveToken = false,
|
||||
tag = "refresh-some"
|
||||
)
|
||||
stopwatch.split("network")
|
||||
|
||||
var registeredIds: Set<RecipientId> = emptySet()
|
||||
|
||||
if (ignoreResults) {
|
||||
Log.w(TAG, "[refresh-some] Ignoring CDSv2 results.")
|
||||
} else {
|
||||
registeredIds = SignalDatabase.recipients.bulkProcessCdsV2Result(
|
||||
response.results
|
||||
.mapValues { entry -> RecipientDatabase.CdsV2Result(entry.value.pni, entry.value.aci.orElse(null)) }
|
||||
refreshInternal(
|
||||
recipientE164s = inputE164s,
|
||||
systemE164s = inputE164s,
|
||||
inputPreviousE164s = emptySet(),
|
||||
saveToken = false,
|
||||
useCompat = useCompat,
|
||||
ignoreResults = ignoreResults
|
||||
)
|
||||
stopwatch.split("recipient-db")
|
||||
|
||||
SignalDatabase.recipients.bulkUpdatedRegisteredStatus(registeredIds.associateWith { null }, emptyList())
|
||||
stopwatch.split("update-registered")
|
||||
}
|
||||
|
||||
Log.d(TAG, "[refresh-some] Used ${response.quotaUsedDebugOnly} units of our quota.")
|
||||
stopwatch.stop(TAG)
|
||||
|
||||
return ContactDiscovery.RefreshResult(registeredIds, emptyMap())
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun makeRequest(previousE164s: Set<String>, newE164s: Set<String>, serviceIds: Map<ServiceId, ProfileKey>, token: ByteArray?, saveToken: Boolean, tag: String): CdsiV2Service.Response {
|
||||
return ApplicationDependencies.getSignalServiceAccountManager().getRegisteredUsersWithCdsi(
|
||||
private fun refreshInternal(
|
||||
recipientE164s: Set<String>,
|
||||
systemE164s: Set<String>,
|
||||
inputPreviousE164s: Set<String>,
|
||||
saveToken: Boolean,
|
||||
useCompat: Boolean,
|
||||
ignoreResults: Boolean
|
||||
): ContactDiscovery.RefreshResult {
|
||||
val stopwatch = Stopwatch("refreshInternal-${if (useCompat) "compat" else "v2"}")
|
||||
|
||||
val previousE164s: Set<String> = if (SignalStore.misc().cdsToken != null) inputPreviousE164s else emptySet()
|
||||
|
||||
val allE164s: Set<String> = recipientE164s + systemE164s
|
||||
val newRawE164s: Set<String> = allE164s - previousE164s
|
||||
val fuzzyInput: InputResult = FuzzyPhoneNumberHelper.generateInput(newRawE164s, recipientE164s)
|
||||
val newE164s: Set<String> = fuzzyInput.numbers
|
||||
|
||||
if (newE164s.isEmpty() && previousE164s.isEmpty()) {
|
||||
Log.w(TAG, "[refreshInternal] No data to send! Ignoring.")
|
||||
return ContactDiscovery.RefreshResult(emptySet(), emptyMap())
|
||||
}
|
||||
|
||||
val token: ByteArray? = if (previousE164s.isNotEmpty()) SignalStore.misc().cdsToken else null
|
||||
|
||||
stopwatch.split("preamble")
|
||||
|
||||
val response: CdsiV2Service.Response = ApplicationDependencies.getSignalServiceAccountManager().getRegisteredUsersWithCdsi(
|
||||
previousE164s,
|
||||
newE164s,
|
||||
serviceIds,
|
||||
SignalDatabase.recipients.getAllServiceIdProfileKeyPairs(),
|
||||
useCompat,
|
||||
Optional.ofNullable(token),
|
||||
BuildConfig.CDSI_MRENCLAVE
|
||||
) { tokenToSave ->
|
||||
if (saveToken) {
|
||||
SignalStore.misc().cdsToken = tokenToSave
|
||||
Log.d(TAG, "[$tag] Token saved!")
|
||||
Log.d(TAG, "Token saved!")
|
||||
} else {
|
||||
Log.d(TAG, "Ignoring token.")
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "[refreshInternal] Used ${response.quotaUsedDebugOnly} quota.")
|
||||
stopwatch.split("network")
|
||||
|
||||
SignalDatabase.cds.updateAfterCdsQuery(newE164s, allE164s + newE164s)
|
||||
stopwatch.split("cds-db")
|
||||
|
||||
val registeredIds: MutableSet<RecipientId> = mutableSetOf()
|
||||
val rewrites: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
if (ignoreResults) {
|
||||
Log.w(TAG, "[refreshInternal] Ignoring CDSv2 results.")
|
||||
} else {
|
||||
if (useCompat) {
|
||||
val transformed: Map<String, ACI?> = response.results.mapValues { entry -> entry.value.aci.orElse(null) }
|
||||
val fuzzyOutput: OutputResult<ACI> = FuzzyPhoneNumberHelper.generateOutput(transformed, fuzzyInput)
|
||||
|
||||
if (transformed.values.any { it == null }) {
|
||||
throw IOException("Unexpected null ACI!")
|
||||
}
|
||||
|
||||
SignalDatabase.recipients.rewritePhoneNumbers(fuzzyOutput.rewrites)
|
||||
stopwatch.split("rewrite-e164")
|
||||
|
||||
val aciMap: Map<RecipientId, ACI?> = SignalDatabase.recipients.bulkProcessCdsResult(fuzzyOutput.numbers)
|
||||
|
||||
registeredIds += aciMap.keys
|
||||
rewrites += fuzzyOutput.rewrites
|
||||
stopwatch.split("process-result")
|
||||
|
||||
val existingIds: Set<RecipientId> = SignalDatabase.recipients.getAllPossiblyRegisteredByE164(recipientE164s + rewrites.values)
|
||||
val inactiveIds: Set<RecipientId> = (existingIds - registeredIds).removeRegisteredButUnlisted()
|
||||
|
||||
SignalDatabase.recipients.bulkUpdatedRegisteredStatus(aciMap, inactiveIds)
|
||||
stopwatch.split("update-registered")
|
||||
} else {
|
||||
val transformed: Map<String, CdsV2Result> = response.results.mapValues { entry -> CdsV2Result(entry.value.pni, entry.value.aci.orElse(null)) }
|
||||
val fuzzyOutput: OutputResult<CdsV2Result> = FuzzyPhoneNumberHelper.generateOutput(transformed, fuzzyInput)
|
||||
|
||||
SignalDatabase.recipients.rewritePhoneNumbers(fuzzyOutput.rewrites)
|
||||
stopwatch.split("rewrite-e164")
|
||||
|
||||
val existingIds: Set<RecipientId> = SignalDatabase.recipients.getAllPossiblyRegisteredByE164(recipientE164s + rewrites.values)
|
||||
val inactiveIds: Set<RecipientId> = (existingIds - registeredIds).removeRegisteredButUnlisted()
|
||||
|
||||
registeredIds += SignalDatabase.recipients.bulkProcessCdsV2Result(fuzzyOutput.numbers)
|
||||
rewrites += fuzzyOutput.rewrites
|
||||
stopwatch.split("process-result")
|
||||
|
||||
SignalDatabase.recipients.bulkUpdatedRegisteredStatusV2(registeredIds, inactiveIds)
|
||||
stopwatch.split("update-registered")
|
||||
}
|
||||
}
|
||||
|
||||
stopwatch.stop(TAG)
|
||||
|
||||
return ContactDiscovery.RefreshResult(registeredIds, rewrites)
|
||||
}
|
||||
|
||||
private fun hasCommunicatedWith(recipient: Recipient): Boolean {
|
||||
val localAci = SignalStore.account().requireAci()
|
||||
return SignalDatabase.threads.hasThread(recipient.id) || (recipient.hasServiceId() && SignalDatabase.sessions.hasSessionFor(localAci, recipient.requireServiceId().toString()))
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun Set<RecipientId>.removeRegisteredButUnlisted(): Set<RecipientId> {
|
||||
val futures: List<Future<Pair<RecipientId, Boolean?>>> = Recipient.resolvedList(this)
|
||||
.filter { hasCommunicatedWith(it) }
|
||||
.map {
|
||||
SignalExecutors.UNBOUNDED.submit(
|
||||
Callable {
|
||||
try {
|
||||
it.id to ApplicationDependencies.getSignalServiceAccountManager().isIdentifierRegistered(it.requireServiceId())
|
||||
} catch (e: IOException) {
|
||||
it.id to null
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val registeredIds: MutableSet<RecipientId> = mutableSetOf()
|
||||
val retryIds: MutableSet<RecipientId> = mutableSetOf()
|
||||
|
||||
for (future in futures) {
|
||||
val (id, registered) = future.get()
|
||||
if (registered == null) {
|
||||
retryIds += id
|
||||
registeredIds += id
|
||||
} else if (registered) {
|
||||
registeredIds += id
|
||||
}
|
||||
}
|
||||
|
||||
if (retryIds.isNotEmpty()) {
|
||||
Log.w(TAG, "Failed to determine registered status of ${retryIds.size} recipients. Assuming registered, but enqueuing profile jobs to check later.")
|
||||
RetrieveProfileJob.enqueue(retryIds)
|
||||
}
|
||||
|
||||
return this - registeredIds
|
||||
}
|
||||
|
||||
private fun Set<String>.toE164s(context: Context): Set<String> {
|
||||
|
|
|
@ -51,8 +51,8 @@ class FuzzyPhoneNumberHelper {
|
|||
* these results and our initial input set, we can decide if we need to rewrite which number we
|
||||
* have stored locally.
|
||||
*/
|
||||
static @NonNull OutputResult generateOutput(@NonNull Map<String, ACI> registeredNumbers, @NonNull InputResult inputResult) {
|
||||
Map<String, ACI> allNumbers = new HashMap<>(registeredNumbers);
|
||||
static @NonNull <E> OutputResult<E> generateOutput(@NonNull Map<String, E> registeredNumbers, @NonNull InputResult inputResult) {
|
||||
Map<String, E> allNumbers = new HashMap<>(registeredNumbers);
|
||||
Map<String, String> rewrites = new HashMap<>();
|
||||
|
||||
for (Map.Entry<String, String> entry : inputResult.getMapOfOriginalToVariant().entrySet()) {
|
||||
|
@ -76,7 +76,7 @@ class FuzzyPhoneNumberHelper {
|
|||
}
|
||||
}
|
||||
|
||||
return new OutputResult(allNumbers, rewrites);
|
||||
return new OutputResult<>(allNumbers, rewrites);
|
||||
}
|
||||
|
||||
private interface FuzzyMatcher {
|
||||
|
@ -170,16 +170,16 @@ class FuzzyPhoneNumberHelper {
|
|||
}
|
||||
}
|
||||
|
||||
public static class OutputResult {
|
||||
private final Map<String, ACI> numbers;
|
||||
public static class OutputResult<E> {
|
||||
private final Map<String, E> numbers;
|
||||
private final Map<String, String> rewrites;
|
||||
|
||||
private OutputResult(@NonNull Map<String, ACI> numbers, @NonNull Map<String, String> rewrites) {
|
||||
private OutputResult(@NonNull Map<String, E> numbers, @NonNull Map<String, String> rewrites) {
|
||||
this.numbers = numbers;
|
||||
this.rewrites = rewrites;
|
||||
}
|
||||
|
||||
public @NonNull Map<String, ACI> getNumbers() {
|
||||
public @NonNull Map<String, E> getNumbers() {
|
||||
return numbers;
|
||||
}
|
||||
|
||||
|
|
|
@ -1141,22 +1141,22 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
Recipient.self().live().refresh()
|
||||
}
|
||||
|
||||
fun updatePhoneNumbers(mapping: Map<String?, String?>) {
|
||||
/**
|
||||
* Takes a mapping of old->new phone numbers and updates the table to match.
|
||||
* Intended to be used to handle changing number formats.
|
||||
*/
|
||||
fun rewritePhoneNumbers(mapping: Map<String, String>) {
|
||||
if (mapping.isEmpty()) return
|
||||
val db = writableDatabase
|
||||
|
||||
db.beginTransaction()
|
||||
try {
|
||||
val query = "$PHONE = ?"
|
||||
for ((key, value) in mapping) {
|
||||
val values = ContentValues().apply {
|
||||
put(PHONE, value)
|
||||
}
|
||||
db.updateWithOnConflict(TABLE_NAME, values, query, arrayOf(key), SQLiteDatabase.CONFLICT_IGNORE)
|
||||
Log.i(TAG, "Rewriting ${mapping.size} phone numbers.")
|
||||
|
||||
writableDatabase.withinTransaction {
|
||||
for ((originalE164, updatedE164) in mapping) {
|
||||
writableDatabase.update(TABLE_NAME)
|
||||
.values(PHONE to updatedE164)
|
||||
.where("$PHONE = ?", originalE164)
|
||||
.run(SQLiteDatabase.CONFLICT_IGNORE)
|
||||
}
|
||||
db.setTransactionSuccessful()
|
||||
} finally {
|
||||
db.endTransaction()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2130,6 +2130,27 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives you all of the recipientIds of possibly-registered users (i.e. REGISTERED or UNKNOWN) that can be found by the set of
|
||||
* provided E164s.
|
||||
*/
|
||||
fun getAllPossiblyRegisteredByE164(e164s: Set<String>): Set<RecipientId> {
|
||||
val results: MutableSet<RecipientId> = mutableSetOf()
|
||||
val queries: List<SqlUtil.Query> = SqlUtil.buildCollectionQuery(PHONE, e164s)
|
||||
|
||||
for (query in queries) {
|
||||
readableDatabase.query(TABLE_NAME, arrayOf(ID, REGISTERED), query.where, query.whereArgs, null, null, null).use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
if (RegisteredState.fromId(cursor.requireInt(REGISTERED)) != RegisteredState.NOT_REGISTERED) {
|
||||
results += RecipientId.from(cursor.requireLong(ID))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
fun setPni(id: RecipientId, pni: PNI) {
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
|
@ -2295,6 +2316,32 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
return ids
|
||||
}
|
||||
|
||||
fun bulkUpdatedRegisteredStatusV2(registered: Set<RecipientId>, unregistered: Collection<RecipientId>) {
|
||||
writableDatabase.withinTransaction {
|
||||
val registeredValues = contentValuesOf(
|
||||
REGISTERED to RegisteredState.REGISTERED.id
|
||||
)
|
||||
|
||||
for (id in registered) {
|
||||
if (update(id, registeredValues)) {
|
||||
setStorageIdIfNotSet(id)
|
||||
ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id)
|
||||
}
|
||||
}
|
||||
|
||||
val unregisteredValues = contentValuesOf(
|
||||
REGISTERED to RegisteredState.NOT_REGISTERED.id,
|
||||
STORAGE_SERVICE_ID to null
|
||||
)
|
||||
|
||||
for (id in unregistered) {
|
||||
if (update(id, unregisteredValues)) {
|
||||
ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a tuple of (e164, pni, aci) and incorporates it into our database.
|
||||
* It is assumed that we are in a transaction.
|
||||
|
@ -2302,7 +2349,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
* @return The [RecipientId] of the resulting recipient.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
fun processPnpTuple(e164: String?, pni: PNI?, aci: ACI?, pniVerified: Boolean, changeSelf: Boolean = false, pnpEnabled: Boolean = FeatureFlags.phoneNumberPrivacy()): ProcessPnpTupleResult {
|
||||
fun processPnpTuple(e164: String?, pni: PNI?, aci: ACI?, pniVerified: Boolean, changeSelf: Boolean = false): ProcessPnpTupleResult {
|
||||
val changeSet: PnpChangeSet = processPnpTupleToChangeSet(e164, pni, aci, pniVerified, changeSelf)
|
||||
|
||||
val affectedIds: MutableSet<RecipientId> = mutableSetOf()
|
||||
|
@ -2328,7 +2375,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
}
|
||||
}
|
||||
|
||||
val finalId: RecipientId = writePnpChangeSetToDisk(changeSet, pnpEnabled, pni)
|
||||
val finalId: RecipientId = writePnpChangeSetToDisk(changeSet, pni)
|
||||
|
||||
return ProcessPnpTupleResult(
|
||||
finalId = finalId,
|
||||
|
@ -2341,7 +2388,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun writePnpChangeSetToDisk(changeSet: PnpChangeSet, pnpEnabled: Boolean, inputPni: PNI?): RecipientId {
|
||||
fun writePnpChangeSetToDisk(changeSet: PnpChangeSet, inputPni: PNI?): RecipientId {
|
||||
for (operation in changeSet.operations) {
|
||||
@Exhaustive
|
||||
when (operation) {
|
||||
|
|
|
@ -103,6 +103,7 @@ public final class FeatureFlags {
|
|||
private static final String RECIPIENT_MERGE_V2 = "android.recipientMergeV2";
|
||||
private static final String CDS_V2_LOAD_TEST = "android.cdsV2LoadTest";
|
||||
private static final String SMS_EXPORTER = "android.sms.exporter";
|
||||
private static final String CDS_V2_COMPAT = "android.cdsV2Compat";
|
||||
|
||||
/**
|
||||
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
||||
|
@ -158,7 +159,8 @@ public final class FeatureFlags {
|
|||
CAMERAX_MODEL_BLOCKLIST,
|
||||
RECIPIENT_MERGE_V2,
|
||||
CDS_V2_LOAD_TEST,
|
||||
SMS_EXPORTER
|
||||
SMS_EXPORTER,
|
||||
CDS_V2_COMPAT
|
||||
);
|
||||
|
||||
@VisibleForTesting
|
||||
|
@ -222,7 +224,8 @@ public final class FeatureFlags {
|
|||
TELECOM_MODEL_BLOCKLIST,
|
||||
CAMERAX_MODEL_BLOCKLIST,
|
||||
RECIPIENT_MERGE_V2,
|
||||
CDS_V2_LOAD_TEST
|
||||
CDS_V2_LOAD_TEST,
|
||||
CDS_V2_COMPAT
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -567,6 +570,13 @@ public final class FeatureFlags {
|
|||
return getBoolean(SMS_EXPORTER, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not we should use CDSv2 with the compat flag on as our primary CDS.
|
||||
*/
|
||||
public static boolean cdsV2Compat() {
|
||||
return getBoolean(CDS_V2_COMPAT, false);
|
||||
}
|
||||
|
||||
/** Only for rendering debug info. */
|
||||
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
||||
return new TreeMap<>(REMOTE_VALUES);
|
||||
|
|
|
@ -99,7 +99,7 @@ public class FuzzyPhoneNumberHelperTest {
|
|||
|
||||
@Test
|
||||
public void generateOutput_noMxNumbers() {
|
||||
OutputResult result = FuzzyPhoneNumberHelper.generateOutput(mapOf(US_A, ACI_A, US_B, ACI_B), new InputResult(setOf(US_A, US_B), Collections.emptyMap()));
|
||||
OutputResult<ACI> result = FuzzyPhoneNumberHelper.generateOutput(mapOf(US_A, ACI_A, US_B, ACI_B), new InputResult(setOf(US_A, US_B), Collections.emptyMap()));
|
||||
|
||||
assertEquals(2, result.getNumbers().size());
|
||||
assertEquals(ACI_A, result.getNumbers().get(US_A));
|
||||
|
@ -109,7 +109,7 @@ public class FuzzyPhoneNumberHelperTest {
|
|||
|
||||
@Test
|
||||
public void generateOutput_bothMatch_no1To1() {
|
||||
OutputResult result = FuzzyPhoneNumberHelper.generateOutput(mapOf(MX_A, ACI_A, MX_A_1, ACI_B), new InputResult(setOf(MX_A, MX_A_1), Collections.singletonMap(MX_A, MX_A_1)));
|
||||
OutputResult<ACI> result = FuzzyPhoneNumberHelper.generateOutput(mapOf(MX_A, ACI_A, MX_A_1, ACI_B), new InputResult(setOf(MX_A, MX_A_1), Collections.singletonMap(MX_A, MX_A_1)));
|
||||
|
||||
assertEquals(1, result.getNumbers().size());
|
||||
assertEquals(ACI_A, result.getNumbers().get(MX_A));
|
||||
|
@ -118,7 +118,7 @@ public class FuzzyPhoneNumberHelperTest {
|
|||
|
||||
@Test
|
||||
public void generateOutput_bothMatch_1toNo1() {
|
||||
OutputResult result = FuzzyPhoneNumberHelper.generateOutput(mapOf(MX_A, ACI_A, MX_A_1, ACI_B), new InputResult(setOf(MX_A, MX_A_1), Collections.singletonMap(MX_A_1, MX_A)));
|
||||
OutputResult<ACI> result = FuzzyPhoneNumberHelper.generateOutput(mapOf(MX_A, ACI_A, MX_A_1, ACI_B), new InputResult(setOf(MX_A, MX_A_1), Collections.singletonMap(MX_A_1, MX_A)));
|
||||
|
||||
assertEquals(1, result.getNumbers().size());
|
||||
assertEquals(ACI_A, result.getNumbers().get(MX_A));
|
||||
|
@ -127,7 +127,7 @@ public class FuzzyPhoneNumberHelperTest {
|
|||
|
||||
@Test
|
||||
public void generateOutput_no1Match_no1To1() {
|
||||
OutputResult result = FuzzyPhoneNumberHelper.generateOutput(mapOf(MX_A, ACI_A), new InputResult(setOf(MX_A, MX_A_1), Collections.singletonMap(MX_A, MX_A_1)));
|
||||
OutputResult<ACI> result = FuzzyPhoneNumberHelper.generateOutput(mapOf(MX_A, ACI_A), new InputResult(setOf(MX_A, MX_A_1), Collections.singletonMap(MX_A, MX_A_1)));
|
||||
|
||||
assertEquals(1, result.getNumbers().size());
|
||||
assertEquals(ACI_A, result.getNumbers().get(MX_A));
|
||||
|
@ -136,7 +136,7 @@ public class FuzzyPhoneNumberHelperTest {
|
|||
|
||||
@Test
|
||||
public void generateOutput_no1Match_1ToNo1() {
|
||||
OutputResult result = FuzzyPhoneNumberHelper.generateOutput(mapOf(MX_A, ACI_A), new InputResult(setOf(MX_A, MX_A_1), Collections.singletonMap(MX_A_1, MX_A)));
|
||||
OutputResult<ACI> result = FuzzyPhoneNumberHelper.generateOutput(mapOf(MX_A, ACI_A), new InputResult(setOf(MX_A, MX_A_1), Collections.singletonMap(MX_A_1, MX_A)));
|
||||
|
||||
assertEquals(1, result.getNumbers().size());
|
||||
assertEquals(ACI_A, result.getNumbers().get(MX_A));
|
||||
|
@ -145,7 +145,7 @@ public class FuzzyPhoneNumberHelperTest {
|
|||
|
||||
@Test
|
||||
public void generateOutput_1Match_1ToNo1() {
|
||||
OutputResult result = FuzzyPhoneNumberHelper.generateOutput(mapOf(MX_A_1, ACI_A), new InputResult(setOf(MX_A, MX_A_1), Collections.singletonMap(MX_A_1, MX_A)));
|
||||
OutputResult<ACI> result = FuzzyPhoneNumberHelper.generateOutput(mapOf(MX_A_1, ACI_A), new InputResult(setOf(MX_A, MX_A_1), Collections.singletonMap(MX_A_1, MX_A)));
|
||||
|
||||
assertEquals(1, result.getNumbers().size());
|
||||
assertEquals(ACI_A, result.getNumbers().get(MX_A_1));
|
||||
|
@ -154,7 +154,7 @@ public class FuzzyPhoneNumberHelperTest {
|
|||
|
||||
@Test
|
||||
public void generateOutput_1Match_no1To1() {
|
||||
OutputResult result = FuzzyPhoneNumberHelper.generateOutput(mapOf(MX_A_1, ACI_A), new InputResult(setOf(MX_A, MX_A_1), Collections.singletonMap(MX_A, MX_A_1)));
|
||||
OutputResult<ACI> result = FuzzyPhoneNumberHelper.generateOutput(mapOf(MX_A_1, ACI_A), new InputResult(setOf(MX_A, MX_A_1), Collections.singletonMap(MX_A, MX_A_1)));
|
||||
|
||||
assertEquals(1, result.getNumbers().size());
|
||||
assertEquals(ACI_A, result.getNumbers().get(MX_A_1));
|
||||
|
|
|
@ -521,6 +521,7 @@ public class SignalServiceAccountManager {
|
|||
public CdsiV2Service.Response getRegisteredUsersWithCdsi(Set<String> previousE164s,
|
||||
Set<String> newE164s,
|
||||
Map<ServiceId, ProfileKey> serviceIds,
|
||||
boolean requireAcis,
|
||||
Optional<byte[]> token,
|
||||
String mrEnclave,
|
||||
Consumer<byte[]> tokenSaver)
|
||||
|
@ -528,7 +529,7 @@ public class SignalServiceAccountManager {
|
|||
{
|
||||
CdsiAuthResponse auth = pushServiceSocket.getCdsiAuth();
|
||||
CdsiV2Service service = new CdsiV2Service(configuration, mrEnclave);
|
||||
CdsiV2Service.Request request = new CdsiV2Service.Request(previousE164s, newE164s, serviceIds, token);
|
||||
CdsiV2Service.Request request = new CdsiV2Service.Request(previousE164s, newE164s, serviceIds, requireAcis, token);
|
||||
Single<ServiceResponse<CdsiV2Service.Response>> single = service.getRegisteredUsers(auth.getUsername(), auth.getPassword(), request, tokenSaver);
|
||||
|
||||
ServiceResponse<CdsiV2Service.Response> serviceResponse;
|
||||
|
|
|
@ -101,7 +101,8 @@ public final class CdsiV2Service {
|
|||
.setPrevE164S(toByteString(previousE164s))
|
||||
.setNewE164S(toByteString(newE164s))
|
||||
.setDiscardE164S(toByteString(removedE164s))
|
||||
.setAciUakPairs(toByteString(request.serviceIds));
|
||||
.setAciUakPairs(toByteString(request.serviceIds))
|
||||
.setReturnAcisWithoutUaks(request.requireAcis);
|
||||
|
||||
if (request.token != null) {
|
||||
builder.setToken(ByteString.copyFrom(request.token));
|
||||
|
@ -154,9 +155,11 @@ public final class CdsiV2Service {
|
|||
|
||||
final Map<ServiceId, ProfileKey> serviceIds;
|
||||
|
||||
final boolean requireAcis;
|
||||
|
||||
final byte[] token;
|
||||
|
||||
public Request(Set<String> previousE164s, Set<String> newE164s, Map<ServiceId, ProfileKey> serviceIds, Optional<byte[]> token) {
|
||||
public Request(Set<String> previousE164s, Set<String> newE164s, Map<ServiceId, ProfileKey> serviceIds, boolean requireAcis, Optional<byte[]> token) {
|
||||
if (previousE164s.size() > 0 && !token.isPresent()) {
|
||||
throw new IllegalArgumentException("You must have a token if you have previousE164s!");
|
||||
}
|
||||
|
@ -165,6 +168,7 @@ public final class CdsiV2Service {
|
|||
this.newE164s = newE164s;
|
||||
this.removedE164s = Collections.emptySet();
|
||||
this.serviceIds = serviceIds;
|
||||
this.requireAcis = requireAcis;
|
||||
this.token = token.orElse(null);
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,10 @@ message ClientRequest {
|
|||
// After receiving a new token from the server, send back a message just
|
||||
// containing a token_ack.
|
||||
bool token_ack = 7;
|
||||
|
||||
// Request that, if the server allows, both ACI and PNI be returned even
|
||||
// if the aci_uak_pairs don't match.
|
||||
bool return_acis_without_uaks = 8;
|
||||
}
|
||||
|
||||
message ClientResponse {
|
||||
|
|
Ładowanie…
Reference in New Issue