/* * 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 an empty entry in the secure storage file. * * @author David Brazdil * */ class Empty { // FILE FORMAT private static final int OFFSET_NEXTINDEX = Storage.ENCRYPTED_ENTRY_SIZE - 4; // STATIC private static ArrayList<Empty> cacheEmpty = new ArrayList<Empty>(); /** * Removes all instances from the list of cached objects. * Be sure you don't use the instances afterwards. */ public static void forceClearCache() { synchronized (cacheEmpty) { cacheEmpty = new ArrayList<Empty>(); } } /** * Returns an instance of Empty class at the end of the file. * * @return the empty * @throws StorageFileException the storage file exception */ static Empty createEmpty() throws StorageFileException { // create a new one at the end of the file Empty empty = new Empty(Storage.getStorage().getEntriesCount(), false); Header.getHeader().attachEmpty(empty); return empty; } /** * Returns an instance of Empty class with given index in file. Reads it from the file if not cached. * * @param index Index in file * @return the empty * @throws StorageFileException the storage file exception */ static Empty getEmpty(long index) throws StorageFileException { if (index <= 0L) return null; // try looking it up synchronized (cacheEmpty) { for (Empty empty: cacheEmpty) if (empty.getEntryIndex() == index) return empty; } // create a new one return new Empty(index, true); } /** * Creates a new Empty class at the index of an already existing element. * This old element has to make sure that it there are no pointers pointing to it before it asks to be written over. * * @param index Index in the file * @return the empty * @throws StorageFileException the storage file exception */ static Empty replaceWithEmpty(long index) throws StorageFileException { Empty empty = new Empty(index, false); Header.getHeader().attachEmpty(empty); return empty; } /** * Returns an index of a single entry that was removed from the linked list of empty entries and is now available to be replaced by useful data entry. * @return * @throws StorageFileException */ static long getEmptyIndex() throws StorageFileException { return getEmptyIndices(1)[0]; } /** * Returns an index of several entries that were removed from the linked list of empty entries and are now available to be replaced by useful data entry. * * @param count Number of entries requested * @return the empty indices * @throws StorageFileException the storage file exception */ static long[] getEmptyIndices(int count) throws StorageFileException { long[] indices = new long[count]; Header header = Header.getHeader(); for (int i = 0; i < count; ++i) { Empty empty; while ((empty = header.getFirstEmpty()) == null) { // there are no free entries left // => add some addEmptyEntries(Storage.ALIGN_SIZE / Storage.CHUNK_SIZE); } // remove the entry from stack header.setIndexEmpty(empty.getIndexNext()); // remove from cache synchronized (cacheEmpty) { cacheEmpty.remove(empty); } // return the index of the freed entry indices[i] = empty.getEntryIndex(); } // save header header.saveToFile(); return indices; } /** * Appends new empty entries to the storage file. * * @param count Number of entries requested * @throws StorageFileException the storage file exception */ static void addEmptyEntries(int count) throws StorageFileException { for (int i = 0; i < count; ++i) { // create the empty entry Empty.createEmpty(); } } /** * Count the number of empty entries available * NOTE: Will cache all of them! It is intended to be used only by the testing classes. * @return * @throws StorageFileException */ static int getEmptyEntriesCount() throws StorageFileException { int count = 0; Empty free = Header.getHeader().getFirstEmpty(); while (free != null) { ++count; free = free.getNextEmpty(); } return count; } // INTERNAL FIELDS private long mEntryIndex; // READ ONLY private long mIndexNext; /** * Constructor * @param index Which chunk of data should occupy in file * @param readFromFile Does this entry already exist in the file? * @throws StorageFileException */ private Empty(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); } setIndexNext(LowLevel.getUnsignedInt(dataPlain, OFFSET_NEXTINDEX)); } else { // default values setIndexNext(0L); saveToFile(); } synchronized (cacheEmpty) { cacheEmpty.add(this); } } // FUNCTIONS /** * Saves contents of the class to the storage file. * * @throws StorageFileException the storage file exception */ public void saveToFile() throws StorageFileException { ByteBuffer entryBuffer = ByteBuffer.allocate(Storage.ENCRYPTED_ENTRY_SIZE); entryBuffer.put(Encryption.getEncryption().generateRandomData(OFFSET_NEXTINDEX)); entryBuffer.put(LowLevel.getBytesUnsignedInt(this.mIndexNext)); byte[] dataEncrypted = null; try { dataEncrypted = Encryption.getEncryption().encryptSymmetricWithMasterKey(entryBuffer.array()); } catch (EncryptionException e) { throw new StorageFileException(e); } Storage.getStorage().setEntry(mEntryIndex, dataEncrypted); } /** * Return an instance of the next Empty entry in the linked list, or null if there isn't any. * @return * @throws StorageFileException */ Empty getNextEmpty() throws StorageFileException { return Empty.getEmpty(mIndexNext); } // GETTERS / SETTERS long getEntryIndex() { return mEntryIndex; } long getIndexNext() { return mIndexNext; } void setIndexNext(long indexNext) { if (indexNext > 0xFFFFFFFFL || indexNext < 0L) throw new IndexOutOfBoundsException(); this.mIndexNext = indexNext; } }