Support for group update messages from paired devices.

Fixes #3566
// FREEBIE
This commit is contained in:
Moxie Marlinspike 2015-10-15 17:10:54 -07:00
parent 4ffb1ea95e
commit 5fd9874ab6
5 changed files with 55 additions and 27 deletions

View File

@ -559,7 +559,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
.setType(GroupContext.Type.QUIT) .setType(GroupContext.Type.QUIT)
.build(); .build();
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(getRecipients(), context, null); OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(getRecipients(), context, null, System.currentTimeMillis());
MessageSender.send(self, masterSecret, outgoingMessage, threadId, false); MessageSender.send(self, masterSecret, outgoingMessage, threadId, false);
DatabaseFactory.getGroupDatabase(self).remove(groupId, TextSecurePreferences.getLocalNumber(self)); DatabaseFactory.getGroupDatabase(self).remove(groupId, TextSecurePreferences.getLocalNumber(self));
initializeEnabledCheck(); initializeEnabledCheck();

View File

@ -476,7 +476,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
Uri avatarUri = SingleUseBlobProvider.getInstance().createUri(avatar); Uri avatarUri = SingleUseBlobProvider.getInstance().createUri(avatar);
Attachment avatarAttachment = new UriAttachment(avatarUri, ContentType.IMAGE_JPEG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length); Attachment avatarAttachment = new UriAttachment(avatarUri, ContentType.IMAGE_JPEG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length);
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, context, avatarAttachment); OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, context, avatarAttachment, System.currentTimeMillis());
long threadId = MessageSender.send(this, masterSecret, outgoingMessage, -1, false); long threadId = MessageSender.send(this, masterSecret, outgoingMessage, -1, false);
return new Pair<>(threadId, groupRecipient); return new Pair<>(threadId, groupRecipient);

View File

