/*
 * utils.c - implements utility functions
 *
 * Copyright (C) 2019 IOPSYS Software Solutions AB. All rights reserved.
 *
 * Author: anjan.chanda@iopsys.eu
 *
 */

#include "utils.h"

#include <1905_tlvs.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <easy/utils.h>
#include <fcntl.h>
#include <libubox/blob.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <json-c/json_object.h>
#include <json-c/json_types.h>
#include <libubox/list.h>
#include <net/if_arp.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
#include <easy/easy.h>
#include <wifidefs.h>

#include "debug.h"

struct json_object;

#define BOARD_JSON_PATH		"/etc/board.json"

#ifndef IEEE80211_FREQUENCY_BAND_6_GHZ
#define IEEE80211_FREQUENCY_BAND_6_GHZ	0x03
#endif

static int hex2num(char c)
{
	if (c >= '0' && c <= '9')
		return c - '0';
	if (c >= 'a' && c <= 'f')
		return c - 'a' + 10;
	if (c >= 'A' && c <= 'F')
		return c - 'A' + 10;
	return -1;
}

int hex2byte(const char *hex)
{
	int a, b;

	a = hex2num(*hex++);
	if (a < 0)
		return -1;

	b = hex2num(*hex++);
	if (b < 0)
		return -1;

	return (a << 4) | b;
}

/* Convert "00:11:22:33:44:55" --> \x001122334455 */
unsigned char *hwaddr_aton(const char *macstr, unsigned char *mac)
{
	size_t i;

	for (i = 0; i < 6; i++) {
		int a;

		a = hex2byte(macstr);
		if (a < 0)
			return NULL;

		macstr += 2;
		mac[i] = a;
		if (i < 6 - 1 && *macstr++ != ':')
			return NULL;
	}
	return mac;
}

/* Convert \x001122334455 --> "00:11:22:33:44:55" */
char *hwaddr_ntoa(const unsigned char *mac, char *macstr)
{
	sprintf(macstr, "%02x:%02x:%02x:%02x:%02x:%02x",
			mac[0]&0xff, mac[1]&0xff,
			mac[2]&0xff, mac[3]&0xff,
			mac[4]&0xff, mac[5]&0xff);

	return macstr;
}

/**
 * Get hw address from ip address using ARP cache
 * @ifname:		interface name
 * @ipstr:		ipaddress in dotted decimal string format
 * @hw:			hwaddress returned from arp cache lookup
 */
int hwaddr_from_ip(char *ifname, char *ipstr, unsigned char *hw)
{
	int s;
	struct arpreq r;
	struct in_addr ip;
	struct sockaddr_in *sin;

	if (!ifname)
		return -1;

	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0)
		return -1;

	inet_aton(ipstr, &ip);
	memset(&r, 0, sizeof(struct arpreq));
	snprintf(r.arp_dev, sizeof(r.arp_dev), "%s", ifname);
	sin = (struct sockaddr_in *)&r.arp_pa;
	sin->sin_family = AF_INET;
	memcpy(&sin->sin_addr, &ip, sizeof(struct in_addr));
	if (ioctl(s, SIOCGARP, &r) < 0) {
		close(s);
		return -1;
	}

	memcpy(hw, r.arp_ha.sa_data, 6);
	close(s);
	return 0;
}


/** list utility functions */
/**
 * list_dup - duplicates a list
 * @h:			head of the list
 * @new:		head of the duplicate list
 * @alloc_entry:	function to allocate a new entry
 * @free_entry:		function to free entry in case of error
 * @copy_entry:		function to copy an entry
 */
int list_dup(struct list_head *h, struct list_head *new,
		void *(*alloc_entry)(void),
		void (*free_entry)(struct list_head *n),
		void (*copy_entry)(struct list_head *from, struct list_head *to))
{
	struct list_head *p, *tmp, *n;

	if (!alloc_entry || !copy_entry)
		return -1;

	list_for_each(p, h) {
		n = alloc_entry();
		if (!n)
			goto rollback_list_dup;

		copy_entry(p, n);
		list_add_tail(n, new);
	}

	return 0;

rollback_list_dup:
	list_for_each_safe(p, tmp, new) {
		list_del(p);
		if (free_entry)
			free_entry(p);
	}
	return -1;
}

/**
 * list_join_uniq - joins two lists merging duplicates based on 'match' criteria
 * @priv:               opaque private data
 * @a:			head of the first list
 * @b:			head of second list
 * @out:		head of the final joined list
 * @match:		matching function which determines duplicate entries
 * @create_entry:	function to create combined merged entry
 * @free_entry:		function to free combined merged entry in case on error
 */
int list_join_uniq_strict(void *priv, struct list_head *a, struct list_head *b,
		struct list_head *out,
		int (*match)(void *priv, struct list_head *a, struct list_head *b),
		struct list_head *(*create_entry)(void *priv, struct list_head *a, struct list_head *b),
		void (*free_entry)(void *priv, struct list_head *a))
{
	struct list_head *p, *q, *t1, *t2, *n;

	if (!create_entry || !free_entry)
		return -1;

	list_for_each_safe(p, t1, a) {
		list_for_each_safe(q, t2, b) {
			if (match && match(priv, p, q)) {
				n = create_entry(priv, p, q);
				if (!n)
					goto err_list_join_uniq;
				list_add_tail(n, out);
			}
		}
	}
#if 0    /* because returns only matching entries */
	list_for_each_safe(p, t1, a) {
		n = create_entry(priv, p, NULL);
		if (!n)
			goto err_list_join_uniq;
		list_add_tail(n, out);
		list_del(p);
	}
	list_for_each_safe(q, t1, b) {
		n = create_entry(priv, NULL, q);
		if (!n)
			goto err_list_join_uniq;
		list_add_tail(n, out);
		list_del(q);
	}
#endif

	return 0;

err_list_join_uniq:
	list_for_each_safe(p, t1, out) {
		list_del(p);
		free_entry(priv, p);
	}
	return -1;
}

int list_join_uniq(void *priv, struct list_head *a, struct list_head *b,
		struct list_head *out,
		int (*match)(void *priv, struct list_head *a, struct list_head *b),
		struct list_head *(*create_jentry)(void *priv, struct list_head *a, struct list_head *b),
		void (*free_jentry)(void *priv, struct list_head *),
		void (*free_entry_a)(struct list_head *),
		void (*free_entry_b)(struct list_head *))
{
	struct list_head *p, *q, *t1, *t2, *n;

	if (!create_jentry || !free_jentry)
		return -1;

	list_for_each_safe(p, t1, a) {
		list_for_each_safe(q, t2, b) {
			if (match && match(priv, p, q)) {
				n = create_jentry(priv, p, q);
				if (!n)
					goto err_list_join_uniq;
				list_add_tail(n, out);
				list_del(p);
				free_entry_a(p);
				list_del(q);
				free_entry_b(q);
			}
		}
	}

	list_for_each_safe(p, t1, a) {
		n = create_jentry(priv, p, NULL);
		if (!n)
			goto err_list_join_uniq;
		list_add_tail(n, out);
		list_del(p);
		free_entry_a(p);
	}
	list_for_each_safe(q, t1, b) {
		n = create_jentry(priv, NULL, q);
		if (!n)
			goto err_list_join_uniq;
		list_add_tail(n, out);
		list_del(q);
		free_entry_b(q);
	}

	return 0;

err_list_join_uniq:
	list_for_each_safe(p, t1, out) {
		list_del(p);
		free_jentry(priv, p);
	}
	return -1;
}

