diff --git a/library/AndroidManifest.xml b/library/AndroidManifest.xml
index 150baa85c0..fdd26b94d8 100644
--- a/library/AndroidManifest.xml
+++ b/library/AndroidManifest.xml
@@ -3,6 +3,6 @@
package="org.whispersystems.textsecure"
android:versionCode="1"
android:versionName="0.1">
-
+
diff --git a/library/build.gradle b/library/build.gradle
index cd7eab85fc..1960626d70 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -4,7 +4,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.6.1'
+ classpath 'com.android.tools.build:gradle:0.7.+'
}
}
diff --git a/library/protobuf/IncomingPushMessageSignal.proto b/library/protobuf/IncomingPushMessageSignal.proto
index 08faf9671b..b3294a5fbb 100644
--- a/library/protobuf/IncomingPushMessageSignal.proto
+++ b/library/protobuf/IncomingPushMessageSignal.proto
@@ -6,6 +6,7 @@ option java_outer_classname = "PushMessageProtos";
message IncomingPushMessageSignal {
optional uint32 type = 1;
optional string source = 2;
+ optional uint32 sourceDevice = 7;
optional string relay = 3;
// repeated string destinations = 4; // No longer supported
optional uint64 timestamp = 5;
diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java
index 92c8cde72e..ebbe2ed1d6 100644
--- a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java
+++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java
@@ -20,7 +20,7 @@ package org.whispersystems.textsecure.crypto;
import android.content.Context;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
-import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
+import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.SessionRecordV1;
import org.whispersystems.textsecure.storage.SessionRecordV2;
@@ -31,13 +31,14 @@ public abstract class SessionCipher {
public abstract CiphertextMessage encrypt(byte[] paddedMessage);
public abstract byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException;
- public static SessionCipher createFor(Context context, MasterSecret masterSecret,
- CanonicalRecipientAddress recipient)
+ public static SessionCipher createFor(Context context,
+ MasterSecret masterSecret,
+ RecipientDevice recipient)
{
if (SessionRecordV2.hasSession(context, masterSecret, recipient)) {
return new SessionCipherV2(context, masterSecret, recipient);
- } else if (SessionRecordV1.hasSession(context, recipient)) {
- return new SessionCipherV1(context, masterSecret, recipient);
+ } else if (SessionRecordV1.hasSession(context, recipient.getRecipientId())) {
+ return new SessionCipherV1(context, masterSecret, recipient.getRecipient());
} else {
throw new AssertionError("Attempt to initialize cipher for non-existing session.");
}
diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipherV1.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV1.java
index bebdf45872..f9f7a1e48e 100644
--- a/library/src/org/whispersystems/textsecure/crypto/SessionCipherV1.java
+++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV1.java
@@ -9,7 +9,8 @@ import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
import org.whispersystems.textsecure.crypto.kdf.NKDF;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.crypto.protocol.WhisperMessageV1;
-import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
+import org.whispersystems.textsecure.storage.CanonicalRecipient;
+import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.LocalKeyRecord;
import org.whispersystems.textsecure.storage.RemoteKeyRecord;
@@ -29,16 +30,17 @@ import javax.crypto.spec.SecretKeySpec;
public class SessionCipherV1 extends SessionCipher {
- private final Context context;
- private final MasterSecret masterSecret;
- private final CanonicalRecipientAddress recipient;
+ private final Context context;
+ private final MasterSecret masterSecret;
+ private final CanonicalRecipient recipient;
- public SessionCipherV1(Context context, MasterSecret masterSecret,
- CanonicalRecipientAddress recipient)
+ public SessionCipherV1(Context context,
+ MasterSecret masterSecret,
+ CanonicalRecipient recipient)
{
- this.context = context;
- this.masterSecret = masterSecret;
- this.recipient = recipient;
+ this.context = context;
+ this.masterSecret = masterSecret;
+ this.recipient = recipient;
}
public CiphertextMessage encrypt(byte[] paddedMessageBody) {
@@ -219,7 +221,7 @@ public class SessionCipherV1 extends SessionCipher {
}
private KeyRecords getKeyRecords(Context context, MasterSecret masterSecret,
- CanonicalRecipientAddress recipient)
+ CanonicalRecipient recipient)
{
LocalKeyRecord localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient);
RemoteKeyRecord remoteKeyRecord = new RemoteKeyRecord(context, recipient);
diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java
index b5fd072681..40b5afbe48 100644
--- a/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java
+++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java
@@ -1,7 +1,6 @@
package org.whispersystems.textsecure.crypto;
import android.content.Context;
-import android.util.Log;
import android.util.Pair;
import org.whispersystems.textsecure.crypto.ecc.Curve;
@@ -13,7 +12,7 @@ import org.whispersystems.textsecure.crypto.protocol.WhisperMessageV2;
import org.whispersystems.textsecure.crypto.ratchet.ChainKey;
import org.whispersystems.textsecure.crypto.ratchet.MessageKeys;
import org.whispersystems.textsecure.crypto.ratchet.RootKey;
-import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
+import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.util.Conversions;
@@ -29,13 +28,13 @@ import javax.crypto.spec.SecretKeySpec;
public class SessionCipherV2 extends SessionCipher {
- private final Context context;
- private final MasterSecret masterSecret;
- private final CanonicalRecipientAddress recipient;
+ private final Context context;
+ private final MasterSecret masterSecret;
+ private final RecipientDevice recipient;
public SessionCipherV2(Context context,
MasterSecret masterSecret,
- CanonicalRecipientAddress recipient)
+ RecipientDevice recipient)
{
this.context = context;
this.masterSecret = masterSecret;
diff --git a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java
index 34bf72ff12..33a89b65e6 100644
--- a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java
+++ b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java
@@ -37,6 +37,7 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
private int type;
private String source;
+ private int sourceDevice;
private byte[] message;
private long timestamp;
private String relay;
@@ -44,6 +45,7 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
private IncomingPushMessage(IncomingPushMessage message, byte[] body) {
this.type = message.type;
this.source = message.source;
+ this.sourceDevice = message.sourceDevice;
this.timestamp = message.timestamp;
this.relay = message.relay;
this.message = body;
@@ -52,14 +54,16 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
public IncomingPushMessage(IncomingPushMessageSignal signal) {
this.type = signal.getType();
this.source = signal.getSource();
+ this.sourceDevice = signal.getSourceDevice();
this.message = signal.getMessage().toByteArray();
this.timestamp = signal.getTimestamp();
this.relay = signal.getRelay();
}
public IncomingPushMessage(Parcel in) {
- this.type = in.readInt();
- this.source = in.readString();
+ this.type = in.readInt();
+ this.source = in.readString();
+ this.sourceDevice = in.readInt();
if (in.readInt() == 1) {
this.relay = in.readString();
@@ -70,11 +74,12 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
this.timestamp = in.readLong();
}
- public IncomingPushMessage(int type, String source,
+ public IncomingPushMessage(int type, String source, int sourceDevice,
byte[] body, long timestamp)
{
this.type = type;
this.source = source;
+ this.sourceDevice = sourceDevice;
this.message = body;
this.timestamp = timestamp;
}
@@ -91,6 +96,10 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
return source;
}
+ public int getSourceDevice() {
+ return sourceDevice;
+ }
+
public byte[] getBody() {
return message;
}
@@ -104,6 +113,7 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(type);
dest.writeString(source);
+ dest.writeInt(sourceDevice);
dest.writeInt(relay == null ? 0 : 1);
if (relay != null) {
dest.writeString(relay);
diff --git a/library/src/org/whispersystems/textsecure/push/MismatchedDevices.java b/library/src/org/whispersystems/textsecure/push/MismatchedDevices.java
new file mode 100644
index 0000000000..714a76d754
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/push/MismatchedDevices.java
@@ -0,0 +1,17 @@
+package org.whispersystems.textsecure.push;
+
+import java.util.List;
+
+public class MismatchedDevices {
+ private List missingDevices;
+
+ private List extraDevices;
+
+ public List getMissingDevices() {
+ return missingDevices;
+ }
+
+ public List getExtraDevices() {
+ return extraDevices;
+ }
+}
diff --git a/library/src/org/whispersystems/textsecure/push/MismatchedDevicesException.java b/library/src/org/whispersystems/textsecure/push/MismatchedDevicesException.java
new file mode 100644
index 0000000000..9b34d4f5c5
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/push/MismatchedDevicesException.java
@@ -0,0 +1,16 @@
+package org.whispersystems.textsecure.push;
+
+import java.io.IOException;
+
+public class MismatchedDevicesException extends IOException {
+
+ private final MismatchedDevices mismatchedDevices;
+
+ public MismatchedDevicesException(MismatchedDevices mismatchedDevices) {
+ this.mismatchedDevices = mismatchedDevices;
+ }
+
+ public MismatchedDevices getMismatchedDevices() {
+ return mismatchedDevices;
+ }
+}
diff --git a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java
index c2ce11d82c..ea797ec346 100644
--- a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java
+++ b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java
@@ -21,23 +21,17 @@ import org.whispersystems.textsecure.util.Base64;
public class OutgoingPushMessage implements PushMessage {
private int type;
- private String destination;
+ private int destinationDeviceId;
private String body;
- private String relay;
- public OutgoingPushMessage(String destination, byte[] body, int type) {
- this(null, destination, body, type);
+ public OutgoingPushMessage(PushAddress address, PushBody body) {
+ this.type = body.getType();
+ this.destinationDeviceId = address.getDeviceId();
+ this.body = Base64.encodeBytes(body.getBody());
}
- public OutgoingPushMessage(String relay, String destination, byte[] body, int type) {
- this.relay = relay;
- this.destination = destination;
- this.body = Base64.encodeBytes(body);
- this.type = type;
- }
-
- public String getDestination() {
- return destination;
+ public int getDestinationDeviceId() {
+ return destinationDeviceId;
}
public String getBody() {
@@ -47,8 +41,4 @@ public class OutgoingPushMessage implements PushMessage {
public int getType() {
return type;
}
-
- public String getRelay() {
- return relay;
- }
}
diff --git a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessageList.java b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessageList.java
index 6504ba5b7a..3b8525857b 100644
--- a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessageList.java
+++ b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessageList.java
@@ -5,18 +5,27 @@ import java.util.List;
public class OutgoingPushMessageList {
+ private String destination;
+
+ private String relay;
+
private List messages;
- public OutgoingPushMessageList(OutgoingPushMessage message) {
- this.messages = new LinkedList();
- this.messages.add(message);
+ public OutgoingPushMessageList(String destination, String relay, List messages) {
+ this.destination = destination;
+ this.relay = relay;
+ this.messages = messages;
}
- public OutgoingPushMessageList(List messages) {
- this.messages = messages;
+ public String getDestination() {
+ return destination;
}
public List getMessages() {
return messages;
}
+
+ public String getRelay() {
+ return relay;
+ }
}
diff --git a/library/src/org/whispersystems/textsecure/push/PreKeyEntity.java b/library/src/org/whispersystems/textsecure/push/PreKeyEntity.java
index 1f9cab90e4..ac967e4e7b 100644
--- a/library/src/org/whispersystems/textsecure/push/PreKeyEntity.java
+++ b/library/src/org/whispersystems/textsecure/push/PreKeyEntity.java
@@ -8,6 +8,7 @@ import com.google.thoughtcrimegson.JsonParseException;
import com.google.thoughtcrimegson.JsonPrimitive;
import com.google.thoughtcrimegson.JsonSerializationContext;
import com.google.thoughtcrimegson.JsonSerializer;
+import com.google.thoughtcrimegson.annotations.Expose;
import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
@@ -20,6 +21,9 @@ import java.lang.reflect.Type;
public class PreKeyEntity {
+ @Expose(serialize = false)
+ private int deviceId;
+
private int keyId;
private ECPublicKey publicKey;
private IdentityKey identityKey;
@@ -30,6 +34,10 @@ public class PreKeyEntity {
this.identityKey = identityKey;
}
+ public int getDeviceId() {
+ return deviceId;
+ }
+
public int getKeyId() {
return keyId;
}
diff --git a/library/src/org/whispersystems/textsecure/push/PushAddress.java b/library/src/org/whispersystems/textsecure/push/PushAddress.java
new file mode 100644
index 0000000000..0522cec920
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/push/PushAddress.java
@@ -0,0 +1,32 @@
+package org.whispersystems.textsecure.push;
+
+import android.content.Context;
+
+import org.whispersystems.textsecure.directory.Directory;
+import org.whispersystems.textsecure.storage.RecipientDevice;
+
+public class PushAddress extends RecipientDevice {
+
+ private final String e164number;
+ private final String relay;
+
+ private PushAddress(long recipientId, String e164number, int deviceId, String relay) {
+ super(recipientId, deviceId);
+ this.e164number = e164number;
+ this.relay = relay;
+ }
+
+ public String getNumber() {
+ return e164number;
+ }
+
+ public String getRelay() {
+ return relay;
+ }
+
+ public static PushAddress create(Context context, long recipientId, String e164number, int deviceId) {
+ String relay = Directory.getInstance(context).getRelay(e164number);
+ return new PushAddress(recipientId, e164number, deviceId, relay);
+ }
+
+}
diff --git a/library/src/org/whispersystems/textsecure/push/PushDestination.java b/library/src/org/whispersystems/textsecure/push/PushDestination.java
deleted file mode 100644
index 1126becc9b..0000000000
--- a/library/src/org/whispersystems/textsecure/push/PushDestination.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.whispersystems.textsecure.push;
-
-import android.content.Context;
-
-import org.whispersystems.textsecure.directory.Directory;
-import org.whispersystems.textsecure.util.InvalidNumberException;
-import org.whispersystems.textsecure.util.PhoneNumberFormatter;
-
-public class PushDestination {
-
- private final String e164number;
- private final String relay;
-
- private PushDestination(String e164number, String relay) {
- this.e164number = e164number;
- this.relay = relay;
- }
-
- public String getNumber() {
- return e164number;
- }
-
- public String getRelay() {
- return relay;
- }
-
- public static PushDestination create(Context context,
- String localNumber,
- String destinationNumber)
- throws InvalidNumberException
- {
- String e164destination = PhoneNumberFormatter.formatNumber(destinationNumber, localNumber);
- String relay = Directory.getInstance(context).getRelay(e164destination);
-
- return new PushDestination(e164destination, relay);
- }
-}
diff --git a/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java b/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java
index 840ddfd749..78db4402c6 100644
--- a/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java
+++ b/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java
@@ -19,6 +19,10 @@ public final class PushMessageProtos {
boolean hasSource();
String getSource();
+ // optional uint32 sourceDevice = 7;
+ boolean hasSourceDevice();
+ int getSourceDevice();
+
// optional string relay = 3;
boolean hasRelay();
String getRelay();
@@ -102,11 +106,21 @@ public final class PushMessageProtos {
}
}
+ // optional uint32 sourceDevice = 7;
+ public static final int SOURCEDEVICE_FIELD_NUMBER = 7;
+ private int sourceDevice_;
+ public boolean hasSourceDevice() {
+ return ((bitField0_ & 0x00000004) == 0x00000004);
+ }
+ public int getSourceDevice() {
+ return sourceDevice_;
+ }
+
// optional string relay = 3;
public static final int RELAY_FIELD_NUMBER = 3;
private java.lang.Object relay_;
public boolean hasRelay() {
- return ((bitField0_ & 0x00000004) == 0x00000004);
+ return ((bitField0_ & 0x00000008) == 0x00000008);
}
public String getRelay() {
java.lang.Object ref = relay_;
@@ -138,7 +152,7 @@ public final class PushMessageProtos {
public static final int TIMESTAMP_FIELD_NUMBER = 5;
private long timestamp_;
public boolean hasTimestamp() {
- return ((bitField0_ & 0x00000008) == 0x00000008);
+ return ((bitField0_ & 0x00000010) == 0x00000010);
}
public long getTimestamp() {
return timestamp_;
@@ -148,7 +162,7 @@ public final class PushMessageProtos {
public static final int MESSAGE_FIELD_NUMBER = 6;
private com.google.protobuf.ByteString message_;
public boolean hasMessage() {
- return ((bitField0_ & 0x00000010) == 0x00000010);
+ return ((bitField0_ & 0x00000020) == 0x00000020);
}
public com.google.protobuf.ByteString getMessage() {
return message_;
@@ -157,6 +171,7 @@ public final class PushMessageProtos {
private void initFields() {
type_ = 0;
source_ = "";
+ sourceDevice_ = 0;
relay_ = "";
timestamp_ = 0L;
message_ = com.google.protobuf.ByteString.EMPTY;
@@ -179,15 +194,18 @@ public final class PushMessageProtos {
if (((bitField0_ & 0x00000002) == 0x00000002)) {
output.writeBytes(2, getSourceBytes());
}
- if (((bitField0_ & 0x00000004) == 0x00000004)) {
+ if (((bitField0_ & 0x00000008) == 0x00000008)) {
output.writeBytes(3, getRelayBytes());
}
- if (((bitField0_ & 0x00000008) == 0x00000008)) {
+ if (((bitField0_ & 0x00000010) == 0x00000010)) {
output.writeUInt64(5, timestamp_);
}
- if (((bitField0_ & 0x00000010) == 0x00000010)) {
+ if (((bitField0_ & 0x00000020) == 0x00000020)) {
output.writeBytes(6, message_);
}
+ if (((bitField0_ & 0x00000004) == 0x00000004)) {
+ output.writeUInt32(7, sourceDevice_);
+ }
getUnknownFields().writeTo(output);
}
@@ -205,18 +223,22 @@ public final class PushMessageProtos {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(2, getSourceBytes());
}
- if (((bitField0_ & 0x00000004) == 0x00000004)) {
+ if (((bitField0_ & 0x00000008) == 0x00000008)) {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(3, getRelayBytes());
}
- if (((bitField0_ & 0x00000008) == 0x00000008)) {
+ if (((bitField0_ & 0x00000010) == 0x00000010)) {
size += com.google.protobuf.CodedOutputStream
.computeUInt64Size(5, timestamp_);
}
- if (((bitField0_ & 0x00000010) == 0x00000010)) {
+ if (((bitField0_ & 0x00000020) == 0x00000020)) {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(6, message_);
}
+ if (((bitField0_ & 0x00000004) == 0x00000004)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeUInt32Size(7, sourceDevice_);
+ }
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
@@ -345,12 +367,14 @@ public final class PushMessageProtos {
bitField0_ = (bitField0_ & ~0x00000001);
source_ = "";
bitField0_ = (bitField0_ & ~0x00000002);
- relay_ = "";
+ sourceDevice_ = 0;
bitField0_ = (bitField0_ & ~0x00000004);
- timestamp_ = 0L;
+ relay_ = "";
bitField0_ = (bitField0_ & ~0x00000008);
- message_ = com.google.protobuf.ByteString.EMPTY;
+ timestamp_ = 0L;
bitField0_ = (bitField0_ & ~0x00000010);
+ message_ = com.google.protobuf.ByteString.EMPTY;
+ bitField0_ = (bitField0_ & ~0x00000020);
return this;
}
@@ -400,14 +424,18 @@ public final class PushMessageProtos {
if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
to_bitField0_ |= 0x00000004;
}
- result.relay_ = relay_;
+ result.sourceDevice_ = sourceDevice_;
if (((from_bitField0_ & 0x00000008) == 0x00000008)) {
to_bitField0_ |= 0x00000008;
}
- result.timestamp_ = timestamp_;
+ result.relay_ = relay_;
if (((from_bitField0_ & 0x00000010) == 0x00000010)) {
to_bitField0_ |= 0x00000010;
}
+ result.timestamp_ = timestamp_;
+ if (((from_bitField0_ & 0x00000020) == 0x00000020)) {
+ to_bitField0_ |= 0x00000020;
+ }
result.message_ = message_;
result.bitField0_ = to_bitField0_;
onBuilt();
@@ -431,6 +459,9 @@ public final class PushMessageProtos {
if (other.hasSource()) {
setSource(other.getSource());
}
+ if (other.hasSourceDevice()) {
+ setSourceDevice(other.getSourceDevice());
+ }
if (other.hasRelay()) {
setRelay(other.getRelay());
}
@@ -482,20 +513,25 @@ public final class PushMessageProtos {
break;
}
case 26: {
- bitField0_ |= 0x00000004;
+ bitField0_ |= 0x00000008;
relay_ = input.readBytes();
break;
}
case 40: {
- bitField0_ |= 0x00000008;
+ bitField0_ |= 0x00000010;
timestamp_ = input.readUInt64();
break;
}
case 50: {
- bitField0_ |= 0x00000010;
+ bitField0_ |= 0x00000020;
message_ = input.readBytes();
break;
}
+ case 56: {
+ bitField0_ |= 0x00000004;
+ sourceDevice_ = input.readUInt32();
+ break;
+ }
}
}
}
@@ -559,10 +595,31 @@ public final class PushMessageProtos {
onChanged();
}
+ // optional uint32 sourceDevice = 7;
+ private int sourceDevice_ ;
+ public boolean hasSourceDevice() {
+ return ((bitField0_ & 0x00000004) == 0x00000004);
+ }
+ public int getSourceDevice() {
+ return sourceDevice_;
+ }
+ public Builder setSourceDevice(int value) {
+ bitField0_ |= 0x00000004;
+ sourceDevice_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearSourceDevice() {
+ bitField0_ = (bitField0_ & ~0x00000004);
+ sourceDevice_ = 0;
+ onChanged();
+ return this;
+ }
+
// optional string relay = 3;
private java.lang.Object relay_ = "";
public boolean hasRelay() {
- return ((bitField0_ & 0x00000004) == 0x00000004);
+ return ((bitField0_ & 0x00000008) == 0x00000008);
}
public String getRelay() {
java.lang.Object ref = relay_;
@@ -578,19 +635,19 @@ public final class PushMessageProtos {
if (value == null) {
throw new NullPointerException();
}
- bitField0_ |= 0x00000004;
+ bitField0_ |= 0x00000008;
relay_ = value;
onChanged();
return this;
}
public Builder clearRelay() {
- bitField0_ = (bitField0_ & ~0x00000004);
+ bitField0_ = (bitField0_ & ~0x00000008);
relay_ = getDefaultInstance().getRelay();
onChanged();
return this;
}
void setRelay(com.google.protobuf.ByteString value) {
- bitField0_ |= 0x00000004;
+ bitField0_ |= 0x00000008;
relay_ = value;
onChanged();
}
@@ -598,19 +655,19 @@ public final class PushMessageProtos {
// optional uint64 timestamp = 5;
private long timestamp_ ;
public boolean hasTimestamp() {
- return ((bitField0_ & 0x00000008) == 0x00000008);
+ return ((bitField0_ & 0x00000010) == 0x00000010);
}
public long getTimestamp() {
return timestamp_;
}
public Builder setTimestamp(long value) {
- bitField0_ |= 0x00000008;
+ bitField0_ |= 0x00000010;
timestamp_ = value;
onChanged();
return this;
}
public Builder clearTimestamp() {
- bitField0_ = (bitField0_ & ~0x00000008);
+ bitField0_ = (bitField0_ & ~0x00000010);
timestamp_ = 0L;
onChanged();
return this;
@@ -619,7 +676,7 @@ public final class PushMessageProtos {
// optional bytes message = 6;
private com.google.protobuf.ByteString message_ = com.google.protobuf.ByteString.EMPTY;
public boolean hasMessage() {
- return ((bitField0_ & 0x00000010) == 0x00000010);
+ return ((bitField0_ & 0x00000020) == 0x00000020);
}
public com.google.protobuf.ByteString getMessage() {
return message_;
@@ -628,13 +685,13 @@ public final class PushMessageProtos {
if (value == null) {
throw new NullPointerException();
}
- bitField0_ |= 0x00000010;
+ bitField0_ |= 0x00000020;
message_ = value;
onChanged();
return this;
}
public Builder clearMessage() {
- bitField0_ = (bitField0_ & ~0x00000010);
+ bitField0_ = (bitField0_ & ~0x00000020);
message_ = getDefaultInstance().getMessage();
onChanged();
return this;
@@ -2800,23 +2857,24 @@ public final class PushMessageProtos {
static {
java.lang.String[] descriptorData = {
"\n\037IncomingPushMessageSignal.proto\022\ntexts" +
- "ecure\"l\n\031IncomingPushMessageSignal\022\014\n\004ty" +
- "pe\030\001 \001(\r\022\016\n\006source\030\002 \001(\t\022\r\n\005relay\030\003 \001(\t\022" +
- "\021\n\ttimestamp\030\005 \001(\004\022\017\n\007message\030\006 \001(\014\"\363\003\n\022" +
- "PushMessageContent\022\014\n\004body\030\001 \001(\t\022E\n\013atta" +
- "chments\030\002 \003(\01320.textsecure.PushMessageCo" +
- "ntent.AttachmentPointer\022:\n\005group\030\003 \001(\0132+" +
- ".textsecure.PushMessageContent.GroupCont" +
- "ext\032A\n\021AttachmentPointer\022\n\n\002id\030\001 \001(\006\022\023\n\013" +
- "contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\032\210\002\n\014Group",
- "Context\022\n\n\002id\030\001 \001(\014\022>\n\004type\030\002 \001(\01620.text" +
- "secure.PushMessageContent.GroupContext.T" +
- "ype\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030\004 \003(\t\022@\n\006av" +
- "atar\030\005 \001(\01320.textsecure.PushMessageConte" +
- "nt.AttachmentPointer\"K\n\004Type\022\013\n\007UNKNOWN\020" +
- "\000\022\n\n\006CREATE\020\001\022\n\n\006MODIFY\020\002\022\013\n\007DELIVER\020\003\022\007" +
- "\n\003ADD\020\004\022\010\n\004QUIT\020\005B7\n\"org.whispersystems." +
- "textsecure.pushB\021PushMessageProtos"
+ "ecure\"\202\001\n\031IncomingPushMessageSignal\022\014\n\004t" +
+ "ype\030\001 \001(\r\022\016\n\006source\030\002 \001(\t\022\024\n\014sourceDevic" +
+ "e\030\007 \001(\r\022\r\n\005relay\030\003 \001(\t\022\021\n\ttimestamp\030\005 \001(" +
+ "\004\022\017\n\007message\030\006 \001(\014\"\363\003\n\022PushMessageConten" +
+ "t\022\014\n\004body\030\001 \001(\t\022E\n\013attachments\030\002 \003(\01320.t" +
+ "extsecure.PushMessageContent.AttachmentP" +
+ "ointer\022:\n\005group\030\003 \001(\0132+.textsecure.PushM" +
+ "essageContent.GroupContext\032A\n\021Attachment" +
+ "Pointer\022\n\n\002id\030\001 \001(\006\022\023\n\013contentType\030\002 \001(\t",
+ "\022\013\n\003key\030\003 \001(\014\032\210\002\n\014GroupContext\022\n\n\002id\030\001 \001" +
+ "(\014\022>\n\004type\030\002 \001(\01620.textsecure.PushMessag" +
+ "eContent.GroupContext.Type\022\014\n\004name\030\003 \001(\t" +
+ "\022\017\n\007members\030\004 \003(\t\022@\n\006avatar\030\005 \001(\01320.text" +
+ "secure.PushMessageContent.AttachmentPoin" +
+ "ter\"K\n\004Type\022\013\n\007UNKNOWN\020\000\022\n\n\006CREATE\020\001\022\n\n\006" +
+ "MODIFY\020\002\022\013\n\007DELIVER\020\003\022\007\n\003ADD\020\004\022\010\n\004QUIT\020\005" +
+ "B7\n\"org.whispersystems.textsecure.pushB\021" +
+ "PushMessageProtos"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@@ -2828,7 +2886,7 @@ public final class PushMessageProtos {
internal_static_textsecure_IncomingPushMessageSignal_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_textsecure_IncomingPushMessageSignal_descriptor,
- new java.lang.String[] { "Type", "Source", "Relay", "Timestamp", "Message", },
+ new java.lang.String[] { "Type", "Source", "SourceDevice", "Relay", "Timestamp", "Message", },
org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.class,
org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.Builder.class);
internal_static_textsecure_PushMessageContent_descriptor =
diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java
index e2961917d8..ae750277ae 100644
--- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java
+++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java
@@ -5,6 +5,7 @@ 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;
@@ -42,13 +43,14 @@ public class PushServiceSocket {
private static final String VERIFY_ACCOUNT_PATH = "/v1/accounts/code/%s";
private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/";
private static final String PREKEY_PATH = "/v1/keys/%s";
+ private static final String PREKEY_DEVICE_PATH = "/v1/keys/%s/%s";
private static final String DIRECTORY_TOKENS_PATH = "/v1/directory/tokens";
private static final String DIRECTORY_VERIFY_PATH = "/v1/directory/%s";
- private static final String MESSAGE_PATH = "/v1/messages/";
+ private static final String MESSAGE_PATH = "/v1/messages/%s";
private static final String ATTACHMENT_PATH = "/v1/attachments/%s";
- private static final boolean ENFORCE_SSL = true;
+ private static final boolean ENFORCE_SSL = false;
private final Context context;
private final String serviceUrl;
@@ -86,44 +88,14 @@ public class PushServiceSocket {
makeRequest(REGISTER_GCM_PATH, "DELETE", null);
}
- public void sendMessage(PushDestination recipient, PushBody pushBody)
+ public void sendMessage(OutgoingPushMessageList bundle)
throws IOException
{
- OutgoingPushMessage message = new OutgoingPushMessage(recipient.getRelay(),
- recipient.getNumber(),
- pushBody.getBody(),
- pushBody.getType());
-
- sendMessage(new OutgoingPushMessageList(message));
- }
-
- public void sendMessage(List recipients, List bodies)
- throws IOException
- {
- List messages = new LinkedList();
-
- Iterator recipientsIterator = recipients.iterator();
- Iterator bodiesIterator = bodies.iterator();
-
- while (recipientsIterator.hasNext()) {
- PushDestination recipient = recipientsIterator.next();
- PushBody body = bodiesIterator.next();
-
- messages.add(new OutgoingPushMessage(recipient.getRelay(), recipient.getNumber(),
- body.getBody(), body.getType()));
+ try {
+ makeRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", new Gson().toJson(bundle));
+ } catch (NotFoundException nfe) {
+ throw new UnregisteredUserException(nfe);
}
-
- sendMessage(new OutgoingPushMessageList(messages));
- }
-
- private void sendMessage(OutgoingPushMessageList messages)
- throws IOException
- {
- String responseText = makeRequest(MESSAGE_PATH, "POST", new Gson().toJson(messages));
- PushMessageResponse response = new Gson().fromJson(responseText, PushMessageResponse.class);
-
- if (response.getFailure().size() != 0)
- throw new UnregisteredUserException(response.getFailure());
}
public void registerPreKeys(IdentityKey identityKey,
@@ -150,20 +122,46 @@ public class PushServiceSocket {
PreKeyList.toJson(new PreKeyList(lastResortEntity, entities)));
}
- public PreKeyEntity getPreKey(PushDestination destination) throws IOException {
+ public List getPreKeys(PushAddress destination) throws IOException {
try {
- String path = String.format(PREKEY_PATH, destination.getNumber());
+ String deviceId = String.valueOf(destination.getDeviceId());
+
+ if (deviceId.equals("1"))
+ deviceId = "*";
+
+ String path = String.format(PREKEY_DEVICE_PATH, destination.getNumber(), deviceId);
if (!Util.isEmpty(destination.getRelay())) {
path = path + "?relay=" + destination.getRelay();
}
String responseText = makeRequest(path, "GET", null);
- Log.w("PushServiceSocket", "Got prekey: " + responseText);
- return PreKeyEntity.fromJson(responseText);
+ PreKeyList response = PreKeyList.fromJson(responseText);
+
+ return response.getKeys();
} catch (JsonParseException e) {
- Log.w("PushServiceSocket", e);
- throw new IOException("Bad prekey");
+ throw new IOException(e);
+ }
+ }
+
+ public PreKeyEntity getPreKey(PushAddress destination) throws IOException {
+ try {
+ String path = String.format(PREKEY_DEVICE_PATH, destination.getNumber(),
+ String.valueOf(destination.getDeviceId()));
+
+ if (!Util.isEmpty(destination.getRelay())) {
+ path = path + "?relay=" + destination.getRelay();
+ }
+
+ String responseText = makeRequest(path, "GET", null);
+ PreKeyList response = PreKeyList.fromJson(responseText);
+
+ if (response.getKeys() == null || response.getKeys().size() < 1)
+ throw new IOException("Empty prekey list");
+
+ return response.getKeys().get(0);
+ } catch (JsonParseException e) {
+ throw new IOException(e);
}
}
@@ -307,17 +305,25 @@ public class PushServiceSocket {
}
if (connection.getResponseCode() == 413) {
+ connection.disconnect();
throw new RateLimitException("Rate limit exceeded: " + connection.getResponseCode());
}
if (connection.getResponseCode() == 401 || connection.getResponseCode() == 403) {
+ connection.disconnect();
throw new AuthorizationFailedException("Authorization failed!");
}
if (connection.getResponseCode() == 404) {
+ connection.disconnect();
throw new NotFoundException("Not found");
}
+ if (connection.getResponseCode() == 409) {
+ String response = Util.readFully(connection.getErrorStream());
+ throw new MismatchedDevicesException(new Gson().fromJson(response, MismatchedDevices.class));
+ }
+
if (connection.getResponseCode() != 200 && connection.getResponseCode() != 204) {
throw new IOException("Bad response: " + connection.getResponseCode() + " " + connection.getResponseMessage());
}
diff --git a/library/src/org/whispersystems/textsecure/push/UnregisteredUserException.java b/library/src/org/whispersystems/textsecure/push/UnregisteredUserException.java
index 3ad0327251..ae87b39006 100644
--- a/library/src/org/whispersystems/textsecure/push/UnregisteredUserException.java
+++ b/library/src/org/whispersystems/textsecure/push/UnregisteredUserException.java
@@ -5,15 +5,8 @@ import java.util.List;
public class UnregisteredUserException extends IOException {
- private final List addresses;
-
- public UnregisteredUserException(List addresses) {
- super();
- this.addresses = addresses;
- }
-
- public List getAddresses() {
- return addresses;
+ public UnregisteredUserException(Exception exception) {
+ super(exception);
}
}
diff --git a/library/src/org/whispersystems/textsecure/storage/CanonicalRecipient.java b/library/src/org/whispersystems/textsecure/storage/CanonicalRecipient.java
new file mode 100644
index 0000000000..389cf3f4bb
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/storage/CanonicalRecipient.java
@@ -0,0 +1,6 @@
+package org.whispersystems.textsecure.storage;
+
+public interface CanonicalRecipient {
+// public String getNumber();
+ public long getRecipientId();
+}
diff --git a/library/src/org/whispersystems/textsecure/storage/CanonicalRecipientAddress.java b/library/src/org/whispersystems/textsecure/storage/CanonicalRecipientAddress.java
deleted file mode 100644
index 7abb19ba4c..0000000000
--- a/library/src/org/whispersystems/textsecure/storage/CanonicalRecipientAddress.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.whispersystems.textsecure.storage;
-
-import android.content.Context;
-
-public interface CanonicalRecipientAddress {
- public long getCanonicalAddress(Context context);
-}
diff --git a/library/src/org/whispersystems/textsecure/storage/LocalKeyRecord.java b/library/src/org/whispersystems/textsecure/storage/LocalKeyRecord.java
index 042f26df2d..97810179a7 100644
--- a/library/src/org/whispersystems/textsecure/storage/LocalKeyRecord.java
+++ b/library/src/org/whispersystems/textsecure/storage/LocalKeyRecord.java
@@ -43,24 +43,24 @@ public class LocalKeyRecord extends Record {
private final MasterCipher masterCipher;
private final MasterSecret masterSecret;
- public LocalKeyRecord(Context context, MasterSecret masterSecret, CanonicalRecipientAddress recipient) {
- super(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
+ public LocalKeyRecord(Context context, MasterSecret masterSecret, CanonicalRecipient recipient) {
+ super(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient));
this.masterSecret = masterSecret;
this.masterCipher = new MasterCipher(masterSecret);
loadData();
}
- public static boolean hasRecord(Context context, CanonicalRecipientAddress recipient) {
- Log.w("LocalKeyRecord", "Checking: " + getFileNameForRecipient(context, recipient));
- return Record.hasRecord(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
+ public static boolean hasRecord(Context context, CanonicalRecipient recipient) {
+ Log.w("LocalKeyRecord", "Checking: " + getFileNameForRecipient(recipient));
+ return Record.hasRecord(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient));
}
- public static void delete(Context context, CanonicalRecipientAddress recipient) {
- Record.delete(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
+ public static void delete(Context context, CanonicalRecipient recipient) {
+ Record.delete(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient));
}
- private static String getFileNameForRecipient(Context context, CanonicalRecipientAddress recipient) {
- return recipient.getCanonicalAddress(context) + "-local";
+ private static String getFileNameForRecipient(CanonicalRecipient recipient) {
+ return recipient.getRecipientId() + "-local";
}
public void advanceKeyIfNecessary(int keyId) {
diff --git a/library/src/org/whispersystems/textsecure/storage/RecipientDevice.java b/library/src/org/whispersystems/textsecure/storage/RecipientDevice.java
new file mode 100644
index 0000000000..d97d9d460f
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/storage/RecipientDevice.java
@@ -0,0 +1,31 @@
+package org.whispersystems.textsecure.storage;
+
+public class RecipientDevice {
+
+ public static final int DEFAULT_DEVICE_ID = 1;
+
+ private final long recipientId;
+ private final int deviceId;
+
+ public RecipientDevice(long recipientId, int deviceId) {
+ this.recipientId = recipientId;
+ this.deviceId = deviceId;
+ }
+
+ public long getRecipientId() {
+ return recipientId;
+ }
+
+ public int getDeviceId() {
+ return deviceId;
+ }
+
+ public CanonicalRecipient getRecipient() {
+ return new CanonicalRecipient() {
+ @Override
+ public long getRecipientId() {
+ return recipientId;
+ }
+ };
+ }
+}
diff --git a/library/src/org/whispersystems/textsecure/storage/Record.java b/library/src/org/whispersystems/textsecure/storage/Record.java
index 4b69da80c8..35f941b712 100644
--- a/library/src/org/whispersystems/textsecure/storage/Record.java
+++ b/library/src/org/whispersystems/textsecure/storage/Record.java
@@ -69,13 +69,19 @@ public abstract class Record {
}
private static File getAddressFile(Context context, String directory, String address) {
+ File parent = getParentDirectory(context, directory);
+
+ return new File(parent, address);
+ }
+
+ protected static File getParentDirectory(Context context, String directory) {
File parent = new File(context.getFilesDir(), directory);
if (!parent.exists()) {
parent.mkdirs();
}
- return new File(parent, address);
+ return parent;
}
protected byte[] readBlob(FileInputStream in) throws IOException {
diff --git a/library/src/org/whispersystems/textsecure/storage/RemoteKeyRecord.java b/library/src/org/whispersystems/textsecure/storage/RemoteKeyRecord.java
index e3c5606437..7b4059d1e2 100644
--- a/library/src/org/whispersystems/textsecure/storage/RemoteKeyRecord.java
+++ b/library/src/org/whispersystems/textsecure/storage/RemoteKeyRecord.java
@@ -43,22 +43,22 @@ public class RemoteKeyRecord extends Record {
private PublicKey remoteKeyCurrent;
private PublicKey remoteKeyLast;
- public RemoteKeyRecord(Context context, CanonicalRecipientAddress recipient) {
- super(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
+ public RemoteKeyRecord(Context context, CanonicalRecipient recipient) {
+ super(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient));
loadData();
}
- public static void delete(Context context, CanonicalRecipientAddress recipient) {
- delete(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
+ public static void delete(Context context, CanonicalRecipient recipient) {
+ delete(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient));
}
- public static boolean hasRecord(Context context, CanonicalRecipientAddress recipient) {
- Log.w("LocalKeyRecord", "Checking: " + getFileNameForRecipient(context, recipient));
- return hasRecord(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
+ public static boolean hasRecord(Context context, CanonicalRecipient recipient) {
+ Log.w("LocalKeyRecord", "Checking: " + getFileNameForRecipient(recipient));
+ return hasRecord(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient));
}
- private static String getFileNameForRecipient(Context context, CanonicalRecipientAddress recipient) {
- return recipient.getCanonicalAddress(context) + "-remote";
+ private static String getFileNameForRecipient(CanonicalRecipient recipient) {
+ return recipient.getRecipientId() + "-remote";
}
public void updateCurrentRemoteKey(PublicKey remoteKey) {
diff --git a/library/src/org/whispersystems/textsecure/storage/Session.java b/library/src/org/whispersystems/textsecure/storage/Session.java
index b57a68f6db..24396acdff 100644
--- a/library/src/org/whispersystems/textsecure/storage/Session.java
+++ b/library/src/org/whispersystems/textsecure/storage/Session.java
@@ -14,21 +14,21 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
public class Session {
- public static void clearV1SessionFor(Context context, CanonicalRecipientAddress recipient) {
+ public static void clearV1SessionFor(Context context, CanonicalRecipient recipient) {
//XXX Obviously we should probably do something more thorough here eventually.
LocalKeyRecord.delete(context, recipient);
RemoteKeyRecord.delete(context, recipient);
SessionRecordV1.delete(context, recipient);
}
- public static void abortSessionFor(Context context, CanonicalRecipientAddress recipient) {
+ public static void abortSessionFor(Context context, CanonicalRecipient recipient) {
Log.w("Session", "Aborting session, deleting keys...");
clearV1SessionFor(context, recipient);
- SessionRecordV2.delete(context, recipient);
+ SessionRecordV2.deleteAll(context, recipient);
}
public static boolean hasSession(Context context, MasterSecret masterSecret,
- CanonicalRecipientAddress recipient)
+ CanonicalRecipient recipient)
{
Log.w("Session", "Checking session...");
return hasV1Session(context, recipient) || hasV2Session(context, masterSecret, recipient);
@@ -36,42 +36,40 @@ public class Session {
public static boolean hasRemoteIdentityKey(Context context,
MasterSecret masterSecret,
- CanonicalRecipientAddress recipient)
+ CanonicalRecipient recipient)
{
- return (hasV2Session(context, masterSecret, recipient) ||
- (hasV1Session(context, recipient) &&
- new SessionRecordV1(context, masterSecret, recipient).getIdentityKey() != null));
+ return (hasV2Session(context, masterSecret, recipient) || (hasV1Session(context, recipient) &&
+ new SessionRecordV1(context, masterSecret, recipient).getIdentityKey() != null));
}
private static boolean hasV2Session(Context context, MasterSecret masterSecret,
- CanonicalRecipientAddress recipient)
+ CanonicalRecipient recipient)
{
- return SessionRecordV2.hasSession(context, masterSecret, recipient);
+ return SessionRecordV2.hasSession(context, masterSecret, recipient.getRecipientId(),
+ RecipientDevice.DEFAULT_DEVICE_ID);
}
- private static boolean hasV1Session(Context context, CanonicalRecipientAddress recipient) {
+ private static boolean hasV1Session(Context context, CanonicalRecipient recipient) {
return SessionRecordV1.hasSession(context, recipient) &&
RemoteKeyRecord.hasRecord(context, recipient) &&
LocalKeyRecord.hasRecord(context, recipient);
}
public static IdentityKey getRemoteIdentityKey(Context context, MasterSecret masterSecret,
- CanonicalRecipientAddress recipient)
+ CanonicalRecipient recipient)
{
- if (SessionRecordV2.hasSession(context, masterSecret, recipient)) {
- return new SessionRecordV2(context, masterSecret, recipient).getRemoteIdentityKey();
- } else if (SessionRecordV1.hasSession(context, recipient)) {
- return new SessionRecordV1(context, masterSecret, recipient).getIdentityKey();
- } else {
- return null;
- }
+ return getRemoteIdentityKey(context, masterSecret, recipient.getRecipientId());
}
- public static IdentityKey getRemoteIdentityKey(Context context, MasterSecret masterSecret,
+ public static IdentityKey getRemoteIdentityKey(Context context,
+ MasterSecret masterSecret,
long recipientId)
{
- if (SessionRecordV2.hasSession(context, masterSecret, recipientId)) {
- return new SessionRecordV2(context, masterSecret, recipientId).getRemoteIdentityKey();
+ if (SessionRecordV2.hasSession(context, masterSecret, recipientId,
+ RecipientDevice.DEFAULT_DEVICE_ID))
+ {
+ return new SessionRecordV2(context, masterSecret, recipientId,
+ RecipientDevice.DEFAULT_DEVICE_ID).getRemoteIdentityKey();
} else if (SessionRecordV1.hasSession(context, recipientId)) {
return new SessionRecordV1(context, masterSecret, recipientId).getIdentityKey();
} else {
@@ -80,10 +78,14 @@ public class Session {
}
public static int getSessionVersion(Context context, MasterSecret masterSecret,
- CanonicalRecipientAddress recipient)
+ CanonicalRecipient recipient)
{
- if (SessionRecordV2.hasSession(context, masterSecret, recipient)) {
- return new SessionRecordV2(context, masterSecret, recipient).getSessionVersion();
+ if (SessionRecordV2.hasSession(context, masterSecret,
+ recipient.getRecipientId(),
+ RecipientDevice.DEFAULT_DEVICE_ID))
+ {
+ return new SessionRecordV2(context, masterSecret, recipient.getRecipientId(),
+ RecipientDevice.DEFAULT_DEVICE_ID).getSessionVersion();
} else if (SessionRecordV1.hasSession(context, recipient)) {
return new SessionRecordV1(context, masterSecret, recipient).getSessionVersion();
}
diff --git a/library/src/org/whispersystems/textsecure/storage/SessionRecordV1.java b/library/src/org/whispersystems/textsecure/storage/SessionRecordV1.java
index 49b1dce871..28ac91a5a9 100644
--- a/library/src/org/whispersystems/textsecure/storage/SessionRecordV1.java
+++ b/library/src/org/whispersystems/textsecure/storage/SessionRecordV1.java
@@ -37,8 +37,8 @@ public class SessionRecordV1 extends Record {
private final MasterSecret masterSecret;
- public SessionRecordV1(Context context, MasterSecret masterSecret, CanonicalRecipientAddress recipient) {
- this(context, masterSecret, getRecipientId(context, recipient));
+ public SessionRecordV1(Context context, MasterSecret masterSecret, CanonicalRecipient recipient) {
+ this(context, masterSecret, recipient.getRecipientId());
}
public SessionRecordV1(Context context, MasterSecret masterSecret, long recipientId) {
@@ -48,12 +48,12 @@ public class SessionRecordV1 extends Record {
loadData();
}
- public static void delete(Context context, CanonicalRecipientAddress recipient) {
- delete(context, SESSIONS_DIRECTORY, getRecipientId(context, recipient) + "");
+ public static void delete(Context context, CanonicalRecipient recipient) {
+ delete(context, SESSIONS_DIRECTORY, recipient.getRecipientId() + "");
}
- public static boolean hasSession(Context context, CanonicalRecipientAddress recipient) {
- return hasSession(context, getRecipientId(context, recipient));
+ public static boolean hasSession(Context context, CanonicalRecipient recipient) {
+ return hasSession(context, recipient.getRecipientId());
}
public static boolean hasSession(Context context, long recipientId) {
@@ -61,10 +61,6 @@ public class SessionRecordV1 extends Record {
return hasRecord(context, SESSIONS_DIRECTORY, recipientId+"");
}
- private static long getRecipientId(Context context, CanonicalRecipientAddress recipient) {
- return recipient.getCanonicalAddress(context);
- }
-
public void setSessionKey(SessionKey sessionKeyRecord) {
this.sessionKeyRecord = sessionKeyRecord;
}
diff --git a/library/src/org/whispersystems/textsecure/storage/SessionRecordV2.java b/library/src/org/whispersystems/textsecure/storage/SessionRecordV2.java
index 18809b22c1..18fc90da59 100644
--- a/library/src/org/whispersystems/textsecure/storage/SessionRecordV2.java
+++ b/library/src/org/whispersystems/textsecure/storage/SessionRecordV2.java
@@ -39,12 +39,14 @@ import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chai
import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange;
import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey;
+import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import javax.crypto.spec.SecretKeySpec;
@@ -64,33 +66,70 @@ public class SessionRecordV2 extends Record {
private StorageProtos.SessionStructure sessionStructure =
StorageProtos.SessionStructure.newBuilder().build();
- public SessionRecordV2(Context context, MasterSecret masterSecret, CanonicalRecipientAddress recipient) {
- this(context, masterSecret, getRecipientId(context, recipient));
+ public SessionRecordV2(Context context, MasterSecret masterSecret, RecipientDevice peer) {
+ this(context, masterSecret, peer.getRecipientId(), peer.getDeviceId());
}
- public SessionRecordV2(Context context, MasterSecret masterSecret, long recipientId) {
- super(context, SESSIONS_DIRECTORY_V2, recipientId+"");
+ public SessionRecordV2(Context context, MasterSecret masterSecret, long recipientId, int deviceId) {
+ super(context, SESSIONS_DIRECTORY_V2, getRecordName(recipientId, deviceId));
this.masterSecret = masterSecret;
loadData();
}
- public static void delete(Context context, CanonicalRecipientAddress recipient) {
- delete(context, SESSIONS_DIRECTORY_V2, getRecipientId(context, recipient) + "");
+ private static String getRecordName(long recipientId, int deviceId) {
+ return recipientId + (deviceId == RecipientDevice.DEFAULT_DEVICE_ID ? "" : "." + deviceId);
+ }
+
+ public static List getSessionSubDevices(Context context, CanonicalRecipient recipient) {
+ List results = new LinkedList();
+ File parent = getParentDirectory(context, SESSIONS_DIRECTORY_V2);
+ String[] children = parent.list();
+
+ if (children == null) return results;
+
+ for (String child : children) {
+ try {
+ String[] parts = child.split("[.]", 2);
+ long sessionRecipientId = Long.parseLong(parts[0]);
+
+ if (sessionRecipientId == recipient.getRecipientId() && parts.length > 1) {
+ results.add(Integer.parseInt(parts[1]));
+ }
+ } catch (NumberFormatException e) {
+ Log.w("SessionRecordV2", e);
+ }
+ }
+
+ return results;
+ }
+
+ public static void deleteAll(Context context, CanonicalRecipient recipient) {
+ List devices = getSessionSubDevices(context, recipient);
+
+ delete(context, SESSIONS_DIRECTORY_V2, getRecordName(recipient.getRecipientId(),
+ RecipientDevice.DEFAULT_DEVICE_ID));
+
+ for (int device : devices) {
+ delete(context, SESSIONS_DIRECTORY_V2, getRecordName(recipient.getRecipientId(), device));
+ }
+ }
+
+ public static void delete(Context context, RecipientDevice recipientDevice) {
+ delete(context, SESSIONS_DIRECTORY_V2, getRecordName(recipientDevice.getRecipientId(),
+ recipientDevice.getDeviceId()));
}
public static boolean hasSession(Context context, MasterSecret masterSecret,
- CanonicalRecipientAddress recipient)
+ RecipientDevice recipient)
{
- return hasSession(context, masterSecret, getRecipientId(context, recipient));
+ return hasSession(context, masterSecret, recipient.getRecipientId(), recipient.getDeviceId());
}
- public static boolean hasSession(Context context, MasterSecret masterSecret, long recipientId) {
- return hasRecord(context, SESSIONS_DIRECTORY_V2, recipientId+"") &&
- new SessionRecordV2(context, masterSecret, recipientId).hasSenderChain();
- }
-
- private static long getRecipientId(Context context, CanonicalRecipientAddress recipient) {
- return recipient.getCanonicalAddress(context);
+ public static boolean hasSession(Context context, MasterSecret masterSecret,
+ long recipientId, int deviceId)
+ {
+ return hasRecord(context, SESSIONS_DIRECTORY_V2, getRecordName(recipientId, deviceId)) &&
+ new SessionRecordV2(context, masterSecret, recipientId, deviceId).hasSenderChain();
}
public void clear() {
diff --git a/library/src/org/whispersystems/textsecure/util/Hex.java b/library/src/org/whispersystems/textsecure/util/Hex.java
index 5db73998e5..a22926c796 100644
--- a/library/src/org/whispersystems/textsecure/util/Hex.java
+++ b/library/src/org/whispersystems/textsecure/util/Hex.java
@@ -16,6 +16,8 @@
*/
package org.whispersystems.textsecure.util;
+import java.io.IOException;
+
/**
* Utility for generating hex dumps.
*/
@@ -43,6 +45,36 @@ public class Hex {
return buf.toString();
}
+ public static String toStringCondensed(byte[] bytes) {
+ StringBuffer buf = new StringBuffer();
+ for (int i=0;i> 1];
+
+ // two characters form the hex value.
+ for (int i = 0, j = 0; j < len; i++) {
+ int f = Character.digit(data[j], 16) << 4;
+ j++;
+ f = f | Character.digit(data[j], 16);
+ j++;
+ out[i] = (byte) (f & 0xFF);
+ }
+
+ return out;
+ }
+
public static String dump(byte[] bytes) {
return dump(bytes, 0, bytes.length);
}
diff --git a/src/org/thoughtcrime/securesms/ContactSelectionActivity.java b/src/org/thoughtcrime/securesms/ContactSelectionActivity.java
index 834b06c1f3..6ba192173d 100644
--- a/src/org/thoughtcrime/securesms/ContactSelectionActivity.java
+++ b/src/org/thoughtcrime/securesms/ContactSelectionActivity.java
@@ -19,15 +19,10 @@ package org.thoughtcrime.securesms;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;
-import org.thoughtcrime.securesms.recipients.Recipients;
-import org.thoughtcrime.securesms.util.ActionBarUtil;
-import org.thoughtcrime.securesms.util.DynamicTheme;
-
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.ActionBar.Tab;
import com.actionbarsherlock.app.ActionBar.TabListener;
@@ -35,6 +30,14 @@ import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
+import org.thoughtcrime.securesms.util.ActionBarUtil;
+import org.thoughtcrime.securesms.util.DynamicTheme;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
+
/**
* Activity container for selecting a list of contacts. Provides a tab frame for
* contact, group, and "recent contact" activity tabs. Used by ComposeMessageActivity
@@ -52,8 +55,6 @@ public class ContactSelectionActivity extends PassphraseRequiredSherlockFragment
private ContactSelectionGroupsFragment groupsFragment;
private ContactSelectionRecentFragment recentFragment;
- private Recipients recipients;
-
@Override
protected void onCreate(Bundle icicle) {
dynamicTheme.onCreate(this);
@@ -97,12 +98,12 @@ public class ContactSelectionActivity extends PassphraseRequiredSherlockFragment
}
private void handleSelectionFinished() {
- recipients = contactsFragment.getSelectedContacts();
- recipients.append(recentFragment.getSelectedContacts());
- recipients.append(groupsFragment.getSelectedContacts(this));
+ List contacts = contactsFragment.getSelectedContacts();
+ contacts.addAll(recentFragment.getSelectedContacts());
+ contacts.addAll(groupsFragment.getSelectedContacts(this));
Intent resultIntent = getIntent();
- resultIntent.putExtra("recipients", this.recipients);
+ resultIntent.putParcelableArrayListExtra("contacts", new ArrayList(contacts));
setResult(RESULT_OK, resultIntent);
diff --git a/src/org/thoughtcrime/securesms/ContactSelectionGroupsFragment.java b/src/org/thoughtcrime/securesms/ContactSelectionGroupsFragment.java
index 48b90be1df..9b314299a6 100644
--- a/src/org/thoughtcrime/securesms/ContactSelectionGroupsFragment.java
+++ b/src/org/thoughtcrime/securesms/ContactSelectionGroupsFragment.java
@@ -84,23 +84,17 @@ public class ContactSelectionGroupsFragment extends SherlockListFragment
this.getListView().setFocusable(true);
}
- public Recipients getSelectedContacts(Context context) {
- List recipientList = new LinkedList();
+ public List getSelectedContacts(Context context) {
+ List contacts = new LinkedList();
for (GroupData groupData : selectedGroups.values()) {
List contactDataList = ContactAccessor.getInstance()
- .getGroupMembership(context, groupData.id);
+ .getGroupMembership(context, groupData.id);
- Log.w("GroupSelectionListActivity", "Got contacts in group: " + contactDataList.size());
-
- for (ContactData contactData : contactDataList) {
- for (NumberData numberData : contactData.numbers) {
- recipientList.add(new Recipient(contactData.name, numberData.number, null, null));
- }
- }
+ contacts.addAll(contactDataList);
}
- return new Recipients(recipientList);
+ return contacts;
}
private void addGroup(GroupData groupData) {
diff --git a/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java
index a999df00cb..3054700e99 100644
--- a/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java
+++ b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java
@@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
+import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -96,16 +97,11 @@ public class ContactSelectionListFragment extends SherlockListFragment
}
- public Recipients getSelectedContacts() {
- List recipientList = new LinkedList();
+ public List getSelectedContacts() {
+ List contacts = new LinkedList();
+ contacts.addAll(selectedContacts.values());
- for (ContactData contactData : selectedContacts.values()) {
- for (NumberData numberData : contactData.numbers) {
- recipientList.add(new Recipient(contactData.name, numberData.number, null, null));
- }
- }
-
- return new Recipients(recipientList);
+ return contacts;
}
diff --git a/src/org/thoughtcrime/securesms/ContactSelectionRecentFragment.java b/src/org/thoughtcrime/securesms/ContactSelectionRecentFragment.java
index bd4efc0eae..b5a4a8d15a 100644
--- a/src/org/thoughtcrime/securesms/ContactSelectionRecentFragment.java
+++ b/src/org/thoughtcrime/securesms/ContactSelectionRecentFragment.java
@@ -84,16 +84,11 @@ public class ContactSelectionRecentFragment extends SherlockListFragment
this.getLoaderManager().initLoader(0, null, this);
}
- public Recipients getSelectedContacts() {
- List recipientList = new LinkedList();
+ public List getSelectedContacts() {
+ List contacts = new LinkedList();
+ contacts.addAll(selectedContacts.values());
- for (ContactData contactData : selectedContacts.values()) {
- for (NumberData numberData : contactData.numbers) {
- recipientList.add(new Recipient(contactData.name, numberData.number, null, null));
- }
- }
-
- return new Recipients(recipientList);
+ return contacts;
}
private void addSingleNumberContact(ContactData contactData) {
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index a1dd2adb27..b6c6c52ff4 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -212,10 +212,10 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
switch (reqCode) {
case PICK_CONTACT:
- Recipients recipients = data.getParcelableExtra("recipients");
+ List contacts = data.getParcelableArrayListExtra("contacts");
- if (recipients != null)
- recipientsPanel.addRecipients(recipients);
+ if (contacts != null)
+ recipientsPanel.addContacts(contacts);
break;
case PICK_IMAGE:
@@ -912,7 +912,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
allocatedThreadId = MessageSender.sendMms(ConversationActivity.this, masterSecret, recipients,
threadId, attachmentManager.getSlideDeck(), body,
distributionType, isEncryptedConversation && !forcePlaintext);
- } else if (recipients.isEmailRecipient() || !recipients.isSingleRecipient()) {
+ } else if (recipients.isEmailRecipient() || !recipients.isSingleRecipient() || recipients.isGroupRecipient()) {
allocatedThreadId = MessageSender.sendMms(ConversationActivity.this, masterSecret, recipients,
threadId, new SlideDeck(), body, distributionType,
isEncryptedConversation && !forcePlaintext);
diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java
index a4e3f95ad8..e065b78aa9 100644
--- a/src/org/thoughtcrime/securesms/ConversationItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationItem.java
@@ -347,6 +347,7 @@ public class ConversationItem extends LinearLayout {
private void handleKeyExchangeClicked() {
Intent intent = new Intent(context, ReceiveKeyActivity.class);
intent.putExtra("recipient", messageRecord.getIndividualRecipient());
+ intent.putExtra("recipient_device_id", messageRecord.getRecipientDeviceId());
intent.putExtra("body", messageRecord.getBody().getBody());
intent.putExtra("thread_id", messageRecord.getThreadId());
intent.putExtra("message_id", messageRecord.getId());
diff --git a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java
index d795695e08..0d236cf89e 100644
--- a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java
+++ b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java
@@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.thoughtcrime.securesms.util.MemoryCleaner;
+import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
import org.whispersystems.textsecure.crypto.InvalidVersionException;
@@ -45,6 +46,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
+import org.whispersystems.textsecure.storage.RecipientDevice;
import java.io.IOException;
@@ -62,6 +64,7 @@ public class ReceiveKeyActivity extends Activity {
private Button cancelButton;
private Recipient recipient;
+ private int recipientDeviceId;
private long threadId;
private long messageId;
@@ -126,12 +129,14 @@ public class ReceiveKeyActivity extends Activity {
}
private boolean isTrusted(KeyExchangeMessage message, PreKeyWhisperMessage messageBundle) {
+ RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId);
+
if (message != null) {
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(this, masterSecret,
- recipient, message);
+ recipientDevice, message);
return processor.isTrusted(message);
} else if (messageBundle != null) {
- KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(this, masterSecret, recipient);
+ KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(this, masterSecret, recipientDevice);
return processor.isTrusted(messageBundle);
}
@@ -162,6 +167,7 @@ public class ReceiveKeyActivity extends Activity {
this.confirmButton = (Button) findViewById(R.id.ok_button);
this.cancelButton = (Button) findViewById(R.id.cancel_button);
this.recipient = getIntent().getParcelableExtra("recipient");
+ this.recipientDeviceId = getIntent().getIntExtra("recipient_device_id", -1);
this.threadId = getIntent().getLongExtra("thread_id", -1);
this.messageId = getIntent().getLongExtra("message_id", -1);
this.masterSecret = getIntent().getParcelableExtra("master_secret");
@@ -190,7 +196,8 @@ public class ReceiveKeyActivity extends Activity {
protected Void doInBackground(Void... params) {
if (keyExchangeMessage != null) {
try {
- KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(ReceiveKeyActivity.this, masterSecret, recipient, keyExchangeMessage);
+ RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId);
+ KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(ReceiveKeyActivity.this, masterSecret, recipientDevice, keyExchangeMessage);
processor.processKeyExchangeMessage(keyExchangeMessage, threadId);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsProcessedKeyExchange(messageId);
@@ -201,8 +208,9 @@ public class ReceiveKeyActivity extends Activity {
}
} else if (keyExchangeMessageBundle != null) {
try {
+ RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId);
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(ReceiveKeyActivity.this,
- masterSecret, recipient);
+ masterSecret, recipientDevice);
processor.processKeyExchangeMessage(keyExchangeMessageBundle);
CiphertextMessage bundledMessage = keyExchangeMessageBundle.getWhisperMessage();
@@ -213,8 +221,8 @@ public class ReceiveKeyActivity extends Activity {
.updateBundleMessageBody(masterSecret, messageId, messageBody);
DecryptingQueue.scheduleDecryption(ReceiveKeyActivity.this, masterSecret, messageId,
- threadId, recipient.getNumber(), messageBody,
- true, false);
+ threadId, recipient.getNumber(), recipientDeviceId,
+ messageBody, true, false);
} catch (InvalidKeyIdException e) {
Log.w("ReceiveKeyActivity", e);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
diff --git a/src/org/thoughtcrime/securesms/components/RecipientsPanel.java b/src/org/thoughtcrime/securesms/components/RecipientsPanel.java
index 8e7770be6b..14aefa4b96 100644
--- a/src/org/thoughtcrime/securesms/components/RecipientsPanel.java
+++ b/src/org/thoughtcrime/securesms/components/RecipientsPanel.java
@@ -24,6 +24,7 @@ import android.view.View;
import android.widget.RelativeLayout;
import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.RecipientsAdapter;
import org.thoughtcrime.securesms.contacts.RecipientsEditor;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -69,6 +70,14 @@ public class RecipientsPanel extends RelativeLayout {
else recipientsText.append(number + ", ");
}
+ public void addContacts(List contacts) {
+ for (ContactAccessor.ContactData contact : contacts) {
+ for (ContactAccessor.NumberData number : contact.numbers) {
+ addRecipient(contact.name, number.number);
+ }
+ }
+ }
+
public void addRecipients(Recipients recipients) {
List recipientList = recipients.getRecipientsList();
Iterator iterator = recipientList.iterator();
diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java
index 0595a6454b..b76e8ca31d 100644
--- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java
+++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java
@@ -46,7 +46,9 @@ import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.push.IncomingPushMessage;
+import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.Session;
+import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Util;
@@ -78,11 +80,12 @@ public class DecryptingQueue {
}
public static void scheduleDecryption(Context context, MasterSecret masterSecret,
- long messageId, long threadId, String originator,
+ long messageId, long threadId, String originator, int deviceId,
String body, boolean isSecureMessage, boolean isKeyExchange)
{
DecryptionWorkItem runnable = new DecryptionWorkItem(context, masterSecret, messageId, threadId,
- originator, body, isSecureMessage, isKeyExchange);
+ originator, deviceId, body,
+ isSecureMessage, isKeyExchange);
executor.execute(runnable);
}
@@ -161,11 +164,13 @@ public class DecryptingQueue {
long threadId = record.getThreadId();
String body = record.getBody().getBody();
String originator = record.getIndividualRecipient().getNumber();
+ int originatorDeviceId = record.getRecipientDeviceId();
boolean isSecureMessage = record.isSecure();
boolean isKeyExchange = record.isKeyExchange();
scheduleDecryption(context, masterSecret, messageId, threadId,
- originator, body, isSecureMessage, isKeyExchange);
+ originator, originatorDeviceId, body,
+ isSecureMessage, isKeyExchange);
}
private static class PushDecryptionWorkItem implements Runnable {
@@ -186,15 +191,16 @@ public class DecryptingQueue {
public void run() {
try {
- Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSource(), false);
- Recipient recipient = recipients.getPrimaryRecipient();
+ Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSource(), false);
+ Recipient recipient = recipients.getPrimaryRecipient();
+ RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSourceDevice());
- if (!Session.hasSession(context, masterSecret, recipient)) {
+ if (!SessionRecordV2.hasSession(context, masterSecret, recipientDevice)) {
sendResult(PushReceiver.RESULT_NO_SESSION);
return;
}
- SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient);
+ SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice);
byte[] plaintextBody = sessionCipher.decrypt(message.getBody());
message = message.withBody(plaintextBody);
@@ -251,10 +257,11 @@ public class DecryptingQueue {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
try {
- String messageFrom = pdu.getFrom().getString();
- Recipients recipients = RecipientFactory.getRecipientsFromString(context, messageFrom, false);
- Recipient recipient = recipients.getPrimaryRecipient();
- byte[] ciphertextPduBytes = getEncryptedData();
+ String messageFrom = pdu.getFrom().getString();
+ Recipients recipients = RecipientFactory.getRecipientsFromString(context, messageFrom, false);
+ Recipient recipient = recipients.getPrimaryRecipient();
+ RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
+ byte[] ciphertextPduBytes = getEncryptedData();
if (ciphertextPduBytes == null) {
Log.w("DecryptingQueue", "No encoded PNG data found on parts.");
@@ -272,7 +279,7 @@ public class DecryptingQueue {
Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes));
TextTransport transportDetails = new TextTransport();
- SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient);
+ SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice);
byte[] decodedCiphertext = transportDetails.getDecodedMessage(ciphertextPduBytes);
try {
@@ -322,11 +329,13 @@ public class DecryptingQueue {
private final MasterSecret masterSecret;
private final String body;
private final String originator;
+ private final int deviceId;
private final boolean isSecureMessage;
private final boolean isKeyExchange;
public DecryptionWorkItem(Context context, MasterSecret masterSecret, long messageId, long threadId,
- String originator, String body, boolean isSecureMessage, boolean isKeyExchange)
+ String originator, int deviceId, String body, boolean isSecureMessage,
+ boolean isKeyExchange)
{
this.context = context;
this.messageId = messageId;
@@ -334,6 +343,7 @@ public class DecryptingQueue {
this.masterSecret = masterSecret;
this.body = body;
this.originator = originator;
+ this.deviceId = deviceId;
this.isSecureMessage = isSecureMessage;
this.isKeyExchange = isKeyExchange;
}
@@ -343,8 +353,9 @@ public class DecryptingQueue {
String plaintextBody;
try {
- Recipients recipients = RecipientFactory.getRecipientsFromString(context, originator, false);
- Recipient recipient = recipients.getPrimaryRecipient();
+ Recipients recipients = RecipientFactory.getRecipientsFromString(context, originator, false);
+ Recipient recipient = recipients.getPrimaryRecipient();
+ RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), deviceId);
if (!Session.hasSession(context, masterSecret, recipient)) {
database.markAsNoSession(messageId);
@@ -352,7 +363,7 @@ public class DecryptingQueue {
}
SmsTransportDetails transportDetails = new SmsTransportDetails();
- SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient);
+ SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice);
byte[] decodedCiphertext = transportDetails.getDecodedMessage(body.getBytes());
byte[] paddedPlaintext = sessionCipher.decrypt(decodedCiphertext);
@@ -401,9 +412,10 @@ public class DecryptingQueue {
private void handleKeyExchangeProcessing(String plaintxtBody) {
if (TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) {
try {
- Recipient recipient = new Recipient(null, originator, null, null);
- KeyExchangeMessage message = KeyExchangeMessage.createFor(plaintxtBody);
- KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipient, message);
+ Recipient recipient = RecipientFactory.getRecipientsFromString(context, originator, false).getPrimaryRecipient();
+ RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), deviceId);
+ KeyExchangeMessage message = KeyExchangeMessage.createFor(plaintxtBody);
+ KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipientDevice, message);
if (processor.isStale(message)) {
DatabaseFactory.getEncryptingSmsDatabase(context).markAsStaleKeyExchange(messageId);
@@ -420,6 +432,9 @@ public class DecryptingQueue {
} catch (InvalidMessageException e) {
Log.w("DecryptingQueue", e);
DatabaseFactory.getEncryptingSmsDatabase(context).markAsCorruptKeyExchange(messageId);
+ } catch (RecipientFormattingException e) {
+ Log.w("DecryptingQueue", e);
+ DatabaseFactory.getEncryptingSmsDatabase(context).markAsCorruptKeyExchange(messageId);
}
}
}
diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java
index e0d06bba1a..616221bf15 100644
--- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java
+++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java
@@ -31,6 +31,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
+import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.SessionRecordV2;
import java.security.NoSuchAlgorithmException;
@@ -70,8 +71,9 @@ public class KeyExchangeInitiator {
identityKey.getPublicKey());
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, message.serialize());
+ RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
- SessionRecordV2 sessionRecordV2 = new SessionRecordV2(context, masterSecret, recipient);
+ SessionRecordV2 sessionRecordV2 = new SessionRecordV2(context, masterSecret, recipientDevice);
sessionRecordV2.setPendingKeyExchange(sequence, baseKey, ephemeralKey, identityKey);
sessionRecordV2.save();
@@ -81,8 +83,9 @@ public class KeyExchangeInitiator {
private static boolean hasInitiatedSession(Context context, MasterSecret masterSecret,
Recipient recipient)
{
+ RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
return
- new SessionRecordV2(context, masterSecret, recipient)
+ new SessionRecordV2(context, masterSecret, recipientDevice)
.hasPendingKeyExchange();
}
diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java
index 998ab1ce99..eef17d4259 100644
--- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java
+++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java
@@ -21,8 +21,10 @@ import android.content.Context;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
import org.whispersystems.textsecure.crypto.MasterSecret;
+import org.whispersystems.textsecure.storage.RecipientDevice;
public abstract class KeyExchangeProcessor {
@@ -34,9 +36,13 @@ public abstract class KeyExchangeProcessor {
throws InvalidMessageException;
public static KeyExchangeProcessor createFor(Context context, MasterSecret masterSecret,
- Recipient recipient, KeyExchangeMessage message)
+ RecipientDevice recipientDevice,
+ KeyExchangeMessage message)
{
- if (message.isLegacy()) return new KeyExchangeProcessorV1(context, masterSecret, recipient);
- else return new KeyExchangeProcessorV2(context, masterSecret, recipient);
+ if (message.isLegacy()) {
+ return new KeyExchangeProcessorV1(context, masterSecret, recipientDevice.getRecipient());
+ } else {
+ return new KeyExchangeProcessorV2(context, masterSecret, recipientDevice);
+ }
}
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV1.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV1.java
index d31bdc397d..6c23b23532 100644
--- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV1.java
+++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV1.java
@@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV1;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
@@ -16,6 +17,7 @@ import org.whispersystems.textsecure.crypto.KeyPair;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
+import org.whispersystems.textsecure.storage.CanonicalRecipient;
import org.whispersystems.textsecure.storage.LocalKeyRecord;
import org.whispersystems.textsecure.storage.RemoteKeyRecord;
import org.whispersystems.textsecure.storage.SessionRecordV1;
@@ -32,17 +34,17 @@ import java.security.SecureRandom;
public class KeyExchangeProcessorV1 extends KeyExchangeProcessor {
- private Context context;
- private Recipient recipient;
- private MasterSecret masterSecret;
- private LocalKeyRecord localKeyRecord;
- private RemoteKeyRecord remoteKeyRecord;
- private SessionRecordV1 sessionRecord;
+ private Context context;
+ private CanonicalRecipient recipient;
+ private MasterSecret masterSecret;
+ private LocalKeyRecord localKeyRecord;
+ private RemoteKeyRecord remoteKeyRecord;
+ private SessionRecordV1 sessionRecord;
- public KeyExchangeProcessorV1(Context context, MasterSecret masterSecret, Recipient recipient) {
- this.context = context;
- this.recipient = recipient;
- this.masterSecret = masterSecret;
+ public KeyExchangeProcessorV1(Context context, MasterSecret masterSecret, CanonicalRecipient recipient) {
+ this.context = context;
+ this.recipient = recipient;
+ this.masterSecret = masterSecret;
this.remoteKeyRecord = new RemoteKeyRecord(context, recipient);
this.localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient);
@@ -55,7 +57,8 @@ public class KeyExchangeProcessorV1 extends KeyExchangeProcessor {
}
public boolean isTrusted(IdentityKey identityKey) {
- return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient,
+ return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret,
+ recipient.getRecipientId(),
identityKey);
}
@@ -80,8 +83,13 @@ public class KeyExchangeProcessorV1 extends KeyExchangeProcessor {
@Override
public void processKeyExchangeMessage(KeyExchangeMessage _message, long threadId) {
- KeyExchangeMessageV1 message = (KeyExchangeMessageV1)_message;
- int initiateKeyId = Conversions.lowBitsToMedium(message.getRemoteKey().getId());
+ KeyExchangeMessageV1 message = (KeyExchangeMessageV1) _message;
+ int initiateKeyId = Conversions.lowBitsToMedium(message.getRemoteKey().getId());
+
+ Recipient recipient = RecipientFactory.getRecipientsForIds(context,
+ this.recipient.getRecipientId()+"",
+ true).getPrimaryRecipient();
+
message.getRemoteKey().setId(initiateKeyId);
if (needsResponseFromUs()) {
@@ -113,7 +121,7 @@ public class KeyExchangeProcessorV1 extends KeyExchangeProcessor {
if (message.hasIdentityKey()) {
DatabaseFactory.getIdentityDatabase(context)
- .saveIdentity(masterSecret, recipient, message.getIdentityKey());
+ .saveIdentity(masterSecret, recipient.getRecipientId(), message.getIdentityKey());
}
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
@@ -130,7 +138,7 @@ public class KeyExchangeProcessorV1 extends KeyExchangeProcessor {
public LocalKeyRecord initializeRecordFor(Context context,
MasterSecret masterSecret,
- Recipient recipient)
+ CanonicalRecipient recipient)
{
Log.w("KeyExchangeProcessorV1", "Initializing local key pairs...");
try {
diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java
index b0190e4df8..5170e1226d 100644
--- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java
+++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java
@@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV2;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
@@ -24,6 +25,7 @@ import org.whispersystems.textsecure.crypto.ratchet.RatchetingSession;
import org.whispersystems.textsecure.push.PreKeyEntity;
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.PreKeyRecord;
+import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.util.Medium;
@@ -37,15 +39,16 @@ import org.whispersystems.textsecure.util.Medium;
public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
private Context context;
- private Recipient recipient;
+ private RecipientDevice recipientDevice;
private MasterSecret masterSecret;
private SessionRecordV2 sessionRecord;
- public KeyExchangeProcessorV2(Context context, MasterSecret masterSecret, Recipient recipient) {
+ public KeyExchangeProcessorV2(Context context, MasterSecret masterSecret, RecipientDevice recipientDevice)
+ {
this.context = context;
- this.recipient = recipient;
+ this.recipientDevice = recipientDevice;
this.masterSecret = masterSecret;
- this.sessionRecord = new SessionRecordV2(context, masterSecret, recipient);
+ this.sessionRecord = new SessionRecordV2(context, masterSecret, recipientDevice);
}
public boolean isTrusted(PreKeyWhisperMessage message) {
@@ -57,7 +60,8 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
}
public boolean isTrusted(IdentityKey identityKey) {
- return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient,
+ return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret,
+ recipientDevice.getRecipientId(),
identityKey);
}
@@ -80,7 +84,7 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
Log.w("KeyExchangeProcessor", "Received pre-key with local key ID: " + preKeyId);
- if (!PreKeyRecord.hasRecord(context, preKeyId) && Session.hasSession(context, masterSecret, recipient)) {
+ if (!PreKeyRecord.hasRecord(context, preKeyId) && SessionRecordV2.hasSession(context, masterSecret, recipientDevice)) {
Log.w("KeyExchangeProcessor", "We've already processed the prekey part, letting bundled message fall through...");
return;
}
@@ -97,7 +101,7 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
RatchetingSession.initializeSession(sessionRecord, ourBaseKey, theirBaseKey, ourEphemeralKey,
theirEphemeralKey, ourIdentityKey, theirIdentityKey);
-
+ Session.clearV1SessionFor(context, recipientDevice.getRecipient());
sessionRecord.save();
if (preKeyId != Medium.MAX_VALUE) {
@@ -105,7 +109,7 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
}
DatabaseFactory.getIdentityDatabase(context)
- .saveIdentity(masterSecret, recipient, theirIdentityKey);
+ .saveIdentity(masterSecret, recipientDevice.getRecipientId(), theirIdentityKey);
}
public void processKeyExchangeMessage(PreKeyEntity message, long threadId)
@@ -129,7 +133,7 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
sessionRecord.save();
DatabaseFactory.getIdentityDatabase(context)
- .saveIdentity(masterSecret, recipient, message.getIdentityKey());
+ .saveIdentity(masterSecret, recipientDevice.getRecipientId(), message.getIdentityKey());
broadcastSecurityUpdateEvent(context, threadId);
}
@@ -140,6 +144,10 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
{
try {
KeyExchangeMessageV2 message = (KeyExchangeMessageV2)_message;
+ Recipient recipient = RecipientFactory.getRecipientsForIds(context,
+ String.valueOf(recipientDevice.getRecipientId()),
+ false)
+ .getPrimaryRecipient();
Log.w("KeyExchangeProcessorV2", "Received key exchange with sequence: " + message.getSequence());
@@ -197,11 +205,11 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
ourIdentityKey, message.getIdentityKey());
sessionRecord.setSessionVersion(message.getVersion());
- Session.clearV1SessionFor(context, recipient);
+ Session.clearV1SessionFor(context, recipientDevice.getRecipient());
sessionRecord.save();
DatabaseFactory.getIdentityDatabase(context)
- .saveIdentity(masterSecret, recipient, message.getIdentityKey());
+ .saveIdentity(masterSecret, recipientDevice.getRecipientId(), message.getIdentityKey());
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
index 3bc6b49819..7de9b696b6 100644
--- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
+++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
@@ -644,6 +644,9 @@ public class DatabaseFactory {
if (oldVersion < INTRODUCED_GROUP_DATABASE_VERSION) {
db.execSQL("CREATE TABLE groups (_id INTEGER PRIMARY KEY, group_id TEXT, owner TEXT, title TEXT, members TEXT, avatar BLOB, avatar_id INTEGER, avatar_key BLOB, avatar_content_type TEXT, timestamp INTEGER);");
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON groups (GROUP_ID);");
+ db.execSQL("ALTER TABLE push ADD COLUMN device_id INTEGER DEFAULT 1;");
+ db.execSQL("ALTER TABLE sms ADD COLUMN address_device_id INTEGER DEFAULT 1;");
+ db.execSQL("ALTER TABLE mms ADD COLUMN address_device_id INTEGER DEFAULT 1;");
}
db.setTransactionSuccessful();
diff --git a/src/org/thoughtcrime/securesms/database/GroupDatabase.java b/src/org/thoughtcrime/securesms/database/GroupDatabase.java
index 1ccb077e5f..ef06e04d81 100644
--- a/src/org/thoughtcrime/securesms/database/GroupDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/GroupDatabase.java
@@ -6,11 +6,18 @@ import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
+import android.util.Log;
+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.util.BitmapUtil;
+import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Util;
+import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
@@ -28,7 +35,7 @@ public class GroupDatabase extends Database {
private static final String AVATAR_ID = "avatar_id";
private static final String AVATAR_KEY = "avatar_key";
private static final String AVATAR_CONTENT_TYPE = "avatar_content_type";
- private static final String RELAY = "relay";
+ private static final String AVATAR_RELAY = "avatar_relay";
private static final String TIMESTAMP = "timestamp";
public static final String CREATE_TABLE =
@@ -42,6 +49,7 @@ public class GroupDatabase extends Database {
AVATAR_ID + " INTEGER, " +
AVATAR_KEY + " BLOB, " +
AVATAR_CONTENT_TYPE + " TEXT, " +
+ AVATAR_RELAY + " TEXT, " +
TIMESTAMP + " INTEGER);";
public static final String[] CREATE_INDEXS = {
@@ -52,19 +60,36 @@ public class GroupDatabase extends Database {
super(context, databaseHelper);
}
- public Reader getGroup(String groupId) {
+ public Reader getGroup(byte[] groupId) {
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, GROUP_ID + " = ?",
- new String[] {groupId}, null, null, null);
+ new String[] {GroupUtil.getEncodedId(groupId)},
+ null, null, null);
return new Reader(cursor);
}
+ public Recipients getGroupMembers(byte[] groupId) {
+ List members = getCurrentMembers(groupId);
+ List recipients = new LinkedList();
+
+ for (String member : members) {
+ try {
+ recipients.addAll(RecipientFactory.getRecipientsFromString(context, member, true)
+ .getRecipientsList());
+ } catch (RecipientFormattingException e) {
+ Log.w("GroupDatabase", e);
+ }
+ }
+
+ return new Recipients(recipients);
+ }
+
public void create(byte[] groupId, String owner, String title,
List members, AttachmentPointer avatar,
String relay)
{
ContentValues contentValues = new ContentValues();
- contentValues.put(GROUP_ID, Hex.toString(groupId));
+ contentValues.put(GROUP_ID, GroupUtil.getEncodedId(groupId));
contentValues.put(OWNER, owner);
contentValues.put(TITLE, title);
contentValues.put(MEMBERS, Util.join(members, ","));
@@ -75,7 +100,7 @@ public class GroupDatabase extends Database {
contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType());
}
- contentValues.put(RELAY, relay);
+ contentValues.put(AVATAR_RELAY, relay);
contentValues.put(TIMESTAMP, System.currentTimeMillis());
databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues);
@@ -93,14 +118,15 @@ public class GroupDatabase extends Database {
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues,
GROUP_ID + " = ? AND " + OWNER + " = ?",
- new String[] {Hex.toString(groupId), source});
+ new String[] {GroupUtil.getEncodedId(groupId), source});
}
- public void updateAvatar(String groupId, Bitmap avatar) {
+ public void updateAvatar(byte[] groupId, Bitmap avatar) {
ContentValues contentValues = new ContentValues();
contentValues.put(AVATAR, BitmapUtil.toByteArray(avatar));
- databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", new String[] {groupId});
+ databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
+ new String[] {GroupUtil.getEncodedId(groupId)});
}
@@ -172,7 +198,7 @@ public class GroupDatabase extends Database {
cursor.getLong(cursor.getColumnIndexOrThrow(AVATAR_ID)),
cursor.getBlob(cursor.getColumnIndexOrThrow(AVATAR_KEY)),
cursor.getString(cursor.getColumnIndexOrThrow(AVATAR_CONTENT_TYPE)),
- cursor.getString(cursor.getColumnIndexOrThrow(RELAY)));
+ cursor.getString(cursor.getColumnIndexOrThrow(AVATAR_RELAY)));
}
public void close() {
@@ -206,8 +232,12 @@ public class GroupDatabase extends Database {
this.relay = relay;
}
- public String getId() {
- return id;
+ public byte[] getId() {
+ try {
+ return GroupUtil.getDecodedId(id);
+ } catch (IOException ioe) {
+ throw new AssertionError(ioe);
+ }
}
public String getTitle() {
diff --git a/src/org/thoughtcrime/securesms/database/IdentityDatabase.java b/src/org/thoughtcrime/securesms/database/IdentityDatabase.java
index 2602559922..1cda2593f8 100644
--- a/src/org/thoughtcrime/securesms/database/IdentityDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/IdentityDatabase.java
@@ -67,12 +67,10 @@ public class IdentityDatabase extends Database {
}
public boolean isValidIdentity(MasterSecret masterSecret,
- Recipient recipient,
+ long recipientId,
IdentityKey theirIdentity)
{
SQLiteDatabase database = databaseHelper.getReadableDatabase();
- String number = recipient.getNumber();
- long recipientId = DatabaseFactory.getAddressDatabase(context).getCanonicalAddress(number);
MasterCipher masterCipher = new MasterCipher(masterSecret);
Cursor cursor = null;
@@ -114,11 +112,9 @@ public class IdentityDatabase extends Database {
}
}
- public void saveIdentity(MasterSecret masterSecret, Recipient recipient, IdentityKey identityKey)
+ public void saveIdentity(MasterSecret masterSecret, long recipientId, IdentityKey identityKey)
{
SQLiteDatabase database = databaseHelper.getWritableDatabase();
- String number = recipient.getNumber();
- long recipientId = DatabaseFactory.getAddressDatabase(context).getCanonicalAddress(number);
MasterCipher masterCipher = new MasterCipher(masterSecret);
String identityKeyString = Base64.encodeBytes(identityKey.serialize());
String macString = Base64.encodeBytes(masterCipher.getMacFor(recipientId +
diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
index 4a32f76deb..70e81a4f8a 100644
--- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
@@ -109,6 +109,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
READ + " INTEGER DEFAULT 0, " + MESSAGE_ID + " TEXT, " + SUBJECT + " TEXT, " +
SUBJECT_CHARSET + " INTEGER, " + BODY + " TEXT, " + PART_COUNT + " INTEGER, " +
CONTENT_TYPE + " TEXT, " + CONTENT_LOCATION + " TEXT, " + ADDRESS + " TEXT, " +
+ ADDRESS_DEVICE_ID + " INTEGER, " +
EXPIRY + " INTEGER, " + MESSAGE_CLASS + " TEXT, " + MESSAGE_TYPE + " INTEGER, " +
MMS_VERSION + " INTEGER, " + MESSAGE_SIZE + " INTEGER, " + PRIORITY + " INTEGER, " +
READ_REPORT + " INTEGER, " + REPORT_ALLOWED + " INTEGER, " + RESPONSE_STATUS + " INTEGER, " +
@@ -131,7 +132,7 @@ 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
+ DELIVERY_TIME, DELIVERY_REPORT, BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID
};
public static final ExecutorService slideResolver = org.thoughtcrime.securesms.util.Util.newSingleThreadedLifoExecutor();
@@ -788,6 +789,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID));
long mailbox = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS));
+ int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID));
Recipients recipients = getRecipientsFor(address);
String contentLocation = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.CONTENT_LOCATION));
@@ -807,8 +809,9 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
return new NotificationMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
- dateSent, dateReceived, threadId, contentLocationBytes,
- messageSize, expiry, status, transactionIdBytes, mailbox);
+ addressDeviceId, dateSent, dateReceived, threadId,
+ contentLocationBytes, messageSize, expiry, status,
+ transactionIdBytes, mailbox);
}
private MediaMmsMessageRecord getMediaMmsMessageRecord(Cursor cursor) {
@@ -818,6 +821,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
long box = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID));
String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS));
+ int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID));
DisplayRecord.Body body = getBody(cursor);
int partCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.PART_COUNT));
Recipients recipients = getRecipientsFor(address);
@@ -825,29 +829,26 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
ListenableFutureTask slideDeck = getSlideDeck(masterSecret, id);
return new MediaMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
- dateSent, dateReceived, threadId, body,
+ addressDeviceId, dateSent, dateReceived, threadId, body,
slideDeck, partCount, box);
}
private Recipients getRecipientsFor(String address) {
try {
if (Util.isEmpty(address) || address.equals("insert-address-token")) {
- return new Recipients(new Recipient("Unknown", "Unknown", null,
- ContactPhotoFactory.getDefaultContactPhoto(context)));
+ return new Recipients(Recipient.getUnknownRecipient(context));
}
Recipients recipients = RecipientFactory.getRecipientsFromString(context, address, false);
if (recipients == null || recipients.isEmpty()) {
- return new Recipients(new Recipient("Unknown", "Unknown", null,
- ContactPhotoFactory.getDefaultContactPhoto(context)));
+ return new Recipients(Recipient.getUnknownRecipient(context));
}
return recipients;
} catch (RecipientFormattingException e) {
Log.w("MmsDatabase", e);
- return new Recipients(new Recipient("Unknown", "Unknown", null,
- ContactPhotoFactory.getDefaultContactPhoto(context)));
+ return new Recipients(Recipient.getUnknownRecipient(context));
}
}
diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java
index 9bdba4565c..99edd838d7 100644
--- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java
+++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java
@@ -8,7 +8,8 @@ public interface MmsSmsColumns {
public static final String THREAD_ID = "thread_id";
public static final String READ = "read";
public static final String BODY = "body";
- public static final String ADDRESS = "address";
+ public static final String ADDRESS = "address";
+ public static final String ADDRESS_DEVICE_ID = "address_device_id";
public static class Types {
diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
index 97210e6092..f9666decd1 100644
--- a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
@@ -42,7 +42,7 @@ public class MmsSmsDatabase extends Database {
public Cursor getConversation(long threadId) {
String[] projection = {MmsSmsColumns.ID, SmsDatabase.BODY, SmsDatabase.TYPE,
MmsSmsColumns.THREAD_ID,
- SmsDatabase.ADDRESS, SmsDatabase.SUBJECT,
+ SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT,
MmsSmsColumns.NORMALIZED_DATE_SENT,
MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX,
@@ -64,7 +64,7 @@ public class MmsSmsDatabase extends Database {
public Cursor getConversationSnippet(long threadId) {
String[] projection = {MmsSmsColumns.ID, SmsDatabase.BODY, SmsDatabase.TYPE,
MmsSmsColumns.THREAD_ID,
- SmsDatabase.ADDRESS, SmsDatabase.SUBJECT,
+ SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT,
MmsSmsColumns.NORMALIZED_DATE_SENT,
MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX,
@@ -81,7 +81,7 @@ public class MmsSmsDatabase extends Database {
public Cursor getUnread() {
String[] projection = {MmsSmsColumns.ID, SmsDatabase.BODY, SmsDatabase.READ, SmsDatabase.TYPE,
- SmsDatabase.ADDRESS, SmsDatabase.SUBJECT, MmsSmsColumns.THREAD_ID,
+ SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsSmsColumns.THREAD_ID,
SmsDatabase.STATUS,
MmsSmsColumns.NORMALIZED_DATE_SENT,
MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
@@ -108,7 +108,7 @@ public class MmsSmsDatabase extends Database {
String[] mmsProjection = {MmsDatabase.DATE_SENT + " * 1000 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
MmsDatabase.DATE_RECEIVED + " * 1000 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
MmsSmsColumns.ID, SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID,
- SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE,
+ SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE,
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
@@ -117,7 +117,7 @@ public class MmsSmsDatabase extends Database {
String[] smsProjection = {SmsDatabase.DATE_SENT + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
SmsDatabase.DATE_RECEIVED + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
MmsSmsColumns.ID, SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID,
- SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE,
+ SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE,
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
@@ -139,6 +139,7 @@ public class MmsSmsDatabase extends Database {
mmsColumnsPresent.add(MmsSmsColumns.THREAD_ID);
mmsColumnsPresent.add(MmsSmsColumns.BODY);
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS);
+ mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
mmsColumnsPresent.add(MmsDatabase.MESSAGE_TYPE);
mmsColumnsPresent.add(MmsDatabase.MESSAGE_BOX);
mmsColumnsPresent.add(MmsDatabase.DATE_SENT);
@@ -154,6 +155,7 @@ public class MmsSmsDatabase extends Database {
smsColumnsPresent.add(MmsSmsColumns.ID);
smsColumnsPresent.add(MmsSmsColumns.BODY);
smsColumnsPresent.add(MmsSmsColumns.ADDRESS);
+ smsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
smsColumnsPresent.add(MmsSmsColumns.READ);
smsColumnsPresent.add(MmsSmsColumns.THREAD_ID);
smsColumnsPresent.add(SmsDatabase.TYPE);
diff --git a/src/org/thoughtcrime/securesms/database/PushDatabase.java b/src/org/thoughtcrime/securesms/database/PushDatabase.java
index 8df5ec7a3e..a4f6552a34 100644
--- a/src/org/thoughtcrime/securesms/database/PushDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/PushDatabase.java
@@ -18,11 +18,12 @@ public class PushDatabase extends Database {
public static final String ID = "_id";
public static final String TYPE = "type";
public static final String SOURCE = "source";
+ public static final String DEVICE_ID = "device_id";
public static final String BODY = "body";
public static final String TIMESTAMP = "timestamp";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
- TYPE + " INTEGER, " + SOURCE + " TEXT, " + BODY + " TEXT, " + TIMESTAMP + " INTEGER);";
+ TYPE + " INTEGER, " + SOURCE + " TEXT, " + DEVICE_ID + " INTEGER, " + BODY + " TEXT, " + TIMESTAMP + " INTEGER);";
public PushDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
@@ -64,10 +65,11 @@ public class PushDatabase extends Database {
int type = cursor.getInt(cursor.getColumnIndexOrThrow(TYPE));
String source = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE));
+ int deviceId = cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE_ID));
byte[] body = Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(BODY)));
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP));
- return new IncomingPushMessage(type, source, body, timestamp);
+ return new IncomingPushMessage(type, source, deviceId, body, timestamp);
} catch (IOException e) {
throw new AssertionError(e);
}
diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java
index 46ca52a280..5beacd9591 100644
--- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java
@@ -63,8 +63,8 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
public static final String SERVICE_CENTER = "service_center";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " integer PRIMARY KEY, " +
- THREAD_ID + " INTEGER, " + ADDRESS + " TEXT, " + PERSON + " INTEGER, " + DATE_RECEIVED + " INTEGER, " +
- DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " +
+ THREAD_ID + " INTEGER, " + ADDRESS + " TEXT, " + ADDRESS_DEVICE_ID + " INTEGER DEFAULT 1, " + PERSON + " INTEGER, " +
+ DATE_RECEIVED + " INTEGER, " + DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " +
STATUS + " INTEGER DEFAULT -1," + TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " +
SUBJECT + " TEXT, " + BODY + " TEXT, " + SERVICE_CENTER + " TEXT);";
@@ -76,7 +76,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
};
private static final String[] MESSAGE_PROJECTION = new String[] {
- ID, THREAD_ID, ADDRESS, PERSON,
+ ID, THREAD_ID, ADDRESS, ADDRESS_DEVICE_ID, PERSON,
DATE_RECEIVED + " AS " + NORMALIZED_DATE_RECEIVED,
DATE_SENT + " AS " + NORMALIZED_DATE_SENT,
PROTOCOL, READ, STATUS, TYPE,
@@ -257,19 +257,39 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
type |= Types.ENCRYPTION_REMOTE_BIT;
}
- Recipient recipient = new Recipient(null, message.getSender(), null, null);
- Recipients recipients = new Recipients(recipient);
- String groupId = message.getGroupId();
+ Recipients recipients;
+
+ try {
+ recipients = RecipientFactory.getRecipientsFromString(context, message.getSender(), true);
+ } catch (RecipientFormattingException e) {
+ Log.w("SmsDatabase", e);
+ recipients = new Recipients(Recipient.getUnknownRecipient(context));
+ }
+
+ Recipients groupRecipients;
+
+ try {
+ if (message.getGroupId() == null) {
+ groupRecipients = null;
+ } else {
+ groupRecipients = RecipientFactory.getRecipientsFromString(context, message.getGroupId(), true);
+ }
+ } catch (RecipientFormattingException e) {
+ Log.w("SmsDatabase", e);
+ groupRecipients = null;
+ }
+
boolean unread = org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context) ||
message.isSecureMessage() || message.isKeyExchange();
long threadId;
- if (groupId == null) threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
- else threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdForGroup(groupId);
+ if (groupRecipients == null) threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
+ else threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipients);
ContentValues values = new ContentValues(6);
values.put(ADDRESS, message.getSender());
+ values.put(ADDRESS_DEVICE_ID, message.getSenderDeviceId());
values.put(DATE_RECEIVED, System.currentTimeMillis());
values.put(DATE_SENT, message.getSentTimestampMillis());
values.put(PROTOCOL, message.getProtocol());
@@ -468,6 +488,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
public SmsMessageRecord getCurrent() {
long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
String address = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
+ int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS_DEVICE_ID));
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_RECEIVED));
long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_SENT));
@@ -478,6 +499,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
return new SmsMessageRecord(context, messageId, body, recipients,
recipients.getPrimaryRecipient(),
+ addressDeviceId,
dateSent, dateReceived, type,
threadId, status);
}
@@ -487,15 +509,13 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
Recipients recipients = RecipientFactory.getRecipientsFromString(context, address, false);
if (recipients == null || recipients.isEmpty()) {
- return new Recipients(new Recipient("Unknown", "Unknown", null,
- ContactPhotoFactory.getDefaultContactPhoto(context)));
+ return new Recipients(Recipient.getUnknownRecipient(context));
}
return recipients;
} catch (RecipientFormattingException e) {
Log.w("EncryptingSmsDatabase", e);
- return new Recipients(new Recipient("Unknown", "Unknown", null,
- ContactPhotoFactory.getDefaultContactPhoto(context)));
+ return new Recipients(Recipient.getUnknownRecipient(context));
}
}
diff --git a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java
index ec124ace47..0aa8842192 100644
--- a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java
@@ -70,13 +70,11 @@ public class ThreadDatabase extends Database {
}
private long[] getRecipientIds(Recipients recipients) {
- Set recipientSet = new HashSet();
+ Set recipientSet = new HashSet();
List recipientList = recipients.getRecipientsList();
for (Recipient recipient : recipientList) {
- // String number = NumberUtil.filterNumber(recipient.getNumber());
- String number = recipient.getNumber();
- recipientSet.add(Long.valueOf(DatabaseFactory.getAddressDatabase(context).getCanonicalAddress(number)));
+ recipientSet.add(recipient.getRecipientId());
}
long[] recipientArray = new long[recipientSet.size()];
diff --git a/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java
index 8d0c5a3c95..46fac48fbd 100644
--- a/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java
@@ -41,12 +41,13 @@ public class MediaMmsMessageRecord extends MessageRecord {
private final ListenableFutureTask slideDeck;
public MediaMmsMessageRecord(Context context, long id, Recipients recipients,
- Recipient individualRecipient, long dateSent, long dateReceived,
- long threadId, Body body, ListenableFutureTask slideDeck,
+ Recipient individualRecipient, int recipientDeviceId,
+ long dateSent, long dateReceived, long threadId, Body body,
+ ListenableFutureTask slideDeck,
int partCount, long mailbox)
{
- super(context, id, body, recipients, individualRecipient, dateSent, dateReceived,
- threadId, DELIVERY_STATUS_NONE, mailbox);
+ super(context, id, body, recipients, individualRecipient, recipientDeviceId,
+ dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, mailbox);
this.context = context.getApplicationContext();
this.partCount = partCount;
diff --git a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
index be07a43698..724fa88939 100644
--- a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
@@ -45,18 +45,20 @@ public abstract class MessageRecord extends DisplayRecord {
public static final int DELIVERY_STATUS_FAILED = 3;
private final Recipient individualRecipient;
- private final long id;
- private final int deliveryStatus;
+ private final int recipientDeviceId;
+ private final long id;
+ private final int deliveryStatus;
- public MessageRecord(Context context, long id, Body body, Recipients recipients,
- Recipient individualRecipient,
- long dateSent, long dateReceived,
- long threadId, int deliveryStatus,
- long type)
+ MessageRecord(Context context, long id, Body body, Recipients recipients,
+ Recipient individualRecipient, int recipientDeviceId,
+ long dateSent, long dateReceived,
+ long threadId, int deliveryStatus,
+ long type)
{
super(context, body, recipients, dateSent, dateReceived, threadId, type);
this.id = id;
this.individualRecipient = individualRecipient;
+ this.recipientDeviceId = recipientDeviceId;
this.deliveryStatus = deliveryStatus;
}
@@ -121,6 +123,10 @@ public abstract class MessageRecord extends DisplayRecord {
return individualRecipient;
}
+ public int getRecipientDeviceId() {
+ return recipientDeviceId;
+ }
+
public long getType() {
return type;
}
diff --git a/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java
index 99039e773f..7ebf70c75d 100644
--- a/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java
@@ -41,13 +41,13 @@ public class NotificationMmsMessageRecord extends MessageRecord {
private final byte[] transactionId;
public NotificationMmsMessageRecord(Context context, long id, Recipients recipients,
- Recipient individualRecipient,
+ Recipient individualRecipient, int recipientDeviceId,
long dateSent, long dateReceived, long threadId,
byte[] contentLocation, long messageSize, long expiry,
int status, byte[] transactionId, long mailbox)
{
- super(context, id, new Body("", true), recipients, individualRecipient, dateSent, dateReceived,
- threadId, DELIVERY_STATUS_NONE, mailbox);
+ super(context, id, new Body("", true), recipients, individualRecipient, recipientDeviceId,
+ dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, mailbox);
this.contentLocation = contentLocation;
this.messageSize = messageSize;
diff --git a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java
index 5fa9eaec98..e630507e96 100644
--- a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java
@@ -38,12 +38,13 @@ public class SmsMessageRecord extends MessageRecord {
public SmsMessageRecord(Context context, long id,
Body body, Recipients recipients,
Recipient individualRecipient,
+ int recipientDeviceId,
long dateSent, long dateReceived,
long type, long threadId,
int status)
{
- super(context, id, body, recipients, individualRecipient, dateSent, dateReceived,
- threadId, getGenericDeliveryStatus(status), type);
+ super(context, id, body, recipients, individualRecipient, recipientDeviceId,
+ dateSent, dateReceived, threadId, getGenericDeliveryStatus(status), type);
}
public long getType() {
diff --git a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java
index 9b66b822f3..1fc0b2ea81 100644
--- a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java
+++ b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java
@@ -1,11 +1,13 @@
package org.thoughtcrime.securesms.mms;
+import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
import org.whispersystems.textsecure.util.Base64;
+import org.whispersystems.textsecure.util.Hex;
import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.PduBody;
@@ -27,12 +29,16 @@ public class IncomingMediaMessage {
public IncomingMediaMessage(MasterSecret masterSecret, String localNumber,
IncomingPushMessage message,
- PushMessageContent messageContent,
- String groupId)
+ PushMessageContent messageContent)
{
this.headers = new PduHeaders();
this.body = new PduBody();
- this.groupId = groupId;
+
+ if (messageContent.hasGroup()) {
+ this.groupId = GroupUtil.getEncodedId(messageContent.getGroup().getId().toByteArray());
+ } else {
+ this.groupId = null;
+ }
this.headers.setEncodedStringValue(new EncodedStringValue(message.getSource()), PduHeaders.FROM);
this.headers.appendEncodedStringValue(new EncodedStringValue(localNumber), PduHeaders.TO);
diff --git a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java
index cc4fa6786e..44b06da4a6 100644
--- a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java
+++ b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java
@@ -296,7 +296,7 @@ public class MessageNotifier {
recipient = RecipientFactory.getRecipientsFromString(context, message.getSource(), false).getPrimaryRecipient();
} catch (RecipientFormattingException e) {
Log.w("MessageNotifier", e);
- recipient = new Recipient("Unknown", "Unknown", null, ContactPhotoFactory.getDefaultContactPhoto(context));
+ recipient = Recipient.getUnknownRecipient(context);
}
Recipients recipients = RecipientFactory.getRecipientsFromMessage(context, message, false);
diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java
index 9715f429f0..634367e6a8 100644
--- a/src/org/thoughtcrime/securesms/recipients/Recipient.java
+++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java
@@ -23,15 +23,16 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
-import org.thoughtcrime.securesms.database.CanonicalAddressDatabase;
+import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails;
+import org.thoughtcrime.securesms.util.GroupUtil;
+import org.whispersystems.textsecure.storage.CanonicalRecipient;
import org.whispersystems.textsecure.util.FutureTaskListener;
import org.whispersystems.textsecure.util.ListenableFutureTask;
-import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
import java.util.HashSet;
-public class Recipient implements Parcelable, CanonicalRecipientAddress {
+public class Recipient implements Parcelable, CanonicalRecipient {
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public Recipient createFromParcel(Parcel in) {
@@ -43,18 +44,21 @@ public class Recipient implements Parcelable, CanonicalRecipientAddress {
}
};
- private final String number;
private final HashSet listeners = new HashSet();
+ private final String number;
+ private final long recipientId;
+
private String name;
private Bitmap contactPhoto;
private Uri contactUri;
- public Recipient(String number, Bitmap contactPhoto,
- ListenableFutureTask future)
+ Recipient(String number, Bitmap contactPhoto, long recipientId,
+ ListenableFutureTask future)
{
this.number = number;
this.contactPhoto = contactPhoto;
+ this.recipientId = recipientId;
future.setListener(new FutureTaskListener() {
@Override
@@ -82,8 +86,9 @@ public class Recipient implements Parcelable, CanonicalRecipientAddress {
});
}
- public Recipient(String name, String number, Uri contactUri, Bitmap contactPhoto) {
+ Recipient(String name, String number, long recipientId, Uri contactUri, Bitmap contactPhoto) {
this.number = number;
+ this.recipientId = recipientId;
this.contactUri = contactUri;
this.name = name;
this.contactPhoto = contactPhoto;
@@ -92,6 +97,7 @@ public class Recipient implements Parcelable, CanonicalRecipientAddress {
public Recipient(Parcel in) {
this.number = in.readString();
this.name = in.readString();
+ this.recipientId = in.readLong();
this.contactUri = (Uri)in.readParcelable(null);
this.contactPhoto = (Bitmap)in.readParcelable(null);
}
@@ -112,6 +118,14 @@ public class Recipient implements Parcelable, CanonicalRecipientAddress {
return 0;
}
+ public long getRecipientId() {
+ return recipientId;
+ }
+
+ public boolean isGroupRecipient() {
+ return GroupUtil.isEncodedGroup(number);
+ }
+
// public void updateAsynchronousContent(RecipientDetails result) {
// if (result != null) {
// Recipient.this.name.set(result.name);
@@ -136,6 +150,7 @@ public class Recipient implements Parcelable, CanonicalRecipientAddress {
public synchronized void writeToParcel(Parcel dest, int flags) {
dest.writeString(number);
dest.writeString(name);
+ dest.writeLong(recipientId);
dest.writeParcelable(contactUri, 0);
dest.writeParcelable(contactPhoto, 0);
}
@@ -148,11 +163,12 @@ public class Recipient implements Parcelable, CanonicalRecipientAddress {
return contactPhoto;
}
- public long getCanonicalAddress(Context context) {
- return CanonicalAddressDatabase.getInstance(context).getCanonicalAddress(getNumber());
+ public static Recipient getUnknownRecipient(Context context) {
+ return new Recipient("Unknown", "Unknown", -1, null, ContactPhotoFactory.getDefaultContactPhoto(context));
}
public static interface RecipientModifiedListener {
public void onModified(Recipient recipient);
}
+
}
diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java b/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java
index 836b6a20f5..aac8f8a172 100644
--- a/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java
+++ b/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.util.Log;
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
+import org.thoughtcrime.securesms.database.CanonicalAddressDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.util.NumberUtil;
import org.whispersystems.textsecure.push.IncomingPushMessage;
@@ -51,7 +52,8 @@ public class RecipientFactory {
}
private static Recipient getRecipientForNumber(Context context, String number, boolean asynchronous) {
- return provider.getRecipient(context, number, asynchronous);
+ long recipientId = CanonicalAddressDatabase.getInstance(context).getCanonicalAddress(number);
+ return provider.getRecipient(context, recipientId, asynchronous);
}
public static Recipients getRecipientsFromString(Context context, String rawText, boolean asynchronous)
@@ -81,17 +83,16 @@ public class RecipientFactory {
return getRecipientsFromString(context, message.getSource(), asynchronous);
} catch (RecipientFormattingException e) {
Log.w("RecipientFactory", e);
- return new Recipients(new Recipient("Unknown", "Unknown", null,
- ContactPhotoFactory.getDefaultContactPhoto(context)));
+ return new Recipients(Recipient.getUnknownRecipient(context));
}
}
private static Recipient getRecipientFromProviderId(Context context, String recipientId, boolean asynchronous) {
- if (recipientId.startsWith("g_")) {
- return provider.getGroupRecipient(context, recipientId, asynchronous);
- } else {
- String number = DatabaseFactory.getAddressDatabase(context).getAddressFromId(recipientId);
- return getRecipientForNumber(context, number, asynchronous);
+ try {
+ return provider.getRecipient(context, Long.parseLong(recipientId), asynchronous);
+ } catch (NumberFormatException e) {
+ Log.w("RecipientFactory", e);
+ return Recipient.getUnknownRecipient(context);
}
}
@@ -126,7 +127,7 @@ public class RecipientFactory {
if (hasBracketedNumber(recipient))
return getRecipientForNumber(context, parseBracketedNumber(recipient), asynchronous);
- if (NumberUtil.isValidSmsOrEmail(recipient))
+ if (NumberUtil.isValidSmsOrEmailOrGroup(recipient))
return getRecipientForNumber(context, recipient, asynchronous);
throw new RecipientFormattingException("Recipient: " + recipient + " is badly formatted.");
diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java
index 149923caae..ab2c78e68e 100644
--- a/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java
+++ b/src/org/thoughtcrime/securesms/recipients/RecipientProvider.java
@@ -27,12 +27,15 @@ import android.provider.ContactsContract.PhoneLookup;
import android.util.Log;
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
+import org.thoughtcrime.securesms.database.CanonicalAddressDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
+import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.LRUCache;
import org.whispersystems.textsecure.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.Util;
+import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Map;
@@ -41,8 +44,8 @@ import java.util.concurrent.ExecutorService;
public class RecipientProvider {
- private static final Map recipientCache = Collections.synchronizedMap(new LRUCache(1000));
- private static final ExecutorService asyncRecipientResolver = Util.newSingleThreadedLifoExecutor();
+ private static final Map recipientCache = Collections.synchronizedMap(new LRUCache(1000));
+ private static final ExecutorService asyncRecipientResolver = Util.newSingleThreadedLifoExecutor();
private static final String[] CALLER_ID_PROJECTION = new String[] {
PhoneLookup.DISPLAY_NAME,
@@ -50,58 +53,45 @@ public class RecipientProvider {
PhoneLookup._ID,
};
- public Recipient getRecipient(Context context, String number, boolean asynchronous) {
- Recipient cachedRecipient = recipientCache.get(number);
+ public Recipient getRecipient(Context context, long recipientId, boolean asynchronous) {
+ Recipient cachedRecipient = recipientCache.get(recipientId);
if (cachedRecipient != null) return cachedRecipient;
- else if (asynchronous) return getAsynchronousRecipient(context, number);
- else return getSynchronousRecipient(context, number);
+ else if (asynchronous) return getAsynchronousRecipient(context, recipientId);
+ else return getSynchronousRecipient(context, recipientId);
}
- public Recipient getGroupRecipient(Context context, String groupId, boolean asynchronous) {
- Recipient cachedRecipient = recipientCache.get(groupId);
-
- if (cachedRecipient != null) return cachedRecipient;
- else if (asynchronous) return getAsynchronousGroupRecipient(context, groupId);
- else return getSynchronousGroupRecipient(context, groupId);
- }
-
- private Recipient getSynchronousRecipient(Context context, String number) {
+ private Recipient getSynchronousRecipient(Context context, long recipientId) {
Log.w("RecipientProvider", "Cache miss [SYNC]!");
- RecipientDetails details = getRecipientDetails(context, number);
+
Recipient recipient;
+ RecipientDetails details;
+
+ String number = CanonicalAddressDatabase.getInstance(context).getAddressFromId(String.valueOf(recipientId));
+
+ if (GroupUtil.isEncodedGroup(number)) details = getGroupRecipientDetails(context, number);
+ else details = getRecipientDetails(context, number);
if (details != null) {
- recipient = new Recipient(details.name, number, details.contactUri, details.avatar);
+ recipient = new Recipient(details.name, number, recipientId, details.contactUri, details.avatar);
} else {
- recipient = new Recipient(null, number, null, ContactPhotoFactory.getDefaultContactPhoto(context));
+ recipient = new Recipient(null, number, recipientId, null, ContactPhotoFactory.getDefaultContactPhoto(context));
}
- recipientCache.put(number, recipient);
+ recipientCache.put(recipientId, recipient);
return recipient;
}
- private Recipient getSynchronousGroupRecipient(Context context, String groupId) {
- RecipientDetails details = getGroupRecipientDetails(context, groupId);
- Recipient recipient;
-
- if (details != null) {
- recipient = new Recipient(details.name, groupId, details.contactUri, details.avatar);
- } else {
- recipient = new Recipient(null, groupId, null, ContactPhotoFactory.getDefaultContactPhoto(context));
- }
-
- recipientCache.put(groupId, recipient);
- return recipient;
- }
-
- private Recipient getAsynchronousRecipient(final Context context, final String number) {
+ private Recipient getAsynchronousRecipient(final Context context, final long recipientId) {
Log.w("RecipientProvider", "Cache miss [ASYNC]!");
+ final String number = CanonicalAddressDatabase.getInstance(context).getAddressFromId(String.valueOf(recipientId));
+
Callable task = new Callable() {
@Override
public RecipientDetails call() throws Exception {
- return getRecipientDetails(context, number);
+ if (GroupUtil.isEncodedGroup(number)) return getGroupRecipientDetails(context, number);
+ else return getRecipientDetails(context, number);
}
};
@@ -109,26 +99,8 @@ public class RecipientProvider {
asyncRecipientResolver.submit(future);
- Recipient recipient = new Recipient(number, ContactPhotoFactory.getDefaultContactPhoto(context), future);
- recipientCache.put(number, recipient);
-
- return recipient;
- }
-
- private Recipient getAsynchronousGroupRecipient(final Context context, final String groupId) {
- Callable task = new Callable() {
- @Override
- public RecipientDetails call() throws Exception {
- return getGroupRecipientDetails(context, groupId);
- }
- };
-
- ListenableFutureTask future = new ListenableFutureTask(task, null);
-
- asyncRecipientResolver.submit(future);
-
- Recipient recipient = new Recipient(groupId, ContactPhotoFactory.getDefaultContactPhoto(context), future);
- recipientCache.put(groupId, recipient);
+ Recipient recipient = new Recipient(number, ContactPhotoFactory.getDefaultContactPhoto(context), recipientId, future);
+ recipientCache.put(recipientId, recipient);
return recipient;
}
@@ -159,24 +131,31 @@ public class RecipientProvider {
}
private RecipientDetails getGroupRecipientDetails(Context context, String groupId) {
- GroupDatabase.Reader reader = DatabaseFactory.getGroupDatabase(context).getGroup(groupId.substring(2));
- GroupDatabase.GroupRecord record;
-
try {
- if ((record = reader.getNext()) != null) {
- byte[] avatarBytes = record.getAvatar();
- Bitmap avatar;
+ GroupDatabase.Reader reader = DatabaseFactory.getGroupDatabase(context)
+ .getGroup(GroupUtil.getDecodedId(groupId));
- if (avatarBytes == null) avatar = ContactPhotoFactory.getDefaultContactPhoto(context);
- else avatar = BitmapFactory.decodeByteArray(avatarBytes, 0, avatarBytes.length);
+ GroupDatabase.GroupRecord record;
- return new RecipientDetails(record.getTitle(), null, avatar);
+ try {
+ if ((record = reader.getNext()) != null) {
+ byte[] avatarBytes = record.getAvatar();
+ Bitmap avatar;
+
+ if (avatarBytes == null) avatar = ContactPhotoFactory.getDefaultContactPhoto(context);
+ else avatar = BitmapFactory.decodeByteArray(avatarBytes, 0, avatarBytes.length);
+
+ return new RecipientDetails(record.getTitle(), null, avatar);
+ }
+ } finally {
+ reader.close();
}
- } finally {
- reader.close();
- }
- return null;
+ return null;
+ } catch (IOException e) {
+ Log.w("RecipientProvider", e);
+ return null;
+ }
}
private Bitmap getContactPhoto(Context context, Uri uri) {
diff --git a/src/org/thoughtcrime/securesms/recipients/Recipients.java b/src/org/thoughtcrime/securesms/recipients/Recipients.java
index 7ea7a56dff..60857f9d70 100644
--- a/src/org/thoughtcrime/securesms/recipients/Recipients.java
+++ b/src/org/thoughtcrime/securesms/recipients/Recipients.java
@@ -21,6 +21,7 @@ import android.os.Parcelable;
import android.util.Patterns;
import org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener;
+import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.NumberUtil;
import java.util.ArrayList;
@@ -89,6 +90,10 @@ public class Recipients implements Parcelable {
return false;
}
+ public boolean isGroupRecipient() {
+ return isSingleRecipient() && GroupUtil.isEncodedGroup(recipients.get(0).getNumber());
+ }
+
// public Recipients getSecureSessionRecipients(Context context) {
// List secureRecipients = new LinkedList();
//
diff --git a/src/org/thoughtcrime/securesms/service/AvatarDownloader.java b/src/org/thoughtcrime/securesms/service/AvatarDownloader.java
index 0e467ff967..8d5c30f7f5 100644
--- a/src/org/thoughtcrime/securesms/service/AvatarDownloader.java
+++ b/src/org/thoughtcrime/securesms/service/AvatarDownloader.java
@@ -33,7 +33,7 @@ public class AvatarDownloader {
if (!SendReceiveService.DOWNLOAD_AVATAR_ACTION.equals(intent.getAction()))
return;
- String groupId = intent.getStringExtra("group_id");
+ byte[] groupId = intent.getByteArrayExtra("group_id");
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
GroupDatabase.Reader reader = database.getGroup(groupId);
diff --git a/src/org/thoughtcrime/securesms/service/PushReceiver.java b/src/org/thoughtcrime/securesms/service/PushReceiver.java
index 7ee7471670..307bd29b2e 100644
--- a/src/org/thoughtcrime/securesms/service/PushReceiver.java
+++ b/src/org/thoughtcrime/securesms/service/PushReceiver.java
@@ -17,12 +17,14 @@ import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
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.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
+import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
@@ -32,6 +34,7 @@ import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
+import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.util.Hex;
import ws.com.google.android.mms.MmsException;
@@ -106,9 +109,10 @@ public class PushReceiver {
}
try {
- Recipient recipient = new Recipient(null, message.getSource(), null, null);
- KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipient);
- PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(message.getBody());
+ Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSource(), false).getPrimaryRecipient();
+ RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSourceDevice());
+ KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipientDevice);
+ PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(message.getBody());
if (processor.isTrusted(preKeyExchange)) {
processor.processKeyExchangeMessage(preKeyExchange);
@@ -135,6 +139,9 @@ public class PushReceiver {
} catch (InvalidMessageException e) {
Log.w("PushReceiver", e);
handleReceivedCorruptedKey(masterSecret, message, false);
+ } catch (RecipientFormattingException e) {
+ Log.w("PushReceiver", e);
+ handleReceivedCorruptedKey(masterSecret, message, false);
}
}
@@ -151,10 +158,10 @@ public class PushReceiver {
handleReceivedGroupMessage(masterSecret, message, messageContent, secure);
} else if (messageContent.getAttachmentsCount() > 0) {
Log.w("PushReceiver", "Received push media message...");
- handleReceivedMediaMessage(masterSecret, message, messageContent, secure, null);
+ handleReceivedMediaMessage(masterSecret, message, messageContent, secure);
} else {
Log.w("PushReceiver", "Received push text message...");
- handleReceivedTextMessage(masterSecret, message, messageContent, secure, null);
+ handleReceivedTextMessage(masterSecret, message, messageContent, secure);
}
} catch (InvalidProtocolBufferException e) {
Log.w("PushReceiver", e);
@@ -207,30 +214,28 @@ public class PushReceiver {
if (group.hasAvatar()) {
Intent intent = new Intent(context, SendReceiveService.class);
intent.setAction(SendReceiveService.DOWNLOAD_AVATAR_ACTION);
+ intent.putExtra("group_id", group.getId().toByteArray());
context.startService(intent);
}
- String groupId = "g_" + Hex.toString(group.getId().toByteArray());
-
if (messageContent.getAttachmentsCount() > 0) {
- handleReceivedMediaMessage(masterSecret, message, messageContent, secure, groupId);
+ handleReceivedMediaMessage(masterSecret, message, messageContent, secure);
} else if (messageContent.hasBody()) {
- handleReceivedTextMessage(masterSecret, message, messageContent, secure, groupId);
+ handleReceivedTextMessage(masterSecret, message, messageContent, secure);
}
}
private void handleReceivedMediaMessage(MasterSecret masterSecret,
IncomingPushMessage message,
PushMessageContent messageContent,
- boolean secure, String groupId)
+ boolean secure)
{
try {
String localNumber = TextSecurePreferences.getLocalNumber(context);
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, localNumber,
- message, messageContent,
- groupId);
+ message, messageContent);
Pair messageAndThreadId;
@@ -255,9 +260,10 @@ public class PushReceiver {
private void handleReceivedTextMessage(MasterSecret masterSecret,
IncomingPushMessage message,
PushMessageContent messageContent,
- boolean secure, String groupId)
+ boolean secure)
{
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
+ String groupId = messageContent.hasGroup() ? GroupUtil.getEncodedId(messageContent.getGroup().getId().toByteArray()) : null;
IncomingTextMessage textMessage = new IncomingTextMessage(message, "", groupId);
if (secure) {
diff --git a/src/org/thoughtcrime/securesms/service/SmsReceiver.java b/src/org/thoughtcrime/securesms/service/SmsReceiver.java
index aaaddb66b4..63eb31a6fa 100644
--- a/src/org/thoughtcrime/securesms/service/SmsReceiver.java
+++ b/src/org/thoughtcrime/securesms/service/SmsReceiver.java
@@ -32,6 +32,8 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.protocol.WirePrefix;
import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.RecipientFactory;
+import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
@@ -46,6 +48,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
import org.whispersystems.textsecure.crypto.protocol.WhisperMessageV2;
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
+import org.whispersystems.textsecure.storage.RecipientDevice;
import java.io.IOException;
import java.util.List;
@@ -80,8 +83,9 @@ public class SmsReceiver {
if (masterSecret != null) {
DecryptingQueue.scheduleDecryption(context, masterSecret, messageAndThreadId.first,
messageAndThreadId.second,
- message.getSender(), message.getMessageBody(),
- message.isSecureMessage(), message.isKeyExchange());
+ message.getSender(), message.getSenderDeviceId(),
+ message.getMessageBody(), message.isSecureMessage(),
+ message.isKeyExchange());
}
return messageAndThreadId;
@@ -106,8 +110,9 @@ public class SmsReceiver {
Log.w("SmsReceiver", "Processing prekey message...");
try {
- Recipient recipient = new Recipient(null, message.getSender(), null, null);
- KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipient);
+ Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient();
+ RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSenderDeviceId());
+ KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipientDevice);
SmsTransportDetails transportDetails = new SmsTransportDetails();
byte[] rawMessage = transportDetails.getDecodedMessage(message.getMessageBody().getBytes());
PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(rawMessage);
@@ -142,6 +147,9 @@ public class SmsReceiver {
} catch (InvalidMessageException e) {
Log.w("SmsReceiver", e);
message.setCorrupted(true);
+ } catch (RecipientFormattingException e) {
+ Log.w("SmsReceiver", e);
+ message.setCorrupted(true);
}
return storeStandardMessage(masterSecret, message);
@@ -152,9 +160,10 @@ public class SmsReceiver {
{
if (masterSecret != null && TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) {
try {
- Recipient recipient = new Recipient(null, message.getSender(), null, null);
+ Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient();
+ RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSenderDeviceId());
KeyExchangeMessage exchangeMessage = KeyExchangeMessage.createFor(message.getMessageBody());
- KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipient, exchangeMessage);
+ KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipientDevice, exchangeMessage);
if (processor.isStale(exchangeMessage)) {
message.setStale(true);
@@ -175,6 +184,9 @@ public class SmsReceiver {
} catch (InvalidMessageException e) {
Log.w("SmsReceiver", e);
message.setCorrupted(true);
+ } catch (RecipientFormattingException e) {
+ Log.w("SmsReceiver", e);
+ message.setCorrupted(true);
}
}
diff --git a/src/org/thoughtcrime/securesms/service/SmsSender.java b/src/org/thoughtcrime/securesms/service/SmsSender.java
index 175f87ef54..f1953564e8 100644
--- a/src/org/thoughtcrime/securesms/service/SmsSender.java
+++ b/src/org/thoughtcrime/securesms/service/SmsSender.java
@@ -46,11 +46,11 @@ public class SmsSender {
}
public void process(MasterSecret masterSecret, Intent intent) {
- if (intent.getAction().equals(SendReceiveService.SEND_SMS_ACTION)) {
+ if (SendReceiveService.SEND_SMS_ACTION.equals(intent.getAction())) {
handleSendMessage(masterSecret, intent);
- } else if (intent.getAction().equals(SendReceiveService.SENT_SMS_ACTION)) {
+ } else if (SendReceiveService.SENT_SMS_ACTION.equals(intent.getAction())) {
handleSentMessage(intent);
- } else if (intent.getAction().equals(SendReceiveService.DELIVERED_SMS_ACTION)) {
+ } else if (SendReceiveService.DELIVERED_SMS_ACTION.equals(intent.getAction())) {
handleDeliveredMessage(intent);
}
}
diff --git a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java
index 3be14955f6..89027974f5 100644
--- a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java
+++ b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java
@@ -5,6 +5,7 @@ import android.os.Parcelable;
import android.telephony.SmsMessage;
import org.whispersystems.textsecure.push.IncomingPushMessage;
+import org.whispersystems.textsecure.storage.RecipientDevice;
import java.util.List;
@@ -24,6 +25,7 @@ public class IncomingTextMessage implements Parcelable {
private final String message;
private final String sender;
+ private final int senderDeviceId;
private final int protocol;
private final String serviceCenterAddress;
private final boolean replyPathPresent;
@@ -34,6 +36,7 @@ public class IncomingTextMessage implements Parcelable {
public IncomingTextMessage(SmsMessage message) {
this.message = message.getDisplayMessageBody();
this.sender = message.getDisplayOriginatingAddress();
+ this.senderDeviceId = RecipientDevice.DEFAULT_DEVICE_ID;
this.protocol = message.getProtocolIdentifier();
this.serviceCenterAddress = message.getServiceCenterAddress();
this.replyPathPresent = message.isReplyPathPresent();
@@ -45,6 +48,7 @@ public class IncomingTextMessage implements Parcelable {
public IncomingTextMessage(IncomingPushMessage message, String encodedBody, String groupId) {
this.message = encodedBody;
this.sender = message.getSource();
+ this.senderDeviceId = message.getSourceDevice();
this.protocol = 31337;
this.serviceCenterAddress = "GCM";
this.replyPathPresent = true;
@@ -56,6 +60,7 @@ public class IncomingTextMessage implements Parcelable {
public IncomingTextMessage(Parcel in) {
this.message = in.readString();
this.sender = in.readString();
+ this.senderDeviceId = in.readInt();
this.protocol = in.readInt();
this.serviceCenterAddress = in.readString();
this.replyPathPresent = (in.readInt() == 1);
@@ -67,6 +72,7 @@ public class IncomingTextMessage implements Parcelable {
public IncomingTextMessage(IncomingTextMessage base, String newBody) {
this.message = newBody;
this.sender = base.getSender();
+ this.senderDeviceId = base.getSenderDeviceId();
this.protocol = base.getProtocol();
this.serviceCenterAddress = base.getServiceCenterAddress();
this.replyPathPresent = base.isReplyPathPresent();
@@ -84,6 +90,7 @@ public class IncomingTextMessage implements Parcelable {
this.message = body.toString();
this.sender = fragments.get(0).getSender();
+ this.senderDeviceId = fragments.get(0).getSenderDeviceId();
this.protocol = fragments.get(0).getProtocol();
this.serviceCenterAddress = fragments.get(0).getServiceCenterAddress();
this.replyPathPresent = fragments.get(0).isReplyPathPresent();
@@ -112,6 +119,10 @@ public class IncomingTextMessage implements Parcelable {
return sender;
}
+ public int getSenderDeviceId() {
+ return senderDeviceId;
+ }
+
public int getProtocol() {
return protocol;
}
@@ -149,6 +160,7 @@ public class IncomingTextMessage implements Parcelable {
public void writeToParcel(Parcel out, int flags) {
out.writeString(message);
out.writeString(sender);
+ out.writeInt(senderDeviceId);
out.writeInt(protocol);
out.writeString(serviceCenterAddress);
out.writeInt(replyPathPresent ? 1 : 0);
diff --git a/src/org/thoughtcrime/securesms/transport/MmsTransport.java b/src/org/thoughtcrime/securesms/transport/MmsTransport.java
index d0b499949c..c7b5e0e610 100644
--- a/src/org/thoughtcrime/securesms/transport/MmsTransport.java
+++ b/src/org/thoughtcrime/securesms/transport/MmsTransport.java
@@ -29,9 +29,12 @@ import org.thoughtcrime.securesms.mms.MmsSendResult;
import org.thoughtcrime.securesms.mms.TextTransport;
import org.thoughtcrime.securesms.protocol.WirePrefix;
import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.RecipientFactory;
+import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
+import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.util.Hex;
import java.io.IOException;
@@ -153,12 +156,18 @@ public class MmsTransport {
}
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes) {
- TextTransport transportDetails = new TextTransport();
- Recipient recipient = new Recipient(null, recipientString, null, null);
- SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient);
- CiphertextMessage ciphertextMessage = sessionCipher.encrypt(pduBytes);
+ try {
+ TextTransport transportDetails = new TextTransport();
+ Recipient recipient = RecipientFactory.getRecipientsFromString(context, recipientString, false).getPrimaryRecipient();
+ RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
+ SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice);
+ CiphertextMessage ciphertextMessage = sessionCipher.encrypt(pduBytes);
- return transportDetails.getEncodedMessage(ciphertextMessage.serialize());
+ return transportDetails.getEncodedMessage(ciphertextMessage.serialize());
+ } catch (RecipientFormattingException e) {
+ Log.w("MmsTransport", e);
+ throw new AssertionError(e);
+ }
}
private boolean isInconsistentResponse(SendReq message, SendConf response) {
diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java
index 1bf26195cc..7aef3939c6 100644
--- a/src/org/thoughtcrime/securesms/transport/PushTransport.java
+++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java
@@ -23,6 +23,7 @@ import android.util.Log;
import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2;
+import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.mms.PartParser;
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
@@ -30,22 +31,24 @@ 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.util.TextSecurePreferences;
+import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.crypto.AttachmentCipher;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
+import org.whispersystems.textsecure.push.MismatchedDevices;
+import org.whispersystems.textsecure.push.MismatchedDevicesException;
import org.whispersystems.textsecure.push.OutgoingPushMessage;
+import org.whispersystems.textsecure.push.OutgoingPushMessageList;
import org.whispersystems.textsecure.push.PreKeyEntity;
+import org.whispersystems.textsecure.push.PushAddress;
import org.whispersystems.textsecure.push.PushAttachmentData;
import org.whispersystems.textsecure.push.PushAttachmentPointer;
import org.whispersystems.textsecure.push.PushBody;
-import org.whispersystems.textsecure.push.PushDestination;
import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
import org.whispersystems.textsecure.push.PushServiceSocket;
-import org.whispersystems.textsecure.push.RateLimitException;
import org.whispersystems.textsecure.push.UnregisteredUserException;
import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.util.InvalidNumberException;
@@ -70,79 +73,70 @@ public class PushTransport extends BaseTransport {
public void deliver(SmsMessageRecord message) throws IOException {
try {
- String localNumber = TextSecurePreferences.getLocalNumber(context);
- Recipient recipient = message.getIndividualRecipient();
- long threadId = message.getThreadId();
- PushServiceSocket socket = PushServiceSocketFactory.create(context);
- PushDestination destination = PushDestination.create(context, localNumber,
- recipient.getNumber());
+ Recipient recipient = message.getIndividualRecipient();
+ long threadId = message.getThreadId();
+ PushServiceSocket socket = PushServiceSocketFactory.create(context);
+ byte[] plaintext = PushMessageContent.newBuilder()
+ .setBody(message.getBody().getBody())
+ .build().toByteArray();
- String plaintextBody = message.getBody().getBody();
- byte[] plaintext = PushMessageContent.newBuilder().setBody(plaintextBody).build().toByteArray();
- PushBody pushBody = getEncryptedMessage(socket, threadId, recipient, destination, plaintext);
-
- socket.sendMessage(destination, pushBody);
+ deliver(socket, recipient, threadId, plaintext);
context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType(), true));
+
} catch (UnregisteredUserException e) {
Log.w("PushTransport", e);
- destroySessions(e.getAddresses());
+ //TODO We should probably remove the user from the directory?
+// destroySessions(message.getIndividualRecipient());
throw new IOException("Not push registered after all.");
- } catch (RateLimitException e) {
- Log.w("PushTransport", e);
- throw new IOException("Rate limit exceeded.");
} catch (InvalidNumberException e) {
Log.w("PushTransport", e);
throw new IOException("Badly formatted number.");
}
}
- public void deliver(SendReq message, List destinations, long threadId)
- throws IOException
- {
+ public void deliver(SendReq message, long threadId) throws IOException {
+ PushServiceSocket socket = PushServiceSocketFactory.create(context);
+ byte[] plaintext = getPlaintextMessage(socket, message);
+ String destination = message.getTo()[0].getString();
+
+ Recipients recipients;
+
try {
- PushServiceSocket socket = PushServiceSocketFactory.create(context);
- String messageBody = PartParser.getMessageText(message.getBody());
- List pushBodies = new LinkedList();
-
- for (PushDestination destination : destinations) {
- Recipients recipients = RecipientFactory.getRecipientsFromString(context, destination.getNumber(), false);
- List attachments = getPushAttachmentPointers(socket, message.getBody());
- PushMessageContent.Builder builder = PushMessageContent.newBuilder();
-
- if (messageBody != null) {
- builder.setBody(messageBody);
- }
-
- for (PushAttachmentPointer attachment : attachments) {
- PushMessageContent.AttachmentPointer.Builder attachmentBuilder =
- PushMessageContent.AttachmentPointer.newBuilder();
-
- attachmentBuilder.setId(attachment.getId());
- attachmentBuilder.setContentType(attachment.getContentType());
- attachmentBuilder.setKey(ByteString.copyFrom(attachment.getKey()));
-
- builder.addAttachments(attachmentBuilder.build());
- }
-
- byte[] plaintext = builder.build().toByteArray();
- PushBody pushBody = getEncryptedMessage(socket, threadId, recipients.getPrimaryRecipient(), destination, plaintext);
-
- pushBodies.add(pushBody);
+ if (GroupUtil.isEncodedGroup(destination)) {
+ recipients = DatabaseFactory.getGroupDatabase(context)
+ .getGroupMembers(GroupUtil.getDecodedId(destination));
+ } else {
+ recipients = RecipientFactory.getRecipientsFromString(context, destination, false);
}
- socket.sendMessage(destinations, pushBodies);
-
- } catch (UnregisteredUserException e) {
- Log.w("PushTransport", e);
- destroySessions(e.getAddresses());
- throw new IOException("No push registered after all.");
- } catch (RateLimitException e) {
- Log.w("PushTransport", e);
- throw new IOException("Rate limit exceeded.");
+ 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) {
- Log.w("PushTransport", e);
- throw new IOException("Bad destination!");
+ throw new IOException(e);
+ } catch (InvalidNumberException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private void deliver(PushServiceSocket socket, Recipient recipient, long threadId, byte[] plaintext)
+ throws IOException, InvalidNumberException
+ {
+ for (int i=0;i<3;i++) {
+ try {
+ OutgoingPushMessageList messages = getEncryptedMessages(socket, threadId,
+ recipient, plaintext);
+ socket.sendMessage(messages);
+
+ return;
+ } catch (MismatchedDevicesException mde) {
+ Log.w("PushTransport", mde);
+ handleMismatchedDevices(socket, threadId, recipient, mde.getMismatchedDevices());
+ }
}
}
@@ -170,23 +164,108 @@ public class PushTransport extends BaseTransport {
return attachments;
}
- private PushBody getEncryptedMessage(PushServiceSocket socket, long threadId, Recipient recipient,
- PushDestination pushDestination, byte[] plaintext)
- throws IOException
+ private void handleMismatchedDevices(PushServiceSocket socket, long threadId,
+ Recipient recipient,
+ MismatchedDevices mismatchedDevices)
+ throws InvalidNumberException, IOException
{
- if (!SessionRecordV2.hasSession(context, masterSecret, recipient)) {
- try {
- PreKeyEntity preKey = socket.getPreKey(pushDestination);
- KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipient);
+ try {
+ String e164number = Util.canonicalizeNumber(context, recipient.getNumber());
+ long recipientId = recipient.getRecipientId();
+
+ for (int extraDeviceId : mismatchedDevices.getExtraDevices()) {
+ PushAddress address = PushAddress.create(context, recipientId, e164number, extraDeviceId);
+ SessionRecordV2.delete(context, address);
+ }
+
+ for (int missingDeviceId : mismatchedDevices.getMissingDevices()) {
+ PushAddress address = PushAddress.create(context, recipientId, e164number, missingDeviceId);
+ PreKeyEntity preKey = socket.getPreKey(address);
+ KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, address);
processor.processKeyExchangeMessage(preKey, threadId);
+ }
+ } catch (InvalidKeyException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private byte[] getPlaintextMessage(PushServiceSocket socket, SendReq message) throws IOException {
+ String messageBody = PartParser.getMessageText(message.getBody());
+ List attachments = getPushAttachmentPointers(socket, message.getBody());
+
+ PushMessageContent.Builder builder = PushMessageContent.newBuilder();
+
+ if (GroupUtil.isEncodedGroup(message.getTo()[0].getString())) {
+ PushMessageContent.GroupContext.Builder groupBuilder =
+ PushMessageContent.GroupContext.newBuilder();
+
+ groupBuilder.setType(PushMessageContent.GroupContext.Type.DELIVER);
+ groupBuilder.setId(ByteString.copyFrom(GroupUtil.getDecodedId(message.getTo()[0].getString())));
+
+ builder.setGroup(groupBuilder.build());
+ }
+
+ if (messageBody != null) {
+ builder.setBody(messageBody);
+ }
+
+ for (PushAttachmentPointer attachment : attachments) {
+ PushMessageContent.AttachmentPointer.Builder attachmentBuilder =
+ PushMessageContent.AttachmentPointer.newBuilder();
+
+ attachmentBuilder.setId(attachment.getId());
+ attachmentBuilder.setContentType(attachment.getContentType());
+ attachmentBuilder.setKey(ByteString.copyFrom(attachment.getKey()));
+
+ builder.addAttachments(attachmentBuilder.build());
+ }
+
+ return builder.build().toByteArray();
+ }
+
+ private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket, long threadId,
+ Recipient recipient, byte[] plaintext)
+ throws IOException, InvalidNumberException
+ {
+ String e164number = Util.canonicalizeNumber(context, recipient.getNumber());
+ long recipientId = recipient.getRecipientId();
+ PushAddress masterDevice = PushAddress.create(context, recipientId, e164number, 1);
+ PushBody masterBody = getEncryptedMessage(socket, threadId, masterDevice, plaintext);
+
+ List messages = new LinkedList();
+ messages.add(new OutgoingPushMessage(masterDevice, masterBody));
+
+ for (int deviceId : SessionRecordV2.getSessionSubDevices(context, recipient)) {
+ PushAddress device = PushAddress.create(context, recipientId, e164number, deviceId);
+ PushBody body = getEncryptedMessage(socket, threadId, device, plaintext);
+
+ messages.add(new OutgoingPushMessage(device, body));
+ }
+
+ return new OutgoingPushMessageList(e164number, masterDevice.getRelay(), messages);
+ }
+
+ private PushBody getEncryptedMessage(PushServiceSocket socket, long threadId,
+ PushAddress pushAddress, byte[] plaintext)
+ throws IOException
+ {
+ if (!SessionRecordV2.hasSession(context, masterSecret, pushAddress)) {
+ try {
+ List preKeys = socket.getPreKeys(pushAddress);
+
+ for (PreKeyEntity preKey : preKeys) {
+ PushAddress device = PushAddress.create(context, pushAddress.getRecipientId(), pushAddress.getNumber(), preKey.getDeviceId());
+ KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, device);
+
+ processor.processKeyExchangeMessage(preKey, threadId);
+ }
} catch (InvalidKeyException e) {
- Log.w("PushTransport", e);
- throw new IOException("Invalid PreKey!");
+ throw new IOException(e);
}
}
- SessionCipher cipher = SessionCipher.createFor(context, masterSecret, recipient);
+ SessionCipher cipher = SessionCipher.createFor(context, masterSecret, pushAddress);
CiphertextMessage message = cipher.encrypt(plaintext);
if (message.getType() == CiphertextMessage.PREKEY_WHISPER_TYPE) {
@@ -198,15 +277,7 @@ public class PushTransport extends BaseTransport {
}
}
- private void destroySessions(List unregisteredUsers) {
- for (String unregisteredUser : unregisteredUsers) {
- Log.w("PushTransport", "Destroying session for: " + unregisteredUser);
- try {
- Recipients recipients = RecipientFactory.getRecipientsFromString(context, unregisteredUser, false);
- SessionRecordV2.delete(context, recipients.getPrimaryRecipient());
- } catch (RecipientFormattingException e) {
- Log.w("PushTransport", e);
- }
- }
+ private void destroySessions(Recipient recipient) {
+ SessionRecordV2.deleteAll(context, recipient);
}
}
diff --git a/src/org/thoughtcrime/securesms/transport/SmsTransport.java b/src/org/thoughtcrime/securesms/transport/SmsTransport.java
index 8959fd234d..2ed909a5a0 100644
--- a/src/org/thoughtcrime/securesms/transport/SmsTransport.java
+++ b/src/org/thoughtcrime/securesms/transport/SmsTransport.java
@@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
+import org.whispersystems.textsecure.storage.RecipientDevice;
import java.util.ArrayList;
@@ -160,9 +161,10 @@ public class SmsTransport extends BaseTransport {
OutgoingTextMessage message)
{
Recipient recipient = message.getRecipients().getPrimaryRecipient();
+ RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
String body = message.getMessageBody();
SmsTransportDetails transportDetails = new SmsTransportDetails();
- SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient);
+ SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice);
byte[] paddedPlaintext = transportDetails.getPaddedMessageBody(body.getBytes());
CiphertextMessage ciphertextMessage = sessionCipher.encrypt(paddedPlaintext);
String encodedCiphertext = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));
diff --git a/src/org/thoughtcrime/securesms/transport/UniversalTransport.java b/src/org/thoughtcrime/securesms/transport/UniversalTransport.java
index 923517141d..5da614301f 100644
--- a/src/org/thoughtcrime/securesms/transport/UniversalTransport.java
+++ b/src/org/thoughtcrime/securesms/transport/UniversalTransport.java
@@ -23,21 +23,18 @@ 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.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
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.ContactTokenDetails;
-import org.whispersystems.textsecure.push.PushDestination;
import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.util.InvalidNumberException;
import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.SendReq;
public class UniversalTransport {
@@ -85,60 +82,56 @@ public class UniversalTransport {
public MmsSendResult deliver(SendReq mediaMessage, long threadId)
throws UndeliverableMessageException
{
+ if (Util.isEmpty(mediaMessage.getTo())) {
+ throw new UndeliverableMessageException("No destination specified");
+ }
+
if (!TextSecurePreferences.isPushRegistered(context)) {
return mmsTransport.deliver(mediaMessage);
}
- try {
- List destinations = getMediaDestinations(mediaMessage);
+ if (isMultipleRecipients(mediaMessage)) {
+ return mmsTransport.deliver(mediaMessage);
+ }
- if (isPushTransport(destinations)) {
- try {
- Log.w("UniversalTransport", "Delivering media message with GCM...");
- pushTransport.deliver(mediaMessage, destinations, threadId);
- return new MmsSendResult("push".getBytes("UTF-8"), 0, true);
- } catch (IOException ioe) {
- Log.w("UniversalTransport", ioe);
- return mmsTransport.deliver(mediaMessage);
- }
- } else {
- Log.w("UniversalTransport", "Delivering media message with MMS...");
+ if (isPushTransport(mediaMessage.getTo()[0].getString())) {
+ try {
+ Log.w("UniversalTransport", "Delivering media message with GCM...");
+ pushTransport.deliver(mediaMessage, threadId);
+ return new MmsSendResult("push".getBytes("UTF-8"), 0, true);
+ } catch (IOException ioe) {
+ Log.w("UniversalTransport", ioe);
return mmsTransport.deliver(mediaMessage);
}
- } catch (InvalidNumberException e) {
- Log.w("UniversalTransport", e);
+ } else {
+ Log.w("UniversalTransport", "Delivering media message with MMS...");
return mmsTransport.deliver(mediaMessage);
}
}
- private List getMediaDestinations(SendReq mediaMessage)
- throws InvalidNumberException
- {
- String localNumber = TextSecurePreferences.getLocalNumber(context);
- LinkedList destinations = new LinkedList();
+ public boolean isMultipleRecipients(SendReq mediaMessage) {
+ int recipientCount = 0;
if (mediaMessage.getTo() != null) {
- for (EncodedStringValue to : mediaMessage.getTo()) {
- destinations.add(PushDestination.create(context, localNumber, to.getString()));
- }
+ recipientCount += mediaMessage.getTo().length;
}
if (mediaMessage.getCc() != null) {
- for (EncodedStringValue cc : mediaMessage.getCc()) {
- destinations.add(PushDestination.create(context, localNumber, cc.getString()));
- }
+ recipientCount += mediaMessage.getCc().length;
}
if (mediaMessage.getBcc() != null) {
- for (EncodedStringValue bcc : mediaMessage.getBcc()) {
- destinations.add(PushDestination.create(context, localNumber, bcc.getString()));
- }
+ recipientCount += mediaMessage.getBcc().length;
}
- return destinations;
+ return recipientCount > 1;
}
private boolean isPushTransport(String destination) {
+ if (GroupUtil.isEncodedGroup(destination)) {
+ return true;
+ }
+
Directory directory = Directory.getInstance(context);
try {
@@ -163,14 +156,4 @@ public class UniversalTransport {
}
}
}
-
- private boolean isPushTransport(List destinations) {
- for (PushDestination destination : destinations) {
- if (!isPushTransport(destination.getNumber())) {
- return false;
- }
- }
-
- return true;
- }
}
diff --git a/src/org/thoughtcrime/securesms/util/GroupUtil.java b/src/org/thoughtcrime/securesms/util/GroupUtil.java
new file mode 100644
index 0000000000..843641c3a7
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/util/GroupUtil.java
@@ -0,0 +1,27 @@
+package org.thoughtcrime.securesms.util;
+
+import org.whispersystems.textsecure.util.Hex;
+
+import java.io.IOException;
+
+public class GroupUtil {
+
+ private static final String ENCODED_GROUP_PREFIX = "__textsecure_group__!";
+
+ public static String getEncodedId(byte[] groupId) {
+ return ENCODED_GROUP_PREFIX + Hex.toStringCondensed(groupId);
+ }
+
+ public static byte[] getDecodedId(String groupId) throws IOException {
+ if (!isEncodedGroup(groupId)) {
+ throw new IOException("Invalid encoding");
+ }
+
+ return Hex.fromStringCondensed(groupId.split("!", 2)[1]);
+ }
+
+ public static boolean isEncodedGroup(String groupId) {
+ return groupId.startsWith(ENCODED_GROUP_PREFIX);
+ }
+
+}
diff --git a/src/org/thoughtcrime/securesms/util/NumberUtil.java b/src/org/thoughtcrime/securesms/util/NumberUtil.java
index 84749ad9e7..5cb3433451 100644
--- a/src/org/thoughtcrime/securesms/util/NumberUtil.java
+++ b/src/org/thoughtcrime/securesms/util/NumberUtil.java
@@ -34,6 +34,12 @@ public class NumberUtil {
return PhoneNumberUtils.isWellFormedSmsAddress(number) || isValidEmail(number);
}
+ public static boolean isValidSmsOrEmailOrGroup(String number) {
+ return PhoneNumberUtils.isWellFormedSmsAddress(number) ||
+ isValidEmail(number) ||
+ GroupUtil.isEncodedGroup(number);
+ }
+
public static String filterNumber(String number) {
if (number == null) return null;