Remove processing of inbound GV1 messages.

This commit is contained in:
Greyson Parrelli 2022-07-29 10:07:12 -04:00
parent 5140353722
commit df3399bde5
22 changed files with 92 additions and 1054 deletions

View file

@ -157,7 +157,6 @@ final class GroupManagerV1 {
Recipient recipient = Recipient.resolved(member); Recipient recipient = Recipient.resolved(member);
if (recipient.hasE164()) { if (recipient.hasE164()) {
e164Members.add(recipient.requireE164()); e164Members.add(recipient.requireE164());
uuidMembers.add(GroupV1MessageProcessor.createMember(recipient.requireE164()));
} }
} }
@ -216,6 +215,6 @@ final class GroupManagerV1 {
return Optional.empty(); return Optional.empty();
} }
return Optional.of(GroupUtil.createGroupV1LeaveMessage(groupId, groupRecipient)); return Optional.empty();
} }
} }

View file

@ -1,309 +0,0 @@
package org.thoughtcrime.securesms.groups;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
import com.google.protobuf.ByteString;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.MessageDatabase;
import org.thoughtcrime.securesms.database.MessageDatabase.InsertResult;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.AvatarGroupsV1DownloadJob;
import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingGroupUpdateMessage;
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sms.IncomingGroupUpdateMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.util.Base64;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer;
import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
public final class GroupV1MessageProcessor {
private static final String TAG = Log.tag(GroupV1MessageProcessor.class);
public static @Nullable Long process(@NonNull Context context,
@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message,
boolean outgoing)
throws BadGroupIdException
{
SignalServiceGroupContext signalServiceGroupContext = message.getGroupContext().get();
Optional<SignalServiceGroup> groupV1 = signalServiceGroupContext.getGroupV1();
if (signalServiceGroupContext.getGroupV2().isPresent()) {
throw new AssertionError("Cannot process GV2");
}
if (!groupV1.isPresent() || groupV1.get().getGroupId() == null) {
Log.w(TAG, "Received group message with no id! Ignoring...");
return null;
}
GroupDatabase database = SignalDatabase.groups();
SignalServiceGroup group = groupV1.get();
GroupId id = GroupId.v1(group.getGroupId());
Optional<GroupRecord> record = database.getGroup(id);
if (record.isPresent() && group.getType() == Type.UPDATE) {
return handleGroupUpdate(context, content, group, record.get(), outgoing);
} else if (!record.isPresent() && group.getType() == Type.UPDATE) {
return handleGroupCreate(context, content, group, outgoing);
} else if (record.isPresent() && group.getType() == Type.QUIT) {
return handleGroupLeave(context, content, group, record.get(), outgoing);
} else if (record.isPresent() && group.getType() == Type.REQUEST_INFO) {
return handleGroupInfoRequest(context, content, record.get());
} else {
Log.w(TAG, "Received unknown type, ignoring...");
return null;
}
}
private static @Nullable Long handleGroupCreate(@NonNull Context context,
@NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group,
boolean outgoing)
{
GroupDatabase database = SignalDatabase.groups();
GroupId.V1 id = GroupId.v1orThrow(group.getGroupId());
GroupContext.Builder builder = createGroupContext(group);
builder.setType(GroupContext.Type.UPDATE);
SignalServiceAttachment avatar = group.getAvatar().orElse(null);
List<RecipientId> members = new LinkedList<>();
if (group.getMembers().isPresent()) {
for (SignalServiceAddress member : group.getMembers().get()) {
members.add(Recipient.externalGV1Member(member).getId());
}
}
database.create(id, group.getName().orElse(null), members,
avatar != null && avatar.isPointer() ? avatar.asPointer() : null, null);
Recipient sender = Recipient.externalPush(content.getSender());
if (sender.isSystemContact() || sender.isProfileSharing()) {
Log.i(TAG, "Auto-enabling profile sharing because 'adder' is trusted. contact: " + sender.isSystemContact() + ", profileSharing: " + sender.isProfileSharing());
SignalDatabase.recipients().setProfileSharing(Recipient.externalGroupExact(id).getId(), true);
}
return storeMessage(context, content, group, builder.build(), outgoing);
}
private static @Nullable Long handleGroupUpdate(@NonNull Context context,
@NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group,
@NonNull GroupRecord groupRecord,
boolean outgoing)
{
GroupDatabase database = SignalDatabase.groups();
GroupId.V1 id = GroupId.v1orThrow(group.getGroupId());
Set<RecipientId> recordMembers = new HashSet<>(groupRecord.getMembers());
Set<RecipientId> messageMembers = new HashSet<>();
if (group.getMembers().isPresent()) {
for (SignalServiceAddress messageMember : group.getMembers().get()) {
messageMembers.add(Recipient.externalGV1Member(messageMember).getId());
}
}
Set<RecipientId> addedMembers = new HashSet<>(messageMembers);
addedMembers.removeAll(recordMembers);
Set<RecipientId> missingMembers = new HashSet<>(recordMembers);
missingMembers.removeAll(messageMembers);
GroupContext.Builder builder = createGroupContext(group);
builder.setType(GroupContext.Type.UPDATE);
if (addedMembers.size() > 0) {
Set<RecipientId> unionMembers = new HashSet<>(recordMembers);
unionMembers.addAll(messageMembers);
database.updateMembers(id, new LinkedList<>(unionMembers));
builder.clearMembers();
builder.clearMembersE164();
for (RecipientId addedMember : addedMembers) {
Recipient recipient = Recipient.resolved(addedMember);
if (recipient.getE164().isPresent()) {
builder.addMembersE164(recipient.requireE164());
builder.addMembers(createMember(recipient.requireE164()));
}
}
} else {
builder.clearMembers();
builder.clearMembersE164();
}
if (missingMembers.size() > 0) {
// TODO We should tell added and missing about each-other.
}
if (group.getName().isPresent() || group.getAvatar().isPresent()) {
SignalServiceAttachment avatar = group.getAvatar().orElse(null);
database.update(id, group.getName().orElse(null), avatar != null ? avatar.asPointer() : null);
}
if (group.getName().isPresent() && group.getName().get().equals(groupRecord.getTitle())) {
builder.clearName();
}
if (!groupRecord.isActive()) database.setActive(id, true);
return storeMessage(context, content, group, builder.build(), outgoing);
}
private static Long handleGroupInfoRequest(@NonNull Context context,
@NonNull SignalServiceContent content,
@NonNull GroupRecord record)
{
Recipient sender = Recipient.externalPush(content.getSender());
if (record.getMembers().contains(sender.getId())) {
ApplicationDependencies.getJobManager().add(new PushGroupUpdateJob(sender.getId(), record.getId()));
}
return null;
}
private static Long handleGroupLeave(@NonNull Context context,
@NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group,
@NonNull GroupRecord record,
boolean outgoing)
{
GroupDatabase database = SignalDatabase.groups();
GroupId id = GroupId.v1orThrow(group.getGroupId());
List<RecipientId> members = record.getMembers();
GroupContext.Builder builder = createGroupContext(group);
builder.setType(GroupContext.Type.QUIT);
RecipientId senderId = RecipientId.from(content.getSender());
if (members.contains(senderId)) {
database.remove(id, senderId);
if (outgoing) database.setActive(id, false);
return storeMessage(context, content, group, builder.build(), outgoing);
}
return null;
}
private static @Nullable Long storeMessage(@NonNull Context context,
@NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group,
@NonNull GroupContext storage,
boolean outgoing)
{
if (group.getAvatar().isPresent()) {
ApplicationDependencies.getJobManager()
.add(new AvatarGroupsV1DownloadJob(GroupId.v1orThrow(group.getGroupId())));
}
try {
if (outgoing) {
MessageDatabase mmsDatabase = SignalDatabase.mms();
RecipientId recipientId = SignalDatabase.recipients().getOrInsertFromGroupId(GroupId.v1orThrow(group.getGroupId()));
Recipient recipient = Recipient.resolved(recipientId);
OutgoingGroupUpdateMessage outgoingMessage = new OutgoingGroupUpdateMessage(recipient, storage, null, content.getTimestamp(), 0, false, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient);
long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null);
mmsDatabase.markAsSent(messageId, true);
return threadId;
} else {
MessageDatabase smsDatabase = SignalDatabase.sms();
String body = Base64.encodeBytes(storage.toByteArray());
IncomingTextMessage incoming = new IncomingTextMessage(Recipient.externalPush(content.getSender()).getId(), content.getSenderDevice(), content.getTimestamp(), content.getServerReceivedTimestamp(), System.currentTimeMillis(), body, Optional.of(GroupId.v1orThrow(group.getGroupId())), 0, content.isNeedsReceipt(), content.getServerUuid());
IncomingGroupUpdateMessage groupMessage = new IncomingGroupUpdateMessage(incoming, storage, body);
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(groupMessage);
if (insertResult.isPresent()) {
ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.get().getThreadId()));
return insertResult.get().getThreadId();
} else {
return null;
}
}
} catch (MmsException e) {
Log.w(TAG, e);
}
return null;
}
private static GroupContext.Builder createGroupContext(SignalServiceGroup group) {
GroupContext.Builder builder = GroupContext.newBuilder();
builder.setId(ByteString.copyFrom(group.getGroupId()));
if (group.getAvatar().isPresent() &&
group.getAvatar().get().isPointer() &&
group.getAvatar().get().asPointer().getRemoteId().getV2().isPresent())
{
builder.setAvatar(AttachmentPointer.newBuilder()
.setCdnId(group.getAvatar().get().asPointer().getRemoteId().getV2().get())
.setKey(ByteString.copyFrom(group.getAvatar().get().asPointer().getKey()))
.setContentType(group.getAvatar().get().getContentType()));
}
if (group.getName().isPresent()) {
builder.setName(group.getName().get());
}
if (group.getMembers().isPresent()) {
builder.addAllMembersE164(Stream.of(group.getMembers().get())
.filter(a -> a.getNumber().isPresent())
.map(a -> a.getNumber().get())
.toList());
builder.addAllMembers(Stream.of(group.getMembers().get())
.filter(address -> address.getNumber().isPresent())
.map(address -> address.getNumber().get())
.map(GroupV1MessageProcessor::createMember)
.toList());
}
return builder;
}
public static GroupContext.Member createMember(@NonNull String e164) {
GroupContext.Member.Builder member = GroupContext.Member.newBuilder();
member.setE164(e164);
return member.build();
}
}

View file

@ -124,7 +124,7 @@ public final class GroupsV1MigrationUtil {
break; break;
case NOT_A_MEMBER: case NOT_A_MEMBER:
Log.w(TAG, "The migrated group already exists, but we are not a member. Doing a local leave."); Log.w(TAG, "The migrated group already exists, but we are not a member. Doing a local leave.");
handleLeftBehind(context, gv1Id, groupRecipient, threadId); handleLeftBehind(gv1Id);
return; return;
case FULL_OR_PENDING_MEMBER: case FULL_OR_PENDING_MEMBER:
Log.w(TAG, "The migrated group already exists, and we're in it. Continuing on."); Log.w(TAG, "The migrated group already exists, and we're in it. Continuing on.");
@ -177,7 +177,7 @@ public final class GroupsV1MigrationUtil {
throw new IOException("[Local] The group should exist already!"); throw new IOException("[Local] The group should exist already!");
} catch (GroupNotAMemberException e) { } catch (GroupNotAMemberException e) {
Log.w(TAG, "[Local] We are not in the group. Doing a local leave."); Log.w(TAG, "[Local] We are not in the group. Doing a local leave.");
handleLeftBehind(context, gv1Id, groupRecipient, threadId); handleLeftBehind(gv1Id);
return null; return null;
} }
@ -195,15 +195,7 @@ public final class GroupsV1MigrationUtil {
} }
} }
private static void handleLeftBehind(@NonNull Context context, @NonNull GroupId.V1 gv1Id, @NonNull Recipient groupRecipient, long threadId) { private static void handleLeftBehind(@NonNull GroupId.V1 gv1Id) {
OutgoingMediaMessage leaveMessage = GroupUtil.createGroupV1LeaveMessage(gv1Id, groupRecipient);
try {
long id = SignalDatabase.mms().insertMessageOutbox(leaveMessage, threadId, false, null);
SignalDatabase.mms().markAsSent(id, true);
} catch (MmsException e) {
Log.w(TAG, "Failed to insert group leave message!", e);
}
SignalDatabase.groups().setActive(gv1Id, false); SignalDatabase.groups().setActive(gv1Id, false);
SignalDatabase.groups().remove(gv1Id, Recipient.self().getId()); SignalDatabase.groups().remove(gv1Id, Recipient.self().getId());
} }

View file

@ -56,14 +56,11 @@ public class PushProcessMessageQueueJobMigration extends JobMigration {
if (content != null && content.getDataMessage().isPresent() && content.getDataMessage().get().getGroupContext().isPresent()) { if (content != null && content.getDataMessage().isPresent() && content.getDataMessage().get().getGroupContext().isPresent()) {
Log.i(TAG, "Migrating a group message."); Log.i(TAG, "Migrating a group message.");
try {
GroupId groupId = GroupUtil.idFromGroupContext(content.getDataMessage().get().getGroupContext().get());
Recipient recipient = Recipient.externalGroupExact(groupId);
suffix = recipient.getId().toQueueKey(); GroupId groupId = GroupId.v2(content.getDataMessage().get().getGroupContext().get().getMasterKey());
} catch (BadGroupIdException e) { Recipient recipient = Recipient.externalGroupExact(groupId);
Log.w(TAG, "Bad groupId! Using default queue.");
} suffix = recipient.getId().toQueueKey();
} else if (content != null) { } else if (content != null) {
Log.i(TAG, "Migrating an individual message."); Log.i(TAG, "Migrating an individual message.");
suffix = RecipientId.from(content.getSender()).toQueueKey(); suffix = RecipientId.from(content.getSender()).toQueueKey();

View file

@ -142,7 +142,6 @@ public final class JobManagerFactories {
put(PushDistributionListSendJob.KEY, new PushDistributionListSendJob.Factory()); put(PushDistributionListSendJob.KEY, new PushDistributionListSendJob.Factory());
put(PushGroupSendJob.KEY, new PushGroupSendJob.Factory()); put(PushGroupSendJob.KEY, new PushGroupSendJob.Factory());
put(PushGroupSilentUpdateSendJob.KEY, new PushGroupSilentUpdateSendJob.Factory()); put(PushGroupSilentUpdateSendJob.KEY, new PushGroupSilentUpdateSendJob.Factory());
put(PushGroupUpdateJob.KEY, new PushGroupUpdateJob.Factory());
put(PushMediaSendJob.KEY, new PushMediaSendJob.Factory()); put(PushMediaSendJob.KEY, new PushMediaSendJob.Factory());
put(PushNotificationReceiveJob.KEY, new PushNotificationReceiveJob.Factory()); put(PushNotificationReceiveJob.KEY, new PushNotificationReceiveJob.Factory());
put(PushProcessEarlyMessagesJob.KEY, new PushProcessEarlyMessagesJob.Factory()); put(PushProcessEarlyMessagesJob.KEY, new PushProcessEarlyMessagesJob.Factory());
@ -156,7 +155,6 @@ public final class JobManagerFactories {
put(RemoteConfigRefreshJob.KEY, new RemoteConfigRefreshJob.Factory()); put(RemoteConfigRefreshJob.KEY, new RemoteConfigRefreshJob.Factory());
put(RemoteDeleteSendJob.KEY, new RemoteDeleteSendJob.Factory()); put(RemoteDeleteSendJob.KEY, new RemoteDeleteSendJob.Factory());
put(ReportSpamJob.KEY, new ReportSpamJob.Factory()); put(ReportSpamJob.KEY, new ReportSpamJob.Factory());
put(RequestGroupInfoJob.KEY, new RequestGroupInfoJob.Factory());
put(ResendMessageJob.KEY, new ResendMessageJob.Factory()); put(ResendMessageJob.KEY, new ResendMessageJob.Factory());
put(ResumableUploadSpecJob.KEY, new ResumableUploadSpecJob.Factory()); put(ResumableUploadSpecJob.KEY, new ResumableUploadSpecJob.Factory());
put(RequestGroupV2InfoWorkerJob.KEY, new RequestGroupV2InfoWorkerJob.Factory()); put(RequestGroupV2InfoWorkerJob.KEY, new RequestGroupV2InfoWorkerJob.Factory());
@ -240,6 +238,8 @@ public final class JobManagerFactories {
put("StorageSyncJob", new StorageSyncJob.Factory()); put("StorageSyncJob", new StorageSyncJob.Factory());
put("WakeGroupV2Job", new FailingJob.Factory()); put("WakeGroupV2Job", new FailingJob.Factory());
put("LeaveGroupJob", new FailingJob.Factory()); put("LeaveGroupJob", new FailingJob.Factory());
put("PushGroupUpdateJob", new FailingJob.Factory());
put("RequestGroupInfoJob", new FailingJob.Factory());
}}; }};
} }

View file

