/******************************************************************************
 *
 *  Copyright 2018 NXP
 *
 *  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.
 *
 ******************************************************************************/
#define LOG_TAG "LSClient"
#include "LsClient.h"
#include <dirent.h>
#include <log/log.h>
#include <pthread.h>
#include <stdlib.h>
#include "LsLib.h"

uint8_t datahex(char c);
extern bool ese_debug_enabled;
static android::sp<ISecureElementHalCallback> cCallback;
void* performLSDownload_thread(void* data);
/*******************************************************************************
**
** Function:        LSC_Start
**
** Description:     Starts the LSC update with encrypted data privided in the
                    updater file
**
** Returns:         SUCCESS if ok.
**
*******************************************************************************/
LSCSTATUS LSC_Start(const char* name, const char* dest, uint8_t* pdata,
                    uint16_t len, uint8_t* respSW) {
  static const char fn[] = "LSC_Start";
  LSCSTATUS status = LSCSTATUS_FAILED;
  if (name != NULL) {
    status = Perform_LSC(name, dest, pdata, len, respSW);
  } else {
    ALOGE("%s: LS script file is missing", fn);
  }
  ALOGD_IF(ese_debug_enabled, "%s: Exit; status=0x0%X", fn, status);
  return status;
}

/*******************************************************************************
**
** Function:        LSC_doDownload
**
** Description:     Start LS download process by creating thread
**
** Returns:         SUCCESS of ok
**
*******************************************************************************/
LSCSTATUS LSC_doDownload(
    const android::sp<ISecureElementHalCallback>& clientCallback) {
  static const char fn[] = "LSC_doDownload";
  LSCSTATUS status;
  pthread_t thread;
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  cCallback = clientCallback;
  if (pthread_create(&thread, &attr, &performLSDownload_thread, NULL) < 0) {
    ALOGE("%s: Thread creation failed", fn);
    status = LSCSTATUS_FAILED;
  } else {
    status = LSCSTATUS_SUCCESS;
  }
  pthread_attr_destroy(&attr);
  return status;
}

/*******************************************************************************
**
** Function:        performLSDownload_thread
**
** Description:     Perform LS during hal init
**
** Returns:         None
**
*******************************************************************************/
void* performLSDownload_thread(__attribute__((unused)) void* data) {
  ALOGD_IF(ese_debug_enabled, "%s enter:  ", __func__);

  const char* lsUpdateBackupPath =
      "/data/vendor/secure_element/loaderservice_updater.txt";
  const char* lsUpdateBackupOutPath =
      "/data/vendor/secure_element/loaderservice_updater_out.txt";

  /*generated SHA-1 string for secureElementLS
  This will remain constant as handled in secureElement HAL*/
  char sha1[] = "6d583e84f2710e6b0f06beebc1a12a1083591373";
  uint8_t hash[20] = {0};

  for (int i = 0; i < 40; i = i + 2) {
    hash[i / 2] =
        (((datahex(sha1[i]) & 0x0F) << 4) | (datahex(sha1[i + 1]) & 0x0F));
  }

  uint8_t resSW[4] = {0x4e, 0x02, 0x69, 0x87};
  FILE* fIn = fopen(lsUpdateBackupPath, "rb");
  if (fIn == NULL) {
    ALOGE("%s Cannot open LS script file %s\n", __func__, lsUpdateBackupPath);
    ALOGE("%s Error : %s", __func__, strerror(errno));
    cCallback->onStateChange(true);
  } else {
    ALOGD_IF(ese_debug_enabled, "%s File opened %s\n", __func__,
             lsUpdateBackupPath);
    fseek(fIn, 0, SEEK_END);
    long fsize = ftell(fIn);
    rewind(fIn);

    char* lsUpdateBuf = (char*)phNxpEse_memalloc(fsize + 1);
    fread(lsUpdateBuf, fsize, 1, fIn);

    FILE* fOut = fopen(lsUpdateBackupOutPath, "wb+");
    if (fOut == NULL) {
      ALOGE("%s Failed to open file %s\n", __func__, lsUpdateBackupOutPath);
      phNxpEse_free(lsUpdateBuf);
      pthread_exit(NULL);
      cCallback->onStateChange(true);
      return NULL;
    }

    long size = fwrite(lsUpdateBuf, 1, fsize, fOut);
    if (size != fsize) {
      ALOGE("%s ERROR - Failed to write %ld bytes to file\n", __func__, fsize);
      phNxpEse_free(lsUpdateBuf);
      pthread_exit(NULL);
      cCallback->onStateChange(true);
      return NULL;
    }

    LSCSTATUS status = LSC_Start(lsUpdateBackupPath, lsUpdateBackupOutPath,
                                 (uint8_t*)hash, (uint16_t)sizeof(hash), resSW);
    ALOGD_IF(ese_debug_enabled, "%s LSC_Start completed\n", __func__);
    if (status == LSCSTATUS_SUCCESS) {
      if (remove(lsUpdateBackupPath) == 0) {
        ALOGD_IF(ese_debug_enabled, "%s  : %s file deleted successfully\n",
                 __func__, lsUpdateBackupPath);
      } else {
        ALOGD_IF(ese_debug_enabled, "%s  : %s file deletion failed!!!\n",
                 __func__, lsUpdateBackupPath);
      }
      cCallback->onStateChange(true);
    } else {
      ESESTATUS status = phNxpEse_deInit();
      if (status == ESESTATUS_SUCCESS) {
        status = phNxpEse_close();
        if (status == ESESTATUS_SUCCESS) {
          ALOGD_IF(ese_debug_enabled, "%s: Ese_close success\n", __func__);
        }
      } else {
        ALOGE("%s: Ese_deInit failed", __func__);
      }
      cCallback->onStateChange(false);
    }
    phNxpEse_free(lsUpdateBuf);
  }
  pthread_exit(NULL);
  ALOGD_IF(ese_debug_enabled, "%s pthread_exit\n", __func__);
  return NULL;
}

/*******************************************************************************
**
** Function:        datahex
**
** Description:     Converts char to uint8_t
**
** Returns:         uint8_t variable
**
*******************************************************************************/
uint8_t datahex(char c) {
  uint8_t value = 0;
  if (c >= '0' && c <= '9')
    value = (c - '0');
  else if (c >= 'A' && c <= 'F')
    value = (10 + (c - 'A'));
  else if (c >= 'a' && c <= 'f')
    value = (10 + (c - 'a'));
  return value;
}