/* SPDX-License-Identifier: GPL-2.0 */
/*
 * qosmngr.c - main qosmngr's code
 *
 * Copyright (C) 2020 iopsys Software Solutions AB. All rights reserved.
 *
 * Author: Oskar Viljasaar <oskar.viljasaar@iopsys.eu>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#include <libubox/blobmsg_json.h>
#include <libubus.h>
#include <uci.h>
#include <json-c/json.h>

/* Needed to get the IFNAMSIZ define */
#include <net/if.h>

#include "qosmngr.h"

/* Used as an internal value to query all the queues */
#define QOS_QUEUE_ANY	-1

/* Used for fetching keys from ubus json reqest */
#define SEPERATOR 44
#define QUOTE	  34

/* Used to validate requested parameters */
#define PARAM1 "ifname"
#define PARAM2 "qid"

#define BOARD_JSON_FILE "/etc/board.json"

/** Container for single interface data (with multiple queues) */
typedef struct qos_interface_data {
	size_t            q_count;               /**< Cached number of queues */
	struct qos_stats *q_stat;                /**< Cached queue statistics */
	char              if_name[IFNAMSIZ];     /**< Cached interface name */
} qos_interface_data;

/** Number of interfaces currently cached by qosmngr */
static size_t interface_count = 0;
/** Array of interfaces cached by qosmngr */
static qos_interface_data *interfaces = NULL;

/**
 *  get_no_queues function for getting number of queues on device by calling uci qos config
 *  @param ifname input parameter pointer to char for which number of queues needed
 *  retrun integer value 0 on success and -1 on failure
 */
static int get_no_queues(const char *ifname)
{
       int queues = 0;

       struct uci_package *uci_pkg = NULL;
       struct uci_element *uci_elmnt = NULL;
       struct uci_context *uci_ctx = uci_alloc_context();

       uci_load(uci_ctx, "qos", &uci_pkg);
       if (!uci_pkg) {
               BBF_ERR("Failed to load configuration\n");
               queues = -1;
               goto done;
       }

       uci_foreach_element(&uci_pkg->sections, uci_elmnt) {
               struct uci_section *uci_sec = uci_to_section(uci_elmnt);
               if (uci_sec && !strcmp(uci_sec->type, "queue")) {
                       const char *opt_enab;
                       opt_enab = uci_lookup_option_string(uci_ctx, uci_sec, "enable");
                       if (opt_enab && !strcmp(opt_enab, "1")) {
                               struct uci_element *e = NULL;
                               uci_foreach_element(&uci_sec->options, e) {
                                       struct uci_option *uci_opn = uci_to_option(e);
                                       if (uci_opn && !strcmp(uci_opn->v.string, ifname))
                                               queues++;
                               }
                       }
               }
       }

       uci_unload(uci_ctx, uci_pkg);
done:
       uci_free_context(uci_ctx);
       return queues;
}

/**
 *  get_interface_index function for getting interface index i.e., eth0 having index 0
 *  @param ifname input parameter pointer to char for which index needs to be fetched
 *  retrun integer value 0 on success and -1 on failure
 */
static int get_interface_index(const char *ifname)
{
	size_t i;
	size_t len = strlen(ifname);

	for (i = 0; i < interface_count; ++i) {
		qos_interface_data *cur = &interfaces[i];

		if (strncmp(cur->if_name, ifname, len) == 0) {
			size_t cur_len = strlen(cur->if_name);

			if (cur_len != len) {
				if (sscanf(ifname + len, ".%*d") != 1) {
					continue;
				}
			}

			return (int)i;
		}
	}

	return -1;
}

static int init_interface_queues(const char *ifname, qos_interface_data *data)
{
	size_t queues = (size_t)get_no_queues(ifname);

	if (data == NULL) {
		BBF_ERR("Null interface entry");
		return -1;
	}

	strncpy(data->if_name, ifname, IFNAMSIZ);
	data->q_count = queues;
	data->q_stat = (struct qos_stats *)calloc(queues, sizeof(struct qos_stats));
	if (data->q_stat == NULL) {
		BBF_ERR("Initialization failed during memory allocation.\n");
		return -1;
	}

	return 0;
}

/**
 *  init_qstat function for initializing global q_stat structure for sustainable stats
 *  @param none
 *  retrun integer value 0 on success and -1 on failure
 */