@ -1,163 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.SignalServiceMessageSender.IndividualSendEvents;
import org.whispersystems.signalservice.api.crypto.ContentHint;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
public class PushGroupUpdateJob extends BaseJob {
public static final String KEY = "PushGroupUpdateJob";
private static final String TAG = Log.tag(PushGroupUpdateJob.class);
private static final String KEY_SOURCE = "source";
private static final String KEY_GROUP_ID = "group_id";
private final RecipientId source;
private final GroupId groupId;
public PushGroupUpdateJob(@NonNull RecipientId source, @NonNull GroupId groupId) {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.build(),
source,
groupId);
}
private PushGroupUpdateJob(@NonNull Job.Parameters parameters, RecipientId source, @NonNull GroupId groupId) {
super(parameters);
this.source = source;
this.groupId = groupId;
}
@Override
public @NonNull Data serialize() {
return new Data.Builder().putString(KEY_SOURCE, source.serialize())
.putString(KEY_GROUP_ID, groupId.toString())
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() throws IOException, UntrustedIdentityException {
if (!Recipient.self().isRegistered()) {
throw new NotPushRegisteredException();
}
Recipient sourceRecipient = Recipient.resolved(source);
if (sourceRecipient.isUnregistered()) {
Log.w(TAG, sourceRecipient.getId() + " not registered!");
return;
}
GroupDatabase groupDatabase = SignalDatabase.groups();
Optional<GroupRecord> record = groupDatabase.getGroup(groupId);
SignalServiceAttachment avatar = null;
if (record == null || !record.isPresent()) {
Log.w(TAG, "No information for group record info request: " + groupId.toString());
return;
}
if (AvatarHelper.hasAvatar(context, record.get().getRecipientId())) {
avatar = SignalServiceAttachmentStream.newStreamBuilder()
.withContentType("image/jpeg")
.withStream(AvatarHelper.getAvatar(context, record.get().getRecipientId()))
.withLength(AvatarHelper.getAvatarLength(context, record.get().getRecipientId()))
.build();
}
List<SignalServiceAddress> members = new LinkedList<>();
for (RecipientId member : record.get().getMembers()) {
Recipient recipient = Recipient.resolved(member);
if (recipient.isMaybeRegistered()) {
members.add(RecipientUtil.toSignalServiceAddress(context, recipient));
}
}
SignalServiceGroup groupContext = SignalServiceGroup.newBuilder(Type.UPDATE)
.withAvatar(avatar)
.withId(groupId.getDecodedId())
.withMembers(members)
.withName(record.get().getTitle())
.build();
RecipientId groupRecipientId = SignalDatabase.recipients().getOrInsertFromGroupId(groupId);
Recipient groupRecipient = Recipient.resolved(groupRecipientId);
SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder()
.asGroupMessage(groupContext)
.withTimestamp(System.currentTimeMillis())
.withExpiration(groupRecipient.getExpiresInSeconds())
.build();
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
messageSender.sendDataMessage(RecipientUtil.toSignalServiceAddress(context, sourceRecipient),
UnidentifiedAccessUtil.getAccessFor(context, sourceRecipient),
ContentHint.DEFAULT,
message,
IndividualSendEvents.EMPTY);
}
@Override
public boolean onShouldRetry(@NonNull Exception e) {
Log.w(TAG, e);
return e instanceof PushNetworkException;
}
@Override
public void onFailure() {
}
public static final class Factory implements Job.Factory<PushGroupUpdateJob> {
@Override
public @NonNull PushGroupUpdateJob create(@NonNull Parameters parameters, @NonNull org.thoughtcrime.securesms.jobmanager.Data data) {
return new PushGroupUpdateJob(parameters,
RecipientId.from(data.getString(KEY_SOURCE)),
GroupId.parseOrThrow(data.getString(KEY_GROUP_ID)));
}
}
}

View file

