mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
Handle identity key mismatch on outgoing group messages.
Additionally, make the group creation process asynchronous.
This commit is contained in:
parent
5810062b25
commit
b9f4fba98a
@ -5,7 +5,6 @@ import android.util.Log;
|
||||
|
||||
import com.google.thoughtcrimegson.Gson;
|
||||
import com.google.thoughtcrimegson.JsonParseException;
|
||||
import com.google.thoughtcrimegson.JsonSyntaxException;
|
||||
|
||||
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
@ -27,7 +26,6 @@ import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@ -94,7 +92,7 @@ public class PushServiceSocket {
|
||||
try {
|
||||
makeRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", new Gson().toJson(bundle));
|
||||
} catch (NotFoundException nfe) {
|
||||
throw new UnregisteredUserException(nfe);
|
||||
throw new UnregisteredUserException(bundle.getDestination(), nfe);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,14 @@ import java.util.List;
|
||||
|
||||
public class UnregisteredUserException extends IOException {
|
||||
|
||||
public UnregisteredUserException(Exception exception) {
|
||||
private final String e164number;
|
||||
|
||||
public UnregisteredUserException(String e164number, Exception exception) {
|
||||
super(exception);
|
||||
this.e164number = e164number;
|
||||
}
|
||||
|
||||
public String getE164Number() {
|
||||
return e164number;
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import android.provider.ContactsContract.QuickContact;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
@ -42,6 +43,7 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||
@ -53,14 +55,17 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.SendReceiveService;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.Emoji;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
import org.whispersystems.textsecure.util.FutureTaskListener;
|
||||
import org.whispersystems.textsecure.util.ListenableFutureTask;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext;
|
||||
|
||||
@ -172,13 +177,14 @@ public class ConversationItem extends LinearLayout {
|
||||
/// MessageRecord Attribute Parsers
|
||||
|
||||
private void setBodyText(MessageRecord messageRecord) {
|
||||
// TODO jake is going to fill these in
|
||||
switch (messageRecord.getGroupAction()) {
|
||||
case GroupContext.Type.QUIT_VALUE:
|
||||
bodyText.setText(messageRecord.getIndividualRecipient().toShortString() + " has left the group.");
|
||||
return;
|
||||
case GroupContext.Type.ADD_VALUE:
|
||||
case GroupContext.Type.CREATE_VALUE:
|
||||
bodyText.setText(messageRecord.getGroupActionArguments() + " have joined the group.");
|
||||
bodyText.setText(Util.join(GroupUtil.getSerializedArgumentMembers(messageRecord.getGroupActionArguments()), ", ") + " have joined the group.");
|
||||
return;
|
||||
case GroupContext.Type.MODIFY_VALUE:
|
||||
bodyText.setText(messageRecord.getIndividualRecipient() + " has updated the group.");
|
||||
|
@ -9,7 +9,6 @@ import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
@ -30,8 +29,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||
import org.thoughtcrime.securesms.transport.PushTransport;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.ActionBarUtil;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
@ -42,7 +40,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.directory.Directory;
|
||||
import org.whispersystems.textsecure.directory.NotInDirectoryException;
|
||||
import org.whispersystems.textsecure.push.PushAttachmentPointer;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
import org.whispersystems.textsecure.util.InvalidNumberException;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@ -54,8 +52,9 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import ws.com.google.android.mms.MmsException;
|
||||
|
||||
import static org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
|
||||
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer;
|
||||
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext;
|
||||
|
||||
|
||||
@ -355,59 +354,32 @@ public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActiv
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<Long, List<Recipient>> handleCreatePushGroup(String groupName,
|
||||
private long handleCreatePushGroup(String groupName,
|
||||
byte[] avatar,
|
||||
Set<Recipient> members)
|
||||
throws IOException, InvalidNumberException
|
||||
{
|
||||
List<String> memberE164Numbers = getE164Numbers(members);
|
||||
PushTransport transport = new PushTransport(this, masterSecret);
|
||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this);
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(this);
|
||||
byte[] groupId = groupDatabase.allocateGroupId();
|
||||
AttachmentPointer avatarPointer = null;
|
||||
|
||||
memberE164Numbers.add(TextSecurePreferences.getLocalNumber(this));
|
||||
|
||||
GroupContext.Builder builder = GroupContext.newBuilder()
|
||||
.setId(ByteString.copyFrom(groupId))
|
||||
.setType(GroupContext.Type.CREATE)
|
||||
.setName(groupName)
|
||||
.addAllMembers(memberE164Numbers);
|
||||
|
||||
if (avatar != null) {
|
||||
PushAttachmentPointer pointer = transport.createAttachment("image/png", avatar);
|
||||
avatarPointer = AttachmentPointer.newBuilder()
|
||||
.setKey(ByteString.copyFrom(pointer.getKey()))
|
||||
.setContentType(pointer.getContentType())
|
||||
.setId(pointer.getId()).build();
|
||||
builder.setAvatar(avatarPointer);
|
||||
}
|
||||
|
||||
List<Recipient> failures = transport.deliver(new LinkedList<Recipient>(members), builder.build());
|
||||
groupDatabase.create(groupId, TextSecurePreferences.getLocalNumber(this), groupName,
|
||||
memberE164Numbers, avatarPointer, null);
|
||||
|
||||
if (avatar != null) {
|
||||
groupDatabase.updateAvatar(groupId, avatar);
|
||||
}
|
||||
|
||||
try {
|
||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this);
|
||||
List<String> memberE164Numbers = getE164Numbers(members);
|
||||
byte[] groupId = groupDatabase.allocateGroupId();
|
||||
String groupRecipientId = GroupUtil.getEncodedId(groupId);
|
||||
Recipient groupRecipient = RecipientFactory.getRecipientsFromString(this, groupRecipientId, false).getPrimaryRecipient();
|
||||
OutgoingTextMessage outgoing = new OutgoingTextMessage(groupRecipient, GroupContext.Type.ADD_VALUE, org.whispersystems.textsecure.util.Util.join(memberE164Numbers, ","));
|
||||
long threadId = threadDatabase.getThreadIdFor(new Recipients(groupRecipient));
|
||||
List<Long> messageIds = DatabaseFactory.getEncryptingSmsDatabase(this)
|
||||
.insertMessageOutbox(masterSecret, threadId, outgoing);
|
||||
|
||||
for (long messageId : messageIds) {
|
||||
DatabaseFactory.getEncryptingSmsDatabase(this).markAsSent(messageId);
|
||||
}
|
||||
String groupActionArguments = GroupUtil.serializeArguments(groupId, groupName, memberE164Numbers);
|
||||
|
||||
groupDatabase.create(groupId, TextSecurePreferences.getLocalNumber(this), groupName,
|
||||
memberE164Numbers, null, null);
|
||||
groupDatabase.updateAvatar(groupId, avatar);
|
||||
|
||||
return new Pair<Long, List<Recipient>>(threadId, failures);
|
||||
Recipients groupRecipient = RecipientFactory.getRecipientsFromString(this, groupRecipientId, false);
|
||||
|
||||
return MessageSender.sendGroupAction(this, masterSecret, groupRecipient, -1,
|
||||
GroupContext.Type.CREATE_VALUE,
|
||||
groupActionArguments, avatar);
|
||||
} catch (RecipientFormattingException e) {
|
||||
throw new AssertionError(e);
|
||||
throw new IOException(e);
|
||||
} catch (MmsException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,7 +132,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
CONTENT_LOCATION, EXPIRY, MESSAGE_CLASS, MESSAGE_TYPE, MMS_VERSION,
|
||||
MESSAGE_SIZE, PRIORITY, REPORT_ALLOWED, STATUS, TRANSACTION_ID, RETRIEVE_STATUS,
|
||||
RETRIEVE_TEXT, RETRIEVE_TEXT_CS, READ_STATUS, CONTENT_CLASS, RESPONSE_TEXT,
|
||||
DELIVERY_TIME, DELIVERY_REPORT, BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID
|
||||
DELIVERY_TIME, DELIVERY_REPORT, BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID,
|
||||
GROUP_ACTION, GROUP_ACTION_ARGUMENTS
|
||||
};
|
||||
|
||||
public static final ExecutorService slideResolver = org.thoughtcrime.securesms.util.Util.newSingleThreadedLifoExecutor();
|
||||
@ -344,8 +345,11 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
messageId = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
|
||||
|
||||
long outboxType = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX));
|
||||
String messageText = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
|
||||
int groupAction = cursor.getInt(cursor.getColumnIndexOrThrow(GROUP_ACTION));
|
||||
String groupActionArguments = cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ACTION_ARGUMENTS));
|
||||
PduHeaders headers = getHeadersFromCursor(cursor);
|
||||
addr.getAddressesForId(messageId, headers);
|
||||
|
||||
@ -361,7 +365,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
Log.w("MmsDatabase", e);
|
||||
}
|
||||
|
||||
requests[i++] = new SendReq(headers, body, messageId, outboxType);
|
||||
requests[i++] = new SendReq(headers, body, messageId, outboxType, groupAction, groupActionArguments);
|
||||
}
|
||||
|
||||
return requests;
|
||||
@ -514,6 +518,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
contentValues.put(THREAD_ID, threadId);
|
||||
contentValues.put(READ, 1);
|
||||
contentValues.put(DATE_RECEIVED, contentValues.getAsLong(DATE_SENT));
|
||||
contentValues.put(GROUP_ACTION, sendRequest.getGroupAction());
|
||||
contentValues.put(GROUP_ACTION_ARGUMENTS, sendRequest.getGroupActionArguments());
|
||||
contentValues.remove(ADDRESS);
|
||||
|
||||
long messageId = insertMediaMessage(masterSecret, sendRequest.getPduHeaders(),
|
||||
|
@ -20,13 +20,17 @@ import android.content.Context;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.whispersystems.textsecure.push.PushMessageProtos;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext;
|
||||
|
||||
/**
|
||||
@ -55,12 +59,13 @@ public class ThreadRecord extends DisplayRecord {
|
||||
|
||||
@Override
|
||||
public SpannableString getDisplayBody() {
|
||||
// TODO jake is going to fill these in
|
||||
if (SmsDatabase.Types.isDecryptInProgressType(type)) {
|
||||
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_decrypting_please_wait));
|
||||
} else if (getGroupAction() == GroupContext.Type.ADD_VALUE ||
|
||||
getGroupAction() == GroupContext.Type.CREATE_VALUE)
|
||||
{
|
||||
return emphasisAdded("Added " + getGroupActionArguments());
|
||||
return emphasisAdded(Util.join(GroupUtil.getSerializedArgumentMembers(getGroupActionArguments()), ", ") + " have joined the group");
|
||||
} else if (getGroupAction() == GroupContext.Type.QUIT_VALUE) {
|
||||
return emphasisAdded(getRecipients().toShortString() + " left the group.");
|
||||
} else if (getGroupAction() == GroupContext.Type.MODIFY_VALUE) {
|
||||
|
@ -41,7 +41,7 @@ public class IncomingMediaMessage {
|
||||
if (messageContent.hasGroup()) {
|
||||
this.groupId = GroupUtil.getEncodedId(messageContent.getGroup().getId().toByteArray());
|
||||
this.groupAction = messageContent.getGroup().getType().getNumber();
|
||||
this.groupActionArguments = GroupUtil.getActionArgument(messageContent.getGroup());
|
||||
this.groupActionArguments = GroupUtil.serializeArguments(messageContent.getGroup());
|
||||
} else {
|
||||
this.groupId = null;
|
||||
this.groupAction = -1;
|
||||
|
@ -92,8 +92,7 @@ public class MmsSender {
|
||||
Recipients recipients = threads.getRecipientsForThreadId(threadId);
|
||||
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
|
||||
} catch (UntrustedIdentityException uie) {
|
||||
IncomingTextMessage base = new IncomingTextMessage(message);
|
||||
IncomingIdentityUpdateMessage identityUpdateMessage = new IncomingIdentityUpdateMessage(base, Base64.encodeBytesWithoutPadding(uie.getIdentityKey().serialize()));
|
||||
IncomingIdentityUpdateMessage identityUpdateMessage = IncomingIdentityUpdateMessage.createFor(message.getTo()[0].getString(), uie.getIdentityKey());
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, identityUpdateMessage);
|
||||
database.markAsSentFailed(messageId);
|
||||
} catch (RetryLaterException e) {
|
||||
|
@ -81,8 +81,7 @@ public class SmsSender {
|
||||
transport.deliver(record);
|
||||
} catch (UntrustedIdentityException e) {
|
||||
Log.w("SmsSender", e);
|
||||
IncomingTextMessage base = new IncomingTextMessage(record);
|
||||
IncomingIdentityUpdateMessage identityUpdateMessage = new IncomingIdentityUpdateMessage(base, Base64.encodeBytesWithoutPadding(e.getIdentityKey().serialize()));
|
||||
IncomingIdentityUpdateMessage identityUpdateMessage = IncomingIdentityUpdateMessage.createFor(e.getE164Number(), e.getIdentityKey());
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, identityUpdateMessage);
|
||||
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId);
|
||||
} catch (UndeliverableMessageException ude) {
|
||||
|
@ -1,5 +1,8 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
|
||||
public class IncomingIdentityUpdateMessage extends IncomingKeyExchangeMessage {
|
||||
|
||||
public IncomingIdentityUpdateMessage(IncomingTextMessage base, String newBody) {
|
||||
@ -15,4 +18,13 @@ public class IncomingIdentityUpdateMessage extends IncomingKeyExchangeMessage {
|
||||
public boolean isIdentityUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static IncomingIdentityUpdateMessage createFor(String sender, IdentityKey identityKey) {
|
||||
return createFor(sender, identityKey, null);
|
||||
}
|
||||
|
||||
public static IncomingIdentityUpdateMessage createFor(String sender, IdentityKey identityKey, String groupId) {
|
||||
IncomingTextMessage base = new IncomingTextMessage(sender, groupId, -1, null);
|
||||
return new IncomingIdentityUpdateMessage(base, Base64.encodeBytesWithoutPadding(identityKey.serialize()));
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
if (group != null) {
|
||||
this.groupId = GroupUtil.getEncodedId(group.getId().toByteArray());
|
||||
this.groupAction = group.getType().getNumber();
|
||||
this.groupActionArgument = GroupUtil.getActionArgument(group);
|
||||
this.groupActionArgument = GroupUtil.serializeArguments(group);
|
||||
} else {
|
||||
this.groupId = null;
|
||||
this.groupAction = -1;
|
||||
@ -152,6 +152,22 @@ public class IncomingTextMessage implements Parcelable {
|
||||
this.groupActionArgument = null;
|
||||
}
|
||||
|
||||
protected IncomingTextMessage(String sender, String groupId,
|
||||
int groupAction, String groupActionArgument)
|
||||
{
|
||||
this.message = "";
|
||||
this.sender = sender;
|
||||
this.senderDeviceId = RecipientDevice.DEFAULT_DEVICE_ID;
|
||||
this.protocol = 31338;
|
||||
this.serviceCenterAddress = "Outgoing";
|
||||
this.replyPathPresent = true;
|
||||
this.pseudoSubject = "";
|
||||
this.sentTimestampMillis = System.currentTimeMillis();
|
||||
this.groupId = groupId;
|
||||
this.groupAction = groupAction;
|
||||
this.groupActionArgument = groupActionArgument;
|
||||
}
|
||||
|
||||
public long getSentTimestampMillis() {
|
||||
return sentTimestampMillis;
|
||||
}
|
||||
@ -235,4 +251,8 @@ public class IncomingTextMessage implements Parcelable {
|
||||
out.writeInt(groupAction);
|
||||
out.writeString(groupActionArgument);
|
||||
}
|
||||
|
||||
public static IncomingTextMessage createForLeavingGroup(String groupId, String user) {
|
||||
return new IncomingTextMessage(user, groupId, GroupContext.Type.QUIT_VALUE, null);
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
@ -34,10 +35,43 @@ import ws.com.google.android.mms.ContentType;
|
||||
import ws.com.google.android.mms.MmsException;
|
||||
import ws.com.google.android.mms.pdu.EncodedStringValue;
|
||||
import ws.com.google.android.mms.pdu.PduBody;
|
||||
import ws.com.google.android.mms.pdu.PduPart;
|
||||
import ws.com.google.android.mms.pdu.SendReq;
|
||||
|
||||
public class MessageSender {
|
||||
|
||||
public static long sendGroupAction(Context context, MasterSecret masterSecret, Recipients recipients,
|
||||
long threadId, int groupAction, String groupActionArguments, byte[] avatar)
|
||||
throws MmsException
|
||||
{
|
||||
if (threadId == -1) {
|
||||
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
|
||||
}
|
||||
|
||||
PduBody body = new PduBody();
|
||||
|
||||
if (avatar != null) {
|
||||
PduPart part = new PduPart();
|
||||
part.setData(avatar);
|
||||
part.setContentType(ContentType.IMAGE_PNG.getBytes());
|
||||
part.setContentId((System.currentTimeMillis()+"").getBytes());
|
||||
part.setName(("Image" + System.currentTimeMillis()).getBytes());
|
||||
body.addPart(part);
|
||||
}
|
||||
|
||||
SendReq sendRequest = new SendReq();
|
||||
sendRequest.setDate(System.currentTimeMillis() / 1000L);
|
||||
sendRequest.setBody(body);
|
||||
sendRequest.setContentType(ContentType.MULTIPART_MIXED.getBytes());
|
||||
sendRequest.setGroupAction(groupAction);
|
||||
sendRequest.setGroupActionArguments(groupActionArguments);
|
||||
|
||||
sendMms(context, recipients, masterSecret, sendRequest, threadId,
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION, true);
|
||||
|
||||
return threadId;
|
||||
}
|
||||
|
||||
public static long sendMms(Context context, MasterSecret masterSecret, Recipients recipients,
|
||||
long threadId, SlideDeck slideDeck, String message, int distributionType,
|
||||
boolean secure)
|
||||
|
@ -0,0 +1,26 @@
|
||||
package org.thoughtcrime.securesms.transport;
|
||||
|
||||
import org.whispersystems.textsecure.push.UnregisteredUserException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EncapsulatedExceptions extends Throwable {
|
||||
|
||||
private final List<UntrustedIdentityException> untrustedIdentityExceptions;
|
||||
private final List<UnregisteredUserException> unregisteredUserExceptions;
|
||||
|
||||
public EncapsulatedExceptions(List<UntrustedIdentityException> untrustedIdentities,
|
||||
List<UnregisteredUserException> unregisteredUsers)
|
||||
{
|
||||
this.untrustedIdentityExceptions = untrustedIdentities;
|
||||
this.unregisteredUserExceptions = unregisteredUsers;
|
||||
}
|
||||
|
||||
public List<UntrustedIdentityException> getUntrustedIdentityExceptions() {
|
||||
return untrustedIdentityExceptions;
|
||||
}
|
||||
|
||||
public List<UnregisteredUserException> getUnregisteredUserExceptions() {
|
||||
return unregisteredUserExceptions;
|
||||
}
|
||||
}
|
@ -51,6 +51,7 @@ import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
|
||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.push.UnregisteredUserException;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
import org.whispersystems.textsecure.util.InvalidNumberException;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -62,6 +63,8 @@ import ws.com.google.android.mms.pdu.PduBody;
|
||||
import ws.com.google.android.mms.pdu.SendReq;
|
||||
|
||||
import static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal;
|
||||
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer;
|
||||
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext;
|
||||
|
||||
public class PushTransport extends BaseTransport {
|
||||
|
||||
@ -100,7 +103,7 @@ public class PushTransport extends BaseTransport {
|
||||
}
|
||||
|
||||
public void deliver(SendReq message, long threadId)
|
||||
throws IOException, UntrustedIdentityException
|
||||
throws IOException, RecipientFormattingException, InvalidNumberException, EncapsulatedExceptions
|
||||
{
|
||||
PushServiceSocket socket = PushServiceSocketFactory.create(context);
|
||||
byte[] plaintext = getPlaintextMessage(socket, message);
|
||||
@ -108,7 +111,6 @@ public class PushTransport extends BaseTransport {
|
||||
|
||||
Recipients recipients;
|
||||
|
||||
try {
|
||||
if (GroupUtil.isEncodedGroup(destination)) {
|
||||
recipients = DatabaseFactory.getGroupDatabase(context)
|
||||
.getGroupMembers(GroupUtil.getDecodedId(destination));
|
||||
@ -116,59 +118,24 @@ public class PushTransport extends BaseTransport {
|
||||
recipients = RecipientFactory.getRecipientsFromString(context, destination, false);
|
||||
}
|
||||
|
||||
List<UntrustedIdentityException> untrustedIdentities = new LinkedList<UntrustedIdentityException>();
|
||||
List<UnregisteredUserException> unregisteredUsers = new LinkedList<UnregisteredUserException>();
|
||||
|
||||
for (Recipient recipient : recipients.getRecipientsList()) {
|
||||
deliver(socket, recipient, threadId, plaintext);
|
||||
}
|
||||
} catch (UnregisteredUserException uue) {
|
||||
// TODO: We should probably remove the user from the directory?
|
||||
throw new IOException(uue);
|
||||
} catch (RecipientFormattingException e) {
|
||||
throw new IOException(e);
|
||||
} catch (InvalidNumberException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Recipient> deliver(List<Recipient> recipients,
|
||||
PushMessageContent.GroupContext groupAction)
|
||||
throws IOException
|
||||
{
|
||||
PushServiceSocket socket = PushServiceSocketFactory.create(context);
|
||||
byte[] plaintext = PushMessageContent.newBuilder()
|
||||
.setGroup(groupAction)
|
||||
.build().toByteArray();
|
||||
List<Recipient> failures = new LinkedList<Recipient>();
|
||||
|
||||
for (Recipient recipient : recipients) {
|
||||
try {
|
||||
deliver(socket, recipient, -1, plaintext);
|
||||
} catch (UnregisteredUserException e) {
|
||||
Log.w("PushTransport", e);
|
||||
failures.add(recipient);
|
||||
} catch (InvalidNumberException e) {
|
||||
Log.w("PushTransport", e);
|
||||
failures.add(recipient);
|
||||
} catch (IOException e) {
|
||||
Log.w("PushTransport", e);
|
||||
failures.add(recipient);
|
||||
deliver(socket, recipient, threadId, plaintext);
|
||||
} catch (UntrustedIdentityException e) {
|
||||
Log.w("PushTransport", e);
|
||||
failures.add(recipient);
|
||||
untrustedIdentities.add(e);
|
||||
} catch (UnregisteredUserException e) {
|
||||
Log.w("PushTransport", e);
|
||||
unregisteredUsers.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.size() == recipients.size()) {
|
||||
throw new IOException("Total failure.");
|
||||
if (!untrustedIdentities.isEmpty() || !unregisteredUsers.isEmpty()) {
|
||||
throw new EncapsulatedExceptions(untrustedIdentities, unregisteredUsers);
|
||||
}
|
||||
|
||||
return failures;
|
||||
}
|
||||
|
||||
public PushAttachmentPointer createAttachment(String contentType, byte[] data)
|
||||
throws IOException
|
||||
{
|
||||
PushServiceSocket socket = PushServiceSocketFactory.create(context);
|
||||
return getPushAttachmentPointer(socket, contentType, data);
|
||||
}
|
||||
|
||||
private void deliver(PushServiceSocket socket, Recipient recipient, long threadId, byte[] plaintext)
|
||||
@ -252,11 +219,40 @@ public class PushTransport extends BaseTransport {
|
||||
PushMessageContent.Builder builder = PushMessageContent.newBuilder();
|
||||
|
||||
if (GroupUtil.isEncodedGroup(message.getTo()[0].getString())) {
|
||||
PushMessageContent.GroupContext.Builder groupBuilder =
|
||||
PushMessageContent.GroupContext.newBuilder();
|
||||
byte[] groupId = GroupUtil.getDecodedId(message.getTo()[0].getString());
|
||||
GroupContext.Builder groupBuilder = GroupContext.newBuilder();
|
||||
|
||||
groupBuilder.setType(PushMessageContent.GroupContext.Type.DELIVER);
|
||||
groupBuilder.setId(ByteString.copyFrom(GroupUtil.getDecodedId(message.getTo()[0].getString())));
|
||||
groupBuilder.setId(ByteString.copyFrom(groupId));
|
||||
|
||||
switch (message.getGroupAction()) {
|
||||
case GroupContext.Type.ADD_VALUE: groupBuilder.setType(GroupContext.Type.ADD); break;
|
||||
case GroupContext.Type.CREATE_VALUE: groupBuilder.setType(GroupContext.Type.CREATE); break;
|
||||
case GroupContext.Type.QUIT_VALUE: groupBuilder.setType(GroupContext.Type.QUIT); break;
|
||||
default: groupBuilder.setType(GroupContext.Type.DELIVER); break;
|
||||
}
|
||||
|
||||
if (message.getGroupAction() == GroupContext.Type.ADD_VALUE ||
|
||||
message.getGroupAction() == GroupContext.Type.CREATE_VALUE)
|
||||
{
|
||||
GroupContext serialized = GroupContext.parseFrom(Base64.decode(message.getGroupActionArguments()));
|
||||
groupBuilder.addAllMembers(serialized.getMembersList());
|
||||
|
||||
if (serialized.hasName()) {
|
||||
groupBuilder.setName(serialized.getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (message.getGroupAction() == GroupContext.Type.CREATE_VALUE && !attachments.isEmpty()) {
|
||||
Log.w("PushTransport", "Adding avatar...");
|
||||
groupBuilder.setAvatar(AttachmentPointer.newBuilder()
|
||||
.setId(attachments.get(0).getId())
|
||||
.setContentType(attachments.get(0).getContentType())
|
||||
.setKey(ByteString.copyFrom(attachments.get(0).getKey()))
|
||||
.build());
|
||||
attachments.remove(0);
|
||||
} else {
|
||||
Log.w("PushTransport", "Not adding avatar: " + message.getGroupAction() + " , " + attachments.isEmpty());
|
||||
}
|
||||
|
||||
builder.setGroup(groupBuilder.build());
|
||||
}
|
||||
@ -266,8 +262,8 @@ public class PushTransport extends BaseTransport {
|
||||
}
|
||||
|
||||
for (PushAttachmentPointer attachment : attachments) {
|
||||
PushMessageContent.AttachmentPointer.Builder attachmentBuilder =
|
||||
PushMessageContent.AttachmentPointer.newBuilder();
|
||||
AttachmentPointer.Builder attachmentBuilder =
|
||||
AttachmentPointer.newBuilder();
|
||||
|
||||
attachmentBuilder.setId(attachment.getId());
|
||||
attachmentBuilder.setContentType(attachment.getContentType());
|
||||
@ -316,7 +312,7 @@ public class PushTransport extends BaseTransport {
|
||||
if (processor.isTrusted(preKey)) {
|
||||
processor.processKeyExchangeMessage(preKey, threadId);
|
||||
} else {
|
||||
throw new UntrustedIdentityException("Untrusted identity key!", preKey.getIdentityKey());
|
||||
throw new UntrustedIdentityException("Untrusted identity key!", pushAddress.getNumber(), preKey.getIdentityKey());
|
||||
}
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
|
@ -1,4 +1,9 @@
|
||||
package org.thoughtcrime.securesms.transport;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class RetryLaterException extends Exception {
|
||||
public RetryLaterException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
|
@ -19,10 +19,14 @@ package org.thoughtcrime.securesms.transport;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.mms.MmsSendResult;
|
||||
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@ -31,7 +35,11 @@ import org.whispersystems.textsecure.directory.Directory;
|
||||
import org.whispersystems.textsecure.directory.NotInDirectoryException;
|
||||
import org.whispersystems.textsecure.push.ContactNumberDetails;
|
||||
import org.whispersystems.textsecure.push.ContactTokenDetails;
|
||||
import org.whispersystems.textsecure.push.IncomingPushMessage;
|
||||
import org.whispersystems.textsecure.push.PushMessageProtos;
|
||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.push.UnregisteredUserException;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
import org.whispersystems.textsecure.util.DirectoryUtil;
|
||||
import org.whispersystems.textsecure.util.InvalidNumberException;
|
||||
|
||||
@ -39,15 +47,19 @@ import java.io.IOException;
|
||||
|
||||
import ws.com.google.android.mms.pdu.SendReq;
|
||||
|
||||
import static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal;
|
||||
|
||||
public class UniversalTransport {
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final PushTransport pushTransport;
|
||||
private final SmsTransport smsTransport;
|
||||
private final MmsTransport mmsTransport;
|
||||
|
||||
public UniversalTransport(Context context, MasterSecret masterSecret) {
|
||||
this.context = context;
|
||||
this.masterSecret = masterSecret;
|
||||
this.pushTransport = new PushTransport(context, masterSecret);
|
||||
this.smsTransport = new SmsTransport(context, masterSecret);
|
||||
this.mmsTransport = new MmsTransport(context, masterSecret);
|
||||
@ -90,6 +102,10 @@ public class UniversalTransport {
|
||||
throw new UndeliverableMessageException("No destination specified");
|
||||
}
|
||||
|
||||
if (GroupUtil.isEncodedGroup(mediaMessage.getTo()[0].getString())) {
|
||||
return deliverGroupMessage(mediaMessage, threadId);
|
||||
}
|
||||
|
||||
if (!TextSecurePreferences.isPushRegistered(context)) {
|
||||
return mmsTransport.deliver(mediaMessage);
|
||||
}
|
||||
@ -98,14 +114,8 @@ public class UniversalTransport {
|
||||
return mmsTransport.deliver(mediaMessage);
|
||||
}
|
||||
|
||||
String destination;
|
||||
|
||||
try {
|
||||
destination = Util.canonicalizeNumber(context, mediaMessage.getTo()[0].getString());
|
||||
} catch (InvalidNumberException ine) {
|
||||
Log.w("UniversalTransport", ine);
|
||||
return mmsTransport.deliver(mediaMessage);
|
||||
}
|
||||
String destination = Util.canonicalizeNumber(context, mediaMessage.getTo()[0].getString());
|
||||
|
||||
if (isPushTransport(destination)) {
|
||||
try {
|
||||
@ -114,17 +124,66 @@ public class UniversalTransport {
|
||||
return new MmsSendResult("push".getBytes("UTF-8"), 0, true);
|
||||
} catch (IOException ioe) {
|
||||
Log.w("UniversalTransport", ioe);
|
||||
if (!GroupUtil.isEncodedGroup(destination)) {
|
||||
return mmsTransport.deliver(mediaMessage);
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("UniversalTransport", e);
|
||||
return mmsTransport.deliver(mediaMessage);
|
||||
} catch (EncapsulatedExceptions ee) {
|
||||
Log.w("UniversalTransport", ee);
|
||||
if (!ee.getUnregisteredUserExceptions().isEmpty()) {
|
||||
return mmsTransport.deliver(mediaMessage);
|
||||
} else {
|
||||
throw new RetryLaterException();
|
||||
throw new UntrustedIdentityException(ee.getUntrustedIdentityExceptions().get(0));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w("UniversalTransport", "Delivering media message with MMS...");
|
||||
return mmsTransport.deliver(mediaMessage);
|
||||
}
|
||||
} catch (InvalidNumberException ine) {
|
||||
Log.w("UniversalTransport", ine);
|
||||
return mmsTransport.deliver(mediaMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private MmsSendResult deliverGroupMessage(SendReq mediaMessage, long threadId)
|
||||
throws RetryLaterException, UndeliverableMessageException
|
||||
{
|
||||
if (!TextSecurePreferences.isPushRegistered(context)) {
|
||||
throw new UndeliverableMessageException("Not push registered!");
|
||||
}
|
||||
|
||||
try {
|
||||
pushTransport.deliver(mediaMessage, threadId);
|
||||
return new MmsSendResult("push".getBytes("UTF-8"), 0, true);
|
||||
} catch (IOException e) {
|
||||
Log.w("UniversalTransport", e);
|
||||
throw new RetryLaterException(e);
|
||||
} catch (RecipientFormattingException e) {
|
||||
throw new UndeliverableMessageException(e);
|
||||
} catch (InvalidNumberException e) {
|
||||
throw new UndeliverableMessageException(e);
|
||||
} catch (EncapsulatedExceptions ee) {
|
||||
Log.w("UniversalTransport", ee);
|
||||
try {
|
||||
for (UnregisteredUserException unregistered : ee.getUnregisteredUserExceptions()) {
|
||||
IncomingTextMessage quitMessage = IncomingTextMessage.createForLeavingGroup(mediaMessage.getTo()[0].getString(), unregistered.getE164Number());
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, quitMessage);
|
||||
DatabaseFactory.getGroupDatabase(context).remove(GroupUtil.getDecodedId(mediaMessage.getTo()[0].getString()), unregistered.getE164Number());
|
||||
}
|
||||
|
||||
for (UntrustedIdentityException untrusted : ee.getUntrustedIdentityExceptions()) {
|
||||
IncomingIdentityUpdateMessage identityMessage = IncomingIdentityUpdateMessage.createFor(untrusted.getE164Number(), untrusted.getIdentityKey(), mediaMessage.getTo()[0].getString());
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, identityMessage);
|
||||
}
|
||||
|
||||
return new MmsSendResult("push".getBytes("UTF-8"), 0, true);
|
||||
} catch (IOException ioe) {
|
||||
throw new AssertionError(ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean isMultipleRecipients(SendReq mediaMessage) {
|
||||
int recipientCount = 0;
|
||||
|
@ -1,17 +1,29 @@
|
||||
package org.thoughtcrime.securesms.transport;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.push.UnregisteredUserException;
|
||||
|
||||
public class UntrustedIdentityException extends Exception {
|
||||
|
||||
private final IdentityKey identityKey;
|
||||
private final String e164number;
|
||||
|
||||
public UntrustedIdentityException(String s, IdentityKey identityKey) {
|
||||
public UntrustedIdentityException(String s, String e164number, IdentityKey identityKey) {
|
||||
super(s);
|
||||
this.e164number = e164number;
|
||||
this.identityKey = identityKey;
|
||||
}
|
||||
|
||||
public UntrustedIdentityException(UntrustedIdentityException e) {
|
||||
this(e.getMessage(), e.getE164Number(), e.getIdentityKey());
|
||||
}
|
||||
|
||||
public IdentityKey getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
public String getE164Number() {
|
||||
return e164number;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,16 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext;
|
||||
|
||||
@ -26,15 +34,29 @@ public class GroupUtil {
|
||||
return groupId.startsWith(ENCODED_GROUP_PREFIX);
|
||||
}
|
||||
|
||||
public static String getActionArgument(GroupContext group) {
|
||||
if (group.getType().equals(GroupContext.Type.CREATE) ||
|
||||
group.getType().equals(GroupContext.Type.ADD))
|
||||
{
|
||||
return org.whispersystems.textsecure.util.Util.join(group.getMembersList(), ",");
|
||||
} else if (group.getType().equals(GroupContext.Type.MODIFY)) {
|
||||
return group.getName();
|
||||
public static String serializeArguments(byte[] id, String name, List<String> members) {
|
||||
return Base64.encodeBytes(GroupContext.newBuilder()
|
||||
.setId(ByteString.copyFrom(id))
|
||||
.setName(name)
|
||||
.addAllMembers(members)
|
||||
.build().toByteArray());
|
||||
}
|
||||
|
||||
return null;
|
||||
public static String serializeArguments(GroupContext context) {
|
||||
return Base64.encodeBytes(context.toByteArray());
|
||||
}
|
||||
|
||||
public static List<String> getSerializedArgumentMembers(String serialized) {
|
||||
if (serialized == null) {
|
||||
return new LinkedList<String>();
|
||||
}
|
||||
|
||||
try {
|
||||
GroupContext context = GroupContext.parseFrom(Base64.decode(serialized));
|
||||
return context.getMembersList();
|
||||
} catch (IOException e) {
|
||||
Log.w("GroupUtil", e);
|
||||
return new LinkedList<String>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ public class SendReq extends MultimediaMessagePdu {
|
||||
private static final String TAG = "SendReq";
|
||||
private long databaseMessageId;
|
||||
private long messageBox;
|
||||
private int groupAction;
|
||||
private String groupActionArguments;
|
||||
|
||||
public SendReq() {
|
||||
super();
|
||||
@ -90,10 +92,14 @@ public class SendReq extends MultimediaMessagePdu {
|
||||
super(headers, body);
|
||||
}
|
||||
|
||||
public SendReq(PduHeaders headers, PduBody body, long messageId, long messageBox) {
|
||||
public SendReq(PduHeaders headers, PduBody body, long messageId, long messageBox,
|
||||
int groupAction, String groupActionArguments)
|
||||
{
|
||||
super(headers, body);
|
||||
this.databaseMessageId = messageId;
|
||||
this.messageBox = messageBox;
|
||||
this.groupAction = groupAction;
|
||||
this.groupActionArguments = groupActionArguments;
|
||||
}
|
||||
|
||||
public long getDatabaseMessageBox() {
|
||||
@ -104,6 +110,22 @@ public class SendReq extends MultimediaMessagePdu {
|
||||
return databaseMessageId;
|
||||
}
|
||||
|
||||
public int getGroupAction() {
|
||||
return this.groupAction;
|
||||
}
|
||||
|
||||
public String getGroupActionArguments() {
|
||||
return this.groupActionArguments;
|
||||
}
|
||||
|
||||
public void setGroupAction(int groupAction) {
|
||||
this.groupAction = groupAction;
|
||||
}
|
||||
|
||||
public void setGroupActionArguments(String groupActionArguments) {
|
||||
this.groupActionArguments = groupActionArguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Bcc value.
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user