#include <net/if.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>

#include <netlink/genl/genl.h>
#include <netlink/genl/family.h>
#include <netlink/genl/ctrl.h>
#include <netlink/msg.h>
#include <netlink/attr.h>

#include <arpa/inet.h>

#include "nl80211.h"
#include "iw.h"

SECTION(coalesce);

static int handle_coalesce_enable(struct nl80211_state *state, struct nl_cb *cb,
				  struct nl_msg *msg, int argc, char **argv,
				  enum id_input id)
{
	struct nlattr *nl_rules, *nl_rule = NULL, *nl_pats, *nl_pat;
	unsigned char *pat, *mask;
	size_t patlen;
	int patnum = 0, pkt_offset, err = 1;
	char *eptr, *value1, *value2, *sptr = NULL, *end, buf[16768];
	enum nl80211_coalesce_condition condition;
	FILE *f = fopen(argv[0], "r");
	enum {
		PS_DELAY,
		PS_CONDITION,
		PS_PATTERNS
	} parse_state = PS_DELAY;
	int rule_num = 0;

	if (!f)
		return 1;

	nl_rules = nla_nest_start(msg, NL80211_ATTR_COALESCE_RULE);
	if (!nl_rules) {
		fclose(f);
		return -ENOBUFS;
	}

	while (!feof(f)) {
		char *eol;

		if (!fgets(buf, sizeof(buf), f))
			break;

		eol = strchr(buf + 5, '\r');
		if (eol)
			*eol = 0;
		eol = strchr(buf + 5, '\n');
		if (eol)
			*eol = 0;

		switch (parse_state) {
		case PS_DELAY:
			if (strncmp(buf, "delay=", 6) == 0) {
				char *delay = buf + 6;

				rule_num++;
				nl_rule = nla_nest_start(msg, rule_num);
				if (!nl_rule)
					goto close;

				NLA_PUT_U32(msg, NL80211_ATTR_COALESCE_RULE_DELAY,
					    strtoul(delay, &end, 10));
				if (*end != '\0')
					goto close;
				parse_state = PS_CONDITION;
			} else {
				goto close;
			}
			break;
		case PS_CONDITION:
			if (strncmp(buf, "condition=", 10) == 0) {
				char *cond = buf + 10;

				condition = strtoul(cond, &end, 10);
				if (*end != '\0')
					goto close;
				NLA_PUT_U32(msg, NL80211_ATTR_COALESCE_RULE_CONDITION,
					    condition);
				parse_state = PS_PATTERNS;
			} else {
				goto close;
			}
			break;
		case PS_PATTERNS:
			if (strncmp(buf, "patterns=", 9) == 0) {
				char *cur_pat = buf + 9;
				char *next_pat = strchr(buf + 9, ',');

				if (next_pat) {
					*next_pat = 0;
					next_pat++;
				}

				nl_pats = nla_nest_start(msg, NL80211_ATTR_COALESCE_RULE_PKT_PATTERN);
				while (1) {
					value1 = strtok_r(cur_pat, "+", &sptr);
					value2 = strtok_r(NULL, "+", &sptr);

					if (!value2) {
						pkt_offset = 0;
						if (!value1)
							goto close;
						value2 = value1;
					} else {
						pkt_offset = strtoul(value1, &eptr, 10);
						if (eptr != value1 + strlen(value1))
							goto close;
					}

					if (parse_hex_mask(value2, &pat, &patlen, &mask))
						goto close;

					nl_pat = nla_nest_start(msg, ++patnum);
					NLA_PUT(msg, NL80211_PKTPAT_MASK,
						DIV_ROUND_UP(patlen, 8), mask);
					NLA_PUT(msg, NL80211_PKTPAT_PATTERN, patlen, pat);
					NLA_PUT_U32(msg, NL80211_PKTPAT_OFFSET,
						    pkt_offset);
					nla_nest_end(msg, nl_pat);
					free(mask);
					free(pat);

					if (!next_pat)
						break;
					cur_pat = next_pat;
					next_pat = strchr(cur_pat, ',');
					if (next_pat) {
						*next_pat = 0;
						next_pat++;
					}
				}
				nla_nest_end(msg, nl_pats);
				nla_nest_end(msg, nl_rule);
				parse_state = PS_DELAY;

			} else {
				goto close;
			}
			break;
		default:
			if (buf[0] == '#')
				continue;
			goto close;
		}
	}

	if (parse_state == PS_DELAY)
		err = 0;
	else
		err = 1;
	goto close;
nla_put_failure:
	err = -ENOBUFS;
close:
	fclose(f);
	nla_nest_end(msg, nl_rules);
	return err;
}

