/* SPDX-License-Identifier: BSD-3-Clause */
/*
 * util.c - implements utility functions.
 *
 * Copyright (C) 2021-2024 IOPSYS Software Solutions AB. All rights reserved.
 * Copyright (C) 2025 Gnenexis AB.
 *
 * Author: anjan.chanda@iopsys.eu
 */

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <regex.h>
#include <dirent.h>
#include <net/if.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
#include <netlink/route/link.h>
#include <netlink/route/addr.h>
#include <netlink/route/link/bridge.h>
#include <netlink/route/link/macvlan.h>

#include <easy/easy.h>
#include "debug.h"
#include "util.h"

#ifdef HAVE_ETHTOOL_NETLINK
#include <linux/ethtool_netlink.h>
#else
#include <linux/sockios.h>
#include <linux/ethtool.h>

#ifndef ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32
#define ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32	32
#endif
#endif /* HAVE_ETHTOOL_NETLINK */


#ifndef uuid_strtob
int uuid_strtob(char *uuidstr, uint8_t *uuid)
{
	uint8_t elen[] = {8, 4, 4, 4, 12};
	char *ptr, *end;
	uint8_t *ret;
	int idx = 0;
	int i = 0;


	if (!uuidstr || !uuid)
		return -1;

	ptr = uuidstr;
	end = uuidstr + strlen(uuidstr);

	while (ptr < end) {
		char tmp[32] = {0};
		char *pos;

		pos = ptr;
		ptr = strchr(pos, '-');
		if (!ptr) {
			if (i == 4) {
				ptr = end;
				if (ptr - pos != elen[i])
					return -1;

				strncpy(tmp, pos, elen[i]);
				ret = strtob(tmp, elen[i] / 2, &uuid[idx]);
				return !ret ? -1 : 0;
			}

			return -1;
		}

		if (ptr - pos != elen[i])
			return -1;

		strncpy(tmp, pos, elen[i]);
		ret = strtob(tmp, elen[i] / 2, &uuid[idx]);
		if (!ret)
			return -1;

		ptr++;
		idx += elen[i] / 2;
		i++;
	}

	return 0;
}

int uuid_btostr(uint8_t *uuid, char *uuidstr)
{
	if (!uuidstr || !uuid)
		return -1;

	sprintf(uuidstr,
		"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
		uuid[0], uuid[1], uuid[2], uuid[3],
		uuid[4], uuid[5], uuid[6], uuid[7],
		uuid[8], uuid[9], uuid[10], uuid[11],
		uuid[12], uuid[13], uuid[14], uuid[15]);

	return 0;
}

#endif

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

	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;
		}
	}
}

int getcurrtime(struct timeval *out)
{
	struct timespec nowts = { 0 };
	struct timeval now = { 0 };
	int ret;

	ret = clock_gettime(CLOCK_MONOTONIC_RAW, &nowts);
	if (!ret) {
		now.tv_sec = nowts.tv_sec;
		now.tv_usec = nowts.tv_nsec / 1000;
	} else {
		ret = gettimeofday(&now, NULL);
	}

	now.tv_usec = (now.tv_usec / 1000) * 1000;
	out->tv_sec = now.tv_sec;
	out->tv_usec = now.tv_usec;

	return ret;
}

int timeradd_msecs(struct timeval *a, unsigned long msecs, struct timeval *res)
{
	struct timeval t;

	if (!a || !res)
		return -1;

	t.tv_sec = msecs / 1000;
	t.tv_usec = (msecs % 1000) * 1000;
	timeradd(a, &t, res);

	return 0;
}

int timersub_msecs(struct timeval *a, unsigned long msecs, struct timeval *res)
{
	struct timeval t;

	if (!a || !res)
		return -1;

	t.tv_sec  = msecs / 1000;
	t.tv_usec = (msecs % 1000) * 1000;
	timersub(a, &t, res);

	return 0;
}

void get_random_bytes(int num, uint8_t *buf)
{
	unsigned int seed;
	struct timespec res = {0};
	int i;

	clock_gettime(CLOCK_REALTIME, &res);

	seed = res.tv_nsec;

	srand(seed);
	for (i = 0; i < num; i++)
		buf[i] = rand_r(&seed) & 0xff;
}

void _bufprintf(uint8_t *buf, int len, const char *label)
{
	int rows, residue;
	int i;
	int k;

	if (label)
		fprintf(stderr, "---- %s ----\n", label);

	rows = len / 16;

	for (k = 0; k < rows; k++) {
		fprintf(stderr, "\n   0x%08x | ", k * 16);

		for (i = 0; i < 16; i++) {
			if (!(i % 4))
				fprintf(stderr, "  ");

			fprintf(stderr, "%02x ", buf[k*16 + i] & 0xff);
		}

		fprintf(stderr, "%8c", ' ');
		for (i = 0; i < 16; i++) {
			fprintf(stderr, "%c ",
				isalnum(buf[k*16 + i] & 0xff) ? buf[k*16 + i] : '.');
		}
	}

	residue = len % 16;
	k = len - len % 16;

	if (residue) {
		fprintf(stderr, "\n   0x%08x | ", rows * 16);
		for (i = k; i < len; i++) {
			if (!(i % 4))
				fprintf(stderr, "  ");

			fprintf(stderr, "%02x ", buf[i] & 0xff);
		}

		for (i = residue; i < 16; i++) {
			if (!(i % 4))
				fprintf(stderr, "  ");

			fprintf(stderr, "%s ", "  ");
		}

		fprintf(stderr, "%8c", ' ');
		for (i = k; i < len; i++) {
			fprintf(stderr, "%c ",
				isalnum(buf[i] & 0xff) ? buf[i] : '.');
		}

	}

	if (label)
		fprintf(stderr, "\n--------------\n");
}

