Make ACI's optional on ContactRecords.
This commit is contained in:
parent
2492b8de34
commit
4b6b87d632
5 changed files with 61 additions and 35 deletions
|
@ -795,19 +795,19 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||||
if (id < 0) {
|
if (id < 0) {
|
||||||
Log.w(TAG, "[applyStorageSyncContactInsert] Failed to insert. Possibly merging.")
|
Log.w(TAG, "[applyStorageSyncContactInsert] Failed to insert. Possibly merging.")
|
||||||
if (FeatureFlags.phoneNumberPrivacy()) {
|
if (FeatureFlags.phoneNumberPrivacy()) {
|
||||||
recipientId = getAndPossiblyMergePnpVerified(if (insert.aci.isValid) insert.aci else null, insert.pni.orElse(null), insert.number.orElse(null))
|
recipientId = getAndPossiblyMergePnpVerified(insert.aci.orNull(), insert.pni.orNull(), insert.number.orNull())
|
||||||
} else {
|
} else {
|
||||||
recipientId = getAndPossiblyMerge(if (insert.aci.isValid) insert.aci else null, insert.number.orElse(null))
|
recipientId = getAndPossiblyMerge(insert.aci.orNull(), insert.number.orNull())
|
||||||
}
|
}
|
||||||
db.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(recipientId))
|
db.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(recipientId))
|
||||||
} else {
|
} else {
|
||||||
recipientId = RecipientId.from(id)
|
recipientId = RecipientId.from(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (insert.identityKey.isPresent && insert.aci.isValid) {
|
if (insert.identityKey.isPresent && insert.aci.isPresent) {
|
||||||
try {
|
try {
|
||||||
val identityKey = IdentityKey(insert.identityKey.get(), 0)
|
val identityKey = IdentityKey(insert.identityKey.get(), 0)
|
||||||
identities.updateIdentityAfterSync(insert.aci.toString(), recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(insert.identityState))
|
identities.updateIdentityAfterSync(insert.aci.get().toString(), recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(insert.identityState))
|
||||||
} catch (e: InvalidKeyException) {
|
} catch (e: InvalidKeyException) {
|
||||||
Log.w(TAG, "Failed to process identity key during insert! Skipping.", e)
|
Log.w(TAG, "Failed to process identity key during insert! Skipping.", e)
|
||||||
}
|
}
|
||||||
|
@ -836,9 +836,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||||
|
|
||||||
Log.w(TAG, "[applyStorageSyncContactUpdate] Found user $recipientId. Possibly merging.")
|
Log.w(TAG, "[applyStorageSyncContactUpdate] Found user $recipientId. Possibly merging.")
|
||||||
if (FeatureFlags.phoneNumberPrivacy()) {
|
if (FeatureFlags.phoneNumberPrivacy()) {
|
||||||
recipientId = getAndPossiblyMergePnpVerified(if (update.new.aci.isValid) update.new.aci else null, update.new.pni.orElse(null), update.new.number.orElse(null))
|
recipientId = getAndPossiblyMergePnpVerified(update.new.aci.orElse(null), update.new.pni.orElse(null), update.new.number.orElse(null))
|
||||||
} else {
|
} else {
|
||||||
recipientId = getAndPossiblyMerge(if (update.new.aci.isValid) update.new.aci else null, update.new.number.orElse(null))
|
recipientId = getAndPossiblyMerge(update.new.aci.orElse(null), update.new.number.orElse(null))
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.w(TAG, "[applyStorageSyncContactUpdate] Merged into $recipientId")
|
Log.w(TAG, "[applyStorageSyncContactUpdate] Merged into $recipientId")
|
||||||
|
@ -855,9 +855,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val oldIdentityRecord = identityStore.getIdentityRecord(recipientId)
|
val oldIdentityRecord = identityStore.getIdentityRecord(recipientId)
|
||||||
if (update.new.identityKey.isPresent && update.new.aci.isValid) {
|
if (update.new.identityKey.isPresent && update.new.aci.isPresent) {
|
||||||
val identityKey = IdentityKey(update.new.identityKey.get(), 0)
|
val identityKey = IdentityKey(update.new.identityKey.get(), 0)
|
||||||
identities.updateIdentityAfterSync(update.new.aci.toString(), recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(update.new.identityState))
|
identities.updateIdentityAfterSync(update.new.aci.get().toString(), recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(update.new.identityState))
|
||||||
}
|
}
|
||||||
|
|
||||||
val newIdentityRecord = identityStore.getIdentityRecord(recipientId)
|
val newIdentityRecord = identityStore.getIdentityRecord(recipientId)
|
||||||
|
@ -3872,9 +3872,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||||
val systemName = ProfileName.fromParts(contact.systemGivenName.orElse(null), contact.systemFamilyName.orElse(null))
|
val systemName = ProfileName.fromParts(contact.systemGivenName.orElse(null), contact.systemFamilyName.orElse(null))
|
||||||
val username = contact.username.orElse(null)
|
val username = contact.username.orElse(null)
|
||||||
|
|
||||||
if (contact.aci.isValid) {
|
put(ACI_COLUMN, contact.aci.orElse(null)?.toString())
|
||||||
put(ACI_COLUMN, contact.aci.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FeatureFlags.phoneNumberPrivacy()) {
|
if (FeatureFlags.phoneNumberPrivacy()) {
|
||||||
put(PNI_COLUMN, contact.pni.orElse(null)?.toString())
|
put(PNI_COLUMN, contact.pni.orElse(null)?.toString())
|
||||||
|
@ -3905,14 +3903,14 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||||
put(UNREGISTERED_TIMESTAMP, contact.unregisteredTimestamp)
|
put(UNREGISTERED_TIMESTAMP, contact.unregisteredTimestamp)
|
||||||
if (contact.unregisteredTimestamp > 0L) {
|
if (contact.unregisteredTimestamp > 0L) {
|
||||||
put(REGISTERED, RegisteredState.NOT_REGISTERED.id)
|
put(REGISTERED, RegisteredState.NOT_REGISTERED.id)
|
||||||
} else if (contact.aci.isValid) {
|
} else if (contact.aci.isPresent) {
|
||||||
put(REGISTERED, RegisteredState.REGISTERED.id)
|
put(REGISTERED, RegisteredState.REGISTERED.id)
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Contact is marked as registered, but has no serviceId! Can't locally mark registered. (Phone: ${contact.number.orElse("null")}, Username: ${username?.isNotEmpty()})")
|
Log.w(TAG, "Contact is marked as registered, but has no serviceId! Can't locally mark registered. (Phone: ${contact.number.orElse("null")}, Username: ${username?.isNotEmpty()})")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInsert) {
|
if (isInsert) {
|
||||||
put(AVATAR_COLOR, AvatarColorHash.forAddress(contact.aci.toString(), contact.number.orNull()).serialize())
|
put(AVATAR_COLOR, AvatarColorHash.forAddress(contact.aci.map { it.toString() }.or(contact.pni.map { it.toString() }).orNull(), contact.number.orNull()).serialize())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,9 +80,9 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remoteRecord.getUnregisteredTimestamp() > 0 && remoteRecord.getAci() != null && !remoteRecord.getPni().isPresent() && !remoteRecord.getNumber().isPresent()) {
|
if (remoteRecord.getUnregisteredTimestamp() > 0 && remoteRecord.getAci().isPresent() && remoteRecord.getPni().isEmpty() && remoteRecord.getNumber().isEmpty()) {
|
||||||
unregisteredAciOnly.add(remoteRecord);
|
unregisteredAciOnly.add(remoteRecord);
|
||||||
} else if (remoteRecord.getAci() != null && remoteRecord.getAci().equals(remoteRecord.getPni().orElse(null))) {
|
} else if (remoteRecord.getAci().isEmpty()) {
|
||||||
pniE164Only.add(remoteRecord);
|
pniE164Only.add(remoteRecord);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,20 +120,20 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error cases:
|
* Error cases:
|
||||||
* - You can't have a contact record without an address component.
|
* - You can't have a contact record without an ACI or PNI.
|
||||||
* - You can't have a contact record for yourself. That should be an account record.
|
* - You can't have a contact record for yourself. That should be an account record.
|
||||||
*
|
*
|
||||||
* Note: This method could be written more succinctly, but the logs are useful :)
|
* Note: This method could be written more succinctly, but the logs are useful :)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
boolean isInvalid(@NonNull SignalContactRecord remote) {
|
boolean isInvalid(@NonNull SignalContactRecord remote) {
|
||||||
if (remote.getAci() == null) {
|
boolean hasAci = remote.getAci().isPresent() && remote.getAci().get().isValid();
|
||||||
Log.w(TAG, "No address on the ContentRecord -- marking as invalid.");
|
boolean hasPni = remote.getPni().isPresent() && remote.getPni().get().isValid();
|
||||||
|
|
||||||
|
if (!hasAci && !hasPni) {
|
||||||
|
Log.w(TAG, "Found a ContactRecord with neither an ACI nor a PNI -- marking as invalid.");
|
||||||
return true;
|
return true;
|
||||||
} else if (remote.getAci().isUnknown()) {
|
} else if (selfAci != null && selfAci.equals(remote.getAci().orElse(null)) ||
|
||||||
Log.w(TAG, "Found a ContactRecord without a UUID -- marking as invalid.");
|
|
||||||
return true;
|
|
||||||
} else if (remote.getAci().equals(selfAci) ||
|
|
||||||
(selfPni != null && selfPni.equals(remote.getPni().orElse(null))) ||
|
(selfPni != null && selfPni.equals(remote.getPni().orElse(null))) ||
|
||||||
(selfE164 != null && remote.getNumber().isPresent() && remote.getNumber().get().equals(selfE164)))
|
(selfE164 != null && remote.getNumber().isPresent() && remote.getNumber().get().equals(selfE164)))
|
||||||
{
|
{
|
||||||
|
@ -153,7 +153,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||||
remote = remote.withoutPni();
|
remote = remote.withoutPni();
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<RecipientId> found = recipientTable.getByAci(remote.getAci());
|
Optional<RecipientId> found = remote.getAci().isPresent() ? recipientTable.getByAci(remote.getAci().get()) : Optional.empty();
|
||||||
|
|
||||||
if (found.isEmpty() && remote.getNumber().isPresent()) {
|
if (found.isEmpty() && remote.getNumber().isPresent()) {
|
||||||
found = recipientTable.getByE164(remote.getNumber().get());
|
found = recipientTable.getByE164(remote.getNumber().get());
|
||||||
|
@ -200,7 +200,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||||
byte[] identityKey;
|
byte[] identityKey;
|
||||||
|
|
||||||
if ((remote.getIdentityState() != local.getIdentityState() && remote.getIdentityKey().isPresent()) ||
|
if ((remote.getIdentityState() != local.getIdentityState() && remote.getIdentityKey().isPresent()) ||
|
||||||
(remote.getIdentityKey().isPresent() && !local.getIdentityKey().isPresent()))
|
(remote.getIdentityKey().isPresent() && local.getIdentityKey().isEmpty()))
|
||||||
{
|
{
|
||||||
identityState = remote.getIdentityState();
|
identityState = remote.getIdentityState();
|
||||||
identityKey = remote.getIdentityKey().get();
|
identityKey = remote.getIdentityKey().get();
|
||||||
|
@ -209,9 +209,9 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||||
identityKey = local.getIdentityKey().orElse(null);
|
identityKey = local.getIdentityKey().orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (identityKey != null && remote.getIdentityKey().isPresent() && !Arrays.equals(identityKey, remote.getIdentityKey().get())) {
|
if (local.getAci().isPresent() && identityKey != null && remote.getIdentityKey().isPresent() && !Arrays.equals(identityKey, remote.getIdentityKey().get())) {
|
||||||
Log.w(TAG, "The local and remote identity keys do not match for " + local.getAci() + ". Enqueueing a profile fetch.");
|
Log.w(TAG, "The local and remote identity keys do not match for " + local.getAci().orElse(null) + ". Enqueueing a profile fetch.");
|
||||||
RetrieveProfileJob.enqueue(Recipient.trustedPush(local.getAci(), local.getPni().orElse(null), local.getNumber().orElse(null)).getId());
|
RetrieveProfileJob.enqueue(Recipient.trustedPush(local.getAci().get(), local.getPni().orElse(null), local.getNumber().orElse(null)).getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
PNI pni;
|
PNI pni;
|
||||||
|
@ -250,7 +250,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] unknownFields = remote.serializeUnknownFields();
|
byte[] unknownFields = remote.serializeUnknownFields();
|
||||||
ACI aci = local.getAci() == ACI.UNKNOWN ? remote.getAci() : local.getAci();
|
ACI aci = local.getAci().isEmpty() ? remote.getAci().orElse(null) : local.getAci().get();
|
||||||
byte[] profileKey = OptionalUtil.or(remote.getProfileKey(), local.getProfileKey()).orElse(null);
|
byte[] profileKey = OptionalUtil.or(remote.getProfileKey(), local.getProfileKey()).orElse(null);
|
||||||
String username = OptionalUtil.or(remote.getUsername(), local.getUsername()).orElse("");
|
String username = OptionalUtil.or(remote.getUsername(), local.getUsername()).orElse("");
|
||||||
boolean blocked = remote.isBlocked();
|
boolean blocked = remote.isBlocked();
|
||||||
|
@ -324,7 +324,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||||
|
|
||||||
private static boolean doParamsMatch(@NonNull SignalContactRecord contact,
|
private static boolean doParamsMatch(@NonNull SignalContactRecord contact,
|
||||||
@Nullable byte[] unknownFields,
|
@Nullable byte[] unknownFields,
|
||||||
@NonNull ServiceId serviceId,
|
@NonNull ACI aci,
|
||||||
@Nullable PNI pni,
|
@Nullable PNI pni,
|
||||||
@Nullable String e164,
|
@Nullable String e164,
|
||||||
@NonNull String profileGivenName,
|
@NonNull String profileGivenName,
|
||||||
|
@ -346,7 +346,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||||
boolean hidden)
|
boolean hidden)
|
||||||
{
|
{
|
||||||
return Arrays.equals(contact.serializeUnknownFields(), unknownFields) &&
|
return Arrays.equals(contact.serializeUnknownFields(), unknownFields) &&
|
||||||
Objects.equals(contact.getAci(), serviceId) &&
|
Objects.equals(contact.getAci().orElse(null), aci) &&
|
||||||
Objects.equals(contact.getPni().orElse(null), pni) &&
|
Objects.equals(contact.getPni().orElse(null), pni) &&
|
||||||
Objects.equals(contact.getNumber().orElse(null), e164) &&
|
Objects.equals(contact.getNumber().orElse(null), e164) &&
|
||||||
Objects.equals(contact.getProfileGivenName().orElse(""), profileGivenName) &&
|
Objects.equals(contact.getProfileGivenName().orElse(""), profileGivenName) &&
|
||||||
|
|
|
@ -164,7 +164,7 @@ public final class StorageSyncValidations {
|
||||||
if (insert.getContact().isPresent()) {
|
if (insert.getContact().isPresent()) {
|
||||||
SignalContactRecord contact = insert.getContact().get();
|
SignalContactRecord contact = insert.getContact().get();
|
||||||
|
|
||||||
if (self.requireAci().equals(contact.getAci()) ||
|
if (self.requireAci().equals(contact.getAci().orElse(null)) ||
|
||||||
self.requirePni().equals(contact.getPni().orElse(null)) ||
|
self.requirePni().equals(contact.getPni().orElse(null)) ||
|
||||||
self.requireE164().equals(contact.getNumber().orElse("")))
|
self.requireE164().equals(contact.getNumber().orElse("")))
|
||||||
{
|
{
|
||||||
|
|
|
@ -69,7 +69,7 @@ class ContactRecordProcessorTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `isInvalid, missing serviceId, true`() {
|
fun `isInvalid, missing ACI and PNI, true`() {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable)
|
val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable)
|
||||||
|
|
||||||
|
@ -84,6 +84,24 @@ class ContactRecordProcessorTest {
|
||||||
assertTrue(result)
|
assertTrue(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `isInvalid, unknown ACI and PNI, true`() {
|
||||||
|
// GIVEN
|
||||||
|
val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable)
|
||||||
|
|
||||||
|
val record = buildRecord {
|
||||||
|
setAci(ACI.UNKNOWN.toString())
|
||||||
|
setPni(PNI.UNKNOWN.toString())
|
||||||
|
setE164(E164_B)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = subject.isInvalid(record)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
assertTrue(result)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `isInvalid, e164 matches self, true`() {
|
fun `isInvalid, e164 matches self, true`() {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
|
|
|
@ -26,7 +26,7 @@ public final class SignalContactRecord implements SignalRecord {
|
||||||
private final ContactRecord proto;
|
private final ContactRecord proto;
|
||||||
private final boolean hasUnknownFields;
|
private final boolean hasUnknownFields;
|
||||||
|
|
||||||
private final ACI aci;
|
private final Optional<ACI> aci;
|
||||||
private final Optional<PNI> pni;
|
private final Optional<PNI> pni;
|
||||||
private final Optional<String> e164;
|
private final Optional<String> e164;
|
||||||
private final Optional<String> profileGivenName;
|
private final Optional<String> profileGivenName;
|
||||||
|
@ -42,7 +42,7 @@ public final class SignalContactRecord implements SignalRecord {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.proto = proto;
|
this.proto = proto;
|
||||||
this.hasUnknownFields = ProtoUtil.hasUnknownFields(proto);
|
this.hasUnknownFields = ProtoUtil.hasUnknownFields(proto);
|
||||||
this.aci = ACI.parseOrUnknown(proto.getAci());
|
this.aci = OptionalUtil.absentIfEmpty(proto.getAci()).map(ACI::parseOrNull);
|
||||||
this.pni = OptionalUtil.absentIfEmpty(proto.getPni()).map(PNI::parseOrNull);
|
this.pni = OptionalUtil.absentIfEmpty(proto.getPni()).map(PNI::parseOrNull);
|
||||||
this.e164 = OptionalUtil.absentIfEmpty(proto.getE164());
|
this.e164 = OptionalUtil.absentIfEmpty(proto.getE164());
|
||||||
this.profileGivenName = OptionalUtil.absentIfEmpty(proto.getGivenName());
|
this.profileGivenName = OptionalUtil.absentIfEmpty(proto.getGivenName());
|
||||||
|
@ -173,7 +173,7 @@ public final class SignalContactRecord implements SignalRecord {
|
||||||
return hasUnknownFields ? proto.toByteArray() : null;
|
return hasUnknownFields ? proto.toByteArray() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ACI getAci() {
|
public Optional<ACI> getAci() {
|
||||||
return aci;
|
return aci;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,6 +181,16 @@ public final class SignalContactRecord implements SignalRecord {
|
||||||
return pni;
|
return pni;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<? extends ServiceId> getServiceId() {
|
||||||
|
if (aci.isPresent()) {
|
||||||
|
return aci;
|
||||||
|
} else if (pni.isPresent()) {
|
||||||
|
return pni;
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<String> getNumber() {
|
public Optional<String> getNumber() {
|
||||||
return e164;
|
return e164;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue