/**
 * \file Utils.c
 * \brief Different helper functions for NWd.
 * \author Dmytro Podgornyi (d.podgornyi@samsung.com)
 * \version 0.1
 * \date Created May 28, 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.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/system_properties.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#include "CommLayerData.h"
#include "Utils.h"
#include "base64.h"
#include "sha1.h"
#include "log.h"

#define UID_ROOT 0
#define UID_SYSTEM 1000

#define MODEL_MANUFACTURER_PART "SAMSUNG-"
static const char modelProp[] = "ro.product.model";

static const char serialNoPath[] = "/efs/FactoryApp/serial_no";
/* this property contains serial number
 * use it if reading from FS fails */
static const char serialNoProp[] = "ril.serialnumber";
static const char serialNoProp2[] = "ro.serialno";
static const char serialNoProp3[] = "ro.boot.serialno";

int getTime(struct KeyInfo *info)
{
	time_t res = 0;
	struct tm t = {0};

	res = time(NULL);
	if (res != (time_t)-1) {
		if (localtime_r(&res, &t) == NULL) {
			return PLATFORM_INTERNAL_ERROR;
		}
		/* UTC format: YYMMDDhhmmss, example: 130527084713 */
		/* NOTE: month is 1-12 (localtime returns 0-11) */
		/*snprintf(info->date, sizeof(info->date), "%.2d%.2d%.2d%.2d%.2d%.2d",
			t.tm_year % 100, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min,
			t.tm_sec);*/

		/* TODO: check UTC and generalized types */

		info->year = t.tm_year;
		info->mon = t.tm_mon + 1;
		info->mday = t.tm_mday;
		info->hour = t.tm_hour;
		info->min = t.tm_min;
		info->sec = t.tm_sec;
	} else {
		return PLATFORM_INTERNAL_ERROR;
	}

	return NO_ERROR;
}

int getModel(uint8_t *model, uint32_t modelLen)
{
	char buf[PROP_VALUE_MAX + 1] = {0, };
	char *cbuf = NULL;
	int len = 0;

	memset(model, 0, modelLen);

	len = __system_property_get(modelProp, buf);
	if (len <= 0) {
		return PLATFORM_INTERNAL_ERROR;
	}

	/* skip manufacturer name */
	if (strncmp(buf, MODEL_MANUFACTURER_PART, sizeof(MODEL_MANUFACTURER_PART) - 1) == 0) {
		cbuf = buf + (sizeof(MODEL_MANUFACTURER_PART) - 1);
		len = len - (sizeof(MODEL_MANUFACTURER_PART) - 1);
	} else {
		cbuf = buf;
	}

	if (len < modelLen) {
		strncpy((char *)model, cbuf, modelLen);
		model[modelLen - 1] = '\0';
	} else {
		return PLATFORM_INTERNAL_ERROR;
	}

	return NO_ERROR;
}

static int _getSerialNoProp(uint8_t *serial, uint32_t serialLen, const char *prop)
{
	char buf[PROP_VALUE_MAX + 1] = {0, };
	int len = 0;

	len = __system_property_get(prop, buf);
	if (len > 0 && len < serialLen) {
		strncpy((char *)serial, buf, serialLen);
		serial[serialLen - 1] = '\0';
	} else {
		return PLATFORM_INTERNAL_ERROR;
	}

	return NO_ERROR;
}

static int _getSerialNoRaw(uint8_t *serial, uint32_t serialLen)
{
	char buf[MAX_SERIALNO_SIZE] = {0, };
	int fd = -1;
	ssize_t len = 0;

	memset(serial, 0, serialLen);

	if (access(serialNoPath, F_OK) != 0) {
		goto efs_fail;
	}

	fd = open(serialNoPath, O_RDONLY);
	if (fd < 0) {
		goto efs_fail;
	}

	/* leave 1 byte for termination \0 */
	len = read(fd, buf, MAX_SERIALNO_SIZE - 1);
	if (len <= 0) {
		goto efs_fail_close;
	}
	close(fd);

	buf[len] = '\0';
	if (len < serialLen) {
		strncpy((char *)serial, buf, serialLen);
		serial[serialLen - 1] = '\0';
	} else {
		goto efs_fail;
	}

	return NO_ERROR;

efs_fail_close:
	close(fd);
efs_fail:
	if (_getSerialNoProp(serial, serialLen, serialNoProp) == NO_ERROR ||
		_getSerialNoProp(serial, serialLen, serialNoProp2) == NO_ERROR ||
		_getSerialNoProp(serial, serialLen, serialNoProp3) == NO_ERROR) {
			return NO_ERROR;
	} else {
		return PLATFORM_INTERNAL_ERROR;
	}
}

int getSerialNo(uint8_t *serial, uint32_t serialLen)
{
	uint8_t buf[MAX_SERIALNO_SIZE] = {0, };
	uint8_t sha1[SHA1_DIGEST_SIZE] = {0, };
	int res = 0;
	char *b64 = NULL;
	SHA1_CTX ctx;
	uint32_t len = 0;

	memset(&ctx, 0, sizeof(ctx));

	res = _getSerialNoRaw(buf, sizeof(buf));
	if (res != NO_ERROR) {
		return res;
	}

	SHA1_Init(&ctx);
	SHA1_Update(&ctx, buf, strlen((char*)buf));
	SHA1_Final(&ctx, sha1);

	/* cleanup buffer */
	memset(buf, 0, sizeof(buf));

	b64 = b64_encode((char*)sha1, sizeof(sha1));
	if (!b64) {
		return PLATFORM_INTERNAL_ERROR;
	}

	len = strlen(b64);
	if (len >= serialLen) {
		LOGE("getSerialNo: base64 string too long to fit into buffer");
		return WRONG_DATA;
	}

	memset(serial, 0, serialLen);
	strncpy((char*)serial, b64, serialLen - 1);
	free(b64);
	return NO_ERROR;
}

int delDirFile(const char* folder)
{
	DIR* dp = NULL;
	struct dirent *dirp = NULL;
	struct stat file_stat = {0};
	char targetfile[512] = {0, };

	if ((dp = opendir(folder)) == NULL) {
		LOGE("delDirFile: opendir: %s", strerror(errno));
		return -1;
	}
	//LOGD("Target Directory open Succeed!\n");

	while (1) {
		if (!(dirp = readdir(dp))) {
			//LOGD("readdir error");
			break;
		}
		int isToBeSkipped = strcmp(dirp->d_name, "..") == 0
					|| strcmp(dirp->d_name, ".") == 0
					|| strcmp(dirp->d_name, "lost+found" ) == 0;
		if (isToBeSkipped) {
			continue;
		}

		snprintf(targetfile, sizeof(targetfile), "%s/%s", folder, dirp->d_name);
		if (stat(targetfile, &file_stat) == -1) {
			LOGE("delDirFile: stat: %s", strerror(errno));
			closedir(dp);
			return -1;
		}

		if (S_ISDIR(file_stat.st_mode)) {
			if (delDirFile(targetfile) == -1) {
				closedir(dp);
				return -1;
			}
			continue;
		}

		if (unlink(targetfile) == -1) {
			closedir(dp);
			return -1;
		}
		continue;
	}

	closedir(dp);
	if (remove(folder) == -1) {
		LOGE("delDirFile: remove: %s", strerror(errno));
		return -1;
	}
	return 0;
}

int32_t createDirsForFile(const char *path)
{
	char *tmp = NULL;
	char *new_path = NULL;
	char *tok = NULL;
	char *tok_next = NULL;
	char *saveptr = NULL;
	int ret = 0;
	int32_t result = NO_ERROR;

	if (!path || *path != '/') {
		LOGD("createDirsForFile: wrong argument");
		return PLATFORM_INTERNAL_ERROR;
	}

	tmp = strdup(path);
	if (!tmp) {
		return PLATFORM_INTERNAL_ERROR;
	}

	new_path = malloc(strlen(path) + 1);
	if (!new_path) {
		free(tmp);
		return PLATFORM_INTERNAL_ERROR;
	}
	*new_path = '\0';

	tok = strtok_r(tmp, "/", &saveptr);
	while (tok) {
		tok_next = strtok_r(NULL, "/", &saveptr);
		strcat(new_path, "/");
		strcat(new_path, tok);
		if (tok_next) {
			/* create only directories */
			ret = mkdir(new_path, 0775);
			if (ret != 0 && errno != EEXIST) {
				result = PLATFORM_INTERNAL_ERROR;
				LOGE("createDirsForFile: mkdir %s: %s", new_path, strerror(errno));
				break;
			}
		}
		tok = tok_next;
	}

	free(new_path);
	free(tmp);

	return result;
}

