/*
 * ethmngr.c - provides datamodel ubus objects
 *
 * Copyright (C) 2020-2025 iopsys Software Solutions AB. All rights reserved.
 *
 * Author: Anjan Chanda <anjan.chanda@iopsys.eu>
 *
 * See LICENSE file for license related information.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <libbbfdm-ubus/bbfdm-ubus.h>
#include <libbbfdm-api/dmcommon.h>

#include "ethernet.h"
#include "helper.h"

struct bbfdm_context bbfdm_ctx = {0};

#ifdef ETHMNGR_EXPOSE_ETHERNET_OBJECT
/* ifstats policy */
enum {
	IFSTATS_POLICY_IFNAME,
	IFSTATS_POLICY_MAX,
	NUM_IFSTATS_POLICY = IFSTATS_POLICY_MAX,
};

static const struct blobmsg_policy ifstats_policy[NUM_IFSTATS_POLICY] = {
	[IFSTATS_POLICY_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
};


/* rmonstats policy */
enum {
	RMONSTATS_POLICY_IFNAME,
	RMONSTATS_POLICY_VLANID,
	RMONSTATS_POLICY_QID,
	RMONSTATS_POLICY_MAX,
	NUM_RMONSTATS_POLICY = RMONSTATS_POLICY_MAX,
};

static const struct blobmsg_policy rmonstats_policy[NUM_RMONSTATS_POLICY] = {
	[RMONSTATS_POLICY_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
	[RMONSTATS_POLICY_VLANID] = { .name = "vid", .type = BLOBMSG_TYPE_INT32 },
	[RMONSTATS_POLICY_QID] = { .name = "qid", .type = BLOBMSG_TYPE_INT32 },

};


int ethmngr_ifstats(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct blob_attr *tb[NUM_IFSTATS_POLICY];
	struct eth_stats s;
	char ifname[16] = {0};
	struct blob_buf bb;
	int ret;

	BBF_INFO("This is deprecated please switch to \"bbfdm.ethmngr\" ubus methods.");
	blobmsg_parse(ifstats_policy, IFSTATS_POLICY_MAX, tb, blob_data(msg),
						(unsigned int)blob_len(msg));

	if (!tb[IFSTATS_POLICY_IFNAME]) {
		BBF_ERR("\"ifname\" not specified!");
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

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

	ret = ethmngr_get_if_stats(ifname, &s);
	if (ret) {
		return ret;
	}

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);
	blobmsg_add_string(&bb, "ifname", ifname);
	blobmsg_add_u64(&bb, "tx_bytes", s.tx_bytes);
	blobmsg_add_u64(&bb, "tx_packets", s.tx_packets);
	blobmsg_add_u64(&bb, "tx_errors", s.tx_errors);
	blobmsg_add_u64(&bb, "tx_unicast_packets", s.tx_ucast_packets);
	blobmsg_add_u64(&bb, "tx_multicast_packets", s.tx_mcast_packets);
	blobmsg_add_u64(&bb, "tx_broadcast_packets", s.tx_bcast_packets);
	blobmsg_add_u64(&bb, "tx_discard_packets", s.tx_discard_packets);

	blobmsg_add_u64(&bb, "rx_bytes", s.rx_bytes);
	blobmsg_add_u64(&bb, "rx_packets", s.rx_packets);
	blobmsg_add_u64(&bb, "rx_errors", s.rx_errors);
	blobmsg_add_u64(&bb, "rx_unicast_packets", s.rx_ucast_packets);
	blobmsg_add_u64(&bb, "rx_multicast_packets", s.rx_mcast_packets);
	blobmsg_add_u64(&bb, "rx_broadcast_packets", s.rx_bcast_packets);
	blobmsg_add_u64(&bb, "rx_discard_packets", s.rx_discard_packets);
	blobmsg_add_u64(&bb, "rx_unknown_packets", s.rx_unknown_packets);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

int ethmngr_rmonstats(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct blob_attr *tb[NUM_RMONSTATS_POLICY];
	struct eth_rmon_stats rmon;
	char ifname[16] = {0};
	struct blob_buf bb;
	int queueid = ETH_TXQ_ALL;
	int vlanid = 0;
	int ret;

	BBF_INFO("This is deprecated please switch to \"bbfdm.ethmngr\" ubus methods.");
	blobmsg_parse(rmonstats_policy, RMONSTATS_POLICY_MAX, tb,
				blob_data(msg), (unsigned int)blob_len(msg));

	if (!tb[RMONSTATS_POLICY_IFNAME]) {
		BBF_ERR("\"ifname\" not specified!");
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

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

	if (tb[RMONSTATS_POLICY_QID])
		queueid = (int)blobmsg_get_u32(tb[RMONSTATS_POLICY_QID]);


	if (tb[RMONSTATS_POLICY_VLANID])
		vlanid = (int)blobmsg_get_u32(tb[RMONSTATS_POLICY_VLANID]);

	if (vlanid < 0 || vlanid > 0xFFFF) {
		BBF_ERR("ethmongr: VLAN ID is out of range");
		return -1;
	}

	ret = ethmngr_get_rmon_stats(ifname, &rmon, queueid, vlanid);
	if (ret != 0) {
		return ret;
	}

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);
	blobmsg_add_string(&bb, "ifname", ifname);

	blobmsg_add_u32(&bb, "tx_priority_q", (uint32_t)rmon.txq);
	blobmsg_add_u64(&bb, "tx_bytes", rmon.tx.bytes);
	blobmsg_add_u64(&bb, "tx_packets", rmon.tx.packets);
	blobmsg_add_u64(&bb, "tx_broadcast_packets", rmon.tx.bcast_packets);
	blobmsg_add_u64(&bb, "tx_multicast_packets", rmon.tx.mcast_packets);
	blobmsg_add_u64(&bb, "tx_crc_error_packets", rmon.tx.crc_err_packets);
	blobmsg_add_u64(&bb, "tx_undersize_packets", rmon.tx.under_sz_packets);
	blobmsg_add_u64(&bb, "tx_oversize_packets", rmon.tx.over_sz_packets);
	blobmsg_add_u64(&bb, "tx_pause_packets", rmon.tx.pause_packets);
	blobmsg_add_u64(&bb, "tx_packets_64bytes", rmon.tx.packets_64bytes);
	blobmsg_add_u64(&bb, "tx_packets_65to127bytes", rmon.tx.packets_65to127bytes);
	blobmsg_add_u64(&bb, "tx_packets_128to255bytes", rmon.tx.packets_128to255bytes);
	blobmsg_add_u64(&bb, "tx_packets_256to511bytes", rmon.tx.packets_256to511bytes);
	blobmsg_add_u64(&bb, "tx_packets_512to1023bytes", rmon.tx.packets_512to1023bytes);
	blobmsg_add_u64(&bb, "tx_packets_1024to1518bytes", rmon.tx.packets_1024to1518bytes);

	blobmsg_add_u64(&bb, "rx_bytes", rmon.rx.bytes);
	blobmsg_add_u64(&bb, "rx_packets", rmon.rx.packets);
	blobmsg_add_u64(&bb, "rx_broadcast_packets", rmon.rx.bcast_packets);
	blobmsg_add_u64(&bb, "rx_multicast_packets", rmon.rx.mcast_packets);
	blobmsg_add_u64(&bb, "rx_crc_error_packets", rmon.rx.crc_err_packets);
	blobmsg_add_u64(&bb, "rx_undersize_packets", rmon.rx.under_sz_packets);
	blobmsg_add_u64(&bb, "rx_oversize_packets", rmon.rx.over_sz_packets);
	blobmsg_add_u64(&bb, "rx_pause_packets", rmon.rx.pause_packets);
	blobmsg_add_u64(&bb, "rx_packets_64bytes", rmon.rx.packets_64bytes);
	blobmsg_add_u64(&bb, "rx_packets_65to127bytes", rmon.rx.packets_65to127bytes);
	blobmsg_add_u64(&bb, "rx_packets_128to255bytes", rmon.rx.packets_128to255bytes);
	blobmsg_add_u64(&bb, "rx_packets_256to511bytes", rmon.rx.packets_256to511bytes);
	blobmsg_add_u64(&bb, "rx_packets_512to1023bytes", rmon.rx.packets_512to1023bytes);
	blobmsg_add_u64(&bb, "rx_packets_1024to1518bytes", rmon.rx.packets_1024to1518bytes);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

int ethmngr_clearstats(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct blob_attr *tb[NUM_IFSTATS_POLICY];
	char ifname[16] = {0};
	int ret;

	BBF_INFO("This is deprecated please switch to \"bbfdm.ethmngr\" ubus methods.");
	blobmsg_parse(ifstats_policy, IFSTATS_POLICY_MAX, tb, blob_data(msg),
							blob_len(msg));

	if (!tb[IFSTATS_POLICY_IFNAME]) {
		BBF_ERR("\"ifname\" not specified!");
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

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

	ret = ethmngr_clear_stats(ifname);
	if (ret) {
		BBF_ERR("ethmngr: failed to clear interface stats!");
		return ret;
	}

	return 0;
}

struct ubus_method ethmngr_methods[] = {
	UBUS_METHOD("ifstats", ethmngr_ifstats, ifstats_policy),
	UBUS_METHOD("rmonstats", ethmngr_rmonstats, rmonstats_policy),
	UBUS_METHOD("clearstats", ethmngr_clearstats, ifstats_policy),
};

struct ubus_object_type ethmngr_type =
	UBUS_OBJECT_TYPE("ethernet", ethmngr_methods);

struct ubus_object ethmngr_object = {
	.name = "ethernet",
	.type = &ethmngr_type,
	.methods = ethmngr_methods,
	.n_methods = ARRAY_SIZE(ethmngr_methods),
};


static int ethmngr_publish_object(struct ubus_context *ctx)
{
	int ret = -1;

	ret = ubus_add_object(ctx, &ethmngr_object);
	if (ret)
		BBF_ERR("Failed to add 'ethernet' ubus object: %s",
				ubus_strerror(ret));

	return ret;
}
#endif //ETHMNGR_EXPOSE_ETHERNET_OBJECT

static void ethmngr_usage(char *prog)
{
	fprintf(stderr, "Usage: %s [options]\n", prog);
	fprintf(stderr, "\n");
	fprintf(stderr, "options:\n");
	fprintf(stderr, "    -l <log level>  As per syslog, 0 to 7\n");
	fprintf(stderr, "    -d <schema dm>  Display the schema data model supported by micro-service\n");
	fprintf(stderr, "    -h <help>       To print this help menu\n");
	fprintf(stderr, "\n");
}

int main(int argc, char **argv)
{
	int ch, dm_type = 0;
	int log_level = 6; // default LOG_INFO

	while ((ch = getopt(argc, argv, "hdl:")) != -1) {
		switch (ch) {
		case 'l':
			log_level = (int)strtoul(optarg, NULL, 10);
			if (log_level < 0 || log_level > 7)
				log_level = 6;
			break;
		case 'd':
			dm_type++;
			break;
		case 'h':
			ethmngr_usage(argv[0]);
			exit(0);
		default:
			break;
		}
	}

	memset(&bbfdm_ctx, 0, sizeof(struct bbfdm_context));
	bbfdm_ubus_set_service_name(&bbfdm_ctx, "ethmngr");
	bbfdm_ubus_set_log_level(log_level);
	bbfdm_ubus_load_data_model(tDynamicObj);

	if (dm_type > 0) {
		int res = bbfdm_print_data_model_schema(&bbfdm_ctx, dm_type);
		exit(res);
	}

	openlog("ethmngr", LOG_PID|LOG_CONS|LOG_NDELAY, LOG_LOCAL1);

	if (bbfdm_ubus_register_init(&bbfdm_ctx))
		goto out;

#ifdef ETHMNGR_EXPOSE_ETHERNET_OBJECT
	if (ethmngr_publish_object(bbfdm_ctx.ubus_ctx) != 0)
		goto out;
#endif

	uloop_run();
out:
	bbfdm_ubus_register_free(&bbfdm_ctx);
	closelog();

	return 0;
}