int init_qstat(void)
{
	int ret = 0;

	interface_count = 0;
	struct json_object *board_json = json_object_from_file(BOARD_JSON_FILE);
	if (board_json == NULL) {
		BBF_ERR("failed to read board.json as json");
		return -1;
	}

	struct json_object *network = NULL;
	if (!json_object_object_get_ex(board_json, "network", &network)) {
		BBF_ERR("network object not found in board.json");
		ret = -1;
		json_object_put(board_json);
		goto free_and_return;
	}

	struct json_object *lan = NULL;
	if (!json_object_object_get_ex(network, "lan", &lan)) {
		BBF_ERR("lan object not found in board.json");
		json_object_put(board_json);
		ret = -1;
		goto free_and_return;
	}

	struct json_object *wan = NULL;
	struct json_object *wan_device = NULL;
	size_t wan_device_count = 0;

	// its not necessary that board.json has wan section at all
	if (json_object_object_get_ex(network, "wan", &wan)) {
		if (!json_object_object_get_ex(wan, "device", &wan_device)) {
			BBF_ERR("wan device object not found in board.json");
			ret = -1;
			json_object_put(board_json);
			goto free_and_return;
		}
		wan_device_count = 1;
	}


	struct json_object *lan_ports = NULL;
	if (!json_object_object_get_ex(lan, "ports", &lan_ports)) {
		BBF_INFO("lan ports object not found in board.json");
		if (!json_object_object_get_ex(lan, "device", &lan_ports)) {
			BBF_INFO("lan device object not found in board.json");
			ret = -1;
			json_object_put(board_json);
			goto free_and_return;
		}
	}


	// create json object for all ports by reading from lan ports and wan device
	if (json_object_is_type(lan_ports, json_type_array)) {
		interface_count = json_object_array_length(lan_ports) + wan_device_count;
	} else {
		// single lan port device
		interface_count = 1 + wan_device_count;
	}
        interfaces = (qos_interface_data *)calloc(interface_count,
	                                          sizeof(qos_interface_data));
	if (interfaces == NULL) {
		BBF_ERR("Initialization failed during memory allocation.");
		ret = -1;
		json_object_put(board_json);
		goto free_and_return;
	}

	size_t i = 0;
	if (json_object_is_type(lan_ports, json_type_array)) {
		for (i = 0; i < json_object_array_length(lan_ports); i++) {
			struct json_object *l_port_name = json_object_array_get_idx(lan_ports, i);
			if (!l_port_name) {
				BBF_ERR("cannot read lan ports array");
				ret = -1;
				json_object_put(board_json);
				free(interfaces);
				goto free_and_return;
			}

			ret = init_interface_queues(json_object_get_string(l_port_name), &interfaces[i]);
			if (ret < 0) {
				BBF_ERR("cannot int lan ports qos");
				json_object_put(board_json);
				free(interfaces);
				goto free_and_return;
			}

		}
	} else {
		ret = init_interface_queues(json_object_get_string(lan_ports), &interfaces[i]);
		if (ret < 0) {
			BBF_ERR("cannot int lan device qos");
			json_object_put(board_json);
			free(interfaces);
			goto free_and_return;
		}
		i++;
	}

	if (wan_device) {
		ret = init_interface_queues(json_object_get_string(wan_device), &interfaces[i]);
	}

	json_object_put(board_json);

free_and_return:
	return ret;
}

void free_qstat(void)
{
	if (interfaces == NULL)
		return;

	for (int i = 0; i < interface_count; i++) {
		if (interfaces[i].q_stat != NULL) {
			free(interfaces[i].q_stat);
			interfaces[i].q_stat = NULL;
		}
	}

	free(interfaces);
	interfaces = NULL;
}

/**
 *  prapare_stats_blob function for getting stats by calling libqos function prepare_stats_blob
 *  @param b output parameter pointer to blob_buf
 *  @param stats output parameter pointer to qos_stats actually queue stats container
 *  @param dd output parameter pointer to void used for blob buffer array elements
 *  @param ifname input parameter pointer to char for which to get stats
 *  @param qid input parameter integer queue identifier for which to get stats
 *  return integer value 0 on success and -1 on failure
 */