size_t ffileSize(FILE *f)
{
	long size = 0;

	if (fseek(f, 0, SEEK_END) < 0) {
		return 0;
	}
	size = ftell(f);
	rewind(f);

	if (size < 0) {
		size = 0;
	}

	return (size_t)size;
}

size_t fileSize(const char *path)
{
	size_t size = 0;
	FILE *f = NULL;

	f = fopen(path, "rb");
	if (!f) {
		return 0;
	}
	size = ffileSize(f);
	fclose(f);

	return size;
}

/**
 * Read file with additional checks
 *
 * @param   [in] path File path to read
 * @param   [out] buf Output buffer
 * @param   [in] size Size of the allocated buffer
 * @return  Number of read bytes on success, 0 if error occurs
 */
size_t readFile(const char *path, uint8_t *buf, uint32_t size)
{
	FILE *fp = NULL;
	size_t len = 0;
	size_t read_len = 0;

	if (!buf || size == 0) {
		LOGD("readFile: wrong argument");
		return 0;
	}

	memset(buf, 0, size);

	/* check real UID and GID, rather than the effective IDs */
	if (access(path, R_OK) < 0) {
		LOGE("DRK access fail");
		return 0;
	}

	fp = fopen(path, "rb");
	if (!fp) {
		LOGE("DRK fopen fail");
		return 0;
	}

	len = ffileSize(fp);
	if (len == 0) {
		LOGD("Unable to determine size of file");
		fclose(fp);
		return 0;
	}
	if (len > size) {
		LOGD("File too large");
		fclose(fp);
		return 0;
	}

	read_len = fread(buf, 1, len, fp);
	fclose(fp);
	if (read_len != len) {
		LOGE("read len (%d) != real len (%d)", read_len, len);
		return 0;
	}

	return read_len;
}

int chmod644(const char *path)
{
	/* chmod 0644 */
	if (chmod(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) != 0) {
		LOGE("chmod in chmod644(): %s", strerror(errno));
		return PLATFORM_INTERNAL_ERROR;
	}

	return NO_ERROR;
}

static int checkDirPermissions(const char *path, mode_t mode)
{
	struct stat sb = {0};

	if (stat(path, &sb) != 0) {
		return PLATFORM_INTERNAL_ERROR;
	}

	if ((sb.st_mode & S_IFMT) != S_IFDIR) {
		return PLATFORM_INTERNAL_ERROR;
	}

	if ((sb.st_mode & 0777) != mode) {
		if (chmod(path, mode) != 0) {
			return PLATFORM_INTERNAL_ERROR;
		}
	}

	/* directory exists and has correct permissions */
	return NO_ERROR;
}

int mkdirWrapper(const char *path, mode_t mode)
{
	if (checkDirPermissions(path, mode) != NO_ERROR) {
		errno = 0;
		if (mkdir(path, mode) != 0) {
			if (errno == EEXIST) {
				/* if someone creted dir, race condition */
				return checkDirPermissions(path, mode);
			}
		return PLATFORM_INTERNAL_ERROR;
		}
	}

	return NO_ERROR;
}

/**
 * Set file's owner and group to system.
 * @note   Don't pass symbolic links to this function
 * @param  [in] path Path to file or directory
 * @return NO_ERROR on success or error code otherwise
 */
int ownerSetSystem(const char *path)
{
	struct stat sb = {0};

	if (stat(path, &sb) != 0) {
		LOGE("ownerSetSystem: stat failed");
		return PLATFORM_INTERNAL_ERROR;
	}

	if ((sb.st_mode & S_IFMT) == S_IFLNK) {
		LOGE("ownerSetSystem: path is symbolic link!");
		return WRONG_DATA;
	}

	if (sb.st_uid == UID_SYSTEM) {
		return NO_ERROR;
	}

	if (chown(path, UID_SYSTEM, UID_SYSTEM) != 0) {
		LOGE("ownerSetSystem: chown failed");
		return PLATFORM_INTERNAL_ERROR;
	}

	return NO_ERROR;
}

/**
 * Check whether current user is system or not
 *
 * @return TRUE if current user is system and FALSE otherwise
 */
int hasSystemPermissions(void)
{
	uid_t uid = getuid();
	if (uid != UID_SYSTEM && uid != UID_ROOT) {
		return FALSE;
	}
	return TRUE;
}
