/*
 * Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Created in Samsung Ukraine R&D Center (SRK) under a contract between
 * LLC "Samsung Electronics Ukraine Company" (Kiev, Ukraine)
 * and "Samsung Electronics Co", Ltd (Seoul, Republic of Korea)
 */

/**
 * @file MbedTlsHooks.c
 * @brief Implementations for MBEDTLS hooks.
 * @author Maksym Skorohod (m.skorohod@samsung.com)
 * @date Created Apr 27, 2016
 */

#include "TigerLogging.h"
#include "TigerMbedTlsHooks.h"
#include "TigerTci.h"
#include "TzwMemory.h"
#include "TzwString.h"

#include <stdarg.h>
#include <mbedtls/platform.h>

// TODO (m.skorohod). make sure this amount is enough.
static const uint32_t LOCAL_HEAP_SIZE = 2048;
static uint8_t* localHeapStart = 0;
static uint8_t* localHeapEnd = 0;

// This is the simple imlementation of 'sbrk', it makes work std 'newlib' functions needed by mbedtls.
// DO NOT PUT it into the header file.
void* _sbrk(int incr);

static void* callocHook(size_t n, size_t size);
static void freeHook(void *ptr);
static int printfHook(const char* fmt, ...);
static void allocateLocalHeap(void);

void setupMbedTlsHooks(void) {
    allocateLocalHeap();

    mbedtls_platform_set_calloc_free(callocHook, freeHook);
    mbedtls_platform_set_printf(printfHook);  // Remove if debug output is not needed.
    mbedtls_platform_set_snprintf(snprintf);  // Used by some important functions during a certificate generation.
}

void* _sbrk(int incr) {
    static uint8_t *heap = NULL;
    uint8_t *resAddr = NULL;

    LOG_E("_sbrk() called with %d", incr);

    if (NULL == heap) {
        heap = localHeapStart;
    }

    uint8_t* new_heap = heap + incr;
    // Not sure if we need to check lower bound.
    if ((new_heap >= localHeapEnd) || (new_heap < localHeapStart)) {
        LOG_E("SBRK error. memory is out of limit.");
        // According to the specification, in case of error it must return -1.
        resAddr = (void*) -1;
    } else {
        resAddr = heap;
        heap = new_heap;
    }

    return resAddr;
}


static void *callocHook(size_t n, size_t size) {
    size_t totalSize = size * n;

    return tzwMalloc(totalSize);
}


static void freeHook(void *ptr) {
    tzwFree(ptr);
}


// Used for only for debug purpose. Can be removed if it's unneeded.
static int printfHook(const char* fmt, ...) {
    #define BUF_SIZE 100
    char buf[BUF_SIZE];
    tzwMemFill((void*) buf, 0, BUF_SIZE);

    va_list args;
    va_start(args, fmt);
    int res = vsnprintf(buf, BUF_SIZE, fmt, args);
    LOG_E("%.*s", res, buf);
    va_end(args);
    return res;
}


static void allocateLocalHeap(void) {
    if (NULL == localHeapStart) {
        localHeapStart = (uint8_t*) tzwMalloc(LOCAL_HEAP_SIZE);
        if (NULL == localHeapStart) {
            LOG_E("Unable to allocate local heap for _sbrk.");
            return;
        }
        localHeapEnd = localHeapStart + LOCAL_HEAP_SIZE;
    }
}