@ -3,35 +3,39 @@ package org.thoughtcrime.securesms.groups;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion; import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.jobs.AvatarDownloadJob; import org.thoughtcrime.securesms.jobs.AvatarDownloadJob;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.IncomingGroupMessage; import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.libaxolotl.util.guava.Optional; import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.messages.TextSecureAttachment; import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
import org.whispersystems.textsecure.api.messages.TextSecureDataMessage;
import org.whispersystems.textsecure.api.messages.TextSecureEnvelope; import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
import org.whispersystems.textsecure.api.messages.TextSecureGroup; import org.whispersystems.textsecure.api.messages.TextSecureGroup;
import org.whispersystems.textsecure.api.messages.TextSecureDataMessage;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import ws.com.google.android.mms.MmsException;
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import static org.whispersystems.textsecure.internal.push.TextSecureProtos.AttachmentPointer; import static org.whispersystems.textsecure.internal.push.TextSecureProtos.AttachmentPointer;
import static org.whispersystems.textsecure.internal.push.TextSecureProtos.GroupContext; import static org.whispersystems.textsecure.internal.push.TextSecureProtos.GroupContext;
@ -43,7 +47,8 @@ public class GroupMessageProcessor {
public static void process(@NonNull Context context, public static void process(@NonNull Context context,
@NonNull MasterSecretUnion masterSecret, @NonNull MasterSecretUnion masterSecret,
@NonNull TextSecureEnvelope envelope, @NonNull TextSecureEnvelope envelope,
@NonNull TextSecureDataMessage message) @NonNull TextSecureDataMessage message,
boolean outgoing)
{ {
if (!message.getGroupInfo().isPresent() || message.getGroupInfo().get().getGroupId() == null) { if (!message.getGroupInfo().isPresent() || message.getGroupInfo().get().getGroupId() == null) {
Log.w(TAG, "Received group message with no id! Ignoring..."); Log.w(TAG, "Received group message with no id! Ignoring...");
@ -56,11 +61,11 @@ public class GroupMessageProcessor {
GroupRecord record = database.getGroup(id); GroupRecord record = database.getGroup(id);
if (record != null && group.getType() == TextSecureGroup.Type.UPDATE) { if (record != null && group.getType() == TextSecureGroup.Type.UPDATE) {
handleGroupUpdate(context, masterSecret, envelope, group, record); handleGroupUpdate(context, masterSecret, envelope, group, record, outgoing);
} else if (record == null && group.getType() == TextSecureGroup.Type.UPDATE) { } else if (record == null && group.getType() == TextSecureGroup.Type.UPDATE) {
handleGroupCreate(context, masterSecret, envelope, group); handleGroupCreate(context, masterSecret, envelope, group, outgoing);
} else if (record != null && group.getType() == TextSecureGroup.Type.QUIT) { } else if (record != null && group.getType() == TextSecureGroup.Type.QUIT) {
handleGroupLeave(context, masterSecret, envelope, group, record); handleGroupLeave(context, masterSecret, envelope, group, record, outgoing);
} else { } else {
Log.w(TAG, "Received unknown type, ignoring..."); Log.w(TAG, "Received unknown type, ignoring...");
} }
@ -69,7 +74,8 @@ public class GroupMessageProcessor {
private static void handleGroupCreate(@NonNull Context context, private static void handleGroupCreate(@NonNull Context context,
@NonNull MasterSecretUnion masterSecret, @NonNull MasterSecretUnion masterSecret,
@NonNull TextSecureEnvelope envelope, @NonNull TextSecureEnvelope envelope,
@NonNull TextSecureGroup group) @NonNull TextSecureGroup group,
boolean outgoing)
{ {
GroupDatabase database = DatabaseFactory.getGroupDatabase(context); GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
byte[] id = group.getGroupId(); byte[] id = group.getGroupId();
@ -82,14 +88,15 @@ public class GroupMessageProcessor {
avatar != null && avatar.isPointer() ? avatar.asPointer() : null, avatar != null && avatar.isPointer() ? avatar.asPointer() : null,
envelope.getRelay()); envelope.getRelay());
storeMessage(context, masterSecret, envelope, group, builder.build()); storeMessage(context, masterSecret, envelope, group, builder.build(), outgoing);
} }
private static void handleGroupUpdate(@NonNull Context context, private static void handleGroupUpdate(@NonNull Context context,
@NonNull MasterSecretUnion masterSecret, @NonNull MasterSecretUnion masterSecret,
@NonNull TextSecureEnvelope envelope, @NonNull TextSecureEnvelope envelope,
@NonNull TextSecureGroup group, @NonNull TextSecureGroup group,
@NonNull GroupRecord groupRecord) @NonNull GroupRecord groupRecord,
boolean outgoing)
{ {
GroupDatabase database = DatabaseFactory.getGroupDatabase(context); GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
@ -132,14 +139,15 @@ public class GroupMessageProcessor {
if (!groupRecord.isActive()) database.setActive(id, true); if (!groupRecord.isActive()) database.setActive(id, true);
storeMessage(context, masterSecret, envelope, group, builder.build()); storeMessage(context, masterSecret, envelope, group, builder.build(), outgoing);
} }
private static void handleGroupLeave(@NonNull Context context, private static void handleGroupLeave(@NonNull Context context,
@NonNull MasterSecretUnion masterSecret, @NonNull MasterSecretUnion masterSecret,
@NonNull TextSecureEnvelope envelope, @NonNull TextSecureEnvelope envelope,
@NonNull TextSecureGroup group, @NonNull TextSecureGroup group,
@NonNull GroupRecord record) @NonNull GroupRecord record,
boolean outgoing)
{ {
GroupDatabase database = DatabaseFactory.getGroupDatabase(context); GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
byte[] id = group.getGroupId(); byte[] id = group.getGroupId();
@ -150,8 +158,9 @@ public class GroupMessageProcessor {
if (members.contains(envelope.getSource())) { if (members.contains(envelope.getSource())) {
database.remove(id, envelope.getSource()); database.remove(id, envelope.getSource());
if (outgoing) database.setActive(id, false);
storeMessage(context, masterSecret, envelope, group, builder.build()); storeMessage(context, masterSecret, envelope, group, builder.build(), outgoing);
} }
} }
@ -160,20 +169,35 @@ public class GroupMessageProcessor {
@NonNull MasterSecretUnion masterSecret, @NonNull MasterSecretUnion masterSecret,
@NonNull TextSecureEnvelope envelope, @NonNull TextSecureEnvelope envelope,
@NonNull TextSecureGroup group, @NonNull TextSecureGroup group,
@NonNull GroupContext storage) @NonNull GroupContext storage,
boolean outgoing)
{ {
if (group.getAvatar().isPresent()) { if (group.getAvatar().isPresent()) {
ApplicationContext.getInstance(context).getJobManager() ApplicationContext.getInstance(context).getJobManager()
.add(new AvatarDownloadJob(context, group.getGroupId())); .add(new AvatarDownloadJob(context, group.getGroupId()));
} }
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); try {
String body = Base64.encodeBytes(storage.toByteArray()); if (outgoing) {
IncomingTextMessage incoming = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(), envelope.getTimestamp(), body, Optional.of(group)); MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body); Recipients recipients = RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(group.getGroupId()), false);
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipients, storage, null, envelope.getTimestamp());
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
long messageId = mmsDatabase.insertMessageOutbox(masterSecret, outgoingMessage, threadId, false);
Pair<Long, Long> messageAndThreadId = smsDatabase.insertMessageInbox(masterSecret, groupMessage); mmsDatabase.markAsSent(messageId);
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second); } else {
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
String body = Base64.encodeBytes(storage.toByteArray());
IncomingTextMessage incoming = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(), envelope.getTimestamp(), body, Optional.of(group));
IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body);
Pair<Long, Long> messageAndThreadId = smsDatabase.insertMessageInbox(masterSecret, groupMessage);
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second);
}
} catch (MmsException e) {
Log.w(TAG, e);
}
} }
private static GroupContext.Builder createGroupContext(TextSecureGroup group) { private static GroupContext.Builder createGroupContext(TextSecureGroup group) {

View File

@ -143,7 +143,7 @@ public class PushDecryptJob extends ContextJob {
} else if (content.getSyncMessage().isPresent()) { } else if (content.getSyncMessage().isPresent()) {
TextSecureSyncMessage syncMessage = content.getSyncMessage().get(); TextSecureSyncMessage syncMessage = content.getSyncMessage().get();
if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(masterSecret, syncMessage.getSent().get(), smsMessageId); if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(masterSecret, envelope, syncMessage.getSent().get(), smsMessageId);
else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(masterSecret, syncMessage.getRequest().get()); else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(masterSecret, syncMessage.getRequest().get());
} }
@ -206,7 +206,7 @@ public class PushDecryptJob extends ContextJob {
@NonNull TextSecureDataMessage message, @NonNull TextSecureDataMessage message,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
{ {
GroupMessageProcessor.process(context, masterSecret, envelope, message); GroupMessageProcessor.process(context, masterSecret, envelope, message, false);
if (smsMessageId.isPresent()) { if (smsMessageId.isPresent()) {
DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get());
@ -214,11 +214,14 @@ public class PushDecryptJob extends ContextJob {
} }
private void handleSynchronizeSentMessage(@NonNull MasterSecretUnion masterSecret, private void handleSynchronizeSentMessage(@NonNull MasterSecretUnion masterSecret,
@NonNull TextSecureEnvelope envelope,
@NonNull SentTranscriptMessage message, @NonNull SentTranscriptMessage message,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
throws MmsException throws MmsException
{ {
if (message.getMessage().getAttachments().isPresent()) { if (message.getMessage().isGroupUpdate()) {
GroupMessageProcessor.process(context, masterSecret, envelope, message.getMessage(), true);
} else if (message.getMessage().getAttachments().isPresent()) {
handleSynchronizeSentMediaMessage(masterSecret, message, smsMessageId); handleSynchronizeSentMediaMessage(masterSecret, message, smsMessageId);
} else { } else {
handleSynchronizeSentTextMessage(masterSecret, message, smsMessageId); handleSynchronizeSentTextMessage(masterSecret, message, smsMessageId);

View File

@ -31,7 +31,8 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
public OutgoingGroupMediaMessage(@NonNull Recipients recipients, public OutgoingGroupMediaMessage(@NonNull Recipients recipients,
@NonNull GroupContext group, @NonNull GroupContext group,
@Nullable final Attachment avatar) @Nullable final Attachment avatar,
long sentTimeMillis)
{ {
super(recipients, Base64.encodeBytes(group.toByteArray()), super(recipients, Base64.encodeBytes(group.toByteArray()),
new LinkedList<Attachment>() {{if (avatar != null) add(avatar);}}, new LinkedList<Attachment>() {{if (avatar != null) add(avatar);}},