Improve and centralize e164 utils.

This commit is contained in:
Greyson Parrelli
2025-03-03 10:42:21 -05:00
parent 0fdcc1c027
commit 9c473fb570
99 changed files with 748 additions and 1826 deletions

View File

@@ -0,0 +1,15 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.jobmanager.migrations
import org.thoughtcrime.securesms.jobmanager.JobMigration
/**
* Used as a replacement for another JobMigration that is no longer necessary.
*/
class DeprecatedJobMigration(version: Int) : JobMigration(version) {
override fun migrate(jobData: JobData): JobData = jobData
}

View File

@@ -1,90 +0,0 @@
package org.thoughtcrime.securesms.jobmanager.migrations;
import android.content.Context;
import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.JobMigration;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.signal.core.util.Base64;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto;
import java.io.IOException;
import java.util.Optional;
/**
* We changed the format of the queue key for legacy PushProcessMessageJob
* to have the recipient ID in it, so this migrates existing jobs to be in that format.
*/
public class PushProcessMessageQueueJobMigration extends JobMigration {
private static final String TAG = Log.tag(PushProcessMessageQueueJobMigration.class);
private final Context context;
public PushProcessMessageQueueJobMigration(@NonNull Context context) {
super(6);
this.context = context;
}
@Override
public @NonNull JobData migrate(@NonNull JobData jobData) {
if ("PushProcessJob".equals(jobData.getFactoryKey())) {
Log.i(TAG, "Found a PushProcessMessageJob to migrate.");
try {
return migratePushProcessMessageJob(context, jobData);
} catch (IOException | InvalidInputException e) {
Log.w(TAG, "Failed to migrate message job.", e);
return jobData;
}
}
return jobData;
}
private static @NonNull JobData migratePushProcessMessageJob(@NonNull Context context, @NonNull JobData jobData) throws IOException, InvalidInputException {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String suffix = "";
if (data.getInt("message_state") == 0) {
SignalServiceContentProto proto = SignalServiceContentProto.ADAPTER.decode(Base64.decode(data.getString("message_content")));
if (proto != null && proto.content != null && proto.content.dataMessage != null && proto.content.dataMessage.groupV2 != null) {
Log.i(TAG, "Migrating a group message.");
GroupId groupId = GroupId.v2(new GroupMasterKey(proto.content.dataMessage.groupV2.masterKey.toByteArray()));
Recipient recipient = Recipient.externalGroupExact(groupId);
suffix = recipient.getId().toQueueKey();
} else if (proto != null && proto.metadata != null && proto.metadata.address != null) {
Log.i(TAG, "Migrating an individual message.");
ServiceId senderServiceId = ServiceId.parseOrThrow(proto.metadata.address.uuid);
String senderE164 = proto.metadata.address.e164;
SignalServiceAddress sender = new SignalServiceAddress(senderServiceId, Optional.ofNullable(senderE164));
suffix = RecipientId.from(sender).toQueueKey();
}
} else {
Log.i(TAG, "Migrating an exception message.");
String exceptionSender = data.getString("exception_sender");
GroupId exceptionGroup = GroupId.parseNullableOrThrow(data.getStringOrDefault("exception_groupId", null));
if (exceptionGroup != null) {
suffix = Recipient.externalGroupExact(exceptionGroup).getId().toQueueKey();
} else if (exceptionSender != null) {
suffix = Recipient.external(context, exceptionSender).getId().toQueueKey();
}
}
return jobData.withQueueKey("__PUSH_PROCESS_JOB__" + suffix);
}
}

View File

@@ -1,59 +0,0 @@
package org.thoughtcrime.securesms.jobmanager.migrations;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.JobMigration;
/**
* Fixes things that went wrong in {@link RecipientIdJobMigration}. In particular, some jobs didn't
* have some necessary data fields carried over. Thankfully they're relatively non-critical, so
* we'll just swap them out with failing jobs if they're missing something.
*/
public class RecipientIdFollowUpJobMigration extends JobMigration {
public RecipientIdFollowUpJobMigration() {
this(3);
}
RecipientIdFollowUpJobMigration(int endVersion) {
super(endVersion);
}
@Override
public @NonNull JobData migrate(@NonNull JobData jobData) {
switch(jobData.getFactoryKey()) {
case "RequestGroupInfoJob": return migrateRequestGroupInfoJob(jobData);
case "SendDeliveryReceiptJob": return migrateSendDeliveryReceiptJob(jobData);
default:
return jobData;
}
}
private static @NonNull JobData migrateRequestGroupInfoJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
if (!data.hasString("source") || !data.hasString("group_id")) {
return failingJobData();
} else {
return jobData;
}
}
private static @NonNull JobData migrateSendDeliveryReceiptJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
if (!data.hasString("recipient") ||
!data.hasLong("message_id") ||
!data.hasLong("timestamp"))
{
return failingJobData();
} else {
return jobData;
}
}
private static JobData failingJobData() {
return JobData.FAILING_JOB_DATA;
}
}

