/**
 * @file tees_rpmb.c
 * @brief Extention API for accessing RPMB in emulator.
 * @author Andrey Neyvanov (a.neyvanov@samsung.com)
 * @date Created Jun 21
 * @par In Samsung Ukraine R&D Center (SURC) under a contract between
 * @par LLC "Samsung Electronics Ukraine Company" (Kiev, Ukraine) and
 * @par "Samsung Elecrtronics Co", Ltd (Seoul, Republic of Korea)
 * @par Copyright: (c) Samsung Electronics Co, Ltd 2017. All rights reserved.
 *
 * This software is proprietary of Samsung Electronics.
 * No part of this software, either material or conceptual may be copied
 * or distributed, transmitted, transcribed, stored in a retrieval system
 * or translated into any human or computer language in any form by any means,
 * electronic, mechanical, manual or otherwise, or disclosed to third parties
 * without the express written permission of Samsung Electronics.
 *
 */

#include <tees_log.h>  /* This is Multibuild's logging API header */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdbool.h>

#include <tees_rpmb.h>

#define D_HANDLE_INITIALIZED  0xA7A6A5A4
#define P_HANDLE_INITIALIZED  0xA4A5A6A7

static const char *filename = "/tmp/rpmb";

typedef struct __TEES_RpmbDeviceHandle {
  TEES_RpmbDeviceInfo deviceInfo;
  uint32_t initialized;
} TEES_RpmbDeviceHandle_st;

typedef struct __TEES_RpmbPartitionHandle {
  TEES_RpmbDeviceHandle deviceHandle;
  TEES_RpmbPartitionInfo partitionInfo;
  uint32_t initialized;
} TEES_RpmbPartitionHandle_st;

static TEE_Result Read(uint8_t *dataBuffer, uint32_t bufferLen,
                       uint32_t startSector) {
  TEE_Result ret = TEE_SUCCESS;
  FILE *fp;

  fp = fopen(filename, "r");
  if (fp == NULL) {
    MB_LOGE("fopen error(%d): %s\n", errno, strerror(errno));
    return TEE_ERROR_GENERIC;
  }
  do {
    if (fseek(fp, (long)(startSector * RPMB_BLOCK_SIZE), SEEK_SET) == -1) {
      MB_LOGE("fseek error.\n");
      ret = TEE_ERROR_GENERIC;
      break;
    }

    size_t sz = fread((void *)dataBuffer, 1, (size_t)bufferLen, fp);
    if (sz != bufferLen) {
      MB_LOGE("fread error. sz = %zu bufferLen = %u\n", sz, bufferLen);
      ret = TEE_ERROR_GENERIC;
      break;
    }

  } while (false);

  fclose(fp);

  return ret;
}

static TEE_Result Write(uint8_t *dataBuffer, uint32_t bufferLen,
                        uint32_t startSector) {
  TEE_Result ret = TEE_SUCCESS;
  FILE *fp;
  uint32_t pad_sz = 0;

  fp = fopen(filename, "w");
  if (fp == NULL) {
    MB_LOGE("fopen error: %s\n", strerror(errno));
    return TEE_ERROR_GENERIC;
  }

  do {
    if (fseek(fp, (long)(startSector * RPMB_BLOCK_SIZE), SEEK_SET) == -1) {
      MB_LOGE("fseek error.\n");
      ret = TEE_ERROR_GENERIC;
      break;
    }

    size_t sz = fwrite((void *)dataBuffer, 1, bufferLen, fp);
    if (sz != bufferLen) {
      MB_LOGE("fwrite error.\n");
      ret = TEE_ERROR_GENERIC;
      break;
    }

    pad_sz = RPMB_BLOCK_SIZE - (bufferLen % RPMB_BLOCK_SIZE);

    if (pad_sz != 0) {
      void *pad;

      pad = malloc(pad_sz);
      if (NULL == pad) {
        MB_LOGE("Can not allocate memory.\n");
        ret = TEE_ERROR_GENERIC;
        break;
      }

      memset(pad, 0, pad_sz);

      sz = fwrite(pad, 1, pad_sz, fp);
      if (sz != pad_sz) {
        MB_LOGE("fwrite error.\n");
        free(pad);
        ret = TEE_ERROR_GENERIC;
        break;
      }

      free(pad);
    }

  } while (false);

  fclose(fp);

  return ret;
}