/* daemonize process */
void do_daemonize(const char *pidfile)
{
	int f;

	info("daemonizing ...\n");
	if (daemon(0, 0)) {
		fprintf(stderr, "Can't becomes a daemon. "
				"Continue as foreground process...\n");
	}

	if (!pidfile)
		return;

	f = open(pidfile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
	if (f >= 0) {
		char buf[128] = {0};
		int flags;

		flags = fcntl(f, F_GETFD);
		if (flags != -1) {
			flags |= FD_CLOEXEC;
			fcntl(f, F_SETFD, flags);
		}
		if (lockf(f, F_TLOCK, 0) < 0) {
			fprintf(stderr, "File '%s' exists. Aborting...\n",
					pidfile);
			exit(-1);
		}
		if (ftruncate(f, 0)) {
			fprintf(stderr, "Continue with invalid pid file %s\n",
					pidfile);
			return;
		}
		snprintf(buf, sizeof(buf), "%ld\n", (long)getpid());
		if (write(f, buf, strlen(buf)) != strlen(buf)) {
			fprintf(stderr, "Continue with invalid pid file %s\n",
					pidfile);
			return;
		}
	}
}

/* install a signal handler */
int set_sighandler(int sig, void (*handler)(int))
{
	struct sigaction sa;

	memset(&sa, 0, sizeof(sa));
	sa.sa_flags = 0;
	sigemptyset(&sa.sa_mask);
	sa.sa_handler = handler;
	if (sigaction(sig, &sa, NULL) < 0) {
		fprintf(stderr, "Error sigaction %d\n", sig);
		return -1;
	}

	return 0;
}

/* uninstall a signal handler */
int unset_sighandler(int sig)
{
	struct sigaction sa;

	memset(&sa, 0, sizeof(sa));
	sa.sa_flags = 0;
	sigemptyset(&sa.sa_mask);
	sa.sa_handler = SIG_DFL;

	return sigaction(sig, &sa, NULL);
}

/* json helpers copied from uproxyd.c */
const char *json_get_string(struct json_object *object, const char *key)
{
	json_bool ret;
	struct json_object *value;

	if (!object || !json_object_is_type(object, json_type_object))
		return NULL;

	ret = json_object_object_get_ex(object, key, &value);
	if (!ret || !value || !json_object_is_type(value, json_type_string))
		return NULL;

	return json_object_get_string(value);
}

int json_get_int(struct json_object *object, const char *key)
{
	json_bool ret;
	struct json_object *value;

	if (!object || !json_object_is_type(object, json_type_object))
		return -1;

	ret = json_object_object_get_ex(object, key, &value);
	if (!ret || !value || !json_object_is_type(value, json_type_int))
		return -1;

	return json_object_get_int(value);

}

int json_get_bool(struct json_object *object, const char *key)
{
	json_bool ret;
	struct json_object *value;

	if (!object || !json_object_is_type(object, json_type_object))
		return -1;

	ret = json_object_object_get_ex(object, key, &value);
	if (!ret || !value || !json_object_is_type(value, json_type_boolean))
		return -1;

	return json_object_get_boolean(value) ? 1 : 0;

}

uint8_t wifi_band_to_ieee1905band(uint8_t band)
{
	uint8_t ieee1905band = 255;

	switch (band) {
	case BAND_2:
		ieee1905band = IEEE80211_FREQUENCY_BAND_2_4_GHZ;
		break;
	case BAND_5:
		ieee1905band = IEEE80211_FREQUENCY_BAND_5_GHZ;
		break;
	case BAND_60:
		ieee1905band = IEEE80211_FREQUENCY_BAND_60_GHZ;
		break;
	case BAND_6:
		ieee1905band = IEEE80211_FREQUENCY_BAND_6_GHZ;
		break;
	default:
		break;
	}

	return ieee1905band;
}

uint8_t wifi_bandstr_to_band(char *bandstr)
{
	enum wifi_band band = BAND_UNKNOWN;

	if (!strncmp(bandstr, "2.4GHz", strlen("2.4GHz")))
		band = BAND_2;
	else if (!strncmp(bandstr, "5GHz", strlen("5GHz")))
		band = BAND_5;
	else if (!strncmp(bandstr, "6GHz", strlen("6GHz")))
		band = BAND_6;

	return band;
}

const char *band_to_str(uint32_t band)
{
	if (band == BAND_2)
		return "2";
	else if (band == BAND_5)
		return "5";
	else if (band == BAND_6)
		return "6";
	return NULL;
}

uint32_t int_to_band(uint32_t band)
{
	if (band == 2)
		return BAND_2;
	else if (band == 5)
		return BAND_5;
	else if (band == 6)
		return BAND_6;
	return BAND_UNKNOWN;
}

uint32_t band_to_int(uint32_t band)
{
	if (band == BAND_2)
		return 2;
	else if (band == BAND_5)
		return 5;
	else if (band == BAND_6)
		return 6;
	return 0;
}

uint8_t noise_to_anpi(int n)
{
	if (!n)
		return 255;
	if (n < -110)
		return 0;
	if (n > 0)
		return 220;
	return (n + 110) * 2;
}

uint8_t get_device_num_from_name(char *device)
{
	char *p;
	char *endptr = NULL;
	int ret;

	/* Workaround for radio0_band0, radio0_band1, radio0_band2 */
	p = strstr(device, "band");
	if (!p)
		p = device;

	while (*p && !isdigit(*p))
		p++;

	errno = 0;
	ret = strtol(p, &endptr, 10);
	if (errno || *endptr != '\0') {
		fprintf(stderr, "Invalid device name %s\n", device);
		return 0;
	}

	return ret;
}

bool is_package_available(const char *package)
{
	char buf[32] = {0};

	chrCmd(buf, sizeof(buf), "opkg list-installed %s", package);
	if (strlen(buf))
		return true;

	return false;
}

bool is_local_cntlr_available(void)
{
	struct stat sb1, sb2, sb3;
	int ret;

	ret = stat("/usr/sbin/mapcontroller", &sb1) ||
		stat("/etc/init.d/mapcontroller", &sb2) ||
		stat("/etc/config/mapcontroller", &sb3);
	if (ret)
		return false;

	if (S_ISREG(sb1.st_mode) && S_ISREG(sb2.st_mode) && S_ISREG(sb3.st_mode))
		return true;

	return false;
}

bool is_process_running(const char *process)
{
	char pid[8] = {0};

	chrCmd(pid, sizeof(pid), "pidof %s", process);
	if (strlen(pid))
		return true;

	return false;
}

bool is_local_cntlr_running(void)
{
	return is_process_running("mapcontroller");
}

int readfrom_configfile(const char *filename, uint8_t **out, size_t *olen)
{
	struct stat sb;
	ssize_t rem;
	uint8_t *pos;
	int ret;
	int fp;

	*olen = 0;
	ret = stat(filename, &sb);
	if (ret == -1)
		return -1;

	fp = open(filename, O_RDONLY);
	if (!fp)
		return -1;

	*out = calloc(1, sb.st_size);
	if (!*out) {
		close(fp);
		return -1;
	}

	*olen = sb.st_size;
	rem = *olen;
	pos = *out;
	while (rem != 0) {
		ssize_t res;

		res = read(fp, pos, rem);
		if (res == -1)
			continue;

		pos += res;
		rem -= res;
	}

	close(fp);
	return 0;
}

int writeto_configfile(const char *filename, void *in, size_t len)
{
	int fp;

	fp = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664);
	if (fp < 0)
		return -1;

	if (!in || len == 0) {
		close(fp);
		return 0;
	}

	while (len != 0) {
		ssize_t rem;

		rem = write(fp, in, len);
		if (rem == -1)
			continue;

		len -= rem;
		in = (uint8_t *)in + rem;
	}

	close(fp);
	return 0;
}

