Support for server federation.

This commit is contained in:
Moxie Marlinspike 2013-10-18 22:45:27 -07:00
parent 25a2ad7289
commit fa5ccc3f8a
15 changed files with 372 additions and 116 deletions

View File

@ -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 {

View File

@ -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;
}

View File

@ -132,6 +132,9 @@ public class PreKeyUtil {
private static class PreKeyRecordIdComparator implements Comparator<String> {
@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);

View File

@ -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<String> activeTokens, Collection<String> inactiveTokens) {
public void setTokens(List<ContactTokenDetails> activeTokens, Collection<String> 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));
}

View File

@ -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);
}
}

View File

@ -0,0 +1,14 @@
package org.whispersystems.textsecure.push;
import java.util.List;
public class ContactTokenDetailsList {
private List<ContactTokenDetails> contacts;
public ContactTokenDetailsList() {}
public List<ContactTokenDetails> getContacts() {
return contacts;
}
}

View File

@ -43,14 +43,16 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
private List<String> 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<String>();
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<String>();
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);

View File

@ -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;
}
}

View File

@ -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<String> 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<String>
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<String>
@ -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 =

View File

@ -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<String> recipients, List<byte[]> bodies, List<Integer> types)
public void sendMessage(List<String> relays, List<String> recipients,
List<byte[]> bodies, List<Integer> types)
throws IOException
{
List<OutgoingPushMessage> messages = new LinkedList<OutgoingPushMessage>();
Iterator<String> relaysIterator = relays.iterator();
Iterator<String> recipientsIterator = recipients.iterator();
Iterator<byte[]> bodiesIterator = bodies.iterator();
Iterator<Integer> 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<String> retrieveDirectory(Set<String> contactTokens) {
public List<ContactTokenDetails> retrieveDirectory(Set<String> 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) {

View File

@ -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;
}
}

View File

@ -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<String> eligibleContactTokens = directory.getPushEligibleContactTokens(localNumber);
List<String> activeTokens = socket.retrieveDirectory(eligibleContactTokens);
Set<String> eligibleContactTokens = directory.getPushEligibleContactTokens(localNumber);
List<ContactTokenDetails> activeTokens = socket.retrieveDirectory(eligibleContactTokens);
if (activeTokens != null) {
eligibleContactTokens.removeAll(activeTokens);
for (ContactTokenDetails activeToken : activeTokens) {
eligibleContactTokens.remove(activeToken.getToken());
}
directory.setTokens(activeTokens, eligibleContactTokens);
}

View File

@ -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<String> contactTokens = Directory.getInstance(this).getPushEligibleContactTokens(number);
List<String> activeTokens = socket.retrieveDirectory(contactTokens);
Set<String> contactTokens = Directory.getInstance(this).getPushEligibleContactTokens(number);
List<ContactTokenDetails> activeTokens = socket.retrieveDirectory(contactTokens);
if (activeTokens != null) {
contactTokens.removeAll(activeTokens);
for (ContactTokenDetails activeToken : activeTokens) {
contactTokens.remove(activeToken.getToken());
}
Directory.getInstance(this).setTokens(activeTokens, contactTokens);
}

View File

@ -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<Integer, byte[]> 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<byte[]> ciphertext = new LinkedList<byte[]> ();
List<String> relays = new LinkedList<String>();
List<byte[]> ciphertext = new LinkedList<byte[]>();
List<Integer> types = new LinkedList<Integer>();
for (String destination : destinations) {
@ -108,11 +110,12 @@ public class PushTransport extends BaseTransport {
Pair<Integer, byte[]> 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);

View File

@ -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;