Refactor recipient access.

1) Refactor recipient class to support asynchronous loading operations.

2) Refactor recipient factory to simplify recipient access.

3) Consoliate everything into one recipient provider that is capable of
doing async lookups and intelligent caching.
This commit is contained in:
Moxie Marlinspike
2012-12-24 08:40:37 -08:00
parent f685cb550b
commit 9939830551
27 changed files with 521 additions and 284 deletions

View File

@@ -1,6 +1,6 @@
/**
/**
* Copyright (C) 2011 Whisper Systems
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -10,15 +10,15 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.crypto;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingMmsDatabase;
@@ -38,13 +38,14 @@ import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.MultimediaMessagePdu;
import ws.com.google.android.mms.pdu.PduParser;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
/**
* A work queue for processing a number of encryption operations.
*
*
* @author Moxie Marlinspike
*/
@@ -52,12 +53,12 @@ public class DecryptingQueue {
private static List<Runnable> workQueue = new LinkedList<Runnable>();
private static Thread workerThread;
static {
workerThread = new WorkerThread(workQueue, "Async Decryption Thread");
workerThread.start();
}
}
public static void scheduleDecryption(Context context, MasterSecret masterSecret, long messageId, long threadId, MultimediaMessagePdu mms) {
MmsDecryptionItem runnable = new MmsDecryptionItem(context, masterSecret, messageId, threadId, mms);
synchronized (workQueue) {
@@ -65,7 +66,7 @@ public class DecryptingQueue {
workQueue.notifyAll();
}
}
public static void scheduleDecryption(Context context, MasterSecret masterSecret, long messageId, String originator, String body) {
DecryptionWorkItem runnable = new DecryptionWorkItem(context, masterSecret, messageId, body, originator);
synchronized (workQueue) {
@@ -73,16 +74,16 @@ public class DecryptingQueue {
workQueue.notifyAll();
}
}
public static void schedulePendingDecrypts(Context context, MasterSecret masterSecret) {
Cursor cursor = null;
Log.w("DecryptingQueue", "Processing pending decrypts...");
try {
cursor = DatabaseFactory.getSmsDatabase(context).getDecryptInProgressMessages();
if (cursor == null || cursor.getCount() == 0 || !cursor.moveToFirst())
return;
do {
scheduleDecryptFromCursor(context, masterSecret, cursor);
} while (cursor.moveToNext());
@@ -94,12 +95,12 @@ public class DecryptingQueue {
public static void scheduleRogueMessages(Context context, MasterSecret masterSecret, Recipient recipient) {
Cursor cursor = null;
try {
cursor = DatabaseFactory.getSmsDatabase(context).getEncryptedRogueMessages(recipient);
if (cursor == null || cursor.getCount() == 0 || !cursor.moveToFirst())
return;
do {
DatabaseFactory.getSmsDatabase(context).markAsDecrypting(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
scheduleDecryptFromCursor(context, masterSecret, cursor);
@@ -107,9 +108,9 @@ public class DecryptingQueue {
} finally {
if (cursor != null)
cursor.close();
}
}
}
private static void scheduleDecryptFromCursor(Context context, MasterSecret masterSecret, Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
String originator = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
@@ -117,14 +118,14 @@ public class DecryptingQueue {
scheduleDecryption(context, masterSecret, id, originator, body);
}
private static class MmsDecryptionItem implements Runnable {
private long messageId;
private long threadId;
private Context context;
private MasterSecret masterSecret;
private MultimediaMessagePdu pdu;
public MmsDecryptionItem(Context context, MasterSecret masterSecret, long messageId, long threadId, MultimediaMessagePdu pdu) {
this.context = context;
this.masterSecret = masterSecret;
@@ -132,7 +133,7 @@ public class DecryptingQueue {
this.threadId = threadId;
this.pdu = pdu;
}
private byte[] getEncryptedData() {
for (int i=0;i<pdu.getBody().getPartsNum();i++) {
Log.w("DecryptingQueue", "Content type (" + i + "): " + new String(pdu.getBody().getPart(i).getContentType()));
@@ -140,16 +141,16 @@ public class DecryptingQueue {
return pdu.getBody().getPart(i).getData();
}
}
return null;
}
public void run() {
EncryptingMmsDatabase database = DatabaseFactory.getEncryptingMmsDatabase(context, masterSecret);
try {
String messageFrom = pdu.getFrom().getString();
Recipients recipients = RecipientFactory.getRecipientsFromString(context, messageFrom);
Recipients recipients = RecipientFactory.getRecipientsFromString(context, messageFrom, false);
Recipient recipient = recipients.getPrimaryRecipient();
byte[] ciphertextPduBytes = getEncryptedData();
@@ -164,42 +165,42 @@ public class DecryptingQueue {
database.markAsNoSession(messageId, threadId);
return;
}
byte[] plaintextPduBytes;
synchronized (SessionCipher.CIPHER_LOCK) {
Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes));
SessionCipher cipher = new SessionCipher(context, masterSecret, recipient, new TextTransport());
SessionCipher cipher = new SessionCipher(context, masterSecret, recipient, new TextTransport());
plaintextPduBytes = cipher.decryptMessage(ciphertextPduBytes);
}
MultimediaMessagePdu plaintextPdu = (MultimediaMessagePdu)new PduParser(plaintextPduBytes).parse();
Log.w("DecryptingQueue", "Successfully decrypted MMS!");
database.insertSecureDecryptedMessageReceived(plaintextPdu, threadId);
database.delete(messageId);
database.delete(messageId);
} catch (RecipientFormattingException rfe) {
Log.w("DecryptingQueue", rfe);
database.markAsDecryptFailed(messageId, threadId);
} catch (InvalidMessageException ime) {
Log.w("DecryptingQueue", ime);
database.markAsDecryptFailed(messageId, threadId);
database.markAsDecryptFailed(messageId, threadId);
} catch (MmsException mme) {
Log.w("DecryptingQueue", mme);
database.markAsDecryptFailed(messageId, threadId);
database.markAsDecryptFailed(messageId, threadId);
}
}
}
private static class DecryptionWorkItem implements Runnable {
private long messageId;
private Context context;
private MasterSecret masterSecret;
private String body;
private String originator;
public DecryptionWorkItem(Context context, MasterSecret masterSecret, long messageId, String body, String originator) {
this.context = context;
this.messageId = messageId;
@@ -207,7 +208,7 @@ public class DecryptingQueue {
this.body = body;
this.originator = originator;
}
private void handleRemoteAsymmetricEncrypt() {
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
String plaintextBody;
@@ -215,17 +216,17 @@ public class DecryptingQueue {
synchronized (SessionCipher.CIPHER_LOCK) {
try {
Log.w("DecryptingQueue", "Parsing recipient for originator: " + originator);
Recipients recipients = RecipientFactory.getRecipientsFromString(context, originator);
Recipients recipients = RecipientFactory.getRecipientsFromString(context, originator, false);
Recipient recipient = recipients.getPrimaryRecipient();
Log.w("DecryptingQueue", "Parsed Recipient: " + recipient.getNumber());
if (!KeyUtil.isSessionFor(context, recipient)) {
Log.w("DecryptingQueue", "No such recipient session...");
database.markAsNoSession(messageId);
return;
}
SessionCipher cipher = new SessionCipher(context, masterSecret, recipient, new SmsTransportDetails());
SessionCipher cipher = new SessionCipher(context, masterSecret, recipient, new SmsTransportDetails());
plaintextBody = new String(cipher.decryptMessage(body.getBytes()));
} catch (InvalidMessageException e) {
Log.w("DecryptionQueue", e);
@@ -240,11 +241,11 @@ public class DecryptingQueue {
database.updateSecureMessageBody(masterSecret, messageId, plaintextBody);
}
private void handleLocalAsymmetricEncrypt() {
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
String plaintextBody;
try {
AsymmetricMasterCipher asymmetricMasterCipher = new AsymmetricMasterCipher(MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret));
String encryptedBody = body.substring(Prefix.ASYMMETRIC_LOCAL_ENCRYPT.length());
@@ -258,15 +259,15 @@ public class DecryptingQueue {
database.markAsDecryptFailed(messageId);
return;
}
database.updateMessageBody(masterSecret, messageId, plaintextBody);
}
public void run() {
public void run() {
if (body.startsWith(Prefix.ASYMMETRIC_ENCRYPT)) handleRemoteAsymmetricEncrypt();
else if (body.startsWith(Prefix.ASYMMETRIC_LOCAL_ENCRYPT)) handleLocalAsymmetricEncrypt();
}
}
}