static bool FileExist(void) {
  return access(filename, R_OK | W_OK) != -1 ? true : false;
}

static TEE_Result FileCreate(uint32_t partitionSize) {
  TEE_Result ret = TEE_SUCCESS;
  FILE *fp;
  uint8_t *dataBuffer;

  fp = fopen(filename, "w");
  if (fp == NULL) {
    MB_LOGE("fopen error: %s\n", strerror(errno));
    return TEE_ERROR_GENERIC;
  }

  dataBuffer = (uint8_t *)malloc(partitionSize);
  if (NULL == dataBuffer) {
    MB_LOGE("Can not allocate memory.\n");
    fclose(fp);
    return TEE_ERROR_GENERIC;
  }

  ret = Write(dataBuffer, partitionSize, 0);

  free(dataBuffer);
  fclose(fp);

  return ret;
}

TEE_Result TEES_RpmbDeviceInit(TEES_RpmbDeviceType deviceType,
                               TEES_RpmbDeviceHandle *device) {
  if (device == NULL) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  if (deviceType != TEES_RPMB_DEVICE_DEFAULT) {
    MB_LOGE("deviceType is not supported: deviceType = %d\n", deviceType);
    return TEE_ERROR_NOT_SUPPORTED;
  }

  *device = (TEES_RpmbDeviceHandle)malloc(sizeof(**device));
  if (NULL == *device) {
    MB_LOGE("Can not allocate memory.\n");
    return TEE_ERROR_GENERIC;
  }

  TEES_RpmbDeviceHandle_st *s = *device;
  s->deviceInfo.deviceId = 0;
  s->deviceInfo.totalSectors = RPMB_MAX_ADDRESS_NUM * RPMB_PART_NUM;
  s->deviceInfo.bytesPerSector = RPMB_BLOCK_SIZE;
  s->deviceInfo.freeSectors = RPMB_MAX_ADDRESS_NUM * RPMB_PART_NUM;
  s->initialized = D_HANDLE_INITIALIZED;

  return TEE_SUCCESS;
}

TEE_Result TEES_RpmbOpenPartition(TEES_RpmbDeviceHandle device,
                                  uint32_t partitionId,
                                  TEES_RpmbPartitionHandle *partition) {
  if (device == NULL || device->initialized != D_HANDLE_INITIALIZED ||
      partition == NULL) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  if (partitionId > RPMB_MAX_PARTITION_ID ||
      partitionId < RPMB_MIN_PARTITION_ID) {
    MB_LOGE("Not supported partitionId: %u\n", partitionId);
    return TEE_ERROR_ITEM_NOT_FOUND;
  }

  if (!FileExist()) {
    MB_LOGE("Partition with partitionId = %u does not exist.\n", partitionId);
    return TEE_ERROR_ITEM_NOT_FOUND;
  }

  *partition = (TEES_RpmbPartitionHandle)malloc(sizeof(**partition));
  if (NULL == *partition) {
    MB_LOGE("Can not allocate memory.\n");
    return TEE_ERROR_GENERIC;
  }

  TEES_RpmbPartitionHandle_st *s = *partition;
  s->deviceHandle = device;

  s->partitionInfo.partitionId = partitionId;
  s->partitionInfo.totalSectors = RPMB_MAX_ADDRESS_NUM;
  s->partitionInfo.bytesPerSector = RPMB_BLOCK_SIZE;
  s->initialized = P_HANDLE_INITIALIZED;


  return TEE_SUCCESS;
}

TEE_Result TEES_RpmbCreatePartition(TEES_RpmbDeviceHandle device,
                                    uint32_t partitionId,
                                    uint32_t partitionSize,
                                    uint32_t flags) {
  TEE_Result ret = TEE_SUCCESS;

  (void)partitionId;

  if (flags != 0 || device == NULL ||
      device->initialized != D_HANDLE_INITIALIZED) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  ret = FileCreate(partitionSize);
  if (ret != TEE_SUCCESS) {
    MB_LOGE("Can not create file.\n");
    return ret;
  }

  device->deviceInfo.freeSectors -= partitionSize /
                                    device->deviceInfo.bytesPerSector;
  if (partitionSize % device->deviceInfo.bytesPerSector != 0) {
    device->deviceInfo.freeSectors--;
  }

  return ret;
}

