/*
* Copyright 2011 David Brazdil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.ac.cam.db538.cryptosms.data;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map.Entry;
import org.joda.time.DateTime;
import uk.ac.cam.db538.cryptosms.MyApplication;
import uk.ac.cam.db538.cryptosms.SimCard;
import uk.ac.cam.db538.cryptosms.crypto.EncryptionInterface.WrongKeyDecryptionException;
import uk.ac.cam.db538.cryptosms.state.State;
import uk.ac.cam.db538.cryptosms.state.State.StateChangeListener;
import uk.ac.cam.db538.cryptosms.storage.Conversation;
import uk.ac.cam.db538.cryptosms.storage.SessionKeys;
import uk.ac.cam.db538.cryptosms.storage.Storage;
import uk.ac.cam.db538.cryptosms.storage.StorageFileException;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
/*
* Static class that parses messages in the database
*/
public class PendingParser {
public enum PendingParseResult {
OK_HANDSHAKE_MESSAGE,
OK_TEXT_MESSAGE,
OK_CONFIRM_MESSAGE,
MISSING_PARTS,
REDUNDANT_PARTS,
COULD_NOT_DECRYPT,
COULD_NOT_VERIFY,
CORRUPTED_DATA,
NO_SESSION_KEYS,
UNKNOWN_SENDER,
INTERNAL_ERROR,
TIMESTAMP_IN_FUTURE,
TIMESTAMP_OLD
}
private static PendingParser mParser;
/**
* Inits the singleton.
*
* @param context the context
*/
public static void init(Context context) {
if (mParser == null)
mParser = new PendingParser(context);
}
public static PendingParser getSingleton() {
return mParser;
}
private Context mContext;
private ArrayList<ParseResult> mParseResults;
private PendingParser(Context context) {
mContext = context;
mParseResults = new ArrayList<ParseResult>();
State.addListener(new StateChangeListener() {
@Override
public void onNewEvent() {
parseEvents();
}
});
}
public ArrayList<ParseResult> getParseResults() {
return mParseResults;
}
/**
* Holds the parsing results of pending messages
* @author db538
*
*/
public static class ParseResult implements Comparable<ParseResult> {
private ArrayList<Pending> mIdGroup;
private PendingParseResult mResult;
private Message mMessage;
private DateTime mTimeStamp;
/**
* Instantiates a new parses the result.
*
* @param idGroup the id group
* @param result the result
* @param message the message
*/
ParseResult(ArrayList<Pending> idGroup, PendingParseResult result, Message message) {
mIdGroup = idGroup;
mResult = result;
mMessage = message;
DateTime timeStamp = new DateTime(0);
for (Pending p : mIdGroup) {
DateTime thisStamp = p.getTimeStamp();
if (thisStamp.compareTo(timeStamp) > 0)
timeStamp = thisStamp;
}
mTimeStamp = timeStamp;
}
public ArrayList<Pending> getIdGroup() {
return mIdGroup;
}
public PendingParseResult getResult() {
return mResult;
}
public Message getMessage() {
return mMessage;
}
public DateTime getTimestamp() {
return mTimeStamp;
}
/* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(ParseResult another) {
return this.getTimestamp().compareTo(another.getTimestamp());
}
public String getSender() {
if (mIdGroup != null && mIdGroup.size() > 0)
return mIdGroup.get(0).getSender();
else
return null;
}
/**
* Removes messages in the id group from database
*
* @param database the database
*/
public void removeFromDb(DbPendingAdapter database) {
for (Pending p : mIdGroup)
database.removeEntry(p);
}
}
/**
* Parses the pending messages.
*/
public synchronized void parseEvents() {
new EventsUpdateTask().execute();
}
private class EventsUpdateTask extends AsyncTask<Void, Void, Void> {
private boolean mUpdateConversations;
private ArrayList<ParseResult> mParseResults;
private Exception mException = null;
@Override
protected void onPreExecute() {
super.onPreExecute();
State.notifyEventParsingStarted();
Log.d(MyApplication.APP_TAG, "Parsing...");
}
@Override
protected Void doInBackground(Void... arg0) {
try {
mParseResults = new ArrayList<PendingParser.ParseResult>();
// parse pending stuff
DbPendingAdapter database = new DbPendingAdapter(mContext);
database.open();
try {
// have the pending messages sorted into groups by their type and ID
ArrayList<ArrayList<Pending>> idGroups = database.getAllIdGroups();
for(ArrayList<Pending> idGroup : idGroups) {
// check that there are not too many parts
if (idGroup.size() > 255)
mParseResults.add(new ParseResult(idGroup, PendingParseResult.REDUNDANT_PARTS, null));
else if (idGroup.size() > 0) {
switch (idGroup.get(0).getType()) {
case HANDSHAKE:
case CONFIRM:
mParseResults.add(KeysMessage.parseKeysMessage(idGroup));
break;
case TEXT:
mParseResults.add(TextMessage.parseTextMessage(idGroup));
break;
}
}
}
mUpdateConversations = false;
HashMap<String, Integer> mapToBeHashed = new HashMap<String, Integer>();
for (ParseResult parseResult : mParseResults) {
if (parseResult.getResult() == PendingParseResult.OK_CONFIRM_MESSAGE ||
parseResult.getResult() == PendingParseResult.OK_TEXT_MESSAGE ) {
mParseResults.remove(parseResult);
parseResult.removeFromDb(database);
mUpdateConversations = true;
}
if (parseResult.getResult() == PendingParseResult.OK_TEXT_MESSAGE) {
Pending msgPart = parseResult.mIdGroup.get(0);
String sender = msgPart.getSender();
int toBeHashed = ((TextMessage) parseResult.getMessage()).getToBeHashed();
Integer toBeHashedMax = mapToBeHashed.get(sender);
if (toBeHashedMax == null)
toBeHashedMax = Integer.valueOf(0);
if (toBeHashed > toBeHashedMax)
mapToBeHashed.put(sender, toBeHashed);
}
}
// increment key ID to the highest we decoded
for (Entry<String, Integer> elem : mapToBeHashed.entrySet()) {
try {
Conversation conv = Conversation.getConversation(elem.getKey());
SessionKeys keys = conv.getSessionKeys(SimCard.getSingleton().getNumber());
keys.incrementIn(elem.getValue());
keys.saveToFile();
} catch (StorageFileException ex) {
mException = ex;
return null;
}
}
Collections.sort(mParseResults, Collections.reverseOrder());
} finally {
database.close();
}
} catch (WrongKeyDecryptionException ex) {
mException = ex;
}
return null;
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
if (mException != null) {
State.fatalException(mException);
return;
}
PendingParser.this.mParseResults = this.mParseResults;
State.notifyEventParsingFinished();
if (mUpdateConversations)
Storage.notifyChange();
}
}
/**
* Starts parsing
*/
public static void forceParsing() {
getSingleton().parseEvents();
}
}