/*
 * Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Created on: Oct 15, 2018
 * Author: Zhongyue Li <zhongyue.li@samsung.com>
 * Brief: Session management.
 */

#include "TzwString.h"
#include "TzwMemory.h"
#include "TigerSession.h"
#include "TigerLogging.h"
#include "TigerStorageUtils.h"
#include "TzwStorage.h"
#include "TigerMacros.h"

uint64_t tigerCtlString(uint8_t* sessionStr,uint32_t sessionLen) {
    uint64_t value = 0;
    const size_t max_hash_len = 18; // maximum number of digits in uint64_t
    size_t len = sessionLen > max_hash_len ? max_hash_len : sessionLen;

    for (size_t i = 0; i < len; ++i) {
        // multiply by uidStrBase(26) to next digit.
        value *= 10;
  
        if(sessionStr[i] >= '0' && sessionStr[i] <= '9'){
            value += (sessionStr[i] - '0');
        } else if (sessionStr[i] >= 'a' && sessionStr[i] <= 'z'){
            value += (sessionStr[i] - 'a');
        } else if (sessionStr[i] >= 'A' && sessionStr[i] <= 'Z') {
            value += (sessionStr[i] - 'A');
        } else {
            value += 7;
        }
    }
 
    return value;
}


TigerSessionContext* tigerAllocateSessionContext() {
    return tzwMalloc(sizeof(TigerSessionContext));
}

void tigerFreeSessionContext(TigerSessionContext* session) {
    tzwFree(session);
}

uint64_t tigerGetCurrTime() {
#ifdef TZ_MODEL_QCOM
    return qsee_get_uptime();
#elif TIGER_TZ_MODEL_BLOWFISH
    TEE_Time t;
    TEE_GetSystemTime(&t);
    uint64_t time = (uint64_t) (t.seconds * 1000 + t.millis);
    return  time;
#elif TIGER_TZ_MODEL_Kinibi
    TEE_Time t;
    TEE_GetSystemTime(&t);
    uint64_t time = (uint64_t) (t.seconds * 1000 + t.millis);
    return  time;
#endif
}

uint64_t tigerGenerateSessionId(TciKeyAlias_t alias, TciChallenge_t challenge, uint32_t uid){
    uint64_t sessionId = 100;
    uint64_t aliasValue = tigerCtlString(alias.value, alias.length);
    uint64_t challengeValue = tigerCtlString(challenge.value, challenge.length);
 
    sessionId = aliasValue & challengeValue;
    sessionId += uid;

    LOG_I("tigerGenerateSessionId session ID(%"PRIu64") generates successfully", sessionId);
    return sessionId;
}

TEE_Result tigerLoadSessionContext(TigerSessionContext* session) {
    LOG_FUNC_BEGIN;

    TIGER_ASSERT(session != NULL);
    TEE_Result status = TEE_SUCCESS;

    TzwSfsObject_t objHandle = TEE_HANDLE_NULL;

    do {
        status = tzwOpenSfsObject((void*) TIGER_SESSION_ALIAS, strlen(TIGER_SESSION_ALIAS),
                                             TEE_DATA_FLAG_ACCESS_READ, &objHandle);
        TIGER_CHECK_TEE_STATUS_SUCCESS_RETURN("tzwOpenSfsObject");

        //sessionId
        int32_t offset = getSessionOffsetTo(SESSION_ID);
        TIGER_ASSERT(offset != -1);
        status = tzwSeekIntoSfsObject(objHandle, offset, TEE_DATA_SEEK_SET);
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("Seek to session id");
        uint32_t count = 0;
        status = tzwReadFromSfsObject(objHandle, &session->sessionId, sizeof(session->sessionId), &count);
        if (count != sizeof(session->sessionId)) {
            TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("tzwReadFromSfsObject_SESSION");
            status = TEE_ERROR_CORRUPT_OBJECT;
        }
        LOG_I("tigerLoadSessionContext session ID(%"PRIu64")", session->sessionId);

        offset = getSessionOffsetTo(ALIAS_NAME);
        TIGER_ASSERT(offset != -1);
        status = tzwSeekIntoSfsObject(objHandle, offset, TEE_DATA_SEEK_SET);
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("Seek to alias value");
        count = 0;
        status = tzwReadFromSfsObject(objHandle, session->alias.value, MAX_KEY_ALIAS_SIZE_BYTES, &count);
        if (count != MAX_KEY_ALIAS_SIZE_BYTES) {
            TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("tzwReadFromSfsObject_ALIAS");
            status = TEE_ERROR_CORRUPT_OBJECT;
        }
        session->alias.length = strlen((char *)(session->alias.value));
        LOG_I("tigerLoadSessionContext alias value(%s), size(%d)",
              session->alias.value, session->alias.length);

        offset = getSessionOffsetTo(UID);
        TIGER_ASSERT(offset != -1);
        status = tzwSeekIntoSfsObject(objHandle, offset, TEE_DATA_SEEK_SET);
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("Seek to uid");
        count = 0;
        status = tzwReadFromSfsObject(objHandle, &session->uid, sizeof(session->uid), &count);
        if (count != sizeof(session->uid)) {
            TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("tzwReadFromSfsObject_UID");
            status = TEE_ERROR_CORRUPT_OBJECT;
        }
        LOG_I("tigerLoadSessionContext session UID(%u)", session->uid);

        offset = getSessionOffsetTo(CHALLENGE);
        TIGER_ASSERT(offset != -1);
        status = tzwSeekIntoSfsObject(objHandle, offset, TEE_DATA_SEEK_SET);
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("Seek to challenge");
        count = 0;
        status = tzwReadFromSfsObject(objHandle, session->challenge.value, MAX_CHALLENGE_NAME_SIZE, &count);
        if (count != MAX_CHALLENGE_NAME_SIZE) {
            TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("tzwReadFromSfsObject_CHALLENGE");
            status = TEE_ERROR_CORRUPT_OBJECT;
        }
        session->challenge.length = strlen((char *)(session->challenge.value));
        LOG_I("tigerLoadSessionContext challenge (%s), size(%d)",
              session->challenge.value, session->challenge.length);

        offset = getSessionOffsetTo(GENTIME);
        TIGER_ASSERT(offset != -1);
        status = tzwSeekIntoSfsObject(objHandle, offset, TEE_DATA_SEEK_SET);
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("Seek to generate time");
        count = 0;
        status = tzwReadFromSfsObject(objHandle, &session->genTime, sizeof(session->genTime), &count);
        if (count != sizeof(session->genTime)) {
            TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("tzwReadFromSfsObject_GENTIME");
            status = TEE_ERROR_CORRUPT_OBJECT;
        }
        LOG_I("tigerLoadSessionContext session time(%"PRIu64")", session->genTime);

    } while(0);

    tzwCloseSfsObject(objHandle);
    LOG_FUNC_END;
    return status;

}