int prepare_stats_blob(struct blob_buf *b, struct qos_stats *stats, void *dd,
				char *ifname, int qid)
{
	int index;
	int ret = 0;
	int is_read_and_reset;
	qos_interface_data *iface;
	struct qos_stats   *q_stat;

	ret = qos_get_stats(ifname, qid, stats, &is_read_and_reset);
	if (ret != 0) {
		BBF_ERR("blob_get_status: ret %d\n", ret);
		return ret;
	}

	index = get_interface_index(ifname);
	if (index < 0 || index >= interface_count) {
		BBF_ERR("Invalid interface index %d (out of %zu) for interface %s",
		       index, interface_count, ifname);
		return -1;
	}

	iface = &interfaces[index];
	if (qid < 0 || qid >= iface->q_count) {
		BBF_ERR("Invalid queue index %d (out of %zu)",
		       qid, iface->q_count);
		return -1;
	}

	q_stat = &iface->q_stat[qid];

	/*BCM968 CHIP, stats read from driver is accunulated stats, while in other its read and reset */
	if (!is_read_and_reset)	{
		q_stat->tx_packets = stats->tx_packets;
		q_stat->tx_bytes = stats->tx_bytes;
		q_stat->tx_dropped_packets = stats->tx_dropped_packets;
		q_stat->tx_dropped_bytes = stats->tx_dropped_bytes;
	} else {
		q_stat->tx_packets += stats->tx_packets;
		q_stat->tx_bytes += stats->tx_bytes;
		q_stat->tx_dropped_packets += stats->tx_dropped_packets;
		q_stat->tx_dropped_bytes += stats->tx_dropped_bytes;

		// update the contents so the caller gets latest data
		memcpy(stats, q_stat, sizeof(struct qos_stats));
	}

	if (b) {
		dd = blobmsg_open_table(b, "");

		blobmsg_add_string(b, "iface", ifname);
		blobmsg_add_u32(b, "qid", (uint32_t)qid);
	        blobmsg_add_u64(b, "tx_packets", (uint64_t)q_stat->tx_packets);
	        blobmsg_add_u64(b, "tx_bytes", (uint64_t)q_stat->tx_bytes);
        	blobmsg_add_u64(b, "tx_dropped_packets", (uint64_t)q_stat->tx_dropped_packets);
	        blobmsg_add_u64(b, "tx_dropped_bytes", (uint64_t)q_stat->tx_dropped_bytes);


		blobmsg_close_table(b, dd);
	}

	return ret;
}

#ifdef UBUS_SUPPORT
/**
 *  get_stats_by_ifname function for getting specific interface stats
 *  @param b output parameter pointer to blob_buf
 *  @param stats output parameter pointer to qos_stats actually queue stats container
 *  @param dd output parameter pointer to void used for blob buffer array elements
 *  @param ifname input parameter pointer to char for which to get stats
 *  @param qid input parameter integer queue identifier for which to get stats
 *  retrun integer value 0 on success and -1 on failure
 */
static int get_stats_by_ifname(struct blob_buf *b, struct qos_stats *stats, void *dd,
				char *ifname, int qid)
{
	int ret = -1;

	if (qid >= 0) {
		ret = prepare_stats_blob(b, stats, dd, ifname, qid);
	} else {
		int i;
		int queues = get_no_queues(ifname);

		if (queues <= 0) {
			ret = 0;
			return ret;
		}

		for (i = 0; i < queues; i++) {
			int pst = prepare_stats_blob(b, stats, dd, ifname, i);
			if (pst == 0)
				ret = 0;
		}
	}

	return ret;
}

/**
 *  get_stats_for_all_intf function for getting all interface stats
 *  @param b output parameter pointer to blob_buf
 *  @param stats output parameter pointer to qos_stats actually queue stats container
 *  @param dd output parameter pointer to void used for blob buffer array elements
 *  retrun integer value 0 on success and -1 on failure
 */
static int get_stats_for_all_intf(struct blob_buf *b, struct qos_stats *stats, void *dd)
{
	int ret = 0;
	size_t i = 0;
	for (i = 0; i < interface_count; i++) {
		qos_interface_data *cur = &interfaces[i];
		ret = get_stats_by_ifname(b, stats, dd,
				cur->if_name, QOS_QUEUE_ANY);

		if (ret != 0) {
			BBF_ERR("get_stats_by_ifname failed: ret %d", ret);
			continue;
		}
	}

	return ret;
}