bool is_vid_valid(unsigned int vid)
{
        return (vid < TS_VID_INVALID) && (vid > 0);
}

uint8_t *blobattrtob(struct blob_attr *attr, uint8_t *out, int size)
{
	int len;

	if (!attr)
		return NULL;

	if (blobmsg_type(attr) != BLOBMSG_TYPE_STRING)
		return NULL;

	len = blobmsg_data_len(attr);
	len /= 2;

	if (len > size)
		return NULL;

	return strtob(blobmsg_data(attr), len, out);
}

int get_ethportslist(int *num, char ifs[][16])
{
	struct blob_buf bb = { 0 };
	static const struct blobmsg_policy attr[] = {
		[0] = { .name = "model", .type = BLOBMSG_TYPE_TABLE },
		[1] = { .name = "network", .type = BLOBMSG_TYPE_TABLE },
		[2] = { .name = "system", .type = BLOBMSG_TYPE_TABLE },
	};
	struct blob_attr *tb[ARRAY_SIZE(attr)];

	*num = 0;

	blob_buf_init(&bb, 0);

	if (!blobmsg_add_json_from_file(&bb, BOARD_JSON_PATH)) {
		fprintf(stderr, "%s: Failed to parse %s\n", __func__, BOARD_JSON_PATH);
		goto out;
	}

	blobmsg_parse(attr, ARRAY_SIZE(attr), tb, blob_data(bb.head), blob_len(bb.head));

	if (tb[1]) {
		static const struct blobmsg_policy net_attr[] = {
			[0] = { .name = "lan", .type = BLOBMSG_TYPE_TABLE },
			[1] = { .name = "wan", .type = BLOBMSG_TYPE_TABLE },
		};
		struct blob_attr *tb1[ARRAY_SIZE(net_attr)];

		blobmsg_parse(net_attr, ARRAY_SIZE(net_attr), tb1, blobmsg_data(tb[1]), blobmsg_data_len(tb[1]));

		if (tb1[0]) {
			static const struct blobmsg_policy lan_attr[] = {
				[0] = { .name = "ports", .type = BLOBMSG_TYPE_ARRAY },
				[1] = { .name = "protocol", .type = BLOBMSG_TYPE_STRING },
				[2] = { .name = "device", .type = BLOBMSG_TYPE_STRING },
			};
			struct blob_attr *tb2[ARRAY_SIZE(lan_attr)];

			blobmsg_parse(lan_attr, ARRAY_SIZE(lan_attr), tb2, blobmsg_data(tb1[0]), blobmsg_data_len(tb1[0]));
			if (tb2[0]) {
				struct blob_attr *port;
				int rem;

				blobmsg_for_each_attr(port, tb2[0], rem) {
					char *ifname = blobmsg_data(port);

					if (!ifname ||
						  !strcmp(ifname, ".") ||
						  !strcmp(ifname, ".."))
						continue;

					strncpy(ifs[*num], ifname, 15);
					ifs[*num][15] = '\0';
					*num += 1;
				}
			} else if (tb2[2]) {
				char *ifname = blobmsg_data(tb2[2]);

				if (ifname && strcmp(ifname, ".") &&
				    strcmp(ifname, "..")) {
					strncpy(ifs[*num], ifname, 15);
					ifs[*num][15] = '\0';
					*num += 1;
				}
			}
		}

		if (tb1[1]) {
			static const struct blobmsg_policy wan_attr[] = {
				[0] = { .name = "device", .type = BLOBMSG_TYPE_STRING },
				[1] = { .name = "protocol", .type = BLOBMSG_TYPE_STRING },
			};
			struct blob_attr *tb2[ARRAY_SIZE(wan_attr)];

			blobmsg_parse(wan_attr, ARRAY_SIZE(wan_attr), tb2, blobmsg_data(tb1[1]), blobmsg_data_len(tb1[1]));

			if (tb2[0]) {
				char *ifname = blobmsg_data(tb2[0]);

				if (ifname && strcmp(ifname, ".") &&
				    strcmp(ifname, "..")) {
					strncpy(ifs[*num], ifname, 15);
					ifs[*num][15] = '\0';
					*num += 1;
				}
			}
		}

	}

	blob_buf_free(&bb);
	return 0;
out:
	blob_buf_free(&bb);
	return -1;
}


/* remove duplicates from a uint8_t array and return the new length */
int remove_uint8_duplicates(uint8_t *arr, int length)
{
	int i;

	for (i = 0; i < length; i++) {
		int j;

		for (j = i + 1; j < length; ) {
			if (arr[i] == arr[j]) {
				int k;

				for (k = j; k < length - 1; k++)
					arr[k] = arr[k + 1];
				length--;
			} else
				j++;
		}
	}

	return length;
}

uint8_t rssi_to_rcpi(int rssi)
{
	if (!rssi)
		return 255;
	else if (rssi < -110)
		return 0;
	else if (rssi > 0)
		return 220;
	else
		return (rssi + 110) * 2;
}