View File

@@ -1,11 +0,0 @@
package org.thoughtcrime.securesms.jobmanager.migrations;
/**
* Unfortunately there was a bug in {@link RecipientIdFollowUpJobMigration} that requires it to be
* run again.
*/
public class RecipientIdFollowUpJobMigration2 extends RecipientIdFollowUpJobMigration {
public RecipientIdFollowUpJobMigration2() {
super(4);
}
}

View File

@@ -1,263 +0,0 @@
package org.thoughtcrime.securesms.jobmanager.migrations;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.JobMigration;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.JsonUtils;
import java.io.IOException;
import java.io.Serializable;
public class RecipientIdJobMigration extends JobMigration {
private final Application application;
public RecipientIdJobMigration(@NonNull Application application) {
super(2);
this.application = application;
}
@Override
public @NonNull JobData migrate(@NonNull JobData jobData) {
switch(jobData.getFactoryKey()) {
case "MultiDeviceContactUpdateJob": return migrateMultiDeviceContactUpdateJob(jobData);
case "MultiDeviceRevealUpdateJob": return migrateMultiDeviceViewOnceOpenJob(jobData);
case "RequestGroupInfoJob": return migrateRequestGroupInfoJob(jobData);
case "SendDeliveryReceiptJob": return migrateSendDeliveryReceiptJob(jobData);
case "MultiDeviceVerifiedUpdateJob": return migrateMultiDeviceVerifiedUpdateJob(jobData);
case "RetrieveProfileJob": return migrateRetrieveProfileJob(jobData);
case "PushGroupSendJob": return migratePushGroupSendJob(jobData);
case "PushGroupUpdateJob": return migratePushGroupUpdateJob(jobData);
case "DirectoryRefreshJob": return migrateDirectoryRefreshJob(jobData);
case "RetrieveProfileAvatarJob": return migrateRetrieveProfileAvatarJob(jobData);
case "MultiDeviceReadUpdateJob": return migrateMultiDeviceReadUpdateJob(jobData);
case "PushTextSendJob": return migratePushTextSendJob(jobData);
case "PushMediaSendJob": return migratePushMediaSendJob(jobData);
case "SmsSendJob": return migrateSmsSendJob(jobData);
default: return jobData;
}
}
private @NonNull JobData migrateMultiDeviceContactUpdateJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String address = data.hasString("address") ? data.getString("address") : null;
JsonJobData updatedData = new JsonJobData.Builder().putString("recipient", address != null ? Recipient.external(application, address).getId().serialize() : null)
.putBoolean("force_sync", data.getBoolean("force_sync"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateMultiDeviceViewOnceOpenJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
try {
String rawOld = data.getString("message_id");
OldSerializableSyncMessageId old = JsonUtils.fromJson(rawOld, OldSerializableSyncMessageId.class);
Recipient recipient = Recipient.external(application, old.sender);
NewSerializableSyncMessageId updated = new NewSerializableSyncMessageId(recipient.getId().serialize(), old.timestamp);
String rawUpdated = JsonUtils.toJson(updated);
JsonJobData updatedData = new JsonJobData.Builder().putString("message_id", rawUpdated).build();
return jobData.withData(updatedData.serialize());
} catch (IOException e) {
throw new AssertionError(e);
}
}
private @NonNull JobData migrateRequestGroupInfoJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String address = data.getString("source");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("source", recipient.getId().serialize())
.putString("group_id", data.getString("group_id"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateSendDeliveryReceiptJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String address = data.getString("address");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("recipient", recipient.getId().serialize())
.putLong("message_id", data.getLong("message_id"))
.putLong("timestamp", data.getLong("timestamp"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateMultiDeviceVerifiedUpdateJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String address = data.getString("destination");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("destination", recipient.getId().serialize())
.putString("identity_key", data.getString("identity_key"))
.putInt("verified_status", data.getInt("verified_status"))
.putLong("timestamp", data.getLong("timestamp"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateRetrieveProfileJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String address = data.getString("address");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("recipient", recipient.getId().serialize()).build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migratePushGroupSendJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
// noinspection ConstantConditions
Recipient queueRecipient = Recipient.external(application, jobData.getQueueKey());
String address = data.hasString("filter_address") ? data.getString("filter_address") : null;
RecipientId recipientId = address != null ? Recipient.external(application, address).getId() : null;
JsonJobData updatedData = new JsonJobData.Builder().putString("filter_recipient", recipientId != null ? recipientId.serialize() : null)
.putLong("message_id", data.getLong("message_id"))
.build();
return jobData.withQueueKey(queueRecipient.getId().toQueueKey())
.withData(updatedData.serialize());
}
private @NonNull JobData migratePushGroupUpdateJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String address = data.getString("source");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("source", recipient.getId().serialize())
.putString("group_id", data.getString("group_id"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateDirectoryRefreshJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String address = data.hasString("address") ? data.getString("address") : null;
Recipient recipient = address != null ? Recipient.external(application, address) : null;
JsonJobData updatedData = new JsonJobData.Builder().putString("recipient", recipient != null ? recipient.getId().serialize() : null)
.putBoolean("notify_of_new_users", data.getBoolean("notify_of_new_users"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateRetrieveProfileAvatarJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
//noinspection ConstantConditions
String queueAddress = jobData.getQueueKey().substring("RetrieveProfileAvatarJob".length());
Recipient queueRecipient = Recipient.external(application, queueAddress);
String address = data.getString("address");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("recipient", recipient.getId().serialize())
.putString("profile_avatar", data.getString("profile_avatar"))
.build();
return jobData.withQueueKey("RetrieveProfileAvatarJob::" + queueRecipient.getId().toQueueKey())
.withData(updatedData.serialize());
}
private @NonNull JobData migrateMultiDeviceReadUpdateJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
try {
String[] rawOld = data.getStringArray("message_ids");
String[] rawUpdated = new String[rawOld.length];
for (int i = 0; i < rawOld.length; i++) {
OldSerializableSyncMessageId old = JsonUtils.fromJson(rawOld[i], OldSerializableSyncMessageId.class);
Recipient recipient = Recipient.external(application, old.sender);
NewSerializableSyncMessageId updated = new NewSerializableSyncMessageId(recipient.getId().serialize(), old.timestamp);
rawUpdated[i] = JsonUtils.toJson(updated);
}
JsonJobData updatedData = new JsonJobData.Builder().putStringArray("message_ids", rawUpdated).build();
return jobData.withData(updatedData.serialize());
} catch (IOException e) {
throw new AssertionError(e);
}
}
private @NonNull JobData migratePushTextSendJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
//noinspection ConstantConditions
Recipient recipient = Recipient.external(application, jobData.getQueueKey());
return jobData.withQueueKey(recipient.getId().toQueueKey());
}
private @NonNull JobData migratePushMediaSendJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
//noinspection ConstantConditions
Recipient recipient = Recipient.external(application, jobData.getQueueKey());
return jobData.withQueueKey(recipient.getId().toQueueKey());
}
private @NonNull JobData migrateSmsSendJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
//noinspection ConstantConditions
if (jobData.getQueueKey() != null) {
Recipient recipient = Recipient.external(application, jobData.getQueueKey());
return jobData.withQueueKey(recipient.getId().toQueueKey());
} else {
return jobData;
}
}
@VisibleForTesting
static class OldSerializableSyncMessageId implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty
private final String sender;
@JsonProperty
private final long timestamp;
OldSerializableSyncMessageId(@JsonProperty("sender") String sender, @JsonProperty("timestamp") long timestamp) {
this.sender = sender;
this.timestamp = timestamp;
}
}
@VisibleForTesting
static class NewSerializableSyncMessageId implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty
private final String recipientId;
@JsonProperty
private final long timestamp;
NewSerializableSyncMessageId(@JsonProperty("recipientId") String recipientId, @JsonProperty("timestamp") long timestamp) {
this.recipientId = recipientId;
this.timestamp = timestamp;
}
}
}