COMMAND(coalesce, enable, "<config-file>",
	NL80211_CMD_SET_COALESCE, 0, CIB_PHY, handle_coalesce_enable,
	"Enable coalesce with given configuration.\n"
	"The configuration file contains coalesce rules:\n"
	"  delay=<delay>\n"
	"  condition=<condition>\n"
	"  patterns=<[offset1+]<pattern1>,<[offset2+]<pattern2>,...>\n"
	"  delay=<delay>\n"
	"  condition=<condition>\n"
	"  patterns=<[offset1+]<pattern1>,<[offset2+]<pattern2>,...>\n"
	"  ...\n"
	"delay: maximum coalescing delay in msec.\n"
	"condition: 1/0 i.e. 'not match'/'match' the patterns\n"
	"patterns: each pattern is given as a bytestring with '-' in\n"
	"places where any byte may be present, e.g. 00:11:22:-:44 will\n"
	"match 00:11:22:33:44 and 00:11:22:33:ff:44 etc. Offset and\n"
	"pattern should be separated by '+', e.g. 18+43:34:00:12 will\n"
	"match '43:34:00:12' after 18 bytes of offset in Rx packet.\n");

static int
handle_coalesce_disable(struct nl80211_state *state, struct nl_cb *cb,
			struct nl_msg *msg, int argc, char **argv,
			enum id_input id)
{
	/* just a set w/o coalesce attribute */
	return 0;
}
COMMAND(coalesce, disable, "", NL80211_CMD_SET_COALESCE, 0, CIB_PHY,
	handle_coalesce_disable, "Disable coalesce.");

static int print_coalesce_handler(struct nl_msg *msg, void *arg)
{
	struct nlattr *attrs[NL80211_ATTR_MAX + 1];
	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
	struct nlattr *pattern, *rule;
	int rem_pattern, rem_rule;
	enum nl80211_coalesce_condition condition;
	int delay;

	nla_parse(attrs, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
		  genlmsg_attrlen(gnlh, 0), NULL);

	if (!attrs[NL80211_ATTR_COALESCE_RULE]) {
		printf("Coalesce is disabled.\n");
		return NL_SKIP;
	}

	printf("Coalesce is enabled:\n");

	nla_for_each_nested(rule, attrs[NL80211_ATTR_COALESCE_RULE], rem_rule) {
		struct nlattr *ruleattr[NUM_NL80211_ATTR_COALESCE_RULE];

		nla_parse(ruleattr, NL80211_ATTR_COALESCE_RULE_MAX,
			  nla_data(rule), nla_len(rule), NULL);

		delay = nla_get_u32(ruleattr[NL80211_ATTR_COALESCE_RULE_DELAY]);
		condition =
		     nla_get_u32(ruleattr[NL80211_ATTR_COALESCE_RULE_CONDITION]);

		printf("Rule - max coalescing delay: %dmsec condition:", delay);
		if (condition)
			printf("not match\n");
		else
			printf("match\n");

		if (ruleattr[NL80211_ATTR_COALESCE_RULE_PKT_PATTERN]) {
			nla_for_each_nested(pattern,
					    ruleattr[NL80211_ATTR_COALESCE_RULE_PKT_PATTERN],
					    rem_pattern) {
				struct nlattr *patattr[NUM_NL80211_PKTPAT];
				int i, patlen, masklen, pkt_offset;
				uint8_t *mask, *pat;

				nla_parse(patattr, MAX_NL80211_PKTPAT,
					  nla_data(pattern), nla_len(pattern),
					  NULL);
				if (!patattr[NL80211_PKTPAT_MASK] ||
				    !patattr[NL80211_PKTPAT_PATTERN] ||
				    !patattr[NL80211_PKTPAT_OFFSET]) {
					printf(" * (invalid pattern specification)\n");
					continue;
				}
				masklen = nla_len(patattr[NL80211_PKTPAT_MASK]);
				patlen = nla_len(patattr[NL80211_PKTPAT_PATTERN]);
				pkt_offset = nla_get_u32(patattr[NL80211_PKTPAT_OFFSET]);
				if (DIV_ROUND_UP(patlen, 8) != masklen) {
					printf(" * (invalid pattern specification)\n");
					continue;
				}
				printf(" * packet offset: %d", pkt_offset);
				printf(" pattern: ");
				pat = nla_data(patattr[NL80211_PKTPAT_PATTERN]);
				mask = nla_data(patattr[NL80211_PKTPAT_MASK]);
				for (i = 0; i < patlen; i++) {
					if (mask[i / 8] & (1 << (i % 8)))
						printf("%.2x", pat[i]);
					else
						printf("--");
					if (i != patlen - 1)
						printf(":");
				}
				printf("\n");
			}
		}
	}

	return NL_SKIP;
}

static int handle_coalesce_show(struct nl80211_state *state, struct nl_cb *cb,
			      struct nl_msg *msg, int argc, char **argv,
			      enum id_input id)
{
	nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM,
		  print_coalesce_handler, NULL);

	return 0;
}
COMMAND(coalesce, show, "", NL80211_CMD_GET_COALESCE, 0, CIB_PHY, handle_coalesce_show,
	"Show coalesce status.");