diff --git a/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java b/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java index c1f3cab284..d597d51424 100644 --- a/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java +++ b/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java @@ -67,7 +67,7 @@ public class PreKeyUtil { for (String keyRecordId : keyRecordIds) { try { - if (Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) { + if (!keyRecordId.equals(PreKeyIndex.FILE_NAME) && Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) { records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId))); } } catch (InvalidKeyIdException e) { diff --git a/library/src/org/whispersystems/textsecure/directory/Directory.java b/library/src/org/whispersystems/textsecure/directory/Directory.java new file mode 100644 index 0000000000..f812e35445 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/directory/Directory.java @@ -0,0 +1,175 @@ +package org.whispersystems.textsecure.directory; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.util.Log; + +import org.whispersystems.textsecure.util.Base64; +import org.whispersystems.textsecure.util.PhoneNumberFormatter; +import org.whispersystems.textsecure.util.Util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +public class Directory { + + private static final String DATABASE_NAME = "whisper_directory.db"; + private static final int DATABASE_VERSION = 1; + + private static final String TABLE_NAME = "directory"; + private static final String ID = "_id"; + private static final String TOKEN = "token"; + private static final String REGISTERED = "registered"; + 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);"; + + private static Directory instance; + + public synchronized static Directory getInstance(Context context) { + if (instance == null) { + instance = new Directory(context); + } + + return instance; + } + + private final DatabaseHelper databaseHelper; + private final Context context; + + private Directory(Context context) { + this.context = context; + this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + public boolean containsNumbers(List e164numbers) { + if (e164numbers == null || e164numbers.isEmpty()) { + return false; + } + + for (String e164number : e164numbers) { + if (!containsNumber(e164number)) + return false; + } + + return true; + } + + public boolean containsNumber(String e164number) { + if (e164number == null || e164number.length() == 0) { + return false; + } + + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + String token = getToken(e164number); + Cursor cursor = null; + + try { + cursor = db.query(TABLE_NAME, + new String[] {REGISTERED}, TOKEN + " = ?", + new String[] {token}, null, null, null); + + if (cursor != null && cursor.moveToFirst()) { + return cursor.getInt(0) == 1; + } + + return false; + } finally { + if (cursor != null) + cursor.close(); + } + } + + public void setActiveTokens(List tokens) { + long timestamp = System.currentTimeMillis(); + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + db.beginTransaction(); + + try { + ContentValues clear = new ContentValues(); + clear.put(REGISTERED, 0); + clear.put(TIMESTAMP, timestamp); + db.update(TABLE_NAME, clear, null, null); + + + for (String token : tokens) { + Log.w("Directory", "Adding token: " + token); + ContentValues values = new ContentValues(); + values.put(TOKEN, token); + values.put(REGISTERED, 1); + values.put(TIMESTAMP, timestamp); + db.replace(TABLE_NAME, null, values); + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + public Set getPushEligibleContactTokens(String localNumber) { + Uri uri = Phone.CONTENT_URI; + Set results = new HashSet(); + Cursor cursor = null; + + try { + cursor = context.getContentResolver().query(uri, new String[] {Phone.NUMBER}, null, null, null); + + while (cursor.moveToNext()) { + String rawNumber = cursor.getString(0); + + if (rawNumber != null) { + String e164Number = PhoneNumberFormatter.formatNumber(rawNumber, localNumber); + results.add(getToken(e164Number)); + } + } + + return results; + } finally { + if (cursor != null) + cursor.close(); + } + } + + private String getToken(String e164number) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA1"); + byte[] token = Util.trim(digest.digest(e164number.getBytes()), 10); + return Base64.encodeBytesWithoutPadding(token); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + + private class DatabaseHelper extends SQLiteOpenHelper { + + public DatabaseHelper(Context context, String name, + SQLiteDatabase.CursorFactory factory, + int version) + { + super(context, name, factory, version); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(CREATE_TABLE); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + + } + } + +} diff --git a/library/src/org/whispersystems/textsecure/push/ContactTokenList.java b/library/src/org/whispersystems/textsecure/push/ContactTokenList.java new file mode 100644 index 0000000000..ac912e14a1 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/push/ContactTokenList.java @@ -0,0 +1,18 @@ +package org.whispersystems.textsecure.push; + +import java.util.List; + +public class ContactTokenList { + + private List contacts; + + public ContactTokenList(List contacts) { + this.contacts = contacts; + } + + public ContactTokenList() {} + + public List getContacts() { + return contacts; + } +} diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java index bee4cd9235..22070d871e 100644 --- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java +++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java @@ -8,7 +8,6 @@ import com.google.thoughtcrimegson.Gson; import org.whispersystems.textsecure.R; import org.whispersystems.textsecure.Release; import org.whispersystems.textsecure.crypto.IdentityKey; -import org.whispersystems.textsecure.directory.DirectoryDescriptor; import org.whispersystems.textsecure.storage.PreKeyRecord; import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Util; @@ -33,6 +32,7 @@ import java.security.cert.CertificateException; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Set; public class PushServiceSocket { @@ -169,16 +169,13 @@ public class PushServiceSocket { return attachment; } - public Pair retrieveDirectory() { + public List retrieveDirectory(Set contactTokens) { try { - DirectoryDescriptor directoryDescriptor = new Gson().fromJson(makeRequest(DIRECTORY_PATH, "GET", null), - DirectoryDescriptor.class); + ContactTokenList contactTokenList = new ContactTokenList(new LinkedList(contactTokens)); + String response = makeRequest(DIRECTORY_PATH, "PUT", new Gson().toJson(contactTokenList)); + ContactTokenList activeTokens = new Gson().fromJson(response, ContactTokenList.class); - File directoryData = File.createTempFile("directory", ".dat", context.getFilesDir()); - - downloadExternalFile(directoryDescriptor.getUrl(), directoryData); - - return new Pair(directoryDescriptor, directoryData); + return activeTokens.getContacts(); } catch (IOException ioe) { Log.w("PushServiceSocket", ioe); return null; diff --git a/library/src/org/whispersystems/textsecure/util/Util.java b/library/src/org/whispersystems/textsecure/util/Util.java index 7bdba4973d..119059e28f 100644 --- a/library/src/org/whispersystems/textsecure/util/Util.java +++ b/library/src/org/whispersystems/textsecure/util/Util.java @@ -73,6 +73,13 @@ public class Util { return parts; } + public static byte[] trim(byte[] input, int length) { + byte[] result = new byte[length]; + System.arraycopy(input, 0, result, 0, result.length); + + return result; + } + public static boolean isEmpty(String value) { return value == null || value.trim().length() == 0; } diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java index 02a6db7724..eeaf11f54b 100644 --- a/src/org/thoughtcrime/securesms/service/RegistrationService.java +++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java @@ -9,7 +9,6 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.util.Log; -import android.util.Pair; import com.google.android.gcm.GCMRegistrar; import org.thoughtcrime.securesms.R; @@ -20,15 +19,14 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.PreKeyUtil; -import org.whispersystems.textsecure.directory.DirectoryDescriptor; -import org.whispersystems.textsecure.directory.NumberFilter; +import org.whispersystems.textsecure.directory.Directory; import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.storage.PreKeyRecord; import org.whispersystems.textsecure.util.Util; -import java.io.File; import java.io.IOException; import java.util.List; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -282,12 +280,14 @@ public class RegistrationService extends Service { setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number)); GCMRegistrar.register(this, GcmIntentService.GCM_SENDER_ID); String gcmRegistrationId = waitForGcmRegistrationId(); - socket.registerGcmId(gcmRegistrationId); - Pair directory = socket.retrieveDirectory(); - if (directory != null) { - NumberFilter.getInstance(this).update(directory.first, directory.second); + Set contactTokens = Directory.getInstance(this).getPushEligibleContactTokens(number); + List activeTokens = socket.retrieveDirectory(contactTokens); + + if (activeTokens != null) { + Directory.getInstance(this).setActiveTokens(activeTokens); +// NumberFilter.getInstance(this).update(directory.first, directory.second); } } diff --git a/src/org/thoughtcrime/securesms/transport/UniversalTransport.java b/src/org/thoughtcrime/securesms/transport/UniversalTransport.java index 144c4592f6..3e4ee69e49 100644 --- a/src/org/thoughtcrime/securesms/transport/UniversalTransport.java +++ b/src/org/thoughtcrime/securesms/transport/UniversalTransport.java @@ -20,12 +20,12 @@ import android.content.Context; import android.util.Log; import android.util.Pair; -import org.whispersystems.textsecure.crypto.MasterSecret; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.textsecure.directory.NumberFilter; +import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.directory.Directory; import java.io.IOException; import java.util.LinkedList; @@ -57,7 +57,7 @@ public class UniversalTransport { Recipient recipient = message.getIndividualRecipient(); String number = Util.canonicalizeNumber(context, recipient.getNumber()); - if (NumberFilter.getInstance(context).containsNumber(number)) { + if (Directory.getInstance(context).containsNumber(number)) { try { Log.w("UniversalTransport", "Delivering with GCM..."); pushTransport.deliver(message); @@ -78,7 +78,7 @@ public class UniversalTransport { List destinations = getMediaDestinations(mediaMessage); - if (NumberFilter.getInstance(context).containsNumbers(destinations)) { + if (Directory.getInstance(context).containsNumbers(destinations)) { try { Log.w("UniversalTransport", "Delivering media message with GCM..."); pushTransport.deliver(mediaMessage, destinations);