/**
* \file sec_alloc.c
* \brief Memory allocator.
* \author Oleksandr Gabrilchuk (o.gabrilchuk@samsung.com)
* \version 0.1
* \date Created May 31, 2013
* \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 2012. All rights reserved.
**/

/* Memory allocator
 *
 * Defines:
 *   MAX_HEAP_MEMORY defines size of buffer for memory allocation. Real
 *   total allocated memory size will be smaller then MAX_HEAP_MEMORY because
 *   of using structure chunk_t.
 *
 *   MIN_ALLOC_BLOCK defines minimal chunk size that can be allocated. This
 *   value should reduce memory fragmentation. It depends on RSA key length.
 *
 * Note: this allocator is optimized for generation of RSA key 2048 bit
 *       and for TropicSSL bignum implementation. For bigger key length
 *       memory may be too fragmented and used not optimal. Allocator can
 *       be tuned through defines MAX_HEAP_MEMORY and MIN_ALLOC_BLOCK.
 */

#include <string.h>
#include <stdint.h>

#include "log.h"

#define MAX_HEAP_MEMORY 0x30000
//#define MIN_ALLOC_BLOCK 132
#define MIN_ALLOC_BLOCK 92

static char *freeHeapPtr = NULL;

typedef struct chunk
{
	uint32_t size;
	struct chunk *next;
	struct chunk *prev;
} __attribute__ ((packed)) chunk_t;

static char heap[MAX_HEAP_MEMORY] = {0};
static chunk_t freeMemoryListHEAD = {0, NULL, NULL};

void sec_init(void)
{
	freeHeapPtr = NULL;
	memset(&freeMemoryListHEAD, 0, sizeof(freeMemoryListHEAD));
	memset(heap, 0, sizeof(heap));
}

static void addElement(chunk_t *chunk, size_t size)
{
	chunk->prev = &freeMemoryListHEAD;
	chunk->next = freeMemoryListHEAD.next;

	if (NULL != freeMemoryListHEAD.next) {
		freeMemoryListHEAD.next->prev = chunk;
	}

	freeMemoryListHEAD.next = chunk;

	chunk->size = size;
}

static void delElement(chunk_t *chunk)
{
	chunk->prev->next = chunk->next;

	if (NULL != chunk->next) {
		chunk->next->prev = chunk->prev;
	}
}

static void addChunk(size_t size, chunk_t *chunk)
{
	if (size == chunk->size || (chunk->size - size - sizeof(chunk->size)) < MIN_ALLOC_BLOCK) {
		delElement(chunk);
	} else {
		addElement((chunk_t *)((char *)chunk + size + sizeof(chunk->size)), chunk->size - size - sizeof(chunk->size));
		chunk->size = size;
		delElement(chunk);
	}
}

static void freeChunk(chunk_t *chunk)
{
	addElement(chunk, chunk->size);
}

void *sec_malloc(size_t size)
{
	chunk_t *currentChunk = freeMemoryListHEAD.next;
	void *ptr = NULL;

	if (NULL == freeHeapPtr) {
		freeHeapPtr = heap;
	}

	if (size < MIN_ALLOC_BLOCK) {
		size = MIN_ALLOC_BLOCK;
	}

	while (NULL != currentChunk) {
		if (currentChunk->size >= size) {
			addChunk(size, currentChunk);
			ptr = (void *)((char *)currentChunk + sizeof(uint32_t));
			return ptr;
		}
		currentChunk = currentChunk->next;
	}


	if (MAX_HEAP_MEMORY - (freeHeapPtr - heap) >= size + sizeof(uint32_t)) {
		currentChunk = (chunk_t *)freeHeapPtr;
		currentChunk->size = size;
		freeHeapPtr += size + sizeof(uint32_t);
		ptr = (void *)((char *)currentChunk + sizeof(uint32_t));
		//memset(ptr, 0, size);
		return ptr;
	}

	return NULL;
}

void sec_free(void *ptr)
{
	char *p = NULL;

	if (ptr == NULL) {
		return;
	}

	p = (char *)ptr - sizeof(uint32_t);
	memset(ptr, 0, *(uint32_t *)p);
	freeChunk((chunk_t *)p);
}

void *sec_realloc(void *ptr, size_t size)
{
	uint32_t *p = NULL;
	void *new_ptr = NULL;

	if (size == 0) {
		/* size == 0 => free(ptr) */
		sec_free(ptr);
		return NULL;
	} else if (ptr == NULL) {
		/* ptr == NULL => malloc(size) */
		return sec_malloc(size);
	}

	p = (uint32_t *)((char *)ptr - sizeof(uint32_t));
	if (size <= *p) {
		return ptr;
	}

	new_ptr = sec_malloc(size);
	if (!new_ptr) {
		return NULL;
	}

	memcpy(new_ptr, ptr, *p);
	sec_free(ptr);

	return new_ptr;
}