diff --git a/library/protobuf/LocalStorageProtocol.proto b/library/protobuf/LocalStorageProtocol.proto index ff8f8c0b2b..db5b9f917b 100644 --- a/library/protobuf/LocalStorageProtocol.proto +++ b/library/protobuf/LocalStorageProtocol.proto @@ -51,6 +51,9 @@ message SessionStructure { optional PendingKeyExchange pendingKeyExchange = 8; optional PendingPreKey pendingPreKey = 9; + + optional uint32 remoteRegistrationId = 10; + optional uint32 localRegistrationId = 11; } message PreKeyRecordStructure { diff --git a/library/protobuf/WhisperTextProtocol.proto b/library/protobuf/WhisperTextProtocol.proto index f14625b96b..458e9edc8e 100644 --- a/library/protobuf/WhisperTextProtocol.proto +++ b/library/protobuf/WhisperTextProtocol.proto @@ -11,6 +11,7 @@ message WhisperMessage { } message PreKeyWhisperMessage { + optional uint32 registrationId = 5; optional uint32 preKeyId = 1; optional bytes baseKey = 2; optional bytes identityKey = 3; diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java index ebbe2ed1d6..cac3657e0a 100644 --- a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java +++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java @@ -30,6 +30,7 @@ public abstract class SessionCipher { public abstract CiphertextMessage encrypt(byte[] paddedMessage); public abstract byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException; + public abstract int getRemoteRegistrationId(); public static SessionCipher createFor(Context context, MasterSecret masterSecret, diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipherV1.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV1.java index f9f7a1e48e..23cf301a40 100644 --- a/library/src/org/whispersystems/textsecure/crypto/SessionCipherV1.java +++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV1.java @@ -82,6 +82,11 @@ public class SessionCipherV1 extends SessionCipher { } } + @Override + public int getRemoteRegistrationId() { + return 0; + } + private SessionCipherContext getEncryptionContext() { try { KeyRecords records = getKeyRecords(context, masterSecret, recipient); diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java index 40b5afbe48..338856deee 100644 --- a/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java +++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java @@ -36,9 +36,9 @@ public class SessionCipherV2 extends SessionCipher { MasterSecret masterSecret, RecipientDevice recipient) { - this.context = context; - this.masterSecret = masterSecret; - this.recipient = recipient; + this.context = context; + this.masterSecret = masterSecret; + this.recipient = recipient; } @Override @@ -56,8 +56,11 @@ public class SessionCipherV2 extends SessionCipher { previousCounter, ciphertextBody); if (sessionRecord.hasPendingPreKey()) { - Pair pendingPreKey = sessionRecord.getPendingPreKey(); - ciphertextMessage = new PreKeyWhisperMessage(pendingPreKey.first, pendingPreKey.second, + Pair pendingPreKey = sessionRecord.getPendingPreKey(); + int localRegistrationId = sessionRecord.getLocalRegistrationId(); + + ciphertextMessage = new PreKeyWhisperMessage(localRegistrationId, pendingPreKey.first, + pendingPreKey.second, sessionRecord.getLocalIdentityKey(), (WhisperMessageV2) ciphertextMessage); } @@ -91,6 +94,14 @@ public class SessionCipherV2 extends SessionCipher { } } + @Override + public int getRemoteRegistrationId() { + synchronized (SESSION_LOCK) { + SessionRecordV2 sessionRecord = getSessionRecord(); + return sessionRecord.getRemoteRegistrationId(); + } + } + private ChainKey getOrCreateChainKey(SessionRecordV2 sessionRecord, ECPublicKey theirEphemeral) throws InvalidMessageException { diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyWhisperMessage.java b/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyWhisperMessage.java index bd0226ce8c..a966194f1e 100644 --- a/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyWhisperMessage.java +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyWhisperMessage.java @@ -15,6 +15,7 @@ import org.whispersystems.textsecure.util.Util; public class PreKeyWhisperMessage implements CiphertextMessage { private final int version; + private final int registrationId; private final int preKeyId; private final ECPublicKey baseKey; private final IdentityKey identityKey; @@ -43,11 +44,12 @@ public class PreKeyWhisperMessage implements CiphertextMessage { throw new InvalidMessageException("Incomplete message."); } - this.serialized = serialized; - this.preKeyId = preKeyWhisperMessage.getPreKeyId(); - this.baseKey = Curve.decodePoint(preKeyWhisperMessage.getBaseKey().toByteArray(), 0); - this.identityKey = new IdentityKey(Curve.decodePoint(preKeyWhisperMessage.getIdentityKey().toByteArray(), 0)); - this.message = new WhisperMessageV2(preKeyWhisperMessage.getMessage().toByteArray()); + this.serialized = serialized; + this.registrationId = preKeyWhisperMessage.getRegistrationId(); + this.preKeyId = preKeyWhisperMessage.getPreKeyId(); + this.baseKey = Curve.decodePoint(preKeyWhisperMessage.getBaseKey().toByteArray(), 0); + this.identityKey = new IdentityKey(Curve.decodePoint(preKeyWhisperMessage.getIdentityKey().toByteArray(), 0)); + this.message = new WhisperMessageV2(preKeyWhisperMessage.getMessage().toByteArray()); } catch (InvalidProtocolBufferException e) { throw new InvalidMessageException(e); } catch (InvalidKeyException e) { @@ -55,14 +57,15 @@ public class PreKeyWhisperMessage implements CiphertextMessage { } } - public PreKeyWhisperMessage(int preKeyId, ECPublicKey baseKey, IdentityKey identityKey, - WhisperMessageV2 message) + public PreKeyWhisperMessage(int registrationId, int preKeyId, ECPublicKey baseKey, + IdentityKey identityKey, WhisperMessageV2 message) { - this.version = CiphertextMessage.CURRENT_VERSION; - this.preKeyId = preKeyId; - this.baseKey = baseKey; - this.identityKey = identityKey; - this.message = message; + this.version = CiphertextMessage.CURRENT_VERSION; + this.registrationId = registrationId; + this.preKeyId = preKeyId; + this.baseKey = baseKey; + this.identityKey = identityKey; + this.message = message; byte[] versionBytes = {Conversions.intsToByteHighAndLow(CURRENT_VERSION, this.version)}; byte[] messageBytes = WhisperProtos.PreKeyWhisperMessage.newBuilder() @@ -70,6 +73,7 @@ public class PreKeyWhisperMessage implements CiphertextMessage { .setBaseKey(ByteString.copyFrom(baseKey.serialize())) .setIdentityKey(ByteString.copyFrom(identityKey.serialize())) .setMessage(ByteString.copyFrom(message.serialize())) + .setRegistrationId(registrationId) .build().toByteArray(); this.serialized = Util.combine(versionBytes, messageBytes); @@ -79,6 +83,10 @@ public class PreKeyWhisperMessage implements CiphertextMessage { return identityKey; } + public int getRegistrationId() { + return registrationId; + } + public int getPreKeyId() { return preKeyId; } diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperProtos.java b/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperProtos.java index ba1dfddbb2..4af21a3699 100644 --- a/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperProtos.java +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperProtos.java @@ -526,6 +526,10 @@ public final class WhisperProtos { public interface PreKeyWhisperMessageOrBuilder extends com.google.protobuf.MessageOrBuilder { + // optional uint32 registrationId = 5; + boolean hasRegistrationId(); + int getRegistrationId(); + // optional uint32 preKeyId = 1; boolean hasPreKeyId(); int getPreKeyId(); @@ -571,11 +575,21 @@ public final class WhisperProtos { } private int bitField0_; + // optional uint32 registrationId = 5; + public static final int REGISTRATIONID_FIELD_NUMBER = 5; + private int registrationId_; + public boolean hasRegistrationId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getRegistrationId() { + return registrationId_; + } + // optional uint32 preKeyId = 1; public static final int PREKEYID_FIELD_NUMBER = 1; private int preKeyId_; public boolean hasPreKeyId() { - return ((bitField0_ & 0x00000001) == 0x00000001); + return ((bitField0_ & 0x00000002) == 0x00000002); } public int getPreKeyId() { return preKeyId_; @@ -585,7 +599,7 @@ public final class WhisperProtos { public static final int BASEKEY_FIELD_NUMBER = 2; private com.google.protobuf.ByteString baseKey_; public boolean hasBaseKey() { - return ((bitField0_ & 0x00000002) == 0x00000002); + return ((bitField0_ & 0x00000004) == 0x00000004); } public com.google.protobuf.ByteString getBaseKey() { return baseKey_; @@ -595,7 +609,7 @@ public final class WhisperProtos { public static final int IDENTITYKEY_FIELD_NUMBER = 3; private com.google.protobuf.ByteString identityKey_; public boolean hasIdentityKey() { - return ((bitField0_ & 0x00000004) == 0x00000004); + return ((bitField0_ & 0x00000008) == 0x00000008); } public com.google.protobuf.ByteString getIdentityKey() { return identityKey_; @@ -605,13 +619,14 @@ public final class WhisperProtos { public static final int MESSAGE_FIELD_NUMBER = 4; private com.google.protobuf.ByteString message_; public boolean hasMessage() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000010) == 0x00000010); } public com.google.protobuf.ByteString getMessage() { return message_; } private void initFields() { + registrationId_ = 0; preKeyId_ = 0; baseKey_ = com.google.protobuf.ByteString.EMPTY; identityKey_ = com.google.protobuf.ByteString.EMPTY; @@ -629,18 +644,21 @@ public final class WhisperProtos { public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (((bitField0_ & 0x00000002) == 0x00000002)) { output.writeUInt32(1, preKeyId_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (((bitField0_ & 0x00000004) == 0x00000004)) { output.writeBytes(2, baseKey_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (((bitField0_ & 0x00000008) == 0x00000008)) { output.writeBytes(3, identityKey_); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (((bitField0_ & 0x00000010) == 0x00000010)) { output.writeBytes(4, message_); } + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeUInt32(5, registrationId_); + } getUnknownFields().writeTo(output); } @@ -650,22 +668,26 @@ public final class WhisperProtos { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (((bitField0_ & 0x00000002) == 0x00000002)) { size += com.google.protobuf.CodedOutputStream .computeUInt32Size(1, preKeyId_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (((bitField0_ & 0x00000004) == 0x00000004)) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(2, baseKey_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (((bitField0_ & 0x00000008) == 0x00000008)) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(3, identityKey_); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (((bitField0_ & 0x00000010) == 0x00000010)) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(4, message_); } + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(5, registrationId_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -790,14 +812,16 @@ public final class WhisperProtos { public Builder clear() { super.clear(); - preKeyId_ = 0; + registrationId_ = 0; bitField0_ = (bitField0_ & ~0x00000001); - baseKey_ = com.google.protobuf.ByteString.EMPTY; + preKeyId_ = 0; bitField0_ = (bitField0_ & ~0x00000002); - identityKey_ = com.google.protobuf.ByteString.EMPTY; + baseKey_ = com.google.protobuf.ByteString.EMPTY; bitField0_ = (bitField0_ & ~0x00000004); - message_ = com.google.protobuf.ByteString.EMPTY; + identityKey_ = com.google.protobuf.ByteString.EMPTY; bitField0_ = (bitField0_ & ~0x00000008); + message_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000010); return this; } @@ -839,18 +863,22 @@ public final class WhisperProtos { if (((from_bitField0_ & 0x00000001) == 0x00000001)) { to_bitField0_ |= 0x00000001; } - result.preKeyId_ = preKeyId_; + result.registrationId_ = registrationId_; if (((from_bitField0_ & 0x00000002) == 0x00000002)) { to_bitField0_ |= 0x00000002; } - result.baseKey_ = baseKey_; + result.preKeyId_ = preKeyId_; if (((from_bitField0_ & 0x00000004) == 0x00000004)) { to_bitField0_ |= 0x00000004; } - result.identityKey_ = identityKey_; + result.baseKey_ = baseKey_; if (((from_bitField0_ & 0x00000008) == 0x00000008)) { to_bitField0_ |= 0x00000008; } + result.identityKey_ = identityKey_; + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + to_bitField0_ |= 0x00000010; + } result.message_ = message_; result.bitField0_ = to_bitField0_; onBuilt(); @@ -868,6 +896,9 @@ public final class WhisperProtos { public Builder mergeFrom(org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage other) { if (other == org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage.getDefaultInstance()) return this; + if (other.hasRegistrationId()) { + setRegistrationId(other.getRegistrationId()); + } if (other.hasPreKeyId()) { setPreKeyId(other.getPreKeyId()); } @@ -912,47 +943,73 @@ public final class WhisperProtos { break; } case 8: { - bitField0_ |= 0x00000001; + bitField0_ |= 0x00000002; preKeyId_ = input.readUInt32(); break; } case 18: { - bitField0_ |= 0x00000002; + bitField0_ |= 0x00000004; baseKey_ = input.readBytes(); break; } case 26: { - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000008; identityKey_ = input.readBytes(); break; } case 34: { - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; message_ = input.readBytes(); break; } + case 40: { + bitField0_ |= 0x00000001; + registrationId_ = input.readUInt32(); + break; + } } } } private int bitField0_; + // optional uint32 registrationId = 5; + private int registrationId_ ; + public boolean hasRegistrationId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getRegistrationId() { + return registrationId_; + } + public Builder setRegistrationId(int value) { + bitField0_ |= 0x00000001; + registrationId_ = value; + onChanged(); + return this; + } + public Builder clearRegistrationId() { + bitField0_ = (bitField0_ & ~0x00000001); + registrationId_ = 0; + onChanged(); + return this; + } + // optional uint32 preKeyId = 1; private int preKeyId_ ; public boolean hasPreKeyId() { - return ((bitField0_ & 0x00000001) == 0x00000001); + return ((bitField0_ & 0x00000002) == 0x00000002); } public int getPreKeyId() { return preKeyId_; } public Builder setPreKeyId(int value) { - bitField0_ |= 0x00000001; + bitField0_ |= 0x00000002; preKeyId_ = value; onChanged(); return this; } public Builder clearPreKeyId() { - bitField0_ = (bitField0_ & ~0x00000001); + bitField0_ = (bitField0_ & ~0x00000002); preKeyId_ = 0; onChanged(); return this; @@ -961,7 +1018,7 @@ public final class WhisperProtos { // optional bytes baseKey = 2; private com.google.protobuf.ByteString baseKey_ = com.google.protobuf.ByteString.EMPTY; public boolean hasBaseKey() { - return ((bitField0_ & 0x00000002) == 0x00000002); + return ((bitField0_ & 0x00000004) == 0x00000004); } public com.google.protobuf.ByteString getBaseKey() { return baseKey_; @@ -970,13 +1027,13 @@ public final class WhisperProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + bitField0_ |= 0x00000004; baseKey_ = value; onChanged(); return this; } public Builder clearBaseKey() { - bitField0_ = (bitField0_ & ~0x00000002); + bitField0_ = (bitField0_ & ~0x00000004); baseKey_ = getDefaultInstance().getBaseKey(); onChanged(); return this; @@ -985,7 +1042,7 @@ public final class WhisperProtos { // optional bytes identityKey = 3; private com.google.protobuf.ByteString identityKey_ = com.google.protobuf.ByteString.EMPTY; public boolean hasIdentityKey() { - return ((bitField0_ & 0x00000004) == 0x00000004); + return ((bitField0_ & 0x00000008) == 0x00000008); } public com.google.protobuf.ByteString getIdentityKey() { return identityKey_; @@ -994,13 +1051,13 @@ public final class WhisperProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000008; identityKey_ = value; onChanged(); return this; } public Builder clearIdentityKey() { - bitField0_ = (bitField0_ & ~0x00000004); + bitField0_ = (bitField0_ & ~0x00000008); identityKey_ = getDefaultInstance().getIdentityKey(); onChanged(); return this; @@ -1009,7 +1066,7 @@ public final class WhisperProtos { // optional bytes message = 4; private com.google.protobuf.ByteString message_ = com.google.protobuf.ByteString.EMPTY; public boolean hasMessage() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000010) == 0x00000010); } public com.google.protobuf.ByteString getMessage() { return message_; @@ -1018,13 +1075,13 @@ public final class WhisperProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; message_ = value; onChanged(); return this; } public Builder clearMessage() { - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000010); message_ = getDefaultInstance().getMessage(); onChanged(); return this; @@ -1586,13 +1643,14 @@ public final class WhisperProtos { "\n\031WhisperTextProtocol.proto\022\ntextsecure\"" + "d\n\016WhisperMessage\022\024\n\014ephemeralKey\030\001 \001(\014\022" + "\017\n\007counter\030\002 \001(\r\022\027\n\017previousCounter\030\003 \001(" + - "\r\022\022\n\nciphertext\030\004 \001(\014\"_\n\024PreKeyWhisperMe" + - "ssage\022\020\n\010preKeyId\030\001 \001(\r\022\017\n\007baseKey\030\002 \001(\014" + - "\022\023\n\013identityKey\030\003 \001(\014\022\017\n\007message\030\004 \001(\014\"\\" + - "\n\022KeyExchangeMessage\022\n\n\002id\030\001 \001(\r\022\017\n\007base" + - "Key\030\002 \001(\014\022\024\n\014ephemeralKey\030\003 \001(\014\022\023\n\013ident" + - "ityKey\030\004 \001(\014B>\n-org.whispersystems.texts" + - "ecure.crypto.protocolB\rWhisperProtos" + "\r\022\022\n\nciphertext\030\004 \001(\014\"w\n\024PreKeyWhisperMe" + + "ssage\022\026\n\016registrationId\030\005 \001(\r\022\020\n\010preKeyI" + + "d\030\001 \001(\r\022\017\n\007baseKey\030\002 \001(\014\022\023\n\013identityKey\030" + + "\003 \001(\014\022\017\n\007message\030\004 \001(\014\"\\\n\022KeyExchangeMes" + + "sage\022\n\n\002id\030\001 \001(\r\022\017\n\007baseKey\030\002 \001(\014\022\024\n\014eph" + + "emeralKey\030\003 \001(\014\022\023\n\013identityKey\030\004 \001(\014B>\n-" + + "org.whispersystems.textsecure.crypto.pro", + "tocolB\rWhisperProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -1612,7 +1670,7 @@ public final class WhisperProtos { internal_static_textsecure_PreKeyWhisperMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_PreKeyWhisperMessage_descriptor, - new java.lang.String[] { "PreKeyId", "BaseKey", "IdentityKey", "Message", }, + new java.lang.String[] { "RegistrationId", "PreKeyId", "BaseKey", "IdentityKey", "Message", }, org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage.class, org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage.Builder.class); internal_static_textsecure_KeyExchangeMessage_descriptor = diff --git a/library/src/org/whispersystems/textsecure/push/AccountAttributes.java b/library/src/org/whispersystems/textsecure/push/AccountAttributes.java index d8328a7dbf..0e00bbb0f7 100644 --- a/library/src/org/whispersystems/textsecure/push/AccountAttributes.java +++ b/library/src/org/whispersystems/textsecure/push/AccountAttributes.java @@ -2,12 +2,14 @@ package org.whispersystems.textsecure.push; public class AccountAttributes { - private String signalingKey; + private String signalingKey; private boolean supportsSms; + private int registrationId; - public AccountAttributes(String signalingKey, boolean supportsSms) { - this.signalingKey = signalingKey; - this.supportsSms = supportsSms; + public AccountAttributes(String signalingKey, boolean supportsSms, int registrationId) { + this.signalingKey = signalingKey; + this.supportsSms = supportsSms; + this.registrationId = registrationId; } public AccountAttributes() {} @@ -19,4 +21,8 @@ public class AccountAttributes { public boolean isSupportsSms() { return supportsSms; } + + public int getRegistrationId() { + return registrationId; + } } diff --git a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java index a0b42e2421..39c250e4a3 100644 --- a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java +++ b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java @@ -22,12 +22,14 @@ public class OutgoingPushMessage { private int type; private int destinationDeviceId; + private int destinationRegistrationId; private String body; public OutgoingPushMessage(PushAddress address, PushBody body) { - this.type = body.getType(); - this.destinationDeviceId = address.getDeviceId(); - this.body = Base64.encodeBytes(body.getBody()); + this.type = body.getType(); + this.destinationDeviceId = address.getDeviceId(); + this.destinationRegistrationId = body.getRemoteRegistrationId(); + this.body = Base64.encodeBytes(body.getBody()); } public int getDestinationDeviceId() { @@ -41,4 +43,8 @@ public class OutgoingPushMessage { public int getType() { return type; } + + public int getDestinationRegistrationId() { + return destinationRegistrationId; + } } diff --git a/library/src/org/whispersystems/textsecure/push/PreKeyEntity.java b/library/src/org/whispersystems/textsecure/push/PreKeyEntity.java index ac967e4e7b..fd0559509d 100644 --- a/library/src/org/whispersystems/textsecure/push/PreKeyEntity.java +++ b/library/src/org/whispersystems/textsecure/push/PreKeyEntity.java @@ -27,11 +27,13 @@ public class PreKeyEntity { private int keyId; private ECPublicKey publicKey; private IdentityKey identityKey; + private int registrationId; public PreKeyEntity(int keyId, ECPublicKey publicKey, IdentityKey identityKey) { - this.keyId = keyId; - this.publicKey = publicKey; - this.identityKey = identityKey; + this.keyId = keyId; + this.publicKey = publicKey; + this.identityKey = identityKey; + this.registrationId = registrationId; } public int getDeviceId() { @@ -50,6 +52,10 @@ public class PreKeyEntity { return identityKey; } + public int getRegistrationId() { + return registrationId; + } + public static String toJson(PreKeyEntity entity) { return getBuilder().create().toJson(entity); } @@ -66,6 +72,7 @@ public class PreKeyEntity { return builder; } + private static class ECPublicKeyJsonAdapter implements JsonSerializer, JsonDeserializer { diff --git a/library/src/org/whispersystems/textsecure/push/PushBody.java b/library/src/org/whispersystems/textsecure/push/PushBody.java index 4e920c5413..a93a27aa1a 100644 --- a/library/src/org/whispersystems/textsecure/push/PushBody.java +++ b/library/src/org/whispersystems/textsecure/push/PushBody.java @@ -3,11 +3,13 @@ package org.whispersystems.textsecure.push; public class PushBody { private final int type; + private final int remoteRegistrationId; private final byte[] body; - public PushBody(int type, byte[] body) { - this.type = type; - this.body = body; + public PushBody(int type, int remoteRegistrationId, byte[] body) { + this.type = type; + this.remoteRegistrationId = remoteRegistrationId; + this.body = body; } public int getType() { @@ -17,4 +19,8 @@ public class PushBody { public byte[] getBody() { return body; } + + public int getRemoteRegistrationId() { + return remoteRegistrationId; + } } diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java index f6b6656e9e..c487e5e7d9 100644 --- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java +++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java @@ -71,10 +71,11 @@ public class PushServiceSocket { makeRequest(String.format(path, localNumber), "GET", null); } - public void verifyAccount(String verificationCode, String signalingKey, boolean supportsSms) + public void verifyAccount(String verificationCode, String signalingKey, + boolean supportsSms, int registrationId) throws IOException { - AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, supportsSms); + AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, supportsSms, registrationId); makeRequest(String.format(VERIFY_ACCOUNT_PATH, verificationCode), "PUT", new Gson().toJson(signalingKeyEntity)); } @@ -324,6 +325,11 @@ public class PushServiceSocket { throw new MismatchedDevicesException(new Gson().fromJson(response, MismatchedDevices.class)); } + if (connection.getResponseCode() == 410) { + String response = Util.readFully(connection.getErrorStream()); + throw new StaleDevicesException(new Gson().fromJson(response, StaleDevices.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/StaleDevices.java b/library/src/org/whispersystems/textsecure/push/StaleDevices.java new file mode 100644 index 0000000000..f0f0d00ce2 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/push/StaleDevices.java @@ -0,0 +1,12 @@ +package org.whispersystems.textsecure.push; + +import java.util.List; + +public class StaleDevices { + + private List staleDevices; + + public List getStaleDevices() { + return staleDevices; + } +} diff --git a/library/src/org/whispersystems/textsecure/push/StaleDevicesException.java b/library/src/org/whispersystems/textsecure/push/StaleDevicesException.java new file mode 100644 index 0000000000..7b638aa02e --- /dev/null +++ b/library/src/org/whispersystems/textsecure/push/StaleDevicesException.java @@ -0,0 +1,16 @@ +package org.whispersystems.textsecure.push; + +import java.io.IOException; + +public class StaleDevicesException extends IOException { + + private final StaleDevices staleDevices; + + public StaleDevicesException(StaleDevices staleDevices) { + this.staleDevices = staleDevices; + } + + public StaleDevices getStaleDevices() { + return staleDevices; + } +} diff --git a/library/src/org/whispersystems/textsecure/storage/SessionRecordV2.java b/library/src/org/whispersystems/textsecure/storage/SessionRecordV2.java index 18fc90da59..6a0fdb6ce4 100644 --- a/library/src/org/whispersystems/textsecure/storage/SessionRecordV2.java +++ b/library/src/org/whispersystems/textsecure/storage/SessionRecordV2.java @@ -497,6 +497,26 @@ public class SessionRecordV2 extends Record { .build(); } + public void setRemoteRegistrationId(int registrationId) { + this.sessionStructure = this.sessionStructure.toBuilder() + .setRemoteRegistrationId(registrationId) + .build(); + } + + public int getRemoteRegistrationId() { + return this.sessionStructure.getRemoteRegistrationId(); + } + + public void setLocalRegistrationId(int registrationId) { + this.sessionStructure = this.sessionStructure.toBuilder() + .setLocalRegistrationId(registrationId) + .build(); + } + + public int getLocalRegistrationId() { + return this.sessionStructure.getLocalRegistrationId(); + } + public void save() { synchronized (FILE_LOCK) { try { diff --git a/library/src/org/whispersystems/textsecure/storage/StorageProtos.java b/library/src/org/whispersystems/textsecure/storage/StorageProtos.java index 0afdf31eb3..898745dcca 100644 --- a/library/src/org/whispersystems/textsecure/storage/StorageProtos.java +++ b/library/src/org/whispersystems/textsecure/storage/StorageProtos.java @@ -55,6 +55,14 @@ public final class StorageProtos { boolean hasPendingPreKey(); org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey getPendingPreKey(); org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKeyOrBuilder getPendingPreKeyOrBuilder(); + + // optional uint32 remoteRegistrationId = 10; + boolean hasRemoteRegistrationId(); + int getRemoteRegistrationId(); + + // optional uint32 localRegistrationId = 11; + boolean hasLocalRegistrationId(); + int getLocalRegistrationId(); } public static final class SessionStructure extends com.google.protobuf.GeneratedMessage @@ -2964,6 +2972,26 @@ public final class StorageProtos { return pendingPreKey_; } + // optional uint32 remoteRegistrationId = 10; + public static final int REMOTEREGISTRATIONID_FIELD_NUMBER = 10; + private int remoteRegistrationId_; + public boolean hasRemoteRegistrationId() { + return ((bitField0_ & 0x00000100) == 0x00000100); + } + public int getRemoteRegistrationId() { + return remoteRegistrationId_; + } + + // optional uint32 localRegistrationId = 11; + public static final int LOCALREGISTRATIONID_FIELD_NUMBER = 11; + private int localRegistrationId_; + public boolean hasLocalRegistrationId() { + return ((bitField0_ & 0x00000200) == 0x00000200); + } + public int getLocalRegistrationId() { + return localRegistrationId_; + } + private void initFields() { sessionVersion_ = 0; localIdentityPublic_ = com.google.protobuf.ByteString.EMPTY; @@ -2974,6 +3002,8 @@ public final class StorageProtos { receiverChains_ = java.util.Collections.emptyList(); pendingKeyExchange_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.getDefaultInstance(); pendingPreKey_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.getDefaultInstance(); + remoteRegistrationId_ = 0; + localRegistrationId_ = 0; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -3014,6 +3044,12 @@ public final class StorageProtos { if (((bitField0_ & 0x00000080) == 0x00000080)) { output.writeMessage(9, pendingPreKey_); } + if (((bitField0_ & 0x00000100) == 0x00000100)) { + output.writeUInt32(10, remoteRegistrationId_); + } + if (((bitField0_ & 0x00000200) == 0x00000200)) { + output.writeUInt32(11, localRegistrationId_); + } getUnknownFields().writeTo(output); } @@ -3059,6 +3095,14 @@ public final class StorageProtos { size += com.google.protobuf.CodedOutputStream .computeMessageSize(9, pendingPreKey_); } + if (((bitField0_ & 0x00000100) == 0x00000100)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(10, remoteRegistrationId_); + } + if (((bitField0_ & 0x00000200) == 0x00000200)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(11, localRegistrationId_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -3221,6 +3265,10 @@ public final class StorageProtos { pendingPreKeyBuilder_.clear(); } bitField0_ = (bitField0_ & ~0x00000100); + remoteRegistrationId_ = 0; + bitField0_ = (bitField0_ & ~0x00000200); + localRegistrationId_ = 0; + bitField0_ = (bitField0_ & ~0x00000400); return this; } @@ -3312,6 +3360,14 @@ public final class StorageProtos { } else { result.pendingPreKey_ = pendingPreKeyBuilder_.build(); } + if (((from_bitField0_ & 0x00000200) == 0x00000200)) { + to_bitField0_ |= 0x00000100; + } + result.remoteRegistrationId_ = remoteRegistrationId_; + if (((from_bitField0_ & 0x00000400) == 0x00000400)) { + to_bitField0_ |= 0x00000200; + } + result.localRegistrationId_ = localRegistrationId_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -3378,6 +3434,12 @@ public final class StorageProtos { if (other.hasPendingPreKey()) { mergePendingPreKey(other.getPendingPreKey()); } + if (other.hasRemoteRegistrationId()) { + setRemoteRegistrationId(other.getRemoteRegistrationId()); + } + if (other.hasLocalRegistrationId()) { + setLocalRegistrationId(other.getLocalRegistrationId()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -3467,6 +3529,16 @@ public final class StorageProtos { setPendingPreKey(subBuilder.buildPartial()); break; } + case 80: { + bitField0_ |= 0x00000200; + remoteRegistrationId_ = input.readUInt32(); + break; + } + case 88: { + bitField0_ |= 0x00000400; + localRegistrationId_ = input.readUInt32(); + break; + } } } } @@ -4043,6 +4115,48 @@ public final class StorageProtos { return pendingPreKeyBuilder_; } + // optional uint32 remoteRegistrationId = 10; + private int remoteRegistrationId_ ; + public boolean hasRemoteRegistrationId() { + return ((bitField0_ & 0x00000200) == 0x00000200); + } + public int getRemoteRegistrationId() { + return remoteRegistrationId_; + } + public Builder setRemoteRegistrationId(int value) { + bitField0_ |= 0x00000200; + remoteRegistrationId_ = value; + onChanged(); + return this; + } + public Builder clearRemoteRegistrationId() { + bitField0_ = (bitField0_ & ~0x00000200); + remoteRegistrationId_ = 0; + onChanged(); + return this; + } + + // optional uint32 localRegistrationId = 11; + private int localRegistrationId_ ; + public boolean hasLocalRegistrationId() { + return ((bitField0_ & 0x00000400) == 0x00000400); + } + public int getLocalRegistrationId() { + return localRegistrationId_; + } + public Builder setLocalRegistrationId(int value) { + bitField0_ |= 0x00000400; + localRegistrationId_ = value; + onChanged(); + return this; + } + public Builder clearLocalRegistrationId() { + bitField0_ = (bitField0_ & ~0x00000400); + localRegistrationId_ = 0; + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:textsecure.SessionStructure) } @@ -4557,7 +4671,7 @@ public final class StorageProtos { static { java.lang.String[] descriptorData = { "\n\032LocalStorageProtocol.proto\022\ntextsecure" + - "\"\312\007\n\020SessionStructure\022\026\n\016sessionVersion\030" + + "\"\205\010\n\020SessionStructure\022\026\n\016sessionVersion\030" + "\001 \001(\r\022\033\n\023localIdentityPublic\030\002 \001(\014\022\034\n\024re" + "moteIdentityPublic\030\003 \001(\014\022\017\n\007rootKey\030\004 \001(" + "\014\022\027\n\017previousCounter\030\005 \001(\r\0227\n\013senderChai" + @@ -4567,24 +4681,26 @@ public final class StorageProtos { "hange\030\010 \001(\0132/.textsecure.SessionStructur" + "e.PendingKeyExchange\022A\n\rpendingPreKey\030\t ", "\001(\0132*.textsecure.SessionStructure.Pendin" + - "gPreKey\032\253\002\n\005Chain\022\027\n\017senderEphemeral\030\001 \001" + - "(\014\022\036\n\026senderEphemeralPrivate\030\002 \001(\014\022=\n\010ch" + - "ainKey\030\003 \001(\0132+.textsecure.SessionStructu" + - "re.Chain.ChainKey\022B\n\013messageKeys\030\004 \003(\0132-" + - ".textsecure.SessionStructure.Chain.Messa" + - "geKey\032&\n\010ChainKey\022\r\n\005index\030\001 \001(\r\022\013\n\003key\030" + - "\002 \001(\014\032>\n\nMessageKey\022\r\n\005index\030\001 \001(\r\022\021\n\tci" + - "pherKey\030\002 \001(\014\022\016\n\006macKey\030\003 \001(\014\032\321\001\n\022Pendin" + - "gKeyExchange\022\020\n\010sequence\030\001 \001(\r\022\024\n\014localB", - "aseKey\030\002 \001(\014\022\033\n\023localBaseKeyPrivate\030\003 \001(" + - "\014\022\031\n\021localEphemeralKey\030\004 \001(\014\022 \n\030localEph" + - "emeralKeyPrivate\030\005 \001(\014\022\030\n\020localIdentityK" + - "ey\030\007 \001(\014\022\037\n\027localIdentityKeyPrivate\030\010 \001(" + - "\014\0322\n\rPendingPreKey\022\020\n\010preKeyId\030\001 \001(\r\022\017\n\007" + - "baseKey\030\002 \001(\014\"J\n\025PreKeyRecordStructure\022\n" + - "\n\002id\030\001 \001(\r\022\021\n\tpublicKey\030\002 \001(\014\022\022\n\nprivate" + - "Key\030\003 \001(\014B6\n%org.whispersystems.textsecu" + - "re.storageB\rStorageProtos" + "gPreKey\022\034\n\024remoteRegistrationId\030\n \001(\r\022\033\n" + + "\023localRegistrationId\030\013 \001(\r\032\253\002\n\005Chain\022\027\n\017" + + "senderEphemeral\030\001 \001(\014\022\036\n\026senderEphemeral" + + "Private\030\002 \001(\014\022=\n\010chainKey\030\003 \001(\0132+.textse" + + "cure.SessionStructure.Chain.ChainKey\022B\n\013" + + "messageKeys\030\004 \003(\0132-.textsecure.SessionSt" + + "ructure.Chain.MessageKey\032&\n\010ChainKey\022\r\n\005" + + "index\030\001 \001(\r\022\013\n\003key\030\002 \001(\014\032>\n\nMessageKey\022\r" + + "\n\005index\030\001 \001(\r\022\021\n\tcipherKey\030\002 \001(\014\022\016\n\006macK", + "ey\030\003 \001(\014\032\321\001\n\022PendingKeyExchange\022\020\n\010seque" + + "nce\030\001 \001(\r\022\024\n\014localBaseKey\030\002 \001(\014\022\033\n\023local" + + "BaseKeyPrivate\030\003 \001(\014\022\031\n\021localEphemeralKe" + + "y\030\004 \001(\014\022 \n\030localEphemeralKeyPrivate\030\005 \001(" + + "\014\022\030\n\020localIdentityKey\030\007 \001(\014\022\037\n\027localIden" + + "tityKeyPrivate\030\010 \001(\014\0322\n\rPendingPreKey\022\020\n" + + "\010preKeyId\030\001 \001(\r\022\017\n\007baseKey\030\002 \001(\014\"J\n\025PreK" + + "eyRecordStructure\022\n\n\002id\030\001 \001(\r\022\021\n\tpublicK" + + "ey\030\002 \001(\014\022\022\n\nprivateKey\030\003 \001(\014B6\n%org.whis" + + "persystems.textsecure.storageB\rStoragePr", + "otos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -4596,7 +4712,7 @@ public final class StorageProtos { internal_static_textsecure_SessionStructure_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_SessionStructure_descriptor, - new java.lang.String[] { "SessionVersion", "LocalIdentityPublic", "RemoteIdentityPublic", "RootKey", "PreviousCounter", "SenderChain", "ReceiverChains", "PendingKeyExchange", "PendingPreKey", }, + new java.lang.String[] { "SessionVersion", "LocalIdentityPublic", "RemoteIdentityPublic", "RootKey", "PreviousCounter", "SenderChain", "ReceiverChains", "PendingKeyExchange", "PendingPreKey", "RemoteRegistrationId", "LocalRegistrationId", }, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.class, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder.class); internal_static_textsecure_SessionStructure_Chain_descriptor = diff --git a/library/src/org/whispersystems/textsecure/util/Util.java b/library/src/org/whispersystems/textsecure/util/Util.java index afa3ac9d67..846645e4d8 100644 --- a/library/src/org/whispersystems/textsecure/util/Util.java +++ b/library/src/org/whispersystems/textsecure/util/Util.java @@ -88,6 +88,14 @@ public class Util { dialog.show(); } + public static int generateRegistrationId() { + try { + return SecureRandom.getInstance("SHA1PRNG").nextInt(16380) + 1; + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + public static String getSecret(int size) { try { byte[] secret = new byte[size]; diff --git a/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java b/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java index e0ae9cb37a..341fdfd2a8 100644 --- a/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java +++ b/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java @@ -33,6 +33,7 @@ import com.actionbarsherlock.app.SherlockActivity; import org.thoughtcrime.securesms.push.PushServiceSocketFactory; import org.thoughtcrime.securesms.service.RegistrationService; import org.thoughtcrime.securesms.util.ActionBarUtil; +import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.RateLimitException; @@ -501,7 +502,8 @@ public class RegistrationProgressActivity extends SherlockActivity { protected Integer doInBackground(Void... params) { try { PushServiceSocket socket = PushServiceSocketFactory.create(context, e164number, password); - socket.verifyAccount(code, signalingKey, true); + int registrationId = TextSecurePreferences.getLocalRegistrationId(context); + socket.verifyAccount(code, signalingKey, true, registrationId); return SUCCESS; } catch (RateLimitException e) { Log.w("RegistrationProgressActivity", e); diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java index bf7f1a08df..561c11be1f 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java @@ -12,6 +12,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; +import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.IdentityKeyPair; import org.whispersystems.textsecure.crypto.InvalidKeyException; @@ -106,6 +107,8 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor { RatchetingSession.initializeSession(sessionRecord, ourBaseKey, theirBaseKey, ourEphemeralKey, theirEphemeralKey, ourIdentityKey, theirIdentityKey); Session.clearV1SessionFor(context, recipientDevice.getRecipient()); + sessionRecord.setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context)); + sessionRecord.setRemoteRegistrationId(message.getRegistrationId()); sessionRecord.save(); if (preKeyId != Medium.MAX_VALUE) { @@ -134,6 +137,8 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor { theirEphemeralKey, ourIdentityKey, theirIdentityKey); sessionRecord.setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey()); + sessionRecord.setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context)); + sessionRecord.setRemoteRegistrationId(message.getRegistrationId()); sessionRecord.save(); DatabaseFactory.getIdentityDatabase(context) diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java index 1f4d8f4de9..3c26a4df78 100644 --- a/src/org/thoughtcrime/securesms/service/RegistrationService.java +++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java @@ -227,6 +227,12 @@ public class RegistrationService extends Service { String number = intent.getStringExtra("e164number"); MasterSecret masterSecret = intent.getParcelableExtra("master_secret"); + int registrationId = TextSecurePreferences.getLocalRegistrationId(this); + + if (registrationId == 0) { + registrationId = Util.generateRegistrationId(); + TextSecurePreferences.setLocalRegistrationId(this, registrationId); + } try { String password = Util.getSecret(18); @@ -236,13 +242,14 @@ public class RegistrationService extends Service { initializeGcmRegistrationListener(); initializePreKeyGenerator(masterSecret); + setState(new RegistrationState(RegistrationState.STATE_CONNECTING, number)); PushServiceSocket socket = PushServiceSocketFactory.create(this, number, password); socket.createAccount(false); setState(new RegistrationState(RegistrationState.STATE_VERIFYING, number)); String challenge = waitForChallenge(); - socket.verifyAccount(challenge, signalingKey, true); + socket.verifyAccount(challenge, signalingKey, true, registrationId); handleCommonRegistration(masterSecret, socket, number); markAsVerified(number, password, signalingKey); diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java index 99fc02971c..756ca67afe 100644 --- a/src/org/thoughtcrime/securesms/transport/PushTransport.java +++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java @@ -49,6 +49,8 @@ import org.whispersystems.textsecure.push.PushAttachmentPointer; import org.whispersystems.textsecure.push.PushBody; import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; import org.whispersystems.textsecure.push.PushServiceSocket; +import org.whispersystems.textsecure.push.StaleDevices; +import org.whispersystems.textsecure.push.StaleDevicesException; import org.whispersystems.textsecure.push.UnregisteredUserException; import org.whispersystems.textsecure.storage.SessionRecordV2; import org.whispersystems.textsecure.util.Base64; @@ -146,6 +148,9 @@ public class PushTransport extends BaseTransport { } catch (MismatchedDevicesException mde) { Log.w("PushTransport", mde); handleMismatchedDevices(socket, threadId, recipient, mde.getMismatchedDevices()); + } catch (StaleDevicesException ste) { + Log.w("PushTransport", ste); + handleStaleDevices(recipient, ste.getStaleDevices()); } } } @@ -211,6 +216,22 @@ public class PushTransport extends BaseTransport { } } + private void handleStaleDevices(Recipient recipient, StaleDevices staleDevices) + throws IOException + { + try { + long recipientId = recipient.getRecipientId(); + String e164number = Util.canonicalizeNumber(context, recipient.getNumber()); + + for (int staleDeviceId : staleDevices.getStaleDevices()) { + PushAddress address = PushAddress.create(context, recipientId, e164number, staleDeviceId); + SessionRecordV2.delete(context, address); + } + } catch (InvalidNumberException 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()); @@ -321,11 +342,12 @@ public class PushTransport extends BaseTransport { SessionCipher cipher = SessionCipher.createFor(context, masterSecret, pushAddress); CiphertextMessage message = cipher.encrypt(plaintext); + int remoteRegistrationId = cipher.getRemoteRegistrationId(); if (message.getType() == CiphertextMessage.PREKEY_WHISPER_TYPE) { - return new PushBody(IncomingPushMessageSignal.Type.PREKEY_BUNDLE_VALUE, message.serialize()); + return new PushBody(IncomingPushMessageSignal.Type.PREKEY_BUNDLE_VALUE, remoteRegistrationId, message.serialize()); } else if (message.getType() == CiphertextMessage.CURRENT_WHISPER_TYPE) { - return new PushBody(IncomingPushMessageSignal.Type.CIPHERTEXT_VALUE, message.serialize()); + return new PushBody(IncomingPushMessageSignal.Type.CIPHERTEXT_VALUE, remoteRegistrationId, message.serialize()); } else { throw new AssertionError("Unknown ciphertext type: " + message.getType()); } diff --git a/src/org/thoughtcrime/securesms/util/DirectoryHelper.java b/src/org/thoughtcrime/securesms/util/DirectoryHelper.java index 7e4129b82f..604bf48bef 100644 --- a/src/org/thoughtcrime/securesms/util/DirectoryHelper.java +++ b/src/org/thoughtcrime/securesms/util/DirectoryHelper.java @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.util; import android.content.Context; -import android.util.Log; import org.thoughtcrime.securesms.push.PushServiceSocketFactory; import org.whispersystems.textsecure.directory.Directory; diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 021788dc41..0fdba0e687 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -43,6 +43,16 @@ public class TextSecurePreferences { private static final String DIRECTORY_FRESH_TIME_PREF = "pref_directory_refresh_time"; private static final String IN_THREAD_NOTIFICATION_PREF = "pref_key_inthread_notifications"; + private static final String LOCAL_REGISTRATION_ID_PREF = "pref_local_registration_id"; + + public static int getLocalRegistrationId(Context context) { + return getIntegerPreference(context, LOCAL_REGISTRATION_ID_PREF, 0); + } + + public static void setLocalRegistrationId(Context context, int registrationId) { + setIntegerPrefrence(context, LOCAL_REGISTRATION_ID_PREF, registrationId); + } + public static boolean isInThreadNotifications(Context context) { return getBooleanPreference(context, IN_THREAD_NOTIFICATION_PREF, true); }