/* * 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.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import uk.ac.cam.db538.cryptosms.crypto.Encryption; public final class Storage { static final int CHUNK_SIZE = 256; static final int ALIGN_SIZE = 256 * 32; // 8KB static final int ENCRYPTED_ENTRY_SIZE = CHUNK_SIZE - Encryption.SYM_OVERHEAD; // SINGLETON STUFF private static Storage mSingleton = null; private static String mFilename = null; /** * Returns the instance of the Database singleton class. * Singleton has to be initialised beforehand with the initSingleton method. * @return instance of Database class * @throws StorageFileException * @throws IOException */ public static Storage getStorage() throws StorageFileException { if (mSingleton == null) mSingleton = new Storage(); return mSingleton; } /** * Initialises the Database singleton to use a specified file as the secure storage. * @param filename Path to the secure storage file. * @throws IOException * @throws StorageFileException */ public static void setFilename(String filename) { mFilename = filename; } // FILE MANIPULATION private StorageFile smsFile; /** * Constructor * @param filename * @throws IOException * @throws StorageFileException */ private Storage() throws StorageFileException { if (mFilename == null) throw new StorageFileException("No filename was set"); boolean exists = true; try { File f = new File(mFilename); exists = f.exists(); smsFile = new StorageFile(mFilename); } catch (IOException ex) { throw new StorageFileException(ex); } if (!exists) createFile(); } /** * Creates empty file when there isn't any * @throws FileNotFoundException * @throws IOException * @throws StorageFileException */ private synchronized void createFile() throws StorageFileException { int countFreeEntries = ALIGN_SIZE / CHUNK_SIZE - 1; // create an empty instance of the header Header.createHeader(); // add some empty entries Empty.addEmptyEntries(countFreeEntries); } /** * Close file. * * @throws StorageFileException the storage file exception */ public synchronized void closeFile() throws StorageFileException { try { smsFile.mFile.close(); } catch (IOException ex) { throw new StorageFileException(ex); } } /** * Delete file. */ public synchronized void deleteFile() { new File(mFilename).delete(); } /** * Returns number of entries in the file based on the file size * @return * @throws StorageFileException */ public synchronized long getEntriesCount() throws StorageFileException { try { return smsFile.mFile.length() / CHUNK_SIZE; } catch (IOException ex) { throw new StorageFileException(ex); } } /** * Reads data from specified entry index the file. * * @param index the index * @return the entry * @throws StorageFileException the storage file exception */ synchronized byte[] getEntry(long index) throws StorageFileException { try { long offset = index * CHUNK_SIZE; if (offset > smsFile.mFile.length() - CHUNK_SIZE) throw new StorageFileException("Index in history file out of bounds"); byte[] data = new byte[CHUNK_SIZE]; smsFile.mFile.seek(offset); smsFile.mFile.read(data); return data; } catch (IOException ex) { throw new StorageFileException(ex); } } /** * Saves data to specified entry index the file. * * @param index the index * @param data the data * @throws StorageFileException the storage file exception */ synchronized void setEntry(long index, byte[] data) throws StorageFileException { try { long offset = index * CHUNK_SIZE; long fileSize = smsFile.mFile.length(); if (offset > fileSize) throw new StorageFileException("Index in history file out of bounds"); smsFile.mFile.seek(offset); smsFile.mFile.write(data); } catch (IOException ex) { throw new StorageFileException(ex); } } // LISTENERS private static ArrayList<StorageChangeListener> mGlobalListeners = new ArrayList<StorageChangeListener>(); public static interface StorageChangeListener { /** * On update. */ public void onUpdate(); } /** * Notify change. */ public static void notifyChange() { for (StorageChangeListener listener: mGlobalListeners) listener.onUpdate(); } /** * Adds a listener. * * @param listener the listener */ public static void addListener(StorageChangeListener listener) { mGlobalListeners.add(listener); } /** * Removes listener. * * @param listener the listener */ public static void removeListener(StorageChangeListener listener) { mGlobalListeners.remove(listener); } // FOR TESTING ONLY /** * Deletes the singleton. */ static void freeSingleton() { if (mSingleton != null) try { mSingleton.smsFile.mFile.close(); } catch (Exception e) { } mSingleton = null; } }