From fa5ccc3f8acdd81bc3a9441421270748b97a7ee2 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Fri, 18 Oct 2013 22:45:27 -0700 Subject: [PATCH] Support for server federation. --- .../protobuf/IncomingPushMessageSignal.proto | 7 +- .../whispersystems/textsecure/Release.java | 2 +- .../textsecure/crypto/PreKeyUtil.java | 3 + .../textsecure/directory/Directory.java | 48 +++- .../textsecure/push/ContactTokenDetails.java | 41 ++++ .../push/ContactTokenDetailsList.java | 14 ++ .../textsecure/push/IncomingPushMessage.java | 20 +- .../textsecure/push/OutgoingPushMessage.java | 10 + .../textsecure/push/PushMessageProtos.java | 213 +++++++++++++----- .../textsecure/push/PushServiceSocket.java | 48 ++-- .../securesms/gcm/GcmIntentService.java | 25 +- .../service/DirectoryRefreshService.java | 10 +- .../service/RegistrationService.java | 9 +- .../securesms/transport/PushTransport.java | 16 +- .../transport/UniversalTransport.java | 22 +- 15 files changed, 372 insertions(+), 116 deletions(-) create mode 100644 library/src/org/whispersystems/textsecure/push/ContactTokenDetails.java create mode 100644 library/src/org/whispersystems/textsecure/push/ContactTokenDetailsList.java diff --git a/library/protobuf/IncomingPushMessageSignal.proto b/library/protobuf/IncomingPushMessageSignal.proto index 90465ae279..a86c2e1a40 100644 --- a/library/protobuf/IncomingPushMessageSignal.proto +++ b/library/protobuf/IncomingPushMessageSignal.proto @@ -6,9 +6,10 @@ option java_outer_classname = "PushMessageProtos"; message IncomingPushMessageSignal { optional uint32 type = 1; optional string source = 2; - repeated string destinations = 3; - optional uint64 timestamp = 4; - optional bytes message = 5; // Contains an encrypted IncomingPushMessageContent + optional string relay = 3; + repeated string destinations = 4; + optional uint64 timestamp = 5; + optional bytes message = 6; // Contains an encrypted PushMessageContent } message PushMessageContent { diff --git a/library/src/org/whispersystems/textsecure/Release.java b/library/src/org/whispersystems/textsecure/Release.java index 13d5b78824..c71a62ac3c 100644 --- a/library/src/org/whispersystems/textsecure/Release.java +++ b/library/src/org/whispersystems/textsecure/Release.java @@ -1,7 +1,7 @@ package org.whispersystems.textsecure; public class Release { - public static final String PUSH_SERVICE_URL = "https://gcm.textsecure.whispersystems.org"; + public static final String PUSH_SERVICE_URL = "https://federated.textsecure.whispersystems.org"; // public static final String PUSH_SERVICE_URL = "http://192.168.1.135:8080"; public static final boolean ENFORCE_SSL = true; } diff --git a/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java b/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java index d597d51424..fddbdca965 100644 --- a/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java +++ b/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java @@ -132,6 +132,9 @@ public class PreKeyUtil { private static class PreKeyRecordIdComparator implements Comparator { @Override public int compare(String lhs, String rhs) { + if (lhs.equals(PreKeyIndex.FILE_NAME)) return -1; + else if (rhs.equals(PreKeyIndex.FILE_NAME)) return 1; + try { long lhsLong = Long.parseLong(lhs); long rhsLong = Long.parseLong(rhs); diff --git a/library/src/org/whispersystems/textsecure/directory/Directory.java b/library/src/org/whispersystems/textsecure/directory/Directory.java index c2e32e8c06..900c652d7e 100644 --- a/library/src/org/whispersystems/textsecure/directory/Directory.java +++ b/library/src/org/whispersystems/textsecure/directory/Directory.java @@ -9,6 +9,7 @@ import android.net.Uri; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.util.Log; +import org.whispersystems.textsecure.push.ContactTokenDetails; import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.PhoneNumberFormatter; import org.whispersystems.textsecure.util.Util; @@ -29,11 +30,15 @@ public class Directory { private static final String ID = "_id"; private static final String TOKEN = "token"; private static final String REGISTERED = "registered"; + private static final String RELAY = "relay"; private static final String SUPPORTS_SMS = "supports_sms"; private static final String TIMESTAMP = "timestamp"; private static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY, " + - TOKEN + " TEXT UNIQUE, " + REGISTERED + " INTEGER, " + - SUPPORTS_SMS + " INTEGER, " + TIMESTAMP + " INTEGER);"; + TOKEN + " TEXT UNIQUE, " + + REGISTERED + " INTEGER, " + + RELAY + " TEXT " + + SUPPORTS_SMS + " INTEGER, " + + TIMESTAMP + " INTEGER);"; private static Directory instance; @@ -79,27 +84,49 @@ public class Directory { } } - public void setToken(String token, boolean active) { + public String getRelay(String e164number) { + String token = getToken(e164number); + SQLiteDatabase database = databaseHelper.getReadableDatabase(); + Cursor cursor = null; + + try { + cursor = database.query(TABLE_NAME, null, TOKEN + " = ?", new String[]{token}, null, null, null); + + if (cursor != null && cursor.moveToFirst()) { + return cursor.getString(cursor.getColumnIndexOrThrow(RELAY)); + } + + return null; + } finally { + if (cursor != null) + cursor.close(); + } + } + + public void setToken(ContactTokenDetails token, boolean active) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); ContentValues values = new ContentValues(); - values.put(TOKEN, token); + values.put(TOKEN, token.getToken()); + values.put(RELAY, token.getRelay()); values.put(REGISTERED, active ? 1 : 0); + values.put(SUPPORTS_SMS, token.isSupportsSms()); values.put(TIMESTAMP, System.currentTimeMillis()); db.replace(TABLE_NAME, null, values); } - public void setTokens(List activeTokens, Collection inactiveTokens) { + public void setTokens(List activeTokens, Collection inactiveTokens) { long timestamp = System.currentTimeMillis(); SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.beginTransaction(); try { - for (String token : activeTokens) { + for (ContactTokenDetails token : activeTokens) { Log.w("Directory", "Adding active token: " + token); ContentValues values = new ContentValues(); - values.put(TOKEN, token); + values.put(TOKEN, token.getToken()); values.put(REGISTERED, 1); values.put(TIMESTAMP, timestamp); + values.put(RELAY, token.getRelay()); db.replace(TABLE_NAME, null, values); } @@ -125,7 +152,7 @@ public class Directory { try { cursor = context.getContentResolver().query(uri, new String[] {Phone.NUMBER}, null, null, null); - while (cursor.moveToNext()) { + while (cursor != null && cursor.moveToNext()) { String rawNumber = cursor.getString(0); if (rawNumber != null) { @@ -134,12 +161,13 @@ public class Directory { } } - cursor.close(); + if (cursor != null) + cursor.close(); cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {TOKEN}, null, null, null, null, null); - while (cursor.moveToNext()) { + while (cursor != null && cursor.moveToNext()) { results.add(cursor.getString(0)); } diff --git a/library/src/org/whispersystems/textsecure/push/ContactTokenDetails.java b/library/src/org/whispersystems/textsecure/push/ContactTokenDetails.java new file mode 100644 index 0000000000..9611d7c1e5 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/push/ContactTokenDetails.java @@ -0,0 +1,41 @@ +package org.whispersystems.textsecure.push; + +import com.google.thoughtcrimegson.Gson; + +public class ContactTokenDetails { + + private String token; + private String relay; + private boolean supportsSms; + + public ContactTokenDetails() {} + + public ContactTokenDetails(String token) { + this.token = token; + } + + public ContactTokenDetails(String token, String relay) { + this.token = token; + this.relay = relay; + } + + public String getToken() { + return token; + } + + public String getRelay() { + return relay; + } + + public void setRelay(String relay) { + this.relay = relay; + } + + public boolean isSupportsSms() { + return supportsSms; + } + + public String toString() { + return new Gson().toJson(this); + } +} diff --git a/library/src/org/whispersystems/textsecure/push/ContactTokenDetailsList.java b/library/src/org/whispersystems/textsecure/push/ContactTokenDetailsList.java new file mode 100644 index 0000000000..4c9f0da7fb --- /dev/null +++ b/library/src/org/whispersystems/textsecure/push/ContactTokenDetailsList.java @@ -0,0 +1,14 @@ +package org.whispersystems.textsecure.push; + +import java.util.List; + +public class ContactTokenDetailsList { + + private List contacts; + + public ContactTokenDetailsList() {} + + public List getContacts() { + return contacts; + } +} diff --git a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java index 93dd829cfc..ad53ae2694 100644 --- a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java +++ b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java @@ -43,14 +43,16 @@ public class IncomingPushMessage implements PushMessage, Parcelable { private List destinations; private byte[] message; private long timestamp; + private String relay; private IncomingPushMessage(IncomingPushMessage message, byte[] body) { this.type = message.type; this.source = message.source; + this.timestamp = message.timestamp; + this.relay = message.relay; + this.message = body; this.destinations = new LinkedList(); this.destinations.addAll(message.destinations); - this.message = body; - this.timestamp = message.timestamp; } public IncomingPushMessage(IncomingPushMessageSignal signal) { @@ -59,12 +61,18 @@ public class IncomingPushMessage implements PushMessage, Parcelable { this.destinations = signal.getDestinationsList(); this.message = signal.getMessage().toByteArray(); this.timestamp = signal.getTimestamp(); + this.relay = signal.getRelay(); } public IncomingPushMessage(Parcel in) { this.destinations = new LinkedList(); this.type = in.readInt(); this.source = in.readString(); + + if (in.readInt() == 1) { + this.relay = in.readString(); + } + in.readStringList(destinations); this.message = new byte[in.readInt()]; in.readByteArray(this.message); @@ -82,6 +90,10 @@ public class IncomingPushMessage implements PushMessage, Parcelable { this.timestamp = timestamp; } + public String getRelay() { + return relay; + } + public long getTimestampMillis() { return timestamp; } @@ -107,6 +119,10 @@ public class IncomingPushMessage implements PushMessage, Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(type); dest.writeString(source); + dest.writeInt(relay == null ? 0 : 1); + if (relay != null) { + dest.writeString(relay); + } dest.writeStringList(destinations); dest.writeInt(message.length); dest.writeByteArray(message); diff --git a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java index 1cfe139895..c2ce11d82c 100644 --- a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java +++ b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java @@ -23,8 +23,14 @@ public class OutgoingPushMessage implements PushMessage { private int type; private String destination; private String body; + private String relay; public OutgoingPushMessage(String destination, byte[] body, int type) { + this(null, destination, body, type); + } + + public OutgoingPushMessage(String relay, String destination, byte[] body, int type) { + this.relay = relay; this.destination = destination; this.body = Base64.encodeBytes(body); this.type = type; @@ -41,4 +47,8 @@ public class OutgoingPushMessage implements PushMessage { public int getType() { return type; } + + public String getRelay() { + return relay; + } } diff --git a/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java b/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java index ca0179643d..b0d528e90c 100644 --- a/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java +++ b/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java @@ -19,16 +19,20 @@ public final class PushMessageProtos { boolean hasSource(); String getSource(); - // repeated string destinations = 3; + // optional string relay = 3; + boolean hasRelay(); + String getRelay(); + + // repeated string destinations = 4; java.util.List getDestinationsList(); int getDestinationsCount(); String getDestinations(int index); - // optional uint64 timestamp = 4; + // optional uint64 timestamp = 5; boolean hasTimestamp(); long getTimestamp(); - // optional bytes message = 5; + // optional bytes message = 6; boolean hasMessage(); com.google.protobuf.ByteString getMessage(); } @@ -103,8 +107,40 @@ public final class PushMessageProtos { } } - // repeated string destinations = 3; - public static final int DESTINATIONS_FIELD_NUMBER = 3; + // optional string relay = 3; + public static final int RELAY_FIELD_NUMBER = 3; + private java.lang.Object relay_; + public boolean hasRelay() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public String getRelay() { + java.lang.Object ref = relay_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (com.google.protobuf.Internal.isValidUtf8(bs)) { + relay_ = s; + } + return s; + } + } + private com.google.protobuf.ByteString getRelayBytes() { + java.lang.Object ref = relay_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + relay_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // repeated string destinations = 4; + public static final int DESTINATIONS_FIELD_NUMBER = 4; private com.google.protobuf.LazyStringList destinations_; public java.util.List getDestinationsList() { @@ -117,21 +153,21 @@ public final class PushMessageProtos { return destinations_.get(index); } - // optional uint64 timestamp = 4; - public static final int TIMESTAMP_FIELD_NUMBER = 4; + // optional uint64 timestamp = 5; + public static final int TIMESTAMP_FIELD_NUMBER = 5; private long timestamp_; public boolean hasTimestamp() { - return ((bitField0_ & 0x00000004) == 0x00000004); + return ((bitField0_ & 0x00000008) == 0x00000008); } public long getTimestamp() { return timestamp_; } - // optional bytes message = 5; - public static final int MESSAGE_FIELD_NUMBER = 5; + // optional bytes message = 6; + public static final int MESSAGE_FIELD_NUMBER = 6; 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_; @@ -140,6 +176,7 @@ public final class PushMessageProtos { private void initFields() { type_ = 0; source_ = ""; + relay_ = ""; destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY; timestamp_ = 0L; message_ = com.google.protobuf.ByteString.EMPTY; @@ -162,14 +199,17 @@ public final class PushMessageProtos { if (((bitField0_ & 0x00000002) == 0x00000002)) { output.writeBytes(2, getSourceBytes()); } - for (int i = 0; i < destinations_.size(); i++) { - output.writeBytes(3, destinations_.getByteString(i)); - } if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeUInt64(4, timestamp_); + output.writeBytes(3, getRelayBytes()); + } + for (int i = 0; i < destinations_.size(); i++) { + output.writeBytes(4, destinations_.getByteString(i)); } if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeBytes(5, message_); + output.writeUInt64(5, timestamp_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeBytes(6, message_); } getUnknownFields().writeTo(output); } @@ -188,6 +228,10 @@ public final class PushMessageProtos { size += com.google.protobuf.CodedOutputStream .computeBytesSize(2, getSourceBytes()); } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, getRelayBytes()); + } { int dataSize = 0; for (int i = 0; i < destinations_.size(); i++) { @@ -197,13 +241,13 @@ public final class PushMessageProtos { size += dataSize; size += 1 * getDestinationsList().size(); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt64Size(4, timestamp_); - } if (((bitField0_ & 0x00000008) == 0x00000008)) { size += com.google.protobuf.CodedOutputStream - .computeBytesSize(5, message_); + .computeUInt64Size(5, timestamp_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(6, message_); } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; @@ -333,12 +377,14 @@ public final class PushMessageProtos { bitField0_ = (bitField0_ & ~0x00000001); source_ = ""; bitField0_ = (bitField0_ & ~0x00000002); - destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY; + relay_ = ""; bitField0_ = (bitField0_ & ~0x00000004); - timestamp_ = 0L; + destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY; 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; } @@ -385,19 +431,23 @@ public final class PushMessageProtos { to_bitField0_ |= 0x00000002; } result.source_ = source_; - if (((bitField0_ & 0x00000004) == 0x00000004)) { - destinations_ = new com.google.protobuf.UnmodifiableLazyStringList( - destinations_); - bitField0_ = (bitField0_ & ~0x00000004); - } - result.destinations_ = destinations_; - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { to_bitField0_ |= 0x00000004; } - result.timestamp_ = timestamp_; + result.relay_ = relay_; + if (((bitField0_ & 0x00000008) == 0x00000008)) { + destinations_ = new com.google.protobuf.UnmodifiableLazyStringList( + destinations_); + bitField0_ = (bitField0_ & ~0x00000008); + } + result.destinations_ = destinations_; if (((from_bitField0_ & 0x00000010) == 0x00000010)) { to_bitField0_ |= 0x00000008; } + result.timestamp_ = timestamp_; + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000010; + } result.message_ = message_; result.bitField0_ = to_bitField0_; onBuilt(); @@ -421,10 +471,13 @@ public final class PushMessageProtos { if (other.hasSource()) { setSource(other.getSource()); } + if (other.hasRelay()) { + setRelay(other.getRelay()); + } if (!other.destinations_.isEmpty()) { if (destinations_.isEmpty()) { destinations_ = other.destinations_; - bitField0_ = (bitField0_ & ~0x00000004); + bitField0_ = (bitField0_ & ~0x00000008); } else { ensureDestinationsIsMutable(); destinations_.addAll(other.destinations_); @@ -479,17 +532,22 @@ public final class PushMessageProtos { break; } case 26: { + bitField0_ |= 0x00000004; + relay_ = input.readBytes(); + break; + } + case 34: { ensureDestinationsIsMutable(); destinations_.add(input.readBytes()); break; } - case 32: { - bitField0_ |= 0x00000008; + case 40: { + bitField0_ |= 0x00000010; timestamp_ = input.readUInt64(); break; } - case 42: { - bitField0_ |= 0x00000010; + case 50: { + bitField0_ |= 0x00000020; message_ = input.readBytes(); break; } @@ -556,12 +614,48 @@ public final class PushMessageProtos { onChanged(); } - // repeated string destinations = 3; + // optional string relay = 3; + private java.lang.Object relay_ = ""; + public boolean hasRelay() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public String getRelay() { + java.lang.Object ref = relay_; + if (!(ref instanceof String)) { + String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); + relay_ = s; + return s; + } else { + return (String) ref; + } + } + public Builder setRelay(String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + relay_ = value; + onChanged(); + return this; + } + public Builder clearRelay() { + bitField0_ = (bitField0_ & ~0x00000004); + relay_ = getDefaultInstance().getRelay(); + onChanged(); + return this; + } + void setRelay(com.google.protobuf.ByteString value) { + bitField0_ |= 0x00000004; + relay_ = value; + onChanged(); + } + + // repeated string destinations = 4; private com.google.protobuf.LazyStringList destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY; private void ensureDestinationsIsMutable() { - if (!((bitField0_ & 0x00000004) == 0x00000004)) { + if (!((bitField0_ & 0x00000008) == 0x00000008)) { destinations_ = new com.google.protobuf.LazyStringArrayList(destinations_); - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000008; } } public java.util.List @@ -602,7 +696,7 @@ public final class PushMessageProtos { } public Builder clearDestinations() { destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY; - bitField0_ = (bitField0_ & ~0x00000004); + bitField0_ = (bitField0_ & ~0x00000008); onChanged(); return this; } @@ -612,31 +706,31 @@ public final class PushMessageProtos { onChanged(); } - // optional uint64 timestamp = 4; + // 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; } - // optional bytes message = 5; + // 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_; @@ -645,13 +739,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; @@ -1833,15 +1927,16 @@ public final class PushMessageProtos { static { java.lang.String[] descriptorData = { "\n\037IncomingPushMessageSignal.proto\022\ntexts" + - "ecure\"s\n\031IncomingPushMessageSignal\022\014\n\004ty" + - "pe\030\001 \001(\r\022\016\n\006source\030\002 \001(\t\022\024\n\014destinations" + - "\030\003 \003(\t\022\021\n\ttimestamp\030\004 \001(\004\022\017\n\007message\030\005 \001" + - "(\014\"\254\001\n\022PushMessageContent\022\014\n\004body\030\001 \001(\t\022" + - "E\n\013attachments\030\002 \003(\01320.textsecure.PushMe" + - "ssageContent.AttachmentPointer\032A\n\021Attach" + - "mentPointer\022\n\n\002id\030\001 \001(\006\022\023\n\013contentType\030\002" + - " \001(\t\022\013\n\003key\030\003 \001(\014B7\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\r\n\005relay\030\003 \001(\t" + + "\022\024\n\014destinations\030\004 \003(\t\022\021\n\ttimestamp\030\005 \001(" + + "\004\022\017\n\007message\030\006 \001(\014\"\254\001\n\022PushMessageConten" + + "t\022\014\n\004body\030\001 \001(\t\022E\n\013attachments\030\002 \003(\01320.t" + + "extsecure.PushMessageContent.AttachmentP" + + "ointer\032A\n\021AttachmentPointer\022\n\n\002id\030\001 \001(\006\022" + + "\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014B7\n\"org" + + ".whispersystems.textsecure.pushB\021PushMes", + "sageProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -1853,7 +1948,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", "Destinations", "Timestamp", "Message", }, + new java.lang.String[] { "Type", "Source", "Relay", "Destinations", "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 a2131cb234..f648ff2c44 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 android.util.Pair; import com.google.thoughtcrimegson.Gson; + import org.whispersystems.textsecure.R; import org.whispersystems.textsecure.Release; import org.whispersystems.textsecure.crypto.IdentityKey; @@ -12,9 +13,6 @@ import org.whispersystems.textsecure.storage.PreKeyRecord; import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Util; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -34,6 +32,10 @@ import java.util.LinkedList; import java.util.List; import java.util.Set; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + public class PushServiceSocket { private static final String CREATE_ACCOUNT_SMS_PATH = "/v1/accounts/sms/code/%s"; @@ -78,28 +80,31 @@ public class PushServiceSocket { makeRequest(REGISTER_GCM_PATH, "DELETE", null); } - public void sendMessage(String recipient, byte[] body, int type) + public void sendMessage(String relay, String recipient, byte[] body, int type) throws IOException { - OutgoingPushMessage message = new OutgoingPushMessage(recipient, body, type); + OutgoingPushMessage message = new OutgoingPushMessage(relay, recipient, body, type); sendMessage(new OutgoingPushMessageList(message)); } - public void sendMessage(List recipients, List bodies, List types) + public void sendMessage(List relays, List recipients, + List bodies, List types) throws IOException { List messages = new LinkedList(); + Iterator relaysIterator = relays.iterator(); Iterator recipientsIterator = recipients.iterator(); Iterator bodiesIterator = bodies.iterator(); Iterator typesIterator = types.iterator(); while (recipientsIterator.hasNext()) { + String relay = relaysIterator.next(); String recipient = recipientsIterator.next(); byte[] body = bodiesIterator.next(); int type = typesIterator.next(); - messages.add(new OutgoingPushMessage(recipient, body, type)); + messages.add(new OutgoingPushMessage(relay, recipient, body, type)); } sendMessage(new OutgoingPushMessageList(messages)); @@ -134,8 +139,14 @@ public class PushServiceSocket { makeRequest(String.format(PREKEY_PATH, ""), "PUT", PreKeyList.toJson(new PreKeyList(lastResortEntity, entities))); } - public PreKeyEntity getPreKey(String number) throws IOException { - String responseText = makeRequest(String.format(PREKEY_PATH, number), "GET", null); + public PreKeyEntity getPreKey(String number, String relay) throws IOException { + String path = String.format(PREKEY_PATH, number); + + if (relay != null) { + path = path + "?relay=" + relay; + } + + String responseText = makeRequest(path, "GET", null); Log.w("PushServiceSocket", "Got prekey: " + responseText); return PreKeyEntity.fromJson(responseText); } @@ -170,11 +181,11 @@ public class PushServiceSocket { return attachment; } - public List retrieveDirectory(Set contactTokens) { + public List retrieveDirectory(Set contactTokens) { try { - ContactTokenList contactTokenList = new ContactTokenList(new LinkedList(contactTokens)); - String response = makeRequest(DIRECTORY_TOKENS_PATH, "PUT", new Gson().toJson(contactTokenList)); - ContactTokenList activeTokens = new Gson().fromJson(response, ContactTokenList.class); + ContactTokenList contactTokenList = new ContactTokenList(new LinkedList(contactTokens)); + String response = makeRequest(DIRECTORY_TOKENS_PATH, "PUT", new Gson().toJson(contactTokenList)); + ContactTokenDetailsList activeTokens = new Gson().fromJson(response, ContactTokenDetailsList.class); return activeTokens.getContacts(); } catch (IOException ioe) { @@ -183,12 +194,12 @@ public class PushServiceSocket { } } - public boolean isRegisteredUser(String contactToken) throws IOException { + public ContactTokenDetails getContactTokenDetails(String contactToken) throws IOException { try { - makeRequest(String.format(DIRECTORY_VERIFY_PATH, contactToken), "GET", null); - return true; + String response = makeRequest(String.format(DIRECTORY_VERIFY_PATH, contactToken), "GET", null); + return new Gson().fromJson(response, ContactTokenDetails.class); } catch (NotFoundException nfe) { - return false; + return null; } } @@ -311,6 +322,9 @@ public class PushServiceSocket { context.init(null, trustManagerFactory.getTrustManagers(), null); URL url = new URL(String.format("%s%s", Release.PUSH_SERVICE_URL, urlFragment)); + Log.w("PushServiceSocket", "Push service URL: " + Release.PUSH_SERVICE_URL); + Log.w("PushServiceSocket", "Opening URL: " + url); + HttpURLConnection connection = (HttpURLConnection)url.openConnection(); if (Release.ENFORCE_SSL) { diff --git a/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java b/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java index 2b34a66018..6bc4371b49 100644 --- a/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java +++ b/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java @@ -10,6 +10,8 @@ import org.thoughtcrime.securesms.service.SendReceiveService; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.InvalidVersionException; import org.whispersystems.textsecure.directory.Directory; +import org.whispersystems.textsecure.directory.NotInDirectoryException; +import org.whispersystems.textsecure.push.ContactTokenDetails; import org.whispersystems.textsecure.push.IncomingEncryptedPushMessage; import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.push.PushServiceSocket; @@ -59,13 +61,16 @@ public class GcmIntentService extends GCMBaseIntentService { IncomingEncryptedPushMessage encryptedMessage = new IncomingEncryptedPushMessage(data, sessionKey); IncomingPushMessage message = encryptedMessage.getIncomingPushMessage(); + if (!isActiveNumber(context, message.getSource())) { + Directory directory = Directory.getInstance(context); + String contactToken = directory.getToken(message.getSource()); + ContactTokenDetails contactTokenDetails = new ContactTokenDetails(contactToken, message.getRelay()); + directory.setToken(contactTokenDetails, true); + } + Intent service = new Intent(context, SendReceiveService.class); service.setAction(SendReceiveService.RECEIVE_PUSH_ACTION); service.putExtra("message", message); - - Directory directory = Directory.getInstance(context); - directory.setToken(directory.getToken(message.getSource()), true); - context.startService(service); } catch (IOException e) { Log.w("GcmIntentService", e); @@ -84,4 +89,16 @@ public class GcmIntentService extends GCMBaseIntentService { String password = TextSecurePreferences.getPushServerPassword(context); return new PushServiceSocket(context, localNumber, password); } + + private boolean isActiveNumber(Context context, String e164number) { + boolean isActiveNumber; + + try { + isActiveNumber = Directory.getInstance(context).isActiveNumber(e164number); + } catch (NotInDirectoryException e) { + isActiveNumber = false; + } + + return isActiveNumber; + } } diff --git a/src/org/thoughtcrime/securesms/service/DirectoryRefreshService.java b/src/org/thoughtcrime/securesms/service/DirectoryRefreshService.java index 72dd9cff36..3ee33e7ff0 100644 --- a/src/org/thoughtcrime/securesms/service/DirectoryRefreshService.java +++ b/src/org/thoughtcrime/securesms/service/DirectoryRefreshService.java @@ -10,6 +10,7 @@ import android.util.Log; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.directory.Directory; +import org.whispersystems.textsecure.push.ContactTokenDetails; import org.whispersystems.textsecure.push.PushServiceSocket; import java.util.List; @@ -61,11 +62,14 @@ public class DirectoryRefreshService extends Service { String password = TextSecurePreferences.getPushServerPassword(context); PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); - Set eligibleContactTokens = directory.getPushEligibleContactTokens(localNumber); - List activeTokens = socket.retrieveDirectory(eligibleContactTokens); + Set eligibleContactTokens = directory.getPushEligibleContactTokens(localNumber); + List activeTokens = socket.retrieveDirectory(eligibleContactTokens); if (activeTokens != null) { - eligibleContactTokens.removeAll(activeTokens); + for (ContactTokenDetails activeToken : activeTokens) { + eligibleContactTokens.remove(activeToken.getToken()); + } + directory.setTokens(activeTokens, eligibleContactTokens); } diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java index 53fa4051ad..07007ed9da 100644 --- a/src/org/thoughtcrime/securesms/service/RegistrationService.java +++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java @@ -20,6 +20,7 @@ import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.PreKeyUtil; import org.whispersystems.textsecure.directory.Directory; +import org.whispersystems.textsecure.push.ContactTokenDetails; import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.storage.PreKeyRecord; import org.whispersystems.textsecure.util.Util; @@ -282,11 +283,13 @@ public class RegistrationService extends Service { String gcmRegistrationId = waitForGcmRegistrationId(); socket.registerGcmId(gcmRegistrationId); - Set contactTokens = Directory.getInstance(this).getPushEligibleContactTokens(number); - List activeTokens = socket.retrieveDirectory(contactTokens); + Set contactTokens = Directory.getInstance(this).getPushEligibleContactTokens(number); + List activeTokens = socket.retrieveDirectory(contactTokens); if (activeTokens != null) { - contactTokens.removeAll(activeTokens); + for (ContactTokenDetails activeToken : activeTokens) { + contactTokens.remove(activeToken.getToken()); + } Directory.getInstance(this).setTokens(activeTokens, contactTokens); } diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java index ffec138cd4..ed338d57b6 100644 --- a/src/org/thoughtcrime/securesms/transport/PushTransport.java +++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java @@ -22,6 +22,7 @@ import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MessageCipher; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; +import org.whispersystems.textsecure.directory.Directory; import org.whispersystems.textsecure.push.OutgoingPushMessage; import org.whispersystems.textsecure.push.PreKeyEntity; import org.whispersystems.textsecure.push.PushAttachmentData; @@ -61,12 +62,12 @@ public class PushTransport extends BaseTransport { String plaintextBody = message.getBody().getBody(); PushMessageContent.Builder builder = PushMessageContent.newBuilder(); byte[] plaintext = builder.setBody(plaintextBody).build().toByteArray(); - String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(), - localNumber); + String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(), localNumber); + String relay = Directory.getInstance(context).getRelay(recipientCanonicalNumber); Pair typeAndCiphertext = getEncryptedMessage(socket, recipient, recipientCanonicalNumber, plaintext); - socket.sendMessage(recipientCanonicalNumber, typeAndCiphertext.second, typeAndCiphertext.first); + socket.sendMessage(relay, recipientCanonicalNumber, typeAndCiphertext.second, typeAndCiphertext.first); context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType())); } catch (RateLimitException e) { @@ -81,7 +82,8 @@ public class PushTransport extends BaseTransport { String password = TextSecurePreferences.getPushServerPassword(context); PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); String messageBody = PartParser.getMessageText(message.getBody()); - List ciphertext = new LinkedList (); + List relays = new LinkedList(); + List ciphertext = new LinkedList(); List types = new LinkedList(); for (String destination : destinations) { @@ -108,11 +110,12 @@ public class PushTransport extends BaseTransport { Pair typeAndCiphertext = getEncryptedMessage(socket, recipients.getPrimaryRecipient(), destination, plaintext); + relays.add(Directory.getInstance(context).getRelay(destination)); types.add(typeAndCiphertext.first); ciphertext.add(typeAndCiphertext.second); } - socket.sendMessage(destinations, ciphertext, types); + socket.sendMessage(relays, destinations, ciphertext, types); } catch (RateLimitException e) { Log.w("PushTransport", e); @@ -187,7 +190,8 @@ public class PushTransport extends BaseTransport { { IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); IdentityKey identityKey = identityKeyPair.getPublicKey(); - PreKeyEntity preKey = socket.getPreKey(canonicalRecipientNumber); + String relay = Directory.getInstance(context).getRelay(canonicalRecipientNumber); + PreKeyEntity preKey = socket.getPreKey(canonicalRecipientNumber, relay); KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient); processor.processKeyExchangeMessage(preKey); diff --git a/src/org/thoughtcrime/securesms/transport/UniversalTransport.java b/src/org/thoughtcrime/securesms/transport/UniversalTransport.java index 1f16214339..95cce2df1e 100644 --- a/src/org/thoughtcrime/securesms/transport/UniversalTransport.java +++ b/src/org/thoughtcrime/securesms/transport/UniversalTransport.java @@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.util.Util; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.directory.Directory; import org.whispersystems.textsecure.directory.NotInDirectoryException; +import org.whispersystems.textsecure.push.ContactTokenDetails; import org.whispersystems.textsecure.push.PushServiceSocket; import java.io.IOException; @@ -126,15 +127,20 @@ public class UniversalTransport { return directory.isActiveNumber(destination); } catch (NotInDirectoryException e) { try { - String localNumber = TextSecurePreferences.getLocalNumber(context); - String pushPassword = TextSecurePreferences.getPushServerPassword(context); - String contactToken = directory.getToken(destination); - PushServiceSocket socket = new PushServiceSocket(context, localNumber, pushPassword); - boolean registeredUser = socket.isRegisteredUser(contactToken); + String localNumber = TextSecurePreferences.getLocalNumber(context); + String pushPassword = TextSecurePreferences.getPushServerPassword(context); + String contactToken = directory.getToken(destination); + PushServiceSocket socket = new PushServiceSocket(context, localNumber, pushPassword); + ContactTokenDetails registeredUser = socket.getContactTokenDetails(contactToken); - directory.setToken(contactToken, registeredUser); - - return registeredUser; + if (registeredUser == null) { + registeredUser = new ContactTokenDetails(contactToken); + directory.setToken(registeredUser, false); + return false; + } else { + directory.setToken(registeredUser, true); + return true; + } } catch (IOException e1) { Log.w("UniversalTransport", e1); return false;