TEE_Result tigerSaveSessionContext(TigerSessionContext* session) {
    LOG_FUNC_BEGIN;

    TIGER_ASSERT(session != NULL);
    TEE_Result status = TEE_SUCCESS;
    TzwSfsObject_t objHandle = TEE_HANDLE_NULL;

    do {
        LOG_D("tigerSaveSessionContext session ID(%"PRIu64"), alias(%s), challenge(%s) genTime(%"PRIu64")",
              session->sessionId, session->alias.value, session->challenge.value, session->genTime);

        status = tzwCreateSfsObject((void*) TIGER_SESSION_ALIAS, strlen(TIGER_SESSION_ALIAS),
                 TEE_DATA_FLAG_ACCESS_WRITE | TEE_DATA_FLAG_ACCESS_WRITE_META,
                 &objHandle);
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("tzwCreatSfsObject");

        status = tzwWriteToSfsObject(objHandle, &session->sessionId, sizeof(session->sessionId));
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("tzwWriteToSfsObject_sessionId");
        status = tzwWriteToSfsObject(objHandle, session->alias.value, MAX_KEY_ALIAS_SIZE_BYTES);
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("tzwWriteToSfsObject_alias");
        status = tzwWriteToSfsObject(objHandle, &session->uid, sizeof(session->uid));
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("tzwWriteToSfsObject_uid");
        status = tzwWriteToSfsObject(objHandle, session->challenge.value, MAX_CHALLENGE_NAME_SIZE);
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("tzwWriteToSfsObject_challenge");
        status = tzwWriteToSfsObject(objHandle, &session->genTime, sizeof(session->genTime));
        TIGER_CHECK_TEE_STATUS_SUCCESS_BREAK("tzwWriteToSfsObject_genTime");

    } while(0);


    tzwCloseSfsObject(objHandle);
    LOG_FUNC_END;
    return status;

}
TEE_Result tigerRemoveSessionObject(){
    LOG_FUNC_BEGIN;

    TEE_Result status = TEE_SUCCESS;

    TzwSfsObject_t objHandle = TEE_HANDLE_NULL;

    do {
        status = tzwOpenSfsObject((void*) TIGER_SESSION_ALIAS, strlen(TIGER_SESSION_ALIAS),
                                             TEE_DATA_FLAG_ACCESS_READ |TEE_DATA_FLAG_ACCESS_WRITE_META,
                                             &objHandle);
        TIGER_CHECK_TEE_STATUS_SUCCESS_RETURN("tzwOpenSfsObject");

        if(TEE_SUCCESS == status) {
            tzwCloseAndDeleteSfsObject(objHandle);
        }
    } while(0);

    LOG_FUNC_END;
    return status;
}
bool tigerCompareSessionContext(TigerSessionContext* session, TciKeyAlias_t alias,
                                      TciChallenge_t challenge, uint32_t uid) {
    LOG_FUNC_BEGIN;

    uint32_t aliasFlag = tzwMemCompare(session->alias.value, alias.value, sizeof(alias.length));
    uint32_t challengeFlag = tzwMemCompare(session->challenge.value, challenge.value, sizeof(challenge.length));
    bool uidFlag = (uid == session->uid ? 0 : 1);

    LOG_FUNC_END;
    return aliasFlag | challengeFlag | uidFlag;

}

bool tigerIsSessionTimout(TigerSessionContext* session) {
    uint64_t currTime = tigerGetCurrTime();
    return (currTime - session->genTime) > 30000 ? 1 : 0;
}