@ -24,7 +24,7 @@ import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.signalservice.api.groupsv2.NoCredentialForRedemptionTimeException; import org.whispersystems.signalservice.api.groupsv2.NoCredentialForRedemptionTimeException;
import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext; import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import java.io.IOException; import java.io.IOException;
@ -120,27 +120,23 @@ public final class PushProcessMessageJob extends BaseJob {
.setMaxAttempts(Parameters.UNLIMITED); .setMaxAttempts(Parameters.UNLIMITED);
if (content != null) { if (content != null) {
SignalServiceGroupContext signalServiceGroupContext = GroupUtil.getGroupContextIfPresent(content); SignalServiceGroupV2 signalServiceGroupContext = GroupUtil.getGroupContextIfPresent(content);
if (signalServiceGroupContext != null) { if (signalServiceGroupContext != null) {
try { GroupId groupId = GroupId.v2(signalServiceGroupContext.getMasterKey());
GroupId groupId = GroupUtil.idFromGroupContext(signalServiceGroupContext);
queueName = getQueueName(Recipient.externalPossiblyMigratedGroup(groupId).getId()); queueName = getQueueName(Recipient.externalPossiblyMigratedGroup(groupId).getId());
if (groupId.isV2()) { if (groupId.isV2()) {
int localRevision = SignalDatabase.groups().getGroupV2Revision(groupId.requireV2()); int localRevision = SignalDatabase.groups().getGroupV2Revision(groupId.requireV2());
if (signalServiceGroupContext.getGroupV2().get().getRevision() > localRevision || if (signalServiceGroupContext.getRevision() > localRevision ||
SignalDatabase.groups().getGroupV1ByExpectedV2(groupId.requireV2()).isPresent()) SignalDatabase.groups().getGroupV1ByExpectedV2(groupId.requireV2()).isPresent())
{ {
Log.i(TAG, "Adding network constraint to group-related job."); Log.i(TAG, "Adding network constraint to group-related job.");
builder.addConstraint(NetworkConstraint.KEY) builder.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(30)); .setLifespan(TimeUnit.DAYS.toMillis(30));
}
} }
} catch (BadGroupIdException e) {
Log.w(TAG, "Bad groupId! Using default queue. ID: " + content.getTimestamp());
} }
} else if (content.getSyncMessage().isPresent() && content.getSyncMessage().get().getSent().isPresent() && content.getSyncMessage().get().getSent().get().getDestination().isPresent()) { } else if (content.getSyncMessage().isPresent() && content.getSyncMessage().get().getSent().isPresent() && content.getSyncMessage().get().getSent().get().getDestination().isPresent()) {
queueName = getQueueName(RecipientId.from(content.getSyncMessage().get().getSent().get().getDestination().get())); queueName = getQueueName(RecipientId.from(content.getSyncMessage().get().getSent().get().getDestination().get()));

View file

@ -1,122 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.SignalServiceMessageSender.IndividualSendEvents;
import org.whispersystems.signalservice.api.crypto.ContentHint;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class RequestGroupInfoJob extends BaseJob {
public static final String KEY = "RequestGroupInfoJob";
@SuppressWarnings("unused")
private static final String TAG = Log.tag(RequestGroupInfoJob.class);
private static final String KEY_SOURCE = "source";
private static final String KEY_GROUP_ID = "group_id";
private final RecipientId source;
private final GroupId groupId;
public RequestGroupInfoJob(@NonNull RecipientId source, @NonNull GroupId groupId) {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.build(),
source,
groupId);
}
private RequestGroupInfoJob(@NonNull Job.Parameters parameters, @NonNull RecipientId source, @NonNull GroupId groupId) {
super(parameters);
this.source = source;
this.groupId = groupId;
}
@Override
public @NonNull Data serialize() {
return new Data.Builder().putString(KEY_SOURCE, source.serialize())
.putString(KEY_GROUP_ID, groupId.toString())
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() throws IOException, UntrustedIdentityException {
if (!Recipient.self().isRegistered()) {
throw new NotPushRegisteredException();
}
SignalServiceGroup group = SignalServiceGroup.newBuilder(Type.REQUEST_INFO)
.withId(groupId.getDecodedId())
.build();
SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder()
.asGroupMessage(group)
.withTimestamp(System.currentTimeMillis())
.build();
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
Recipient recipient = Recipient.resolved(source);
if (recipient.isUnregistered()) {
Log.w(TAG, recipient.getId() + " is unregistered!");
return;
}
messageSender.sendDataMessage(RecipientUtil.toSignalServiceAddress(context, recipient),
UnidentifiedAccessUtil.getAccessFor(context, recipient),
ContentHint.IMPLICIT,
message,
IndividualSendEvents.EMPTY);
}
@Override
public boolean onShouldRetry(@NonNull Exception e) {
if (e instanceof ServerRejectedException) return false;
return e instanceof PushNetworkException;
}
@Override
public void onFailure() {
}
public static final class Factory implements Job.Factory<RequestGroupInfoJob> {
@Override
public @NonNull RequestGroupInfoJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new RequestGroupInfoJob(parameters,
RecipientId.from(data.getString(KEY_SOURCE)),
GroupId.parseOrThrow(data.getString(KEY_GROUP_ID)));
}
}
}

View file

@ -30,7 +30,7 @@ import org.thoughtcrime.securesms.util.Stopwatch;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.SignalSessionLock;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext; import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
@ -177,24 +177,19 @@ public class IncomingMessageProcessor {
} }
private boolean needsToEnqueueProcessing(@NonNull DecryptionResult result) { private boolean needsToEnqueueProcessing(@NonNull DecryptionResult result) {
SignalServiceGroupContext groupContext = GroupUtil.getGroupContextIfPresent(result.getContent()); SignalServiceGroupV2 groupContext = GroupUtil.getGroupContextIfPresent(result.getContent());
if (groupContext != null) { if (groupContext != null) {
try { GroupId groupId = GroupId.v2(groupContext.getMasterKey());
GroupId groupId = GroupUtil.idFromGroupContext(groupContext);
if (groupId.isV2()) { if (groupId.isV2()) {
String queueName = PushProcessMessageJob.getQueueName(Recipient.externalPossiblyMigratedGroup(groupId).getId()); String queueName = PushProcessMessageJob.getQueueName(Recipient.externalPossiblyMigratedGroup(groupId).getId());
GroupDatabase groupDatabase = SignalDatabase.groups(); GroupDatabase groupDatabase = SignalDatabase.groups();
return !jobManager.isQueueEmpty(queueName) || return !jobManager.isQueueEmpty(queueName) ||
groupContext.getGroupV2().get().getRevision() > groupDatabase.getGroupV2Revision(groupId.requireV2()) || groupContext.getRevision() > groupDatabase.getGroupV2Revision(groupId.requireV2()) ||
groupDatabase.getGroupV1ByExpectedV2(groupId.requireV2()).isPresent(); groupDatabase.getGroupV1ByExpectedV2(groupId.requireV2()).isPresent();
} else { } else {
return false;
}
} catch (BadGroupIdException e) {
Log.w(TAG, "Bad group ID!");
return false; return false;
} }
} else if (result.getContent() != null) { } else if (result.getContent() != null) {

View file

@ -69,7 +69,6 @@ import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.groups.GroupNotAMemberException; import org.thoughtcrime.securesms.groups.GroupNotAMemberException;
import org.thoughtcrime.securesms.groups.GroupV1MessageProcessor;
import org.thoughtcrime.securesms.groups.GroupsV1MigrationUtil; import org.thoughtcrime.securesms.groups.GroupsV1MigrationUtil;
import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob; import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
@ -92,7 +91,6 @@ import org.thoughtcrime.securesms.jobs.PushProcessEarlyMessagesJob;
import org.thoughtcrime.securesms.jobs.PushProcessMessageJob; import org.thoughtcrime.securesms.jobs.PushProcessMessageJob;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob; import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob;
import org.thoughtcrime.securesms.jobs.RequestGroupInfoJob;
import org.thoughtcrime.securesms.jobs.ResendMessageJob; import org.thoughtcrime.securesms.jobs.ResendMessageJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob; import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob;
@ -142,8 +140,6 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
import org.whispersystems.signalservice.api.messages.SignalServicePreview; import org.whispersystems.signalservice.api.messages.SignalServicePreview;
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
@ -283,7 +279,7 @@ public final class MessageContentProcessor {
boolean isGv2Message = groupId.isPresent() && groupId.get().isV2(); boolean isGv2Message = groupId.isPresent() && groupId.get().isV2();
if (isGv2Message) { if (isGv2Message) {
if (handleGv2PreProcessing(groupId.orElse(null).requireV2(), content, content.getDataMessage().get().getGroupContext().get().getGroupV2().get(), senderRecipient)) { if (handleGv2PreProcessing(groupId.orElse(null).requireV2(), content, content.getDataMessage().get().getGroupContext().get(), senderRecipient)) {
return; return;
} }
} }
@ -292,7 +288,6 @@ public final class MessageContentProcessor {
if (isInvalidMessage(message)) handleInvalidMessage(content.getSender(), content.getSenderDevice(), groupId, content.getTimestamp(), smsMessageId); if (isInvalidMessage(message)) handleInvalidMessage(content.getSender(), content.getSenderDevice(), groupId, content.getTimestamp(), smsMessageId);
else if (message.isEndSession()) messageId = handleEndSessionMessage(content, smsMessageId, senderRecipient); else if (message.isEndSession()) messageId = handleEndSessionMessage(content, smsMessageId, senderRecipient);
else if (message.isGroupV1Update()) handleGroupV1Message(content, message, smsMessageId, groupId.get().requireV1(), senderRecipient, threadRecipient, receivedTime);
else if (message.isExpirationUpdate()) messageId = handleExpirationUpdate(content, message, smsMessageId, groupId, senderRecipient, threadRecipient, receivedTime, false); else if (message.isExpirationUpdate()) messageId = handleExpirationUpdate(content, message, smsMessageId, groupId, senderRecipient, threadRecipient, receivedTime, false);
else if (message.getReaction().isPresent() && message.getStoryContext().isPresent()) messageId = handleStoryReaction(content, message, senderRecipient); else if (message.getReaction().isPresent() && message.getStoryContext().isPresent()) messageId = handleStoryReaction(content, message, senderRecipient);
else if (message.getReaction().isPresent()) messageId = handleReaction(content, message, senderRecipient); else if (message.getReaction().isPresent()) messageId = handleReaction(content, message, senderRecipient);
@ -516,20 +511,6 @@ public final class MessageContentProcessor {
return false; return false;
} }
private static @Nullable SignalServiceGroupContext getGroupContextIfPresent(@NonNull SignalServiceContent content) {
if (content.getDataMessage().isPresent() && content.getDataMessage().get().getGroupContext().isPresent()) {
return content.getDataMessage().get().getGroupContext().get();
} else if (content.getSyncMessage().isPresent() &&
content.getSyncMessage().get().getSent().isPresent() &&
content.getSyncMessage().get().getSent().get().getDataMessage().get().getGroupContext().isPresent())
{
return content.getSyncMessage().get().getSent().get().getDataMessage().get().getGroupContext().get();
} else {
return null;
}
}
/** /**
* Attempts to update the group to the revision mentioned in the message. * Attempts to update the group to the revision mentioned in the message.
* If the local version is at least the revision in the message it will not query the server. * If the local version is at least the revision in the message it will not query the server.
@ -790,51 +771,20 @@ public final class MessageContentProcessor {
return threadId; return threadId;
} }
private void handleGroupV1Message(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId,
@NonNull GroupId.V1 groupId,
@NonNull Recipient senderRecipient,
@NonNull Recipient threadRecipient,
long receivedTime)
throws StorageFailedException, BadGroupIdException
{
log(content.getTimestamp(), "GroupV1 message.");
GroupV1MessageProcessor.process(context, content, message, false);
handlePossibleExpirationUpdate(content, message, Optional.of(groupId), senderRecipient, threadRecipient, receivedTime);
if (smsMessageId.isPresent()) {
SignalDatabase.sms().deleteMessage(smsMessageId.get());
}
}
private void handleUnknownGroupMessage(@NonNull SignalServiceContent content, private void handleUnknownGroupMessage(@NonNull SignalServiceContent content,
@NonNull SignalServiceGroupContext group, @NonNull SignalServiceGroupV2 group,
@NonNull Recipient senderRecipient) @NonNull Recipient senderRecipient)
throws BadGroupIdException throws BadGroupIdException
{ {
log(content.getTimestamp(), "Unknown group message."); log(content.getTimestamp(), "Unknown group message.");
if (group.getGroupV1().isPresent()) { warn(content.getTimestamp(), "Received a GV2 message for a group we have no knowledge of -- attempting to fix this state.");
SignalServiceGroup groupV1 = group.getGroupV1().get(); ServiceId authServiceId = ServiceId.parseOrNull(content.getDestinationUuid());
if (groupV1.getType() != SignalServiceGroup.Type.REQUEST_INFO) { if (authServiceId == null) {
ApplicationDependencies.getJobManager().add(new RequestGroupInfoJob(senderRecipient.getId(), GroupId.v1(groupV1.getGroupId()))); warn(content.getTimestamp(), "Group message missing destination uuid, defaulting to ACI");
} else { authServiceId = SignalStore.account().requireAci();
warn(content.getTimestamp(), "Received a REQUEST_INFO message for a group we don't know about. Ignoring.");
}
} else if (group.getGroupV2().isPresent()) {
warn(content.getTimestamp(), "Received a GV2 message for a group we have no knowledge of -- attempting to fix this state.");
ServiceId authServiceId = ServiceId.parseOrNull(content.getDestinationUuid());
if (authServiceId == null) {
warn(content.getTimestamp(), "Group message missing destination uuid, defaulting to ACI");
authServiceId = SignalStore.account().requireAci();
}
SignalDatabase.groups().fixMissingMasterKey(authServiceId, group.getGroupV2().get().getMasterKey());
} else {
warn(content.getTimestamp(), "Received a message for a group we don't know about without a group context. Ignoring.");
} }
SignalDatabase.groups().fixMissingMasterKey(authServiceId, group.getMasterKey());
} }
/** /**
@ -876,8 +826,8 @@ public final class MessageContentProcessor {
return null; return null;
} }
int expiresInSeconds = message.getExpiresInSeconds(); int expiresInSeconds = message.getExpiresInSeconds();
Optional<SignalServiceGroupContext> groupContext = message.getGroupContext(); Optional<SignalServiceGroupV2> groupContext = message.getGroupContext();
if (threadRecipient.getExpiresInSeconds() == expiresInSeconds) { if (threadRecipient.getExpiresInSeconds() == expiresInSeconds) {
log(String.valueOf(content.getTimestamp()), "No change in message expiry for group. Ignoring."); log(String.valueOf(content.getTimestamp()), "No change in message expiry for group. Ignoring.");
@ -1212,8 +1162,8 @@ public final class MessageContentProcessor {
SignalServiceDataMessage dataMessage = message.getDataMessage().get(); SignalServiceDataMessage dataMessage = message.getDataMessage().get();
if (dataMessage.isGroupV2Message()) { if (dataMessage.isGroupV2Message()) {
GroupId.V2 groupId = GroupId.v2(dataMessage.getGroupContext().get().getGroupV2().get().getMasterKey()); GroupId.V2 groupId = GroupId.v2(dataMessage.getGroupContext().get().getMasterKey());
if (handleGv2PreProcessing(groupId, content, dataMessage.getGroupContext().get().getGroupV2().get(), senderRecipient)) { if (handleGv2PreProcessing(groupId, content, dataMessage.getGroupContext().get(), senderRecipient)) {
return; return;
} }
} }
@ -1224,9 +1174,6 @@ public final class MessageContentProcessor {
handleGroupRecipientUpdate(message, content.getTimestamp()); handleGroupRecipientUpdate(message, content.getTimestamp());
} else if (dataMessage.isEndSession()) { } else if (dataMessage.isEndSession()) {
threadId = handleSynchronizeSentEndSessionMessage(message, content.getTimestamp()); threadId = handleSynchronizeSentEndSessionMessage(message, content.getTimestamp());
} else if (dataMessage.isGroupV1Update()) {
Long gv1ThreadId = GroupV1MessageProcessor.process(context, content, dataMessage, true);
threadId = gv1ThreadId == null ? -1 : gv1ThreadId;
} else if (dataMessage.isGroupV2Update()) { } else if (dataMessage.isGroupV2Update()) {
handleSynchronizeSentGv2Update(content, message); handleSynchronizeSentGv2Update(content, message);
threadId = SignalDatabase.threads().getOrCreateThreadIdFor(getSyncMessageDestination(message)); threadId = SignalDatabase.threads().getOrCreateThreadIdFor(getSyncMessageDestination(message));
@ -1249,7 +1196,7 @@ public final class MessageContentProcessor {
threadId = handleSynchronizeSentTextMessage(message, content.getTimestamp()); threadId = handleSynchronizeSentTextMessage(message, content.getTimestamp());
} }
if (dataMessage.getGroupContext().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.idFromGroupContext(dataMessage.getGroupContext().get()))) { if (dataMessage.getGroupContext().isPresent() && groupDatabase.isUnknownGroup(GroupId.v2(dataMessage.getGroupContext().get().getMasterKey()))) {
handleUnknownGroupMessage(content, dataMessage.getGroupContext().get(), senderRecipient); handleUnknownGroupMessage(content, dataMessage.getGroupContext().get(), senderRecipient);
} }
@ -1284,7 +1231,7 @@ public final class MessageContentProcessor {
log(content.getTimestamp(), "Synchronize sent GV2 update for message with timestamp " + message.getTimestamp()); log(content.getTimestamp(), "Synchronize sent GV2 update for message with timestamp " + message.getTimestamp());
SignalServiceDataMessage dataMessage = message.getDataMessage().get(); SignalServiceDataMessage dataMessage = message.getDataMessage().get();
SignalServiceGroupV2 signalServiceGroupV2 = dataMessage.getGroupContext().get().getGroupV2().get(); SignalServiceGroupV2 signalServiceGroupV2 = dataMessage.getGroupContext().get();
GroupId.V2 groupIdV2 = GroupId.v2(signalServiceGroupV2.getMasterKey()); GroupId.V2 groupIdV2 = GroupId.v2(signalServiceGroupV2.getMasterKey());
if (!updateGv2GroupFromServerOrP2PChange(content, signalServiceGroupV2)) { if (!updateGv2GroupFromServerOrP2PChange(content, signalServiceGroupV2)) {
@ -3012,9 +2959,7 @@ public final class MessageContentProcessor {
return database.insertMessageInbox(textMessage); return database.insertMessageInbox(textMessage);
} }
private Recipient getSyncMessageDestination(@NonNull SentTranscriptMessage message) private Recipient getSyncMessageDestination(@NonNull SentTranscriptMessage message) {
throws BadGroupIdException
{
return getGroupRecipient(message.getDataMessage().get().getGroupContext()).orElseGet(() -> Recipient.externalPush(message.getDestination().get())); return getGroupRecipient(message.getDataMessage().get().getGroupContext()).orElseGet(() -> Recipient.externalPush(message.getDestination().get()));
} }
@ -3023,13 +2968,12 @@ public final class MessageContentProcessor {
return getGroupRecipient(message != null ? message.getGroupContext() : Optional.empty()).orElseGet(() -> Recipient.externalPush(content.getSender())); return getGroupRecipient(message != null ? message.getGroupContext() : Optional.empty()).orElseGet(() -> Recipient.externalPush(content.getSender()));
} }
private Optional<Recipient> getGroupRecipient(Optional<SignalServiceGroupContext> message) private Optional<Recipient> getGroupRecipient(Optional<SignalServiceGroupV2> message) {
throws BadGroupIdException
{
if (message.isPresent()) { if (message.isPresent()) {
return Optional.of(Recipient.externalPossiblyMigratedGroup(GroupUtil.idFromGroupContext(message.get()))); return Optional.of(Recipient.externalPossiblyMigratedGroup(GroupId.v2(message.get().getMasterKey())));
} else {
return Optional.empty();
} }
return Optional.empty();
} }
private void notifyTypingStoppedFromIncomingMessage(@NonNull Recipient senderRecipient, @NonNull Recipient conversationRecipient, int device) { private void notifyTypingStoppedFromIncomingMessage(@NonNull Recipient senderRecipient, @NonNull Recipient conversationRecipient, int device) {
@ -3053,15 +2997,6 @@ public final class MessageContentProcessor {
GroupDatabase groupDatabase = SignalDatabase.groups(); GroupDatabase groupDatabase = SignalDatabase.groups();
Optional<GroupId> groupId = GroupUtil.idFromGroupContext(message.getGroupContext()); Optional<GroupId> groupId = GroupUtil.idFromGroupContext(message.getGroupContext());
if (groupId.isPresent() &&
groupId.get().isV1() &&
message.isGroupV1Update() &&
groupDatabase.groupExists(groupId.get().requireV1().deriveV2MigrationGroupId()))
{
warn(String.valueOf(content.getTimestamp()), "Ignoring V1 update for a group we've already migrated to V2.");
return true;
}
if (groupId.isPresent() && groupDatabase.isUnknownGroup(groupId.get())) { if (groupId.isPresent() && groupDatabase.isUnknownGroup(groupId.get())) {
return sender.isBlocked(); return sender.isBlocked();
} }
@ -3070,11 +3005,10 @@ public final class MessageContentProcessor {
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getSticker().isPresent(); boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getSticker().isPresent();
boolean isExpireMessage = message.isExpirationUpdate(); boolean isExpireMessage = message.isExpirationUpdate();
boolean isGv2Update = message.isGroupV2Update(); boolean isGv2Update = message.isGroupV2Update();
boolean isContentMessage = !message.isGroupV1Update() && !isGv2Update && !isExpireMessage && (isTextMessage || isMediaMessage); boolean isContentMessage = !isGv2Update && !isExpireMessage && (isTextMessage || isMediaMessage);
boolean isGroupActive = groupId.isPresent() && groupDatabase.isActive(groupId.get()); boolean isGroupActive = groupId.isPresent() && groupDatabase.isActive(groupId.get());
boolean isLeaveMessage = message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1Type() == SignalServiceGroup.Type.QUIT;
return (isContentMessage && !isGroupActive) || (sender.isBlocked() && !isLeaveMessage && !isGv2Update); return (isContentMessage && !isGroupActive) || (sender.isBlocked() && !isGv2Update);
} else { } else {
return sender.isBlocked(); return sender.isBlocked();
} }

View file

@ -207,15 +207,7 @@ public final class MessageDecryptionUtil {
if (sender == null) throw new NoSenderException(); if (sender == null) throw new NoSenderException();
GroupId groupId = null; GroupId groupId = e.getGroup().isPresent() ? GroupId.v2(e.getGroup().get().getMasterKey()) : null;
if (e.getGroup().isPresent()) {
try {
groupId = GroupUtil.idFromGroupContext(e.getGroup().get());
} catch (BadGroupIdException ex) {
Log.w(TAG, "Bad group id found in unsupported data message", ex);
}
}
return new ExceptionMetadata(sender, e.getSenderDevice(), groupId); return new ExceptionMetadata(sender, e.getSenderDevice(), groupId);
} }

View file

@ -11,9 +11,8 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.linkpreview.LinkPreview import org.thoughtcrime.securesms.linkpreview.LinkPreview
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.GroupUtil
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment import org.whispersystems.signalservice.api.messages.SignalServiceAttachment
import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2
import java.util.Optional import java.util.Optional
class IncomingMediaMessage( class IncomingMediaMessage(
@ -96,7 +95,7 @@ class IncomingMediaMessage(
viewOnce: Boolean, viewOnce: Boolean,
unidentified: Boolean, unidentified: Boolean,
body: Optional<String>, body: Optional<String>,
group: Optional<SignalServiceGroupContext>, group: Optional<SignalServiceGroupV2>,
attachments: Optional<List<SignalServiceAttachment>>, attachments: Optional<List<SignalServiceAttachment>>,
quote: Optional<QuoteModel>, quote: Optional<QuoteModel>,
sharedContacts: Optional<List<Contact>>, sharedContacts: Optional<List<Contact>>,
@ -107,7 +106,7 @@ class IncomingMediaMessage(
giftBadge: GiftBadge? giftBadge: GiftBadge?
) : this( ) : this(
from = from, from = from,
groupId = if (group.isPresent) GroupUtil.idFromGroupContextOrThrow(group.get()) else null, groupId = if (group.isPresent) GroupId.v2(group.get().masterKey) else null,
body = body.orElse(null), body = body.orElse(null),
isPushMessage = true, isPushMessage = true,
storyType = storyType, storyType = storyType,

View file

@ -6,31 +6,23 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
import com.google.protobuf.ByteString;
import org.signal.core.util.StringUtil; import org.signal.core.util.StringUtil;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.InvalidMessageException;
import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey; import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.groups.BadGroupIdException;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.mms.MessageGroupContext; import org.thoughtcrime.securesms.mms.MessageGroupContext;
import org.thoughtcrime.securesms.mms.OutgoingGroupUpdateMessage;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -44,7 +36,7 @@ public final class GroupUtil {
/** /**
* @return The group context present on the content if one exists, otherwise null. * @return The group context present on the content if one exists, otherwise null.
*/ */
public static @Nullable SignalServiceGroupContext getGroupContextIfPresent(@Nullable SignalServiceContent content) { public static @Nullable SignalServiceGroupV2 getGroupContextIfPresent(@Nullable SignalServiceContent content) {
if (content == null) { if (content == null) {
return null; return null;
} else if (content.getDataMessage().isPresent() && content.getDataMessage().get().getGroupContext().isPresent()) { } else if (content.getDataMessage().isPresent() && content.getDataMessage().get().getGroupContext().isPresent()) {
@ -56,11 +48,7 @@ public final class GroupUtil {
{ {
return content.getSyncMessage().get().getSent().get().getDataMessage().get().getGroupContext().get(); return content.getSyncMessage().get().getSent().get().getDataMessage().get().getGroupContext().get();
} else if (content.getStoryMessage().isPresent() && content.getStoryMessage().get().getGroupContext().isPresent()) { } else if (content.getStoryMessage().isPresent() && content.getStoryMessage().get().getGroupContext().isPresent()) {
try { return content.getStoryMessage().get().getGroupContext().get();
return SignalServiceGroupContext.create(null, content.getStoryMessage().get().getGroupContext().get());
} catch (InvalidMessageException e) {
throw new AssertionError(e);
}
} else { } else {
return null; return null;
} }
@ -69,36 +57,12 @@ public final class GroupUtil {
/** /**
* Result may be a v1 or v2 GroupId. * Result may be a v1 or v2 GroupId.
*/ */
public static @NonNull GroupId idFromGroupContext(@NonNull SignalServiceGroupContext groupContext) public static @NonNull Optional<GroupId> idFromGroupContext(@NonNull Optional<SignalServiceGroupV2> groupContext) {
throws BadGroupIdException
{
if (groupContext.getGroupV1().isPresent()) {
return GroupId.v1(groupContext.getGroupV1().get().getGroupId());
} else if (groupContext.getGroupV2().isPresent()) {
return GroupId.v2(groupContext.getGroupV2().get().getMasterKey());
} else {
throw new AssertionError();
}
}
public static @NonNull GroupId idFromGroupContextOrThrow(@NonNull SignalServiceGroupContext groupContext) {
try {
return idFromGroupContext(groupContext);
} catch (BadGroupIdException e) {
throw new AssertionError(e);
}
}
/**
* Result may be a v1 or v2 GroupId.
*/
public static @NonNull Optional<GroupId> idFromGroupContext(@NonNull Optional<SignalServiceGroupContext> groupContext)
throws BadGroupIdException
{
if (groupContext.isPresent()) { if (groupContext.isPresent()) {
return Optional.of(idFromGroupContext(groupContext.get())); return Optional.of(GroupId.v2(groupContext.get().getMasterKey()));
} else {
return Optional.empty();
} }
return Optional.empty();
} }
public static @NonNull GroupMasterKey requireMasterKey(@NonNull byte[] masterKey) { public static @NonNull GroupMasterKey requireMasterKey(@NonNull byte[] masterKey) {
@ -129,36 +93,14 @@ public final class GroupUtil {
@NonNull GroupId.Push groupId) @NonNull GroupId.Push groupId)
{ {
if (groupId.isV2()) { if (groupId.isV2()) {
GroupDatabase groupDatabase = SignalDatabase.groups(); GroupDatabase groupDatabase = SignalDatabase.groups();
GroupDatabase.GroupRecord groupRecord = groupDatabase.requireGroup(groupId); GroupDatabase.GroupRecord groupRecord = groupDatabase.requireGroup(groupId);
GroupDatabase.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties(); GroupDatabase.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties();
SignalServiceGroupV2 group = SignalServiceGroupV2.newBuilder(v2GroupProperties.getGroupMasterKey()) SignalServiceGroupV2 group = SignalServiceGroupV2.newBuilder(v2GroupProperties.getGroupMasterKey())
.withRevision(v2GroupProperties.getGroupRevision()) .withRevision(v2GroupProperties.getGroupRevision())
.build(); .build();
dataMessageBuilder.asGroupMessage(group); dataMessageBuilder.asGroupMessage(group);
} else { }
dataMessageBuilder.asGroupMessage(new SignalServiceGroup(groupId.getDecodedId()));
}
}
public static OutgoingGroupUpdateMessage createGroupV1LeaveMessage(@NonNull GroupId.V1 groupId,
@NonNull Recipient groupRecipient)
{
GroupContext groupContext = GroupContext.newBuilder()
.setId(ByteString.copyFrom(groupId.getDecodedId()))
.setType(GroupContext.Type.QUIT)
.build();
return new OutgoingGroupUpdateMessage(groupRecipient,
groupContext,
null,
System.currentTimeMillis(),
0,
false,
null,
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList());
} }
public static class GroupDescription { public static class GroupDescription {

View file

@ -10,7 +10,6 @@ import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobMigration.JobData; import org.thoughtcrime.securesms.jobmanager.JobMigration.JobData;
import org.thoughtcrime.securesms.jobs.FailingJob; import org.thoughtcrime.securesms.jobs.FailingJob;
import org.thoughtcrime.securesms.jobs.RequestGroupInfoJob;
import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob; import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
@ -29,35 +28,6 @@ public class RecipientIdFollowUpJobMigrationTest {
@Mock @Mock
private MockedStatic<Job.Parameters> jobParametersMockedStatic; private MockedStatic<Job.Parameters> jobParametersMockedStatic;
@Test
public void migrate_requestGroupInfoJob_good() throws Exception {
JobData testData = new JobData("RequestGroupInfoJob", null, new Data.Builder().putString("source", "1")
.putString("group_id", "__textsecure_group__!abcdef0123456789abcdef0123456789")
.build());
RecipientIdFollowUpJobMigration subject = new RecipientIdFollowUpJobMigration();
JobData converted = subject.migrate(testData);
assertEquals("RequestGroupInfoJob", converted.getFactoryKey());
assertNull(converted.getQueueKey());
assertEquals("1", converted.getData().getString("source"));
assertEquals("__textsecure_group__!abcdef0123456789abcdef0123456789", converted.getData().getString("group_id"));
new RequestGroupInfoJob.Factory().create(mock(Job.Parameters.class), converted.getData());
}
@Test
public void migrate_requestGroupInfoJob_bad() throws Exception {
JobData testData = new JobData("RequestGroupInfoJob", null, new Data.Builder().putString("source", "1")
.build());
RecipientIdFollowUpJobMigration subject = new RecipientIdFollowUpJobMigration();
JobData converted = subject.migrate(testData);
assertEquals("FailingJob", converted.getFactoryKey());
assertNull(converted.getQueueKey());
new FailingJob.Factory().create(mock(Job.Parameters.class), converted.getData());
}
@Test @Test
public void migrate_sendDeliveryReceiptJob_good() throws Exception { public void migrate_sendDeliveryReceiptJob_good() throws Exception {
JobData testData = new JobData("SendDeliveryReceiptJob", null, new Data.Builder().putString("recipient", "1") JobData testData = new JobData("SendDeliveryReceiptJob", null, new Data.Builder().putString("recipient", "1")

View file

@ -19,10 +19,8 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceVerifiedUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceVerifiedUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceViewOnceOpenJob; import org.thoughtcrime.securesms.jobs.MultiDeviceViewOnceOpenJob;
import org.thoughtcrime.securesms.jobs.PushGroupSendJob; import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob;
import org.thoughtcrime.securesms.jobs.PushMediaSendJob; import org.thoughtcrime.securesms.jobs.PushMediaSendJob;
import org.thoughtcrime.securesms.jobs.PushTextSendJob; import org.thoughtcrime.securesms.jobs.PushTextSendJob;
import org.thoughtcrime.securesms.jobs.RequestGroupInfoJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob; import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob;
import org.thoughtcrime.securesms.jobs.SmsSendJob; import org.thoughtcrime.securesms.jobs.SmsSendJob;
@ -83,24 +81,6 @@ public class RecipientIdJobMigrationTest {
new MultiDeviceViewOnceOpenJob.Factory().create(mock(Job.Parameters.class), converted.getData()); new MultiDeviceViewOnceOpenJob.Factory().create(mock(Job.Parameters.class), converted.getData());
} }
@Test
public void migrate_requestGroupInfoJob() throws Exception {
JobData testData = new JobData("RequestGroupInfoJob", null, new Data.Builder().putString("source", "+16101234567")
.putString("group_id", "__textsecure_group__!abcdef0123456789abcdef0123456789")
.build());
mockRecipientResolve("+16101234567", 1);
RecipientIdJobMigration subject = new RecipientIdJobMigration(mock(Application.class));
JobData converted = subject.migrate(testData);
assertEquals("RequestGroupInfoJob", converted.getFactoryKey());
assertNull(converted.getQueueKey());
assertEquals("1", converted.getData().getString("source"));
assertEquals("__textsecure_group__!abcdef0123456789abcdef0123456789", converted.getData().getString("group_id"));
new RequestGroupInfoJob.Factory().create(mock(Job.Parameters.class), converted.getData());
}
@Test @Test
public void migrate_sendDeliveryReceiptJob() throws Exception { public void migrate_sendDeliveryReceiptJob() throws Exception {
JobData testData = new JobData("SendDeliveryReceiptJob", null, new Data.Builder().putString("address", "+16101234567") JobData testData = new JobData("SendDeliveryReceiptJob", null, new Data.Builder().putString("address", "+16101234567")
@ -180,24 +160,6 @@ public class RecipientIdJobMigrationTest {
new PushGroupSendJob.Factory().create(mock(Job.Parameters.class), converted.getData()); new PushGroupSendJob.Factory().create(mock(Job.Parameters.class), converted.getData());
} }
@Test
public void migrate_pushGroupUpdateJob() throws Exception {
JobData testData = new JobData("PushGroupUpdateJob", null, new Data.Builder().putString("source", "+16101234567")
.putString("group_id", "__textsecure_group__!abcdef0123456789abcdef0123456789")
.build());
mockRecipientResolve("+16101234567", 1);
RecipientIdJobMigration subject = new RecipientIdJobMigration(mock(Application.class));
JobData converted = subject.migrate(testData);
assertEquals("PushGroupUpdateJob", converted.getFactoryKey());
assertNull(converted.getQueueKey());
assertEquals("1", converted.getData().getString("source"));
assertEquals("__textsecure_group__!abcdef0123456789abcdef0123456789", converted.getData().getString("group_id"));
new PushGroupUpdateJob.Factory().create(mock(Job.Parameters.class), converted.getData());
}
@Test @Test
public void migrate_directoryRefreshJob_null() throws Exception { public void migrate_directoryRefreshJob_null() throws Exception {
JobData testData = new JobData("DirectoryRefreshJob", "DirectoryRefreshJob", new Data.Builder().putString("address", null).putBoolean("notify_of_new_users", true).build()); JobData testData = new JobData("DirectoryRefreshJob", "DirectoryRefreshJob", new Data.Builder().putString("address", null).putBoolean("notify_of_new_users", true).build());

View file

@ -36,8 +36,6 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPoin
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
import org.whispersystems.signalservice.api.messages.SignalServicePreview; import org.whispersystems.signalservice.api.messages.SignalServicePreview;
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
@ -102,7 +100,6 @@ import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Attach
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.CallMessage; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.CallMessage;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.NullMessage; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.NullMessage;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Preview; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Preview;
@ -834,14 +831,7 @@ public class SignalServiceMessageSender {
} }
if (message.getGroupContext().isPresent()) { if (message.getGroupContext().isPresent()) {
SignalServiceGroupContext groupContext = message.getGroupContext().get(); builder.setGroupV2(createGroupContent(message.getGroupContext().get()));
if (groupContext.getGroupV1().isPresent()) {
builder.setGroup(createGroupContent(groupContext.getGroupV1().get()));
}
if (groupContext.getGroupV2().isPresent()) {
builder.setGroupV2(createGroupContent(groupContext.getGroupV2().get()));
}
} }
if (message.isEndSession()) { if (message.isEndSession()) {
@ -1473,47 +1463,6 @@ public class SignalServiceMessageSender {
return builder; return builder;
} }
private GroupContext createGroupContent(SignalServiceGroup group) throws IOException {
GroupContext.Builder builder = GroupContext.newBuilder();
builder.setId(ByteString.copyFrom(group.getGroupId()));
if (group.getType() != SignalServiceGroup.Type.DELIVER) {
if (group.getType() == SignalServiceGroup.Type.UPDATE) builder.setType(GroupContext.Type.UPDATE);
else if (group.getType() == SignalServiceGroup.Type.QUIT) builder.setType(GroupContext.Type.QUIT);
else if (group.getType() == SignalServiceGroup.Type.REQUEST_INFO) builder.setType(GroupContext.Type.REQUEST_INFO);
else throw new AssertionError("Unknown type: " + group.getType());
if (group.getName().isPresent()) {
builder.setName(group.getName().get());
}
if (group.getMembers().isPresent()) {
for (SignalServiceAddress address : group.getMembers().get()) {
if (address.getNumber().isPresent()) {
builder.addMembersE164(address.getNumber().get());
GroupContext.Member.Builder memberBuilder = GroupContext.Member.newBuilder();
memberBuilder.setE164(address.getNumber().get());
builder.addMembers(memberBuilder.build());
}
}
}
if (group.getAvatar().isPresent()) {
if (group.getAvatar().get().isStream()) {
builder.setAvatar(createAttachmentPointer(group.getAvatar().get().asStream()));
} else {
builder.setAvatar(createAttachmentPointer(group.getAvatar().get().asPointer()));
}
}
} else {
builder.setType(GroupContext.Type.DELIVER);
}
return builder.build();
}
private static GroupContextV2 createGroupContent(SignalServiceGroupV2 group) { private static GroupContextV2 createGroupContent(SignalServiceGroupV2 group) {
GroupContextV2.Builder builder = GroupContextV2.newBuilder() GroupContextV2.Builder builder = GroupContextV2.newBuilder()
.setMasterKey(ByteString.copyFrom(group.getMasterKey().serialize())) .setMasterKey(ByteString.copyFrom(group.getMasterKey().serialize()))

View file

@ -594,15 +594,8 @@ public final class SignalServiceContent {
SignalServiceProtos.DataMessage content) SignalServiceProtos.DataMessage content)
throws UnsupportedDataMessageException, InvalidMessageStructureException throws UnsupportedDataMessageException, InvalidMessageStructureException
{ {
SignalServiceGroupV2 groupInfoV2 = createGroupV2Info(content); SignalServiceGroupV2 groupInfoV2 = createGroupV2Info(content);
Optional<SignalServiceGroupContext> groupContext; Optional<SignalServiceGroupV2> groupContext = Optional.ofNullable(groupInfoV2);
try {
groupContext = SignalServiceGroupContext.createOptional(null, groupInfoV2);
} catch (InvalidMessageException e) {
throw new InvalidMessageStructureException(e);
}
List<SignalServiceAttachment> attachments = new LinkedList<>(); List<SignalServiceAttachment> attachments = new LinkedList<>();
boolean endSession = ((content.getFlags() & SignalServiceProtos.DataMessage.Flags.END_SESSION_VALUE ) != 0); boolean endSession = ((content.getFlags() & SignalServiceProtos.DataMessage.Flags.END_SESSION_VALUE ) != 0);
@ -649,7 +642,6 @@ public final class SignalServiceContent {
} }
return new SignalServiceDataMessage(metadata.getTimestamp(), return new SignalServiceDataMessage(metadata.getTimestamp(),
null,
groupInfoV2, groupInfoV2,
attachments, attachments,
content.hasBody() ? content.getBody() : null, content.hasBody() ? content.getBody() : null,

View file

@ -27,7 +27,7 @@ public class SignalServiceDataMessage {
private final long timestamp; private final long timestamp;
private final Optional<List<SignalServiceAttachment>> attachments; private final Optional<List<SignalServiceAttachment>> attachments;
private final Optional<String> body; private final Optional<String> body;
private final Optional<SignalServiceGroupContext> group; private final Optional<SignalServiceGroupV2> group;
private final Optional<byte[]> profileKey; private final Optional<byte[]> profileKey;
private final boolean endSession; private final boolean endSession;
private final boolean expirationUpdate; private final boolean expirationUpdate;
@ -50,7 +50,6 @@ public class SignalServiceDataMessage {
* Construct a SignalServiceDataMessage. * Construct a SignalServiceDataMessage.
* *
* @param timestamp The sent timestamp. * @param timestamp The sent timestamp.
* @param group The group information (or null if none).
* @param groupV2 The group information (or null if none). * @param groupV2 The group information (or null if none).
* @param attachments The attachments (or null if none). * @param attachments The attachments (or null if none).
* @param body The message contents. * @param body The message contents.
@ -58,7 +57,6 @@ public class SignalServiceDataMessage {
* @param expiresInSeconds Number of seconds in which the message should disappear after being seen. * @param expiresInSeconds Number of seconds in which the message should disappear after being seen.
*/ */
SignalServiceDataMessage(long timestamp, SignalServiceDataMessage(long timestamp,
SignalServiceGroup group,
SignalServiceGroupV2 groupV2, SignalServiceGroupV2 groupV2,
List<SignalServiceAttachment> attachments, List<SignalServiceAttachment> attachments,
String body, String body,
@ -80,12 +78,7 @@ public class SignalServiceDataMessage {
StoryContext storyContext, StoryContext storyContext,
GiftBadge giftBadge) GiftBadge giftBadge)
{ {
try { this.group = Optional.ofNullable(groupV2);
this.group = SignalServiceGroupContext.createOptional(group, groupV2);
} catch (InvalidMessageException e) {
throw new AssertionError(e);
}
this.timestamp = timestamp; this.timestamp = timestamp;
this.body = OptionalUtil.absentIfEmpty(body); this.body = OptionalUtil.absentIfEmpty(body);
this.endSession = endSession; this.endSession = endSession;
@ -156,7 +149,7 @@ public class SignalServiceDataMessage {
/** /**
* @return The message group context (if any). * @return The message group context (if any).
*/ */
public Optional<SignalServiceGroupContext> getGroupContext() { public Optional<SignalServiceGroupV2> getGroupContext() {
return group; return group;
} }
@ -172,20 +165,13 @@ public class SignalServiceDataMessage {
return profileKeyUpdate; return profileKeyUpdate;
} }
public boolean isGroupV1Update() {
return group.isPresent() &&
group.get().getGroupV1().isPresent() &&
group.get().getGroupV1().get().getType() != SignalServiceGroup.Type.DELIVER;
}
public boolean isGroupV2Message() { public boolean isGroupV2Message() {
return group.isPresent() && return group.isPresent();
group.get().getGroupV2().isPresent();
} }
public boolean isGroupV2Update() { public boolean isGroupV2Update() {
return isGroupV2Message() && return group.isPresent() &&
group.get().getGroupV2().get().hasSignedGroupChange() && group.get().hasSignedGroupChange() &&
!hasRenderableContent(); !hasRenderableContent();
} }
@ -265,8 +251,8 @@ public class SignalServiceDataMessage {
public Optional<byte[]> getGroupId() { public Optional<byte[]> getGroupId() {
byte[] groupId = null; byte[] groupId = null;
if (getGroupContext().isPresent() && getGroupContext().get().getGroupV2().isPresent()) { if (getGroupContext().isPresent() && getGroupContext().isPresent()) {
SignalServiceGroupV2 gv2 = getGroupContext().get().getGroupV2().get(); SignalServiceGroupV2 gv2 = getGroupContext().get();
groupId = GroupSecretParams.deriveFromMasterKey(gv2.getMasterKey()) groupId = GroupSecretParams.deriveFromMasterKey(gv2.getMasterKey())
.getPublicParams() .getPublicParams()
.getGroupIdentifier() .getGroupIdentifier()
@ -284,7 +270,6 @@ public class SignalServiceDataMessage {
private List<Mention> mentions = new LinkedList<>(); private List<Mention> mentions = new LinkedList<>();
private long timestamp; private long timestamp;
private SignalServiceGroup group;
private SignalServiceGroupV2 groupV2; private SignalServiceGroupV2 groupV2;
private String body; private String body;
private boolean endSession; private boolean endSession;
@ -309,18 +294,7 @@ public class SignalServiceDataMessage {
return this; return this;
} }
public Builder asGroupMessage(SignalServiceGroup group) {
if (this.groupV2 != null) {
throw new AssertionError("Can not contain both V1 and V2 group contexts.");
}
this.group = group;
return this;
}
public Builder asGroupMessage(SignalServiceGroupV2 group) { public Builder asGroupMessage(SignalServiceGroupV2 group) {
if (this.group != null) {
throw new AssertionError("Can not contain both V1 and V2 group contexts.");
}
this.groupV2 = group; this.groupV2 = group;
return this; return this;
} }
@ -440,7 +414,7 @@ public class SignalServiceDataMessage {
public SignalServiceDataMessage build() { public SignalServiceDataMessage build() {
if (timestamp == 0) timestamp = System.currentTimeMillis(); if (timestamp == 0) timestamp = System.currentTimeMillis();
return new SignalServiceDataMessage(timestamp, group, groupV2, attachments, body, endSession, return new SignalServiceDataMessage(timestamp, groupV2, attachments, body, endSession,
expiresInSeconds, expirationUpdate, profileKey, expiresInSeconds, expirationUpdate, profileKey,
profileKeyUpdate, quote, sharedContacts, previews, profileKeyUpdate, quote, sharedContacts, previews,
mentions, sticker, viewOnce, reaction, remoteDelete, mentions, sticker, viewOnce, reaction, remoteDelete,

View file

@ -1,61 +0,0 @@
package org.whispersystems.signalservice.api.messages;
import org.signal.libsignal.protocol.InvalidMessageException;
import java.util.Optional;
public final class SignalServiceGroupContext {
private final Optional<SignalServiceGroup> groupV1;
private final Optional<SignalServiceGroupV2> groupV2;
private SignalServiceGroupContext(SignalServiceGroup groupV1) {
this.groupV1 = Optional.of(groupV1);
this.groupV2 = Optional.empty();
}
private SignalServiceGroupContext(SignalServiceGroupV2 groupV2) {
this.groupV1 = Optional.empty();
this.groupV2 = Optional.of(groupV2);
}
public Optional<SignalServiceGroup> getGroupV1() {
return groupV1;
}
public Optional<SignalServiceGroupV2> getGroupV2() {
return groupV2;
}
static Optional<SignalServiceGroupContext> createOptional(SignalServiceGroup groupV1, SignalServiceGroupV2 groupV2)
throws InvalidMessageException
{
return Optional.ofNullable(create(groupV1, groupV2));
}
public static SignalServiceGroupContext create(SignalServiceGroup groupV1, SignalServiceGroupV2 groupV2)
throws InvalidMessageException
{
if (groupV1 == null && groupV2 == null) {
return null;
}
if (groupV1 != null && groupV2 != null) {
throw new InvalidMessageException("Message cannot have both V1 and V2 group contexts.");
}
if (groupV1 != null) {
return new SignalServiceGroupContext(groupV1);
} else {
return new SignalServiceGroupContext(groupV2);
}
}
public SignalServiceGroup.Type getGroupV1Type() {
if (groupV1.isPresent()) {
return groupV1.get().getType();
}
return null;
}
}

View file

@ -1,7 +1,7 @@
package org.whispersystems.signalservice.internal.push; package org.whispersystems.signalservice.internal.push;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext; import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
import java.util.Optional; import java.util.Optional;
@ -12,14 +12,14 @@ import java.util.Optional;
*/ */
public abstract class UnsupportedDataMessageException extends Exception { public abstract class UnsupportedDataMessageException extends Exception {
private final String sender; private final String sender;
private final int senderDevice; private final int senderDevice;
private final Optional<SignalServiceGroupContext> group; private final Optional<SignalServiceGroupV2> group;
protected UnsupportedDataMessageException(String message, protected UnsupportedDataMessageException(String message,
String sender, String sender,
int senderDevice, int senderDevice,
Optional<SignalServiceGroupContext> group) Optional<SignalServiceGroupV2> group)
{ {
super(message); super(message);
this.sender = sender; this.sender = sender;
@ -35,7 +35,7 @@ public abstract class UnsupportedDataMessageException extends Exception {
return senderDevice; return senderDevice;
} }
public Optional<SignalServiceGroupContext> getGroup() { public Optional<SignalServiceGroupV2> getGroup() {
return group; return group;
} }
} }

View file

@ -1,7 +1,7 @@
package org.whispersystems.signalservice.internal.push; package org.whispersystems.signalservice.internal.push;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext; import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
import java.util.Optional; import java.util.Optional;
@ -16,7 +16,7 @@ public final class UnsupportedDataMessageProtocolVersionException extends Unsupp
int requiredVersion, int requiredVersion,
String sender, String sender,
int senderDevice, int senderDevice,
Optional<SignalServiceGroupContext> group) { Optional<SignalServiceGroupV2> group) {
super("Required version: " + requiredVersion + ", Our version: " + currentVersion, sender, senderDevice, group); super("Required version: " + requiredVersion + ", Our version: " + currentVersion, sender, senderDevice, group);
this.requiredVersion = requiredVersion; this.requiredVersion = requiredVersion;
} }

View file

@ -313,7 +313,7 @@ message DataMessage {
optional string body = 1; optional string body = 1;
repeated AttachmentPointer attachments = 2; repeated AttachmentPointer attachments = 2;
optional GroupContext group = 3; reserved /*groupV1*/ 3;
optional GroupContextV2 groupV2 = 15; optional GroupContextV2 groupV2 = 15;
optional uint32 flags = 4; optional uint32 flags = 4;
optional uint32 expireTimer = 5; optional uint32 expireTimer = 5;