TEE_Result TEES_RpmbRemovePartition(TEES_RpmbDeviceHandle device,
                                    uint32_t partitionId,
                                    uint32_t flags) {
  (void)device;
  (void)partitionId;
  (void)flags;

  if (flags != 0 || device == NULL ||
      device->initialized != D_HANDLE_INITIALIZED) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  if (FileExist()) {
    unlink(filename);
  }

  return TEE_SUCCESS;
}

void TEES_RpmbClosePartition(TEES_RpmbPartitionHandle partition) {
  memset(partition, 0, sizeof(*partition));
  free(partition);
}

void TEES_RpmbDeviceClose(TEES_RpmbDeviceHandle device) {
  memset(device, 0, sizeof(*device));
  free(device);
}

TEE_Result TEES_RpmbGetDeviceInfo(TEES_RpmbDeviceHandle device,
                                  TEES_RpmbDeviceInfo *deviceInfo) {
  if (device == NULL || deviceInfo == NULL ||
      device->initialized != D_HANDLE_INITIALIZED) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  memcpy(deviceInfo, &device->deviceInfo, sizeof(*deviceInfo));

  return TEE_SUCCESS;
}

TEE_Result TEES_RpmbGetPartitionInfo(TEES_RpmbPartitionHandle partition,
                                     TEES_RpmbPartitionInfo *partitionInfo) {
  if (partition == NULL || partitionInfo == NULL ||
      partition->initialized != P_HANDLE_INITIALIZED) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  memcpy(partitionInfo, &partition->partitionInfo, sizeof(*partitionInfo));

  return TEE_SUCCESS;
}

TEE_Result TEES_RpmbWriteData(TEES_RpmbPartitionHandle partition,
                              uint8_t *dataBuffer,
                              uint32_t bufferLen,
                              uint32_t startSector,
                              uint32_t *sectors) {
  unsigned int ret;

  if (dataBuffer == NULL || bufferLen == 0 ||
      partition == NULL ||
      partition->initialized != P_HANDLE_INITIALIZED ||
      startSector >= partition->partitionInfo.totalSectors || sectors ==
      NULL) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  if (bufferLen > (partition->partitionInfo.totalSectors - startSector) *
      partition->partitionInfo.bytesPerSector) {
    return TEE_ERROR_STORAGE_NO_SPACE;
  }

  *sectors = (bufferLen / partition->partitionInfo.bytesPerSector);
  if (bufferLen % partition->partitionInfo.bytesPerSector != 0) {
    *sectors += 1;
  }

  ret = Write(dataBuffer, bufferLen, startSector);
  if (ret != TEE_SUCCESS) {
    MB_LOGE("tlApiRpmbWrite failure: 0x%x\n", ret);
    *sectors = 0;
    return TEE_ERROR_GENERIC;
  }

  return TEE_SUCCESS;
}

TEE_Result TEES_RpmbReadData(TEES_RpmbPartitionHandle partition,
                             uint8_t *dataBuffer,
                             uint32_t *bufferLen,
                             uint32_t startSector,
                             uint32_t sectors) {
  unsigned int ret;

  if (dataBuffer == NULL || bufferLen == NULL ||
      partition == NULL ||
      partition->initialized != P_HANDLE_INITIALIZED ||
      startSector >= partition->partitionInfo.totalSectors ||
      sectors > partition->partitionInfo.totalSectors) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  if (*bufferLen < sectors * partition->partitionInfo.bytesPerSector) {
    return TEE_ERROR_SHORT_BUFFER;
  }

  *bufferLen = sectors * partition->partitionInfo.bytesPerSector;

  ret = Read(dataBuffer, *bufferLen, startSector);
  if (ret != TEE_SUCCESS) {
    MB_LOGE("tlApiRpmbRead failure: 0x%x\n", ret);
    *bufferLen = 0;
    return TEE_ERROR_GENERIC;
  }

  return TEE_SUCCESS;
}