void bufprintf(uint8_t *buf, int len, const char *label)
{
	//_bufprintf(buf, len, label);
}

int if_brportnum(const char *ifname)
{
	char path[512] = {0};
	int portnum;
	FILE *f;

	snprintf(path, 512, "/sys/class/net/%s/brport/port_no", ifname);
	f = fopen(path, "r"); /* cppcheck-suppress cert-MSC24-C */
	if (!f)
		return -1;

	if (fscanf(f, "%i", &portnum) != 1) {
		fclose(f);
		return -1;
	}

	fclose(f);

	return portnum;
}

int if_getcarrier(const char *ifname, int *carrier)
{
	char path[256] = {0};
	char buf[32] = {0};
	int ret;
	int fd;
	char *endptr = NULL;

	*carrier = -1;
	snprintf(path, sizeof(path), "/sys/class/net/%s/carrier", ifname);
	fd = open(path, O_RDONLY);
	if (fd < 0)
		return -1;

	ret = read(fd, buf, sizeof(buf));
	close(fd);

	if (ret <= 0)
		return -1;

	errno = 0;
	*carrier = strtol(buf, &endptr, 10);
	if (errno) {
		fprintf(stderr, "Invalid carrier value: %s\n", buf);
		return -1;
	}

	return 0;
}

const char *regex_match(const char *str, const char *pattern)
{
	const char *s = str;
	regmatch_t pmatch[1];
	regex_t regex;
	int status;

	if (regcomp(&regex, pattern, REG_NEWLINE | REG_EXTENDED))
		return NULL;

	status = regexec(&regex, s, 1, pmatch, 0);
	regfree(&regex);

	if (status != 0)
		return NULL;

	return s + pmatch[0].rm_so;
}

char *strstr_exact(char *haystack, const char *needle)
{
	char *s, *tmp;
	char *str;

	if (!haystack || !needle)
		return NULL;

	str = strdup(haystack);
	foreach_token_r(s, str, tmp, ", ") {
		if (!strncmp(s, needle, max(strlen(s), strlen(needle))))
			break;
	}

	free(str);
	return s;
}

#ifdef HAVE_ETHTOOL_NETLINK
static int linkmodes_cb(struct nl_msg *msg, void *arg)
{
	struct nlattr *tb[ETHTOOL_A_LINKMODES_MAX + 1];
	struct genlmsghdr *ghdr = nlmsg_data(nlmsg_hdr(msg));
	int *speed = arg;

	nla_parse(tb, ETHTOOL_A_LINKMODES_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_attrlen(ghdr, 0), NULL);

	if (tb[ETHTOOL_A_LINKMODES_SPEED])
		*speed = nla_get_u32(tb[ETHTOOL_A_LINKMODES_SPEED]);

	return NL_OK;
}

int eth_get_linkspeed(const char *ifname, uint32_t *speed)
{
	struct nl_sock *sk = NULL;
	struct nl_msg *msg = NULL;
	int ret = -1;
	int ifindex;
	int family;

	*speed = 0xffffffff;
	ifindex = if_nametoindex(ifname);
	if (!ifindex)
		return -1;

	sk = nl_socket_alloc();
	if (!sk)
		return -1;

	if (genl_connect(sk))
		goto out;

	family = genl_ctrl_resolve(sk, ETHTOOL_GENL_NAME);
	if (family < 0)
		goto out;

	msg = nlmsg_alloc();
	if (!msg)
		goto out;

	genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, family, 0, NLM_F_REQUEST,
		    ETHTOOL_MSG_LINKMODES_GET, ETHTOOL_GENL_VERSION);

	nla_put_u32(msg, ETHTOOL_A_LINKMODES_HEADER, ifindex);
	nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, linkmodes_cb, speed);

	if (nl_send_auto(sk, msg) < 0)
		goto out;

	if (nl_recvmsgs_default(sk) < 0)
		goto out;

	ret = 0;

out:
	nlmsg_free(msg);
	nl_socket_free(sk);

	return ret;
}
#else
int eth_get_linkspeed(const char *ifname, uint32_t *speed)
{
	struct {
		struct ethtool_link_settings req;
		__u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
	} ecmd = {0};
	int ifindex = if_nametoindex(ifname);
	struct ifreq ifr = {0};
	int fd;

	*speed = UINT32_MAX;
	if (ifindex <= 0)
		return -1;

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

	strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
	ifr.ifr_ifindex = ifindex;
	ecmd.req.cmd = ETHTOOL_GLINKSETTINGS;
	ifr.ifr_data = (void *)&ecmd;
	if (ioctl(fd, SIOCETHTOOL, &ifr) < 0) {
		close(fd);
		return -1;
	}

	if (ecmd.req.link_mode_masks_nwords >= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS) {
		close(fd);
		return -1;
	}

	ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords;
	ifr.ifr_ifindex = ifindex;
	ifr.ifr_data = (void *)&ecmd;
	if (ioctl(fd, SIOCETHTOOL, &ifr) < 0) {
		close(fd);
		return -1;
	}

	if (ecmd.req.link_mode_masks_nwords <= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS) {
		close(fd);
		return -1;
	}

	close(fd);
	*speed = ecmd.req.speed;
	return 0;
}
#endif	/* HAVE_ETHTOOL_NETLINK */
