/*
 * =====================================================================================
 *
 *       Filename:  logSaver.c
 *
 *    Description:  save log of normal / secure world.
 *
 *        Version:  1.1
 *        Created:  08/26/2016 09:24:13 AM
 *        Updated:  11/08/2018 09:24:13 AM
 *        Compiler:  armcc
 *
 *        Company:  Samsung Electronics
 *        Copyright (c) 2016 by Samsung Electronics, All rights reserved.
 *
 * =====================================================================================
 */
#include <stdio.h>
#include <sys/stat.h>
#include <errno.h>
#include <time.h>
#include "commonConfig.h"
#include "Log.h"

#ifndef DISABLE_LOG_SAVE
#include "File.h"
using vendor::samsung::hardware::security::drk::File;

#define MAX_LOG_MSG_LEN         5120
#define ENC_KEY_SIZE            32
#define ENC_BLOCK_SIZE          16
#define ENC_DATA_SIZE           256

typedef struct {
    char *logFilePath;
    char *lastLogFilePath;
    uint32_t logFileMaxSize;
} LogFileType;

static LogFileType gLogFileList[] = {
    {
        // ENCRYPTION_LOG
        (char *)COMMON_RAON_LOG_DIR"/raon.log",
        (char *)COMMON_RAON_LOG_DIR"/raon_last.log",
        MAX_LOG_FILE_LEN,
    },
    {
        // ISSUED_LOG
        (char *)COMMON_ISSUED_LOG_DIR"/issued.log",
        (char *)COMMON_ISSUED_LOG_DIR"/issued_last.log",
        MAX_LOG_FILE_LEN,
    },
};


static uint8_t   log_buf[MAX_LOG_MSG_LEN + 16];
static uint32_t  log_pos = 0;
#endif

Mutex DRKLog::mLogLock;

void DRKLog::LogMsg(char level, const char *format, ...)
{
    va_list     lc_arg;
    time_t      lc_log_time_t;
    struct tm   lc_log_tm;

    char        lc_log_buf[UNIT_LOG_MSG_LEN + 1] = {0};
    int         lc_len = 0;

    memset(&lc_log_tm, 0, sizeof(struct tm));

    lc_log_time_t = time(NULL);
    lc_log_tm = *localtime(&lc_log_time_t);

    lc_len = snprintf(lc_log_buf, UNIT_LOG_MSG_LEN, "%02d-%02d %02d:%02d:%02d[%c] ",
                      lc_log_tm.tm_mon += 1,  lc_log_tm.tm_mday,
                      lc_log_tm.tm_hour, lc_log_tm.tm_min, lc_log_tm.tm_sec, level);

    va_start(lc_arg, format);
    lc_len += vsnprintf(lc_log_buf + lc_len, UNIT_LOG_MSG_LEN - lc_len, format, lc_arg);
    va_end(lc_arg);

    if (level == 'E' || level == 'I') PLATFORM_LOGCAT("%s", lc_log_buf + TIME_LOG_LEN);
    else PLATFORM_LOGCATDBG("%s", lc_log_buf + TIME_LOG_LEN);

#ifndef DISABLE_LOG_SAVE
    if ((MAX_LOG_MSG_LEN > log_pos) &&
        (MAX_LOG_MSG_LEN - log_pos) >= lc_len) {
        log_pos += snprintf((char *)(log_buf + log_pos), (MAX_LOG_MSG_LEN - log_pos), "%s\n", lc_log_buf);
    }
#endif
    memset(lc_log_buf, 0, sizeof(lc_log_buf));
}

void DRKLog::LogHex(char *data, int len)
{
    char   buf[16 + 1], *pdata;
    int    nleft, i, j, buf_len;

    char   lc_log_buf[UNIT_LOG_MSG_LEN] = {0};
    int    lc_len = 0;

    if (data == NULL) {
        return;
    }

    lc_len = snprintf(lc_log_buf, UNIT_LOG_MSG_LEN, "[length:%d]\n", len);

    nleft = len;
    pdata = data;

    for (i = 0; i < len;) {
        if (nleft >= 16) {
            buf_len = 16;
        } else {
            buf_len = nleft;
        }

        if (lc_len + (buf_len * 3) + 1 > UNIT_LOG_MSG_LEN) {
            break;
        }

        memcpy(buf, pdata, buf_len);
        for (j = 0; j < buf_len; j++) {
            if (j + 1 == buf_len) {
                lc_len += snprintf(lc_log_buf + lc_len, UNIT_LOG_MSG_LEN - lc_len, "%02x\n", buf[j]);
            } else {
                lc_len += snprintf(lc_log_buf + lc_len, UNIT_LOG_MSG_LEN - lc_len, "%02x:", buf[j]);
            }
        }
        /* Update variables */
        nleft -= buf_len;
        i += buf_len;
        pdata += buf_len;
    }

    PLATFORM_LOGCATDBG("%s", lc_log_buf);
    memset(lc_log_buf, 0, sizeof(lc_log_buf));
}

