/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * 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 com.android.server.locksettings.recoverablekeystore.storage;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RootOfTrustEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;

/**
 * Helper for creating the recoverable key database.
 */
class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper {
    private static final String TAG = "RecoverableKeyStoreDbHp";

    static final int DATABASE_VERSION = 6; // Added user id serial number.
    private static final String DATABASE_NAME = "recoverablekeystore.db";

    private static final String SQL_CREATE_KEYS_ENTRY =
            "CREATE TABLE " + KeysEntry.TABLE_NAME + "( "
                    + KeysEntry._ID + " INTEGER PRIMARY KEY,"
                    + KeysEntry.COLUMN_NAME_USER_ID + " INTEGER,"
                    + KeysEntry.COLUMN_NAME_UID + " INTEGER,"
                    + KeysEntry.COLUMN_NAME_ALIAS + " TEXT,"
                    + KeysEntry.COLUMN_NAME_NONCE + " BLOB,"
                    + KeysEntry.COLUMN_NAME_WRAPPED_KEY + " BLOB,"
                    + KeysEntry.COLUMN_NAME_GENERATION_ID + " INTEGER,"
                    + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER,"
                    + KeysEntry.COLUMN_NAME_RECOVERY_STATUS + " INTEGER,"
                    + KeysEntry.COLUMN_NAME_KEY_METADATA + " BLOB,"
                    + "UNIQUE(" + KeysEntry.COLUMN_NAME_UID + ","
                    + KeysEntry.COLUMN_NAME_ALIAS + "))";

    private static final String SQL_CREATE_USER_METADATA_ENTRY =
            "CREATE TABLE " + UserMetadataEntry.TABLE_NAME + "( "
                    + UserMetadataEntry._ID + " INTEGER PRIMARY KEY,"
                    + UserMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER UNIQUE,"
                    + UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER,"
                    + UserMetadataEntry.COLUMN_NAME_USER_SERIAL_NUMBER + " INTEGER DEFAULT -1)";

    private static final String SQL_CREATE_RECOVERY_SERVICE_METADATA_ENTRY =
            "CREATE TABLE " + RecoveryServiceMetadataEntry.TABLE_NAME + " ("
                    + RecoveryServiceMetadataEntry._ID + " INTEGER PRIMARY KEY,"
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER,"
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " INTEGER,"
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION + " INTEGER,"
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT + " INTEGER,"
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST + " TEXT,"
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH + " BLOB,"
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL + " INTEGER,"
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES + " TEXT,"
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID + " INTEGER,"
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS + " BLOB,"
                    + "UNIQUE("
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID  + ","
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + "))";

    private static final String SQL_CREATE_ROOT_OF_TRUST_ENTRY =
            "CREATE TABLE " + RootOfTrustEntry.TABLE_NAME + " ("
                    + RootOfTrustEntry._ID + " INTEGER PRIMARY KEY,"
                    + RootOfTrustEntry.COLUMN_NAME_USER_ID + " INTEGER,"
                    + RootOfTrustEntry.COLUMN_NAME_UID + " INTEGER,"
                    + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " TEXT,"
                    + RootOfTrustEntry.COLUMN_NAME_CERT_PATH + " BLOB,"
                    + RootOfTrustEntry.COLUMN_NAME_CERT_SERIAL + " INTEGER,"
                    + "UNIQUE("
                    + RootOfTrustEntry.COLUMN_NAME_USER_ID  + ","
                    + RootOfTrustEntry.COLUMN_NAME_UID  + ","
                    + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + "))";

    private static final String SQL_DELETE_KEYS_ENTRY =
            "DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME;

    private static final String SQL_DELETE_USER_METADATA_ENTRY =
            "DROP TABLE IF EXISTS " + UserMetadataEntry.TABLE_NAME;

    private static final String SQL_DELETE_RECOVERY_SERVICE_METADATA_ENTRY =
            "DROP TABLE IF EXISTS " + RecoveryServiceMetadataEntry.TABLE_NAME;

    private static final String SQL_DELETE_ROOT_OF_TRUST_ENTRY =
            "DROP TABLE IF EXISTS " + RootOfTrustEntry.TABLE_NAME;

    RecoverableKeyStoreDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(SQL_CREATE_KEYS_ENTRY);
        db.execSQL(SQL_CREATE_USER_METADATA_ENTRY);
        db.execSQL(SQL_CREATE_RECOVERY_SERVICE_METADATA_ENTRY);
        db.execSQL(SQL_CREATE_ROOT_OF_TRUST_ENTRY);
    }

    @Override
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.e(TAG, "Recreating recoverablekeystore after unexpected version downgrade.");
        dropAllKnownTables(db); // Wipe database.
        onCreate(db);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if (oldVersion < 2) {
            dropAllKnownTables(db); // Wipe database.
            onCreate(db);
            return;
        }

        if (oldVersion < 3 && newVersion >= 3) {
            upgradeDbForVersion3(db);
            oldVersion = 3;
        }

        if (oldVersion < 4 && newVersion >= 4) {
            upgradeDbForVersion4(db);
            oldVersion = 4;
        }

        if (oldVersion < 5 && newVersion >= 5) {
            upgradeDbForVersion5(db);
            oldVersion = 5;
        }

        if (oldVersion < 6 && newVersion >= 6) {
            upgradeDbForVersion6(db);
            oldVersion = 6;
        }

        if (oldVersion != newVersion) {
            Log.e(TAG, "Failed to update recoverablekeystore database to the most recent version");
        }
    }

    private void dropAllKnownTables(SQLiteDatabase db) {
            db.execSQL(SQL_DELETE_KEYS_ENTRY);
            db.execSQL(SQL_DELETE_USER_METADATA_ENTRY);
            db.execSQL(SQL_DELETE_RECOVERY_SERVICE_METADATA_ENTRY);
            db.execSQL(SQL_DELETE_ROOT_OF_TRUST_ENTRY);
    }

    private void upgradeDbForVersion3(SQLiteDatabase db) {
        // Add the two columns for cert path and cert serial number
        addColumnToTable(db, RecoveryServiceMetadataEntry.TABLE_NAME,
                RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH, "BLOB", /*defaultStr=*/ null);
        addColumnToTable(db, RecoveryServiceMetadataEntry.TABLE_NAME,
                RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL, "INTEGER", /*defaultStr=*/
                null);
    }

    private void upgradeDbForVersion4(SQLiteDatabase db) {
        Log.d(TAG, "Updating recoverable keystore database to version 4");
        // Add new table with two columns for cert path and cert serial number.
        db.execSQL(SQL_CREATE_ROOT_OF_TRUST_ENTRY);
        // adds column to store root of trust currently used by the recovery agent
        addColumnToTable(db, RecoveryServiceMetadataEntry.TABLE_NAME,
                RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST, "TEXT",
                /*defaultStr=*/ null);
    }

    private void upgradeDbForVersion5(SQLiteDatabase db) {
        Log.d(TAG, "Updating recoverable keystore database to version 5");
        // adds a column to store the metadata for application keys
        addColumnToTable(db, KeysEntry.TABLE_NAME,
                KeysEntry.COLUMN_NAME_KEY_METADATA, "BLOB", /*defaultStr=*/ null);
    }

    private void upgradeDbForVersion6(SQLiteDatabase db) {
        Log.d(TAG, "Updating recoverable keystore database to version 6");
        // adds a column to store the user serial number
        addColumnToTable(db, UserMetadataEntry.TABLE_NAME,
                UserMetadataEntry.COLUMN_NAME_USER_SERIAL_NUMBER,
                "INTEGER DEFAULT -1",
                 /*defaultStr=*/ null);
    }

    private static void addColumnToTable(
            SQLiteDatabase db, String tableName, String column, String columnType,
            String defaultStr) {
        Log.d(TAG, "Adding column " + column + " to " + tableName + ".");

        String alterStr = "ALTER TABLE " + tableName + " ADD COLUMN " + column + " " + columnType;
        if (defaultStr != null && !defaultStr.isEmpty()) {
            alterStr += " DEFAULT " + defaultStr;
        }

        db.execSQL(alterStr + ";");
    }
}

