422 lines
18 KiB
Java
422 lines
18 KiB
Java
package org.thoughtcrime.securesms.database.helpers;
|
|
|
|
|
|
import android.content.Context;
|
|
import android.database.Cursor;
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import net.sqlcipher.database.SQLiteDatabase;
|
|
import net.sqlcipher.database.SQLiteDatabaseHook;
|
|
import net.sqlcipher.database.SQLiteOpenHelper;
|
|
|
|
import org.session.libsession.utilities.TextSecurePreferences;
|
|
import org.session.libsignal.utilities.Log;
|
|
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
|
import org.thoughtcrime.securesms.database.DraftDatabase;
|
|
import org.thoughtcrime.securesms.database.GroupDatabase;
|
|
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
|
import org.thoughtcrime.securesms.database.JobDatabase;
|
|
import org.thoughtcrime.securesms.database.LokiAPIDatabase;
|
|
import org.thoughtcrime.securesms.database.LokiBackupFilesDatabase;
|
|
import org.thoughtcrime.securesms.database.LokiMessageDatabase;
|
|
import org.thoughtcrime.securesms.database.LokiThreadDatabase;
|
|
import org.thoughtcrime.securesms.database.LokiUserDatabase;
|
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
|
import org.thoughtcrime.securesms.database.PushDatabase;
|
|
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
|
import org.thoughtcrime.securesms.database.SearchDatabase;
|
|
import org.thoughtcrime.securesms.database.SessionContactDatabase;
|
|
import org.thoughtcrime.securesms.database.SessionJobDatabase;
|
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
|
|
|
public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|
|
|
@SuppressWarnings("unused")
|
|
private static final String TAG = SQLCipherOpenHelper.class.getSimpleName();
|
|
|
|
// First public release (1.0.0) DB version was 27.
|
|
// So we have to keep the migrations onwards.
|
|
private static final int lokiV7 = 28;
|
|
private static final int lokiV8 = 29;
|
|
private static final int lokiV9 = 30;
|
|
private static final int lokiV10 = 31;
|
|
private static final int lokiV11 = 32;
|
|
private static final int lokiV12 = 33;
|
|
private static final int lokiV13 = 34;
|
|
private static final int lokiV14_BACKUP_FILES = 35;
|
|
private static final int lokiV15 = 36;
|
|
private static final int lokiV16 = 37;
|
|
private static final int lokiV17 = 38;
|
|
private static final int lokiV18_CLEAR_BG_POLL_JOBS = 39;
|
|
private static final int lokiV19 = 40;
|
|
private static final int lokiV20 = 41;
|
|
private static final int lokiV21 = 42;
|
|
private static final int lokiV22 = 43;
|
|
private static final int lokiV23 = 44;
|
|
private static final int lokiV24 = 45;
|
|
private static final int lokiV25 = 46;
|
|
private static final int lokiV26 = 47;
|
|
private static final int lokiV27 = 48;
|
|
private static final int lokiV28 = 49;
|
|
private static final int lokiV29 = 50;
|
|
private static final int lokiV30 = 51;
|
|
private static final int lokiV31 = 52;
|
|
private static final int lokiV32 = 53;
|
|
private static final int lokiV33 = 54;
|
|
private static final int lokiV34 = 55;
|
|
|
|
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
|
private static final int DATABASE_VERSION = lokiV34;
|
|
private static final String DATABASE_NAME = "signal.db";
|
|
|
|
private final Context context;
|
|
private final DatabaseSecret databaseSecret;
|
|
|
|
public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) {
|
|
super(context, DATABASE_NAME, null, DATABASE_VERSION, new SQLiteDatabaseHook() {
|
|
@Override
|
|
public void preKey(SQLiteDatabase db) {
|
|
db.rawExecSQL("PRAGMA cipher_default_kdf_iter = 1;");
|
|
db.rawExecSQL("PRAGMA cipher_default_page_size = 4096;");
|
|
}
|
|
|
|
@Override
|
|
public void postKey(SQLiteDatabase db) {
|
|
db.rawExecSQL("PRAGMA kdf_iter = '1';");
|
|
db.rawExecSQL("PRAGMA cipher_page_size = 4096;");
|
|
// if not vacuumed in a while, perform that operation
|
|
long currentTime = System.currentTimeMillis();
|
|
// 7 days
|
|
if (currentTime - TextSecurePreferences.getLastVacuumTime(context) > 604_800_000) {
|
|
db.rawExecSQL("VACUUM;");
|
|
TextSecurePreferences.setLastVacuumNow(context);
|
|
}
|
|
}
|
|
});
|
|
|
|
this.context = context.getApplicationContext();
|
|
this.databaseSecret = databaseSecret;
|
|
}
|
|
|
|
@Override
|
|
public void onCreate(SQLiteDatabase db) {
|
|
db.execSQL(SmsDatabase.CREATE_TABLE);
|
|
db.execSQL(MmsDatabase.CREATE_TABLE);
|
|
db.execSQL(AttachmentDatabase.CREATE_TABLE);
|
|
db.execSQL(ThreadDatabase.CREATE_TABLE);
|
|
db.execSQL(DraftDatabase.CREATE_TABLE);
|
|
db.execSQL(PushDatabase.CREATE_TABLE);
|
|
db.execSQL(GroupDatabase.CREATE_TABLE);
|
|
db.execSQL(RecipientDatabase.CREATE_TABLE);
|
|
db.execSQL(GroupReceiptDatabase.CREATE_TABLE);
|
|
for (String sql : SearchDatabase.CREATE_TABLE) {
|
|
db.execSQL(sql);
|
|
}
|
|
for (String sql : JobDatabase.CREATE_TABLE) {
|
|
db.execSQL(sql);
|
|
}
|
|
db.execSQL(LokiAPIDatabase.getCreateSnodePoolTableCommand());
|
|
db.execSQL(LokiAPIDatabase.getCreateOnionRequestPathTableCommand());
|
|
db.execSQL(LokiAPIDatabase.getCreateSwarmTableCommand());
|
|
db.execSQL(LokiAPIDatabase.getCreateLastMessageHashValueTable2Command());
|
|
db.execSQL(LokiAPIDatabase.getCreateReceivedMessageHashValuesTable3Command());
|
|
db.execSQL(LokiAPIDatabase.getCreateOpenGroupAuthTokenTableCommand());
|
|
db.execSQL(LokiAPIDatabase.getCreateLastMessageServerIDTableCommand());
|
|
db.execSQL(LokiAPIDatabase.getCreateLastDeletionServerIDTableCommand());
|
|
db.execSQL(LokiAPIDatabase.getCreateDeviceLinkCacheCommand());
|
|
db.execSQL(LokiAPIDatabase.getCreateUserCountTableCommand());
|
|
db.execSQL(LokiAPIDatabase.getCreateSessionRequestTimestampCacheCommand());
|
|
db.execSQL(LokiAPIDatabase.getCreateSessionRequestSentTimestampTableCommand());
|
|
db.execSQL(LokiAPIDatabase.getCreateSessionRequestProcessedTimestampTableCommand());
|
|
db.execSQL(LokiAPIDatabase.getCreateOpenGroupPublicKeyTableCommand());
|
|
db.execSQL(LokiAPIDatabase.getCreateOpenGroupProfilePictureTableCommand());
|
|
db.execSQL(LokiAPIDatabase.getCreateClosedGroupEncryptionKeyPairsTable());
|
|
db.execSQL(LokiAPIDatabase.getCreateClosedGroupPublicKeysTable());
|
|
db.execSQL(LokiMessageDatabase.getCreateMessageIDTableCommand());
|
|
db.execSQL(LokiMessageDatabase.getCreateMessageToThreadMappingTableCommand());
|
|
db.execSQL(LokiMessageDatabase.getCreateErrorMessageTableCommand());
|
|
db.execSQL(LokiMessageDatabase.getCreateMessageHashTableCommand());
|
|
db.execSQL(LokiThreadDatabase.getCreateSessionResetTableCommand());
|
|
db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand());
|
|
db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand());
|
|
db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand());
|
|
db.execSQL(SessionJobDatabase.getCreateSessionJobTableCommand());
|
|
db.execSQL(LokiMessageDatabase.getUpdateMessageIDTableForType());
|
|
db.execSQL(LokiMessageDatabase.getUpdateMessageMappingTable());
|
|
db.execSQL(SessionContactDatabase.getCreateSessionContactTableCommand());
|
|
db.execSQL(RecipientDatabase.getCreateNotificationTypeCommand());
|
|
db.execSQL(ThreadDatabase.getCreatePinnedCommand());
|
|
db.execSQL(GroupDatabase.getCreateUpdatedTimestampCommand());
|
|
db.execSQL(RecipientDatabase.getCreateApprovedCommand());
|
|
db.execSQL(RecipientDatabase.getCreateApprovedMeCommand());
|
|
db.execSQL(MmsDatabase.createMessageRequestResponseCommand);
|
|
db.execSQL(LokiAPIDatabase.CREATE_FORK_INFO_TABLE_COMMAND);
|
|
db.execSQL(LokiAPIDatabase.CREATE_DEFAULT_FORK_INFO_COMMAND);
|
|
db.execSQL(LokiAPIDatabase.UPDATE_HASHES_INCLUDE_NAMESPACE_COMMAND);
|
|
db.execSQL(LokiAPIDatabase.UPDATE_RECEIVED_INCLUDE_NAMESPACE_COMMAND);
|
|
db.execSQL(LokiAPIDatabase.INSERT_LAST_HASH_DATA);
|
|
db.execSQL(LokiAPIDatabase.DROP_LEGACY_LAST_HASH);
|
|
db.execSQL(LokiAPIDatabase.INSERT_RECEIVED_HASHES_DATA);
|
|
db.execSQL(LokiAPIDatabase.DROP_LEGACY_RECEIVED_HASHES);
|
|
|
|
executeStatements(db, SmsDatabase.CREATE_INDEXS);
|
|
executeStatements(db, MmsDatabase.CREATE_INDEXS);
|
|
executeStatements(db, AttachmentDatabase.CREATE_INDEXS);
|
|
executeStatements(db, ThreadDatabase.CREATE_INDEXS);
|
|
executeStatements(db, DraftDatabase.CREATE_INDEXS);
|
|
executeStatements(db, GroupDatabase.CREATE_INDEXS);
|
|
executeStatements(db, GroupReceiptDatabase.CREATE_INDEXES);
|
|
}
|
|
|
|
@Override
|
|
public void onConfigure(SQLiteDatabase db) {
|
|
super.onConfigure(db);
|
|
// Loki - Enable write ahead logging mode and increase the cache size.
|
|
// This should be disabled if we ever run into serious race condition bugs.
|
|
db.enableWriteAheadLogging();
|
|
db.execSQL("PRAGMA cache_size = 10000");
|
|
}
|
|
|
|
@Override
|
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
Log.i(TAG, "Upgrading database: " + oldVersion + ", " + newVersion);
|
|
|
|
db.beginTransaction();
|
|
|
|
try {
|
|
|
|
if (oldVersion < lokiV7) {
|
|
db.execSQL(LokiMessageDatabase.getCreateErrorMessageTableCommand());
|
|
}
|
|
|
|
if (oldVersion < lokiV8) {
|
|
db.execSQL(LokiAPIDatabase.getCreateSessionRequestTimestampCacheCommand());
|
|
}
|
|
|
|
if (oldVersion < lokiV9) {
|
|
db.execSQL(LokiAPIDatabase.getCreateSnodePoolTableCommand());
|
|
db.execSQL(LokiAPIDatabase.getCreateOnionRequestPathTableCommand());
|
|
}
|
|
|
|
if (oldVersion < lokiV10) {
|
|
db.execSQL(LokiAPIDatabase.getCreateSessionRequestSentTimestampTableCommand());
|
|
db.execSQL(LokiAPIDatabase.getCreateSessionRequestProcessedTimestampTableCommand());
|
|
}
|
|
|
|
if (oldVersion < lokiV11) {
|
|
db.execSQL(LokiAPIDatabase.getCreateOpenGroupPublicKeyTableCommand());
|
|
}
|
|
|
|
if (oldVersion < lokiV12) {
|
|
db.execSQL(LokiAPIDatabase.getCreateLastMessageHashValueTable2Command());
|
|
}
|
|
|
|
if (oldVersion < lokiV13) {
|
|
db.execSQL(LokiAPIDatabase.getCreateReceivedMessageHashValuesTable3Command());
|
|
}
|
|
|
|
if (oldVersion < lokiV14_BACKUP_FILES) {
|
|
db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand());
|
|
}
|
|
|
|
if (oldVersion < lokiV16) {
|
|
db.execSQL(LokiAPIDatabase.getCreateOpenGroupProfilePictureTableCommand());
|
|
}
|
|
|
|
if (oldVersion < lokiV17) {
|
|
db.execSQL("ALTER TABLE part ADD COLUMN audio_visual_samples BLOB");
|
|
db.execSQL("ALTER TABLE part ADD COLUMN audio_duration INTEGER");
|
|
}
|
|
|
|
if (oldVersion < lokiV18_CLEAR_BG_POLL_JOBS) {
|
|
// BackgroundPollJob was replaced with BackgroundPollWorker. Clear all the scheduled job records.
|
|
db.execSQL("DELETE FROM job_spec WHERE factory_key = 'BackgroundPollJob'");
|
|
db.execSQL("DELETE FROM constraint_spec WHERE factory_key = 'BackgroundPollJob'");
|
|
}
|
|
|
|
// Many classes were removed. We need to update DB structure and data to match the code changes.
|
|
if (oldVersion < lokiV19) {
|
|
db.execSQL(LokiAPIDatabase.getCreateClosedGroupEncryptionKeyPairsTable());
|
|
db.execSQL(LokiAPIDatabase.getCreateClosedGroupPublicKeysTable());
|
|
db.execSQL("DROP TABLE identities");
|
|
deleteJobRecords(db, "RetrieveProfileJob");
|
|
deleteJobRecords(db,
|
|
"RefreshAttributesJob",
|
|
"RotateProfileKeyJob",
|
|
"RefreshUnidentifiedDeliveryAbilityJob",
|
|
"RotateCertificateJob"
|
|
);
|
|
}
|
|
|
|
if (oldVersion < lokiV20) {
|
|
deleteJobRecords(db,
|
|
"CleanPreKeysJob",
|
|
"RefreshPreKeysJob",
|
|
"CreateSignedPreKeyJob",
|
|
"RotateSignedPreKeyJob",
|
|
"MultiDeviceBlockedUpdateJob",
|
|
"MultiDeviceConfigurationUpdateJob",
|
|
"MultiDeviceContactUpdateJob",
|
|
"MultiDeviceGroupUpdateJob",
|
|
"MultiDeviceOpenGroupUpdateJob",
|
|
"MultiDeviceProfileKeyUpdateJob",
|
|
"MultiDeviceReadUpdateJob",
|
|
"MultiDeviceStickerPackOperationJob",
|
|
"MultiDeviceStickerPackSyncJob",
|
|
"MultiDeviceVerifiedUpdateJob",
|
|
"ServiceOutageDetectionJob",
|
|
"SessionRequestMessageSendJob"
|
|
);
|
|
}
|
|
|
|
if (oldVersion < lokiV21) {
|
|
deleteJobRecords(db,
|
|
"ClosedGroupUpdateMessageSendJob",
|
|
"NullMessageSendJob",
|
|
"StickerDownloadJob",
|
|
"StickerPackDownloadJob",
|
|
"MmsSendJob",
|
|
"MmsReceiveJob",
|
|
"MmsDownloadJob",
|
|
"SmsSendJob",
|
|
"SmsSentJob",
|
|
"SmsReceiveJob",
|
|
"PushGroupUpdateJob",
|
|
"ResetThreadSessionJob");
|
|
}
|
|
|
|
if (oldVersion < lokiV22) {
|
|
db.execSQL(SessionJobDatabase.getCreateSessionJobTableCommand());
|
|
deleteJobRecords(db,
|
|
"PushGroupSendJob",
|
|
"PushMediaSendJob",
|
|
"PushTextSendJob",
|
|
"SendReadReceiptJob",
|
|
"TypingSendJob",
|
|
"AttachmentUploadJob",
|
|
"RequestGroupInfoJob",
|
|
"ClosedGroupUpdateMessageSendJobV2",
|
|
"SendDeliveryReceiptJob");
|
|
}
|
|
|
|
if (oldVersion < lokiV23) {
|
|
db.execSQL("ALTER TABLE groups ADD COLUMN zombie_members TEXT");
|
|
db.execSQL(LokiMessageDatabase.getUpdateMessageIDTableForType());
|
|
db.execSQL(LokiMessageDatabase.getUpdateMessageMappingTable());
|
|
}
|
|
|
|
if (oldVersion < lokiV24) {
|
|
String swarmTable = LokiAPIDatabase.Companion.getSwarmTable();
|
|
String snodePoolTable = LokiAPIDatabase.Companion.getSnodePoolTable();
|
|
db.execSQL("DROP TABLE " + swarmTable);
|
|
db.execSQL("DROP TABLE " + snodePoolTable);
|
|
db.execSQL(LokiAPIDatabase.getCreateSnodePoolTableCommand());
|
|
db.execSQL(LokiAPIDatabase.getCreateSwarmTableCommand());
|
|
}
|
|
|
|
if (oldVersion < lokiV25) {
|
|
String jobTable = SessionJobDatabase.sessionJobTable;
|
|
db.execSQL("DROP TABLE " + jobTable);
|
|
db.execSQL(SessionJobDatabase.getCreateSessionJobTableCommand());
|
|
}
|
|
|
|
if (oldVersion < lokiV26) {
|
|
db.execSQL(SessionContactDatabase.getCreateSessionContactTableCommand());
|
|
}
|
|
|
|
if (oldVersion < lokiV27) {
|
|
db.execSQL(RecipientDatabase.getCreateNotificationTypeCommand());
|
|
}
|
|
|
|
if (oldVersion < lokiV28) {
|
|
db.execSQL(LokiMessageDatabase.getCreateMessageHashTableCommand());
|
|
}
|
|
|
|
if (oldVersion < lokiV29) {
|
|
db.execSQL(ThreadDatabase.getCreatePinnedCommand());
|
|
}
|
|
|
|
if (oldVersion < lokiV30) {
|
|
db.execSQL(GroupDatabase.getCreateUpdatedTimestampCommand());
|
|
}
|
|
|
|
if (oldVersion < lokiV31) {
|
|
db.execSQL(RecipientDatabase.getCreateApprovedCommand());
|
|
db.execSQL(RecipientDatabase.getCreateApprovedMeCommand());
|
|
db.execSQL(RecipientDatabase.getUpdateApprovedCommand());
|
|
db.execSQL(MmsDatabase.createMessageRequestResponseCommand);
|
|
}
|
|
|
|
if (oldVersion < lokiV32) {
|
|
db.execSQL(RecipientDatabase.getUpdateResetApprovedCommand());
|
|
db.execSQL(RecipientDatabase.getUpdateApprovedSelectConversations());
|
|
}
|
|
|
|
if (oldVersion < lokiV33) {
|
|
db.execSQL(LokiAPIDatabase.CREATE_FORK_INFO_TABLE_COMMAND);
|
|
db.execSQL(LokiAPIDatabase.CREATE_DEFAULT_FORK_INFO_COMMAND);
|
|
db.execSQL(LokiAPIDatabase.UPDATE_HASHES_INCLUDE_NAMESPACE_COMMAND);
|
|
db.execSQL(LokiAPIDatabase.UPDATE_RECEIVED_INCLUDE_NAMESPACE_COMMAND);
|
|
}
|
|
|
|
if (oldVersion < lokiV34) {
|
|
db.execSQL(LokiAPIDatabase.INSERT_LAST_HASH_DATA);
|
|
db.execSQL(LokiAPIDatabase.DROP_LEGACY_LAST_HASH);
|
|
db.execSQL(LokiAPIDatabase.INSERT_RECEIVED_HASHES_DATA);
|
|
db.execSQL(LokiAPIDatabase.DROP_LEGACY_RECEIVED_HASHES);
|
|
}
|
|
|
|
db.setTransactionSuccessful();
|
|
} finally {
|
|
db.endTransaction();
|
|
}
|
|
}
|
|
|
|
public SQLiteDatabase getReadableDatabase() {
|
|
return getReadableDatabase(databaseSecret.asString());
|
|
}
|
|
|
|
public SQLiteDatabase getWritableDatabase() {
|
|
return getWritableDatabase(databaseSecret.asString());
|
|
}
|
|
|
|
public void markCurrent(SQLiteDatabase db) {
|
|
db.setVersion(DATABASE_VERSION);
|
|
}
|
|
|
|
private void executeStatements(SQLiteDatabase db, String[] statements) {
|
|
for (String statement : statements)
|
|
db.execSQL(statement);
|
|
}
|
|
|
|
private static boolean columnExists(@NonNull SQLiteDatabase db, @NonNull String table, @NonNull String column) {
|
|
try (Cursor cursor = db.rawQuery("PRAGMA table_info(" + table + ")", null)) {
|
|
int nameColumnIndex = cursor.getColumnIndexOrThrow("name");
|
|
|
|
while (cursor.moveToNext()) {
|
|
String name = cursor.getString(nameColumnIndex);
|
|
|
|
if (name.equals(column)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Cleans up all the records related to the job keys specified.
|
|
* This method should be called once the Signal job class is deleted from the project.
|
|
*/
|
|
private static void deleteJobRecords(SQLiteDatabase db, String... jobKeys) {
|
|
for (String jobKey : jobKeys) {
|
|
db.execSQL("DELETE FROM job_spec WHERE factory_key = ?", new String[]{jobKey});
|
|
db.execSQL("DELETE FROM constraint_spec WHERE factory_key = ?", new String[]{jobKey});
|
|
}
|
|
}
|
|
}
|