/**
 *  validate_keys function to validate requested json keys
 *  @param rea_json parameter pointer to char string containing json request
 *  retrun integer value 0 on success and -1 on failure
 */
static int validate_keys(char *req_json)
{
	size_t i;
	int ret = 0;

	size_t len = strlen(req_json);

	for (i = 0; i < len; i++) {
		if (req_json[i] == QUOTE) {
			char key[IFNAMSIZ] = {0};
			size_t j = 0;
			i++;

			while ((i < len) && (req_json[i] != QUOTE)) {
				key[j] = req_json[i];
				j++;
				i++;
			}
			i++;

			while ((i < len) && (req_json[i] != SEPERATOR))
				i++;

			if (!(!strncmp(key, PARAM1, strlen(PARAM1)) ||
					!strncmp(key, PARAM2, strlen(PARAM2)))) {
				BBF_ERR("ERROR :: unknown parameter : %s\n", key);
				return -1;
			}
		}
	}

	return ret;
}
/**
 *  validate_request function to validate requested blob message
 *  @param msg parameter pointer to blob_attr containing blob request
 *  retrun integer value 0 on success and -1 on failure
 */
static int validate_request(struct blob_attr *msg)
{
	int ret = 0;
	char *json_blob = NULL;

	if (msg) {
		json_blob = blobmsg_format_json(msg, true);
		ret = validate_keys(json_blob);
	}

	return ret;
}

/**
 *  qosmngr_get_stats function callback on ubus method queue_stats
 *  @param ctx input parameter pointer to ubus context
 *  @param obj input parameter pointer to ubus object in out case qos
 *  @param req input parameter pointer to ubus requested data
 *  @param method input parameter pointer to char method i.e., queue_stats
 *  @param msg input parameter pointer containing qid and ifname
 *  retrun integer value 0 on success and -1 on failure
 */
int qosmngr_get_stats(struct ubus_context *ctx, struct ubus_object *obj,
	       struct ubus_request_data *req, const char *method,
	       struct blob_attr *msg)
{
	int ret = 0;
	int qid = QOS_QUEUE_ANY;
	char ifname[IFNAMSIZ] = {0};

	struct blob_attr *tb[NUM_QOS_POLICY];
	struct blob_buf b = {0};
	struct qos_stats stats = {0};

	BBF_WARNING("Warning: qos ubus object has been deprecated, please use bbfdm to fetch the stats!\n");
	/* Validate requested parameters, i.e., ifname and qid */
	ret = validate_request(msg);
	if (ret) {
		BBF_ERR("validate_request failed : ret %d\n", ret);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	/* These are for the blobbuf array elements */
	void *d = NULL, *dd = NULL;

	blobmsg_parse(get_status_policy, QOS_POLICY_MAX, tb, blob_data(msg),
							(unsigned int)blob_len(msg));

	if (tb[QOS_POLICY_IFNAME])
		strncpy(ifname, blobmsg_data(tb[QOS_POLICY_IFNAME]), sizeof(ifname)-1);

	/* Parse optional arguments */
	if (tb[QOS_POLICY_QID])
		qid = (int)blobmsg_get_u32(tb[QOS_POLICY_QID]);

	/* Can't have a queue id specified without an interface */
	if (tb[QOS_POLICY_QID] && !tb[QOS_POLICY_IFNAME])
		return UBUS_STATUS_INVALID_ARGUMENT;

	blob_buf_init(&b, 0);

	d = blobmsg_open_array(&b, "queues");

	if (tb[QOS_POLICY_IFNAME]) {
		ret = get_stats_by_ifname(&b, &stats, &dd, ifname, qid);
		if (ret != 0) {
			BBF_ERR("get_stats_by_ifname : ret %d\n", ret);
			goto fail_get_status;
		}
	} else {
		ret = get_stats_for_all_intf(&b, &stats, &dd);
		if (ret != 0) {
			BBF_ERR("get_stats_for_all_intf : ret %d\n", ret);
			goto fail_get_status;
		}
	}

	blobmsg_close_array(&b, d);

	ubus_send_reply(ctx, req, b.head);

 fail_get_status:
	blob_buf_free(&b);
	return ret;
}
#endif
