diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0123f46..0a89861 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3,7 +3,7 @@ package="net.zetetic" android:versionCode="1" android:versionName="1.0-SNAPSHOT"> - + diff --git a/assets/corrupt.db b/assets/corrupt.db new file mode 100644 index 0000000..878444c Binary files /dev/null and b/assets/corrupt.db differ diff --git a/src/main/java/net/zetetic/tests/AttachDatabaseTest.java b/src/main/java/net/zetetic/tests/AttachDatabaseTest.java index 1d259a3..a7fbfd0 100644 --- a/src/main/java/net/zetetic/tests/AttachDatabaseTest.java +++ b/src/main/java/net/zetetic/tests/AttachDatabaseTest.java @@ -1,11 +1,10 @@ package net.zetetic.tests; - import net.sqlcipher.database.SQLiteDatabase; import net.zetetic.QueryHelper; + import net.zetetic.ZeteticApplication; -import javax.management.Query; import java.io.File; public class AttachDatabaseTest extends SQLCipherTest { diff --git a/src/main/java/net/zetetic/tests/ClosedDatabaseTest.java b/src/main/java/net/zetetic/tests/ClosedDatabaseTest.java new file mode 100644 index 0000000..c955fb3 --- /dev/null +++ b/src/main/java/net/zetetic/tests/ClosedDatabaseTest.java @@ -0,0 +1,335 @@ +package net.zetetic.tests; + +import android.util.Log; + +import java.io.File; +import java.io.IOException; + +import java.util.Locale; + +import net.sqlcipher.database.SQLiteDatabase; +import net.sqlcipher.database.SQLiteException; +import net.zetetic.ZeteticApplication; + +public class ClosedDatabaseTest extends SQLCipherTest { + + @Override + public TestResult run() { + + TestResult result = new TestResult(getName(), false); + try { + result.setResult(execute(null)); + SQLiteDatabase.releaseMemory(); + } catch (Exception e) { + Log.v(ZeteticApplication.TAG, e.toString()); + } + return result; + } + + @Override + public boolean execute(SQLiteDatabase null_database_ignored) { + + File testDatabasePath = ZeteticApplication.getInstance().getDatabasePath("closed-db-test.db"); + + boolean status = false; + + try { + SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(testDatabasePath, "", null); + + database.close(); + + status = execute_closed_database_tests(database); + } catch (Exception e) { + // Uncaught [unexpected] exception: + Log.e(ZeteticApplication.TAG, "Unexpected exception", e); + return false; + } + finally { + testDatabasePath.delete(); + } + + return status; + } + + boolean execute_closed_database_tests(SQLiteDatabase database) { + try { + /* operations that check if db is closed (and throw IllegalStateException): */ + try { + // should throw IllegalStateException: + database.beginTransaction(); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.beginTransaction() did NOT throw exception on closed database"); + return false; + } catch (IllegalStateException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.beginTransaction() did throw exception on closed database OK", e); + } + + try { + // should throw IllegalStateException: + database.endTransaction(); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.endTransaction() did NOT throw exception on closed database"); + return false; + } catch (IllegalStateException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.endTransaction() did throw exception on closed database OK", e); + } + + try { + // should throw IllegalStateException: + database.setTransactionSuccessful(); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.setTransactionSuccessful() did NOT throw throw exception on closed database"); + return false; + } catch (IllegalStateException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.setTransactionSuccessful() did throw exception on closed database OK", e); + } + + try { + // should throw IllegalStateException: + database.getVersion(); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.getVersion() did NOT throw exception on closed database"); + return false; + } catch (IllegalStateException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.getVersion() did throw exception on closed database OK", e); + } + + try { + // should throw IllegalStateException: + database.setVersion(111); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.setVersion() did NOT throw exception on closed database"); + return false; + } catch (IllegalStateException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.setVersion() did throw exception on closed database OK", e); + } + + try { + // should throw IllegalStateException: + database.getMaximumSize(); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.getMaximumSize() did NOT throw exception on closed database"); + return false; + } catch (IllegalStateException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.getMaximumSize() did throw exception on closed database OK", e); + } + + try { + // should throw IllegalStateException: + database.setMaximumSize(111); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.setMaximumSize() did NOT throw exception on closed database"); + return false; + } catch (IllegalStateException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.setMaximumSize() did throw exception on closed database OK", e); + } + + try { + // should throw IllegalStateException: + database.getPageSize(); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.getPageSize() did NOT throw exception on closed database"); + return false; + } catch (IllegalStateException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.getPageSize() did throw exception on closed database OK", e); + } + + try { + // should throw IllegalStateException: + database.setPageSize(111); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.setPageSize() did NOT throw exception on closed database"); + return false; + } catch (IllegalStateException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.setPageSize() did throw exception on closed database OK", e); + } + + try { + // should throw IllegalStateException: + database.compileStatement("SELECT 1;"); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.compileStatement() did NOT throw exception on closed database"); + return false; + } catch (IllegalStateException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.compileStatement() did throw exception on closed database OK", e); + } + + try { + // should throw IllegalStateException: + database.query("t1", new String[]{"a", "b"}, null, null, null, null, null); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.query() did NOT throw exception on closed database"); + return false; + } catch (IllegalStateException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.query() did throw exception on closed database OK", e); + } + + // TODO: cover more query functions + + try { + // should throw IllegalStateException: + database.execSQL("SELECT 1;"); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.execSQL(String) did NOT throw exception on closed database"); + return false; + } catch (IllegalStateException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.execSQL(String) did throw exception on closed database OK", e); + } + + try { + // should throw IllegalStateException: + database.execSQL("SELECT 1;", new Object[1]); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.execSQL(String, Object[]) did NOT throw exception on closed database"); + return false; + } catch (IllegalStateException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.execSQL(String, Object[]) did throw exception on closed database OK", e); + } + + try { + // should throw IllegalStateException: + database.rawExecSQL("SELECT 1;"); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.rawExecSQL() did NOT throw exception on closed database"); + return false; + } catch (IllegalStateException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.rawExecSQL() did throw exception on closed database OK", e); + } + + /* operations that do not explicitly check if db is closed + * ([should] throw SQLiteException on a closed database): */ + + try { + // should throw IllegalStateException: + database.setLocale(Locale.getDefault()); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.setLocale() did NOT throw exception on closed database"); + return false; + } catch (SQLiteException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.setLocale() did throw exception on closed database OK", e); + } + + try { + // [should] throw an exception on a closed database: + database.changePassword("new-password"); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.changePassword(String) did NOT throw exception on closed database"); + return false; + } catch (SQLiteException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.changePassword(String) did throw exception on closed database OK", e); + } + + try { + // [should] throw an exception on a closed database: + database.changePassword("new-password".toCharArray()); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.changePassword(char []) did NOT throw exception on closed database"); + return false; + } catch (SQLiteException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.changePassword(char []) did throw exception on closed database OK", e); + } + + try { + // [should] throw an exception on a closed database: + database.markTableSyncable("aa", "bb"); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.markTableSyncable(String, String) did NOT throw exception on closed database"); + return false; + } catch (SQLiteException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.markTableSyncable(String, String) did throw exception on closed database OK", e); + } + + // TBD SQLiteDatabase.markTableSyncable(String, String, String) does NOT throw exception on closed database: + try { + // NOTE: does not (yet) throw an exception on a closed database: + database.markTableSyncable("aa", "bb", "cc"); + + // ... + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.markTableSyncable(String, String, String) did NOT throw exception on closed database"); + return false; + } catch (SQLiteException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.markTableSyncable(String, String, String) did throw exception on closed database OK", e); + + // SIGNAL that this test must be updated: + //Log.e(ZeteticApplication.TAG, "BEHAVIOR CHANGED - please update the test"); + //return false; + } + + try { + // should throw IllegalStateException [since it calls getVersion()]: + database.needUpgrade(111); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.needUpgrade() did NOT throw exception on closed database"); + return false; + } catch (IllegalStateException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.needUpgrade() did throw exception on closed database OK", e); + } + + /* operations that are NOT expected to throw an exception if the database is closed ([should] not crash) */ + + + /* XXX TODO: these functions should check the db state, + * TBD either throw or simply return false if the db is closed */ + database.yieldIfContended(); + database.yieldIfContendedSafely(); + database.yieldIfContendedSafely(100); + + database.setLockingEnabled(false); + database.setLockingEnabled(true); + + database.close(); + + database.isReadOnly(); + database.isOpen(); + + database.isInCompiledSqlCache("SELECT 1;"); + database.purgeFromCompiledSqlCache("SELECT 1;"); + database.resetCompiledSqlCache(); + + database.getMaxSqlCacheSize(); + + try { + // should throw IllegalStateException: + database.setMaxSqlCacheSize(111); + + // should not get here: + Log.e(ZeteticApplication.TAG, "SQLiteDatabase.setMaxSqlCacheSize() did NOT throw exception on closed database"); + return false; + } catch (IllegalStateException e) { + Log.v(ZeteticApplication.TAG, "SQLiteDatabase.setMaxSqlCacheSize() did throw exception on closed database OK", e); + } + + } catch (Exception e) { + // Uncaught [unexpected] exception: + Log.e(ZeteticApplication.TAG, "Unexpected exception", e); + return false; + } + + return true; + } + + @Override + public String getName() { + return "Closed Database Test"; + } +} diff --git a/src/main/java/net/zetetic/tests/CorruptDatabaseTest.java b/src/main/java/net/zetetic/tests/CorruptDatabaseTest.java new file mode 100644 index 0000000..4d70d90 --- /dev/null +++ b/src/main/java/net/zetetic/tests/CorruptDatabaseTest.java @@ -0,0 +1,62 @@ +package net.zetetic.tests; + +import android.util.Log; + +import android.database.Cursor; + +import net.sqlcipher.database.SQLiteDatabase; + +import net.zetetic.ZeteticApplication; + +import java.io.File; +import java.io.IOException; + +public class CorruptDatabaseTest extends SQLCipherTest { + + @Override + public TestResult run() { + + TestResult result = new TestResult(getName(), false); + try { + result.setResult(execute(null)); + SQLiteDatabase.releaseMemory(); + } catch (Exception e) { + Log.v(ZeteticApplication.TAG, e.toString()); + } + return result; + } + + @Override + public boolean execute(SQLiteDatabase null_database_ignored) { + + File unencryptedDatabase = ZeteticApplication.getInstance().getDatabasePath("corrupt.db"); + + try { + ZeteticApplication.getInstance().extractAssetToDatabaseDirectory("corrupt.db"); + + SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(unencryptedDatabase, "", null); + + // NOTE: database not expected to be null, but check: + if (database == null) { + Log.e(TAG, "ERROR: got null database object"); + return false; + } + + database.close(); + + return true; + } catch (Exception ex) { + // Uncaught exception (not expected): + Log.e(TAG, "UNEXPECTED EXCEPTION", ex); + return false; + } + finally { + unencryptedDatabase.delete(); + } + } + + @Override + public String getName() { + return "Corrupt Database Test"; + } +} diff --git a/src/main/java/net/zetetic/tests/MultiThreadReadWriteTest.java b/src/main/java/net/zetetic/tests/MultiThreadReadWriteTest.java index 23f154b..a7e060d 100644 --- a/src/main/java/net/zetetic/tests/MultiThreadReadWriteTest.java +++ b/src/main/java/net/zetetic/tests/MultiThreadReadWriteTest.java @@ -1,8 +1,12 @@ package net.zetetic.tests; import android.util.Log; -import net.sqlcipher.Cursor; + +import android.database.Cursor; + import net.sqlcipher.database.SQLiteDatabase; +import net.sqlcipher.database.SQLiteException; + import net.zetetic.ZeteticApplication; import java.io.File; @@ -59,13 +63,20 @@ public Writer(int id, int size, DatabaseAccessType accessType) { public void run() { android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND); Log.i(TAG, String.format("writer thread %d beginning", id)); + // not expected to throw: SQLiteDatabase writer = getDatabase(accessType); - writer.execSQL("create table if not exists t1(a,b)"); - for (int index = 0; index < size; index++) { - Log.i(TAG, String.format("writer thread %d - insert data for row:%d", id, index)); - writer.execSQL("insert into t1(a,b) values(?, ?)", + + try { + writer.execSQL("create table if not exists t1(a,b)"); + + for (int index = 0; index < size; index++) { + Log.i(TAG, String.format("writer thread %d - insert data for row:%d", id, index)); + writer.execSQL("insert into t1(a,b) values(?, ?)", new Object[]{"one for the money", "two for the show"}); - } + } + } catch (SQLiteException ex) { Log.e(TAG, "caught exception, bailing", ex); } + catch (IllegalStateException ex) { Log.e(TAG, "caught exception, bailing", ex); } + closeDatabase(writer, accessType); Log.i(TAG, String.format("writer thread %d terminating", id)); } @@ -99,8 +110,13 @@ public void run() { synchronized void logRecordsBetween(SQLiteDatabase reader, int start, int end) { if(!reader.isOpen()) return; - Cursor results = reader.rawQuery("select rowid, * from t1 where rowid between ? and ?", + Cursor results = null; + try { + results = reader.rawQuery("select rowid, * from t1 where rowid between ? and ?", new String[]{String.valueOf(start), String.valueOf(end)}); + } catch (IllegalStateException ex) { Log.e(TAG, "caught exception, bailing", ex); } + catch (SQLiteException ex) { Log.e(TAG, "caught exception, bailing", ex); } + if (results != null) { Log.i(TAG, String.format("reader thread %d - writing results %d to %d", id, start, end)); while (results.moveToNext()) { @@ -113,16 +129,26 @@ synchronized void logRecordsBetween(SQLiteDatabase reader, int start, int end) { synchronized int getCurrentTableCount(SQLiteDatabase database) { int count = 0; + + if (!database.isOpen()) return -1; + Cursor cursor = null; + + // I. Attempt database.rawQuery()-bail (explicitly return 0) if it throws: try { - if (!database.isOpen()) return -1; - Cursor cursor = database.rawQuery("select count(*) from t1;", new String[]{}); - if (cursor != null) { + cursor = database.rawQuery("select count(*) from t1;", new String[]{}); + } catch (IllegalStateException ex) { Log.e(TAG, "caught exception, bailing with count = " + count, ex); return 0; } + catch (SQLiteException ex) { Log.e(TAG, "caught exception, bailing", ex); return 0; } + + // II. Attempt to get the count from the cursor: + if (cursor != null) { + try { if (cursor.moveToFirst()) { count = cursor.getInt(0); } cursor.close(); - } - } catch (IllegalStateException ex){} + } catch (IllegalStateException ex) { Log.e(TAG, "caught exception, bailing with count = " + count, ex); } + } + return count; } } @@ -186,4 +212,4 @@ private enum DatabaseAccessType { public String getName() { return "Multi-threaded read/write test"; } -} \ No newline at end of file +} diff --git a/src/main/java/net/zetetic/tests/TestSuiteRunner.java b/src/main/java/net/zetetic/tests/TestSuiteRunner.java index f93b6a1..0bbb8d3 100644 --- a/src/main/java/net/zetetic/tests/TestSuiteRunner.java +++ b/src/main/java/net/zetetic/tests/TestSuiteRunner.java @@ -44,6 +44,7 @@ private void runSuite(){ private List getTestsToRun(){ List tests = new ArrayList(); + tests.add(new AttachDatabaseTest()); tests.add(new CipherMigrateTest()); tests.add(new GetTypeFromCrossProcessCursorWrapperTest()); @@ -83,6 +84,9 @@ private List getTestsToRun(){ tests.add(new RawQueryTest()); tests.add(new OpenReadOnlyDatabaseTest()); tests.add(new RawRekeyTest()); + tests.add(new ClosedDatabaseTest()); + tests.add(new CorruptDatabaseTest()); + return tests; } }