void DRKLog::IssuedLogMsg(int32_t retCode)
{
#ifndef DISABLE_LOG_SAVE
    time_t      lc_log_time_t;
    struct tm   lc_log_tm;

    char        lc_log_buf[UNIT_LOG_MSG_LEN + 1] = {0};
    int         lc_len = 0;

    memset(&lc_log_tm, 0, sizeof(struct tm));

    lc_log_time_t = time(NULL);
    lc_log_tm = *localtime(&lc_log_time_t);

    if (retCode == NOT_ERROR) {
        lc_len = snprintf(lc_log_buf, UNIT_LOG_MSG_LEN, "%02d-%02d %02d:%02d:%02d DRK v2 Installation : Success.\n",
                          lc_log_tm.tm_mon += 1,  lc_log_tm.tm_mday,
                          lc_log_tm.tm_hour, lc_log_tm.tm_min, lc_log_tm.tm_sec);
    } else {
        lc_len = snprintf(lc_log_buf, UNIT_LOG_MSG_LEN, "%02d-%02d %02d:%02d:%02d DRK v2 Installation : Failure. (%X)\n",
                          lc_log_tm.tm_mon += 1,  lc_log_tm.tm_mday,
                          lc_log_tm.tm_hour, lc_log_tm.tm_min, lc_log_tm.tm_sec, retCode);
    }
    if (SaveLog(ISSUED_LOG, (uint8_t *)lc_log_buf, lc_len) != NOT_ERROR) {
        PLATFORM_LOGCAT("Issued log is not saved : %d", retCode);
    }
#else
    (void) retCode;
#endif
    return;
}

void DRKLog::PrintIssuedLog() {
#ifndef DISABLE_LOG_SAVE
    char text[NORMAL_ISSUED_LOG_LINE_LEN] = {0};
    int32_t ret = NOT_ERROR;
    uint32_t text_len = sizeof(text);
    File lcFile;

    if ((ret = lcFile.openFile(gLogFileList[ISSUED_LOG].logFilePath, READ)) != NOT_ERROR) {
        PLATFORM_LOGCAT("Failed to open IssuedLog with error %d, %s.", ret, strerror(errno));
        return;
    }

    if ((ret = lcFile.readLine((uint8_t*)text, &text_len)) != NOT_ERROR) {
        PLATFORM_LOGCAT("Failed to read IssuedLog with error %d, %s.", ret, strerror(errno));
        return;
    }

    PLATFORM_LOGCAT("%s", text);

    memset(text,0,sizeof(text));
#endif
    return;
}

int DRKLog::SaveLog(LOG_TYPE logType, uint8_t *data, uint32_t dataLen)
{
#ifndef DISABLE_LOG_SAVE
    int         ret = 0;
    struct stat fileStat;
    File        lcFile;

    Mutex::Autolock _lc(mLogLock);

    if (logType > sizeof(gLogFileList) / sizeof(LogFileType) - 1) {
        PLATFORM_LOGCAT("Unsupported log type : %d", logType);
        return ERR_NOT_IMPLEMENTED;
    }

    if (data == NULL || dataLen <= 0) {
        PLATFORM_LOGCAT("Invalid argument.");
        return ERR_INVALID_ARGUMENT;
    }

    if (stat(gLogFileList[logType].logFilePath, &fileStat) == 0) {
        if ((uint32_t)fileStat.st_size > gLogFileList[logType].logFileMaxSize) {
            if ((ret = remove(gLogFileList[logType].lastLogFilePath)) != NOT_ERROR) {
                PLATFORM_LOGCAT("remove log failed with ret : %d", ret);
            }

            if ((ret = rename(gLogFileList[logType].logFilePath, gLogFileList[logType].lastLogFilePath)) != NOT_ERROR) {
                PLATFORM_LOGCAT("rename log failed with ret : %d", ret);
            }
        }
    }

    if ((ret = lcFile.openFile(gLogFileList[logType].logFilePath, APPEND)) != NOT_ERROR) {
        PLATFORM_LOGCAT("Failed to open file to write : %s.", strerror(errno));
        return ERR_FILE_WRITE_FAILED;
    }

    if ((ret = lcFile.writeFile(data, dataLen)) != NOT_ERROR) {
        PLATFORM_LOGCAT("Failed to write into file  : %s.", strerror(errno));
        return ERR_FILE_WRITE_FAILED;
    }

    return ret;
#else
    (void)logType; (void)data; (void)dataLen;
    return NOT_ERROR;
#endif
}

int DRKLog::SaveNwdLog()
{
    int ret = 0;
#ifndef DISABLE_LOG_SAVE
    uint8_t     encrypteddata[MAX_LOG_MSG_LEN + 1] = {0};
    uint32_t    encrypteddataLen = 0;

    encrypteddataLen = snprintf((char *)encrypteddata, MAX_LOG_MSG_LEN, "%s%04d%s", (char *)LOG_MAGIC_CODE, 0, (char *)log_buf);

    SET_UINT32(encrypteddata, strlen((char *)LOG_MAGIC_CODE), log_pos);

    ret = SaveLog(ENCRYPTION_LOG, encrypteddata, encrypteddataLen);
    if (ret == NOT_ERROR) {
        chmod(gLogFileList[ENCRYPTION_LOG].logFilePath, 0664);
    }

    memset(&encrypteddata, 0, sizeof(encrypteddata));
    memset(log_buf, 0, log_pos);
    log_pos = 0;
#endif
    return ret;
}

int DRKLog::GetLog(LOG_TYPE logType, uint8_t **data)
{
#ifndef DISABLE_LOG_SAVE
    int      ret = 0;
    uint8_t  *logdata = NULL;
    uint32_t readn = 0, logdataLen = 0;
    File     lcFile;
    Bytes    lcLogData, lcRaonLogData;

    Mutex::Autolock _lc(mLogLock);

    if (logType > sizeof(gLogFileList) / sizeof(LogFileType) - 1) {
        PLATFORM_LOGCAT("Unsupported log type : %d", logType);
        return ERR_INVALID_ARGUMENT;
    }

    if (data == NULL) {
        PLATFORM_LOGCAT("Invalid argument.");
        return ERR_INVALID_ARGUMENT;
    }

    if ((ret = lcFile.openFile(gLogFileList[logType].logFilePath, READ)) != NOT_ERROR) {
        PLATFORM_LOGCAT("Failed to open file to write : %s.", strerror(errno));
        return ERR_FILE_READ_FAILED;
    }

    if ((ret = lcFile.readFile(lcLogData)) != NOT_ERROR) {
        PLATFORM_LOGCAT("Failed to write into file  : %s.", strerror(errno));
        return ERR_FILE_READ_FAILED;
    }

    if (logType != ENCRYPTION_LOG) {
        if (lcLogData.length() > 0) {
            *data = (uint8_t *)calloc(lcLogData.length() + 1, 1);
            memcpy(*data, (uint8_t *)lcLogData, lcLogData.length());
            ret = (int)lcLogData.length();
        }
    } else {
        lcRaonLogData.set(lcLogData.length() + 1);
        readn = 0;
        logdata = (uint8_t *)lcLogData;
        while (readn < lcLogData.length()) {
            if (strncmp((char *)(logdata + readn), LOG_MAGIC_CODE, strlen(LOG_MAGIC_CODE)) == 0) {
                readn += 8;
                logdataLen = GET_UINT32(logdata, readn);
                readn += 4;
                lcRaonLogData.set(logdata + readn, logdataLen);
                readn += logdataLen;
            } else {
                readn += 8;
                logdataLen = GET_UINT32(logdata, readn);
                readn += 4;
                readn += logdataLen;
            }
        }
        if (lcRaonLogData.length() > 0) {
            *data = (uint8_t *)calloc(lcRaonLogData.length() + 1, 1);
            memcpy(*data, (uint8_t *)lcRaonLogData, lcRaonLogData.length());
            ret = (int)lcRaonLogData.length();
        }
    }

    return ret;
#else
    (void)logType; (void)data;
    return NOT_ERROR;
#endif
}

void DRKLog::InitIssuedLog()
{
#ifndef DISABLE_LOG_SAVE
    int         ret = 0;
    struct stat fileStat;
    File        lcFile;

    Mutex::Autolock _lc(mLogLock);

    if (stat(gLogFileList[ISSUED_LOG].logFilePath, &fileStat) == 0) {
        if ((ret = remove(gLogFileList[ISSUED_LOG].lastLogFilePath)) != NOT_ERROR) {
            PLATFORM_LOGCAT("remove log failed with ret : %d %s", ret, strerror(errno));
        }

        if ((ret = rename(gLogFileList[ISSUED_LOG].logFilePath, gLogFileList[ISSUED_LOG].lastLogFilePath)) != NOT_ERROR) {
            PLATFORM_LOGCAT("rename log failed with ret : %d %s", ret, strerror(errno));
        }
    }
#endif
    return;
}
