/*
* 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.storage;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import uk.ac.cam.db538.cryptosms.crypto.Encryption;
import uk.ac.cam.db538.cryptosms.crypto.EncryptionInterface.EncryptionException;
import uk.ac.cam.db538.cryptosms.utils.LowLevel;
/**
*
* Class representing a message part entry in the secure storage file.
* This should not be accessible outside the package. Message has API for handling it seamlessly
*
* @author David Brazdil
*
*/
class MessageDataPart {
// FILE FORMAT
private static final int LENGTH_FLAGS = 1;
private static final int LENGTH_MESSAGEBODYLEN = 2;
private static final int LENGTH_MESSAGEBODY = 140;
private static final int OFFSET_FLAGS = 0;
private static final int OFFSET_MESSAGEBODYLEN = OFFSET_FLAGS + LENGTH_FLAGS;
private static final int OFFSET_MESSAGEBODY = OFFSET_MESSAGEBODYLEN + LENGTH_MESSAGEBODYLEN;
private static final int OFFSET_RANDOMDATA = OFFSET_MESSAGEBODY + LENGTH_MESSAGEBODY;
private static final int OFFSET_NEXTINDEX = Storage.ENCRYPTED_ENTRY_SIZE - 4;
private static final int OFFSET_PREVINDEX = OFFSET_NEXTINDEX - 4;
private static final int OFFSET_PARENTINDEX = OFFSET_PREVINDEX - 4;
private static final int LENGTH_RANDOMDATA = OFFSET_PARENTINDEX - OFFSET_RANDOMDATA;
// STATIC
private static ArrayList<MessageDataPart> cacheMessageDataPart = new ArrayList<MessageDataPart>();
/**
* Removes all instances from the list of cached objects.
* Be sure you don't use the instances afterwards.
*/
public static void forceClearCache() {
synchronized (cacheMessageDataPart) {
cacheMessageDataPart = new ArrayList<MessageDataPart>();
}
}
/**
* Replaces an empty entry with new MessagePart.
*
* @return the message data part
* @throws StorageFileException the storage file exception
*/
static MessageDataPart createMessageDataPart() throws StorageFileException {
return new MessageDataPart(Empty.getEmptyIndex(), false);
}
/**
* Returns an instance of Empty class with given index in file.
*
* @param index Index in file
* @return the message data part
* @throws StorageFileException the storage file exception
*/
static MessageDataPart getMessageDataPart(long index) throws StorageFileException {
if (index <= 0L)
return null;
// try looking it up
synchronized (cacheMessageDataPart) {
for (MessageDataPart msgPart: cacheMessageDataPart)
if (msgPart.getEntryIndex() == index)
return msgPart;
}
// create a new one
return new MessageDataPart(index, true);
}
// INTERNAL FIELDS
private long mEntryIndex; // READ ONLY
private boolean mDeliveredPart;
private byte[] mMessageBody;
private long mIndexParent;
private long mIndexPrev;
private long mIndexNext;
// CONSTRUCTORS
/**
* Constructor
* @param index Which chunk of data should occupy in file
* @param readFromFile Does this entry already exist in the file?
* @throws StorageFileException
*/
private MessageDataPart(long index, boolean readFromFile) throws StorageFileException {
mEntryIndex = index;
if (readFromFile) {
byte[] dataEncrypted = Storage.getStorage().getEntry(index);
byte[] dataPlain;
try {
dataPlain = Encryption.getEncryption().decryptSymmetricWithMasterKey(dataEncrypted);
} catch (EncryptionException e) {
throw new StorageFileException(e);
}
byte flags = dataPlain[OFFSET_FLAGS];
boolean deliveredPart = ((flags & (1 << 7)) == 0) ? false : true;
setDeliveredPart(deliveredPart);
int messageBodyLength = Math.min(LENGTH_MESSAGEBODY, LowLevel.getUnsignedShort(dataPlain, OFFSET_MESSAGEBODYLEN));
setMessageBody(LowLevel.cutData(dataPlain, OFFSET_MESSAGEBODY, messageBodyLength));
setIndexParent(LowLevel.getUnsignedInt(dataPlain, OFFSET_PARENTINDEX));
setIndexPrev(LowLevel.getUnsignedInt(dataPlain, OFFSET_PREVINDEX));
setIndexNext(LowLevel.getUnsignedInt(dataPlain, OFFSET_NEXTINDEX));
}
else {
// default values
setDeliveredPart(false);
setMessageBody(new byte[0]);
setIndexParent(0L);
setIndexPrev(0L);
setIndexNext(0L);
saveToFile();
}
synchronized (cacheMessageDataPart) {
cacheMessageDataPart.add(this);
}
}
// FUNCTIONS
/**
* Save contents of the class to the storage file.
*
* @throws StorageFileException the storage file exception
*/
void saveToFile() throws StorageFileException {
ByteBuffer msgBuffer = ByteBuffer.allocate(Storage.ENCRYPTED_ENTRY_SIZE);
// flags
byte flags = 0;
if (this.mDeliveredPart)
flags |= (byte) ((1 << 7) & 0xFF);
msgBuffer.put(flags);
// message body
msgBuffer.put(LowLevel.getBytesUnsignedShort(this.mMessageBody.length));
msgBuffer.put(LowLevel.wrapData(mMessageBody, LENGTH_MESSAGEBODY));
// random data
msgBuffer.put(Encryption.getEncryption().generateRandomData(LENGTH_RANDOMDATA));
// indices
msgBuffer.put(LowLevel.getBytesUnsignedInt(this.mIndexParent));
msgBuffer.put(LowLevel.getBytesUnsignedInt(this.mIndexPrev));
msgBuffer.put(LowLevel.getBytesUnsignedInt(this.mIndexNext));
byte[] dataEncrypted = null;
try {
dataEncrypted = Encryption.getEncryption().encryptSymmetricWithMasterKey(msgBuffer.array());
} catch (EncryptionException e) {
throw new StorageFileException(e);
}
Storage.getStorage().setEntry(mEntryIndex, dataEncrypted);
}
/**
* Returns Message that is a parent to this MessagePart in the data structure
* @return
* @throws StorageFileException
*/
MessageData getParent() throws StorageFileException {
return MessageData.getMessageData(mIndexParent);
}
/**
* Returns next MessagePart in the linked list, or null if there isn't any
* @return
* @throws StorageFileException
*/
MessageDataPart getPreviousMessageDataPart() throws StorageFileException {
return MessageDataPart.getMessageDataPart(mIndexPrev);
}
/**
* Returns next MessagePart in the linked list, or null if there isn't any
* @return
* @throws StorageFileException
*/
MessageDataPart getNextMessageDataPart() throws StorageFileException {
return getMessageDataPart(mIndexNext);
}
/**
* Replace the file space with Empty entry.
*
* @throws StorageFileException the storage file exception
*/
void delete() throws StorageFileException {
MessageDataPart prev = this.getPreviousMessageDataPart();
MessageDataPart next = this.getNextMessageDataPart();
if (prev != null) {
// this is not the first message part in the list
// update the previous one
prev.setIndexNext(this.getIndexNext());
prev.saveToFile();
} else {
// this IS the first message part in the list
// update parent
MessageData parent = this.getParent();
parent.setIndexMessageParts(this.getIndexNext());
parent.saveToFile();
}
// update next one
if (next != null) {
next.setIndexPrev(this.getIndexPrev());
next.saveToFile();
}
// delete this message
Empty.replaceWithEmpty(mEntryIndex);
// remove from cache
synchronized (cacheMessageDataPart) {
cacheMessageDataPart.remove(this);
}
// make this instance invalid
this.mEntryIndex = -1L;
}
// GETTERS / SETTERS
long getEntryIndex() {
return mEntryIndex;
}
void setDeliveredPart(boolean deliveredPart) {
this.mDeliveredPart = deliveredPart;
}
boolean getDeliveredPart() {
return mDeliveredPart;
}
void setMessageBody(byte[] messageBody) {
this.mMessageBody = messageBody;
}
byte[] getMessageBody() {
return mMessageBody;
}
long getIndexParent() {
return mIndexParent;
}
void setIndexParent(long indexParent) {
if (indexParent > 0xFFFFFFFFL || indexParent < 0L)
throw new IndexOutOfBoundsException();
this.mIndexParent = indexParent;
}
long getIndexPrev() {
return mIndexPrev;
}
void setIndexPrev(long indexPrev) {
if (indexPrev > 0xFFFFFFFFL || indexPrev < 0L)
throw new IndexOutOfBoundsException();
this.mIndexPrev = indexPrev;
}
long getIndexNext() {
return mIndexNext;
}
void setIndexNext(long indexNext) {
if (indexNext > 0xFFFFFFFFL || indexNext < 0L)
throw new IndexOutOfBoundsException();
this.mIndexNext = indexNext;
}
}