#include <net/if.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.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 "nl80211.h"
#include "iw.h"

struct scan_params {
	bool unknown;
};

static int handle_scan(struct nl80211_state *state,
		       struct nl_cb *cb,
		       struct nl_msg *msg,
		       int argc, char **argv)
{
	struct nl_msg *ssids = NULL;
	int err = -ENOBUFS;

	ssids = nlmsg_alloc();
	if (!ssids)
		return -ENOMEM;
	NLA_PUT(ssids, 1, 0, "");
	nla_put_nested(msg, NL80211_ATTR_SCAN_SSIDS, ssids);

	err = 0;
 nla_put_failure:
	nlmsg_free(ssids);
	return err;
}
COMMAND(scan, trigger, NULL,
	NL80211_CMD_TRIGGER_SCAN, 0, CIB_NETDEV, handle_scan);

typedef void (*printfn)(unsigned char type, unsigned char len, unsigned char *data);

static void print_ssid(unsigned char type, unsigned char len, unsigned char *data)
{
	int i;
	printf("\tSSID: ");
	for (i=0; i<len; i++) {
		if (isprint(data[i]))
			printf("%c", data[i]);
		else
			printf("\\x%.2x", data[i]);
	}
	printf("\n");
}

static void print_supprates(unsigned char type, unsigned char len, unsigned char *data)
{
	int i;

	if (type == 1)
		printf("\tSupported rates: ");
	else
		printf("\tExtended supported rates: ");

	for (i=0; i<len; i++) {
		int r = data[i] & 0x7f;
		printf("%d.%d%s ", r/2, 5*(r&1), data[i] & 0x80 ? "*":"");
	}
	printf("\n");
}

static void print_ds(unsigned char type, unsigned char len, unsigned char *data)
{
	printf("\tDS Parameter set: channel %d\n", data[0]);
}

static void print_ign(unsigned char type, unsigned char len, unsigned char *data)
{
	/* ignore for now, not too useful */
}

static const printfn ieprinters[] = {
	[0] = print_ssid,
	[1] = print_supprates,
	[3] = print_ds,
	[5] = print_ign,
	[50] = print_supprates,
};

static void print_vendor(unsigned char len, unsigned char *data,
			 struct scan_params *params)
{
	int i;

	/* currently _all_ vendor IEs are unknown (not parsed) */
	if (!params->unknown)
		return;

	printf("\tVendor specific: OUI %.2x:%.2x:%.2x, data: ",
		data[0], data[1], data[2]);
	for (i=3; i<len; i++)
		printf("\\x%.2x", data[i]);
	printf("\n");
}

static void print_ies(unsigned char *ie, int ielen, struct scan_params *params)
{
	while (ielen >= 2 && ielen >= ie[1]) {
		if (ie[0] < ARRAY_SIZE(ieprinters) && ieprinters[ie[0]]) {
			ieprinters[ie[0]](ie[0], ie[1], ie + 2);
		} else if (ie[0] == 221 /* vendor */) {
			print_vendor(ie[1], ie + 2, params);
		} else if (params->unknown) {
			int i;

			printf("\tUnknown IE (%d): ", ie[0]);
			for (i=0; i<ie[1]; i++)
				printf("\\x%.2x", ie[2+i]);
			printf("\n");
		}
		ielen -= ie[1] + 2;
		ie += ie[1] + 2;
	}
}

static int print_bss_handler(struct nl_msg *msg, void *arg)
{
	struct nlattr *tb[NL80211_ATTR_MAX + 1];
	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
	struct nlattr *bss[NL80211_BSS_MAX + 1];
	char mac_addr[20], dev[20];
	static struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = {
		[NL80211_BSS_TSF] = { .type = NLA_U64 },
		[NL80211_BSS_FREQUENCY] = { .type = NLA_U32 },
		[NL80211_BSS_BSSID] = { },
		[NL80211_BSS_BEACON_INTERVAL] = { .type = NLA_U16 },
		[NL80211_BSS_CAPABILITY] = { .type = NLA_U16 },
		[NL80211_BSS_INFORMATION_ELEMENTS] = { },
		[NL80211_BSS_SIGNAL_MBM] = { .type = NLA_U32 },
		[NL80211_BSS_SIGNAL_UNSPEC] = { .type = NLA_U8 },
	};

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

	if (!tb[NL80211_ATTR_BSS]) {
		fprintf(stderr, "bss info missing!");
		return NL_SKIP;
	}
	if (nla_parse_nested(bss, NL80211_BSS_MAX,
			     tb[NL80211_ATTR_BSS],
			     bss_policy)) {
		fprintf(stderr, "failed to parse nested attributes!");
		return NL_SKIP;
	}

	if (!bss[NL80211_BSS_BSSID])
		return NL_SKIP;

	mac_addr_n2a(mac_addr, nla_data(bss[NL80211_BSS_BSSID]));
	if_indextoname(nla_get_u32(tb[NL80211_ATTR_IFINDEX]), dev);
	printf("BSS %s (on %s)\n", mac_addr, dev);

	if (bss[NL80211_BSS_TSF]) {
		unsigned long long tsf;
		tsf = (unsigned long long)nla_get_u64(bss[NL80211_BSS_TSF]);
		printf("\tTSF: %llu usec (%llud, %.2lld:%.2llu:%.2llu)\n",
			tsf, tsf/1000/1000/60/60/24, (tsf/1000/1000/60/60) % 24,
			(tsf/1000/1000/60) % 60, (tsf/1000/1000) % 60);
	}
	if (bss[NL80211_BSS_FREQUENCY])
		printf("\tfreq: %d\n",
			nla_get_u32(bss[NL80211_BSS_FREQUENCY]));
	if (bss[NL80211_BSS_BEACON_INTERVAL])
		printf("\tbeacon interval: %d\n",
			nla_get_u16(bss[NL80211_BSS_BEACON_INTERVAL]));
	if (bss[NL80211_BSS_CAPABILITY])
		printf("\tcapability: 0x%.4x\n",
			nla_get_u16(bss[NL80211_BSS_CAPABILITY]));
	if (bss[NL80211_BSS_SIGNAL_MBM]) {
		int s = nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM]);
		printf("\tsignal: %d.%.2d dBm\n", s/100, s%100);
	}
	if (bss[NL80211_BSS_SIGNAL_UNSPEC]) {
		unsigned char s = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]);
		printf("\tsignal: %d/100\n", s);
	}
	if (bss[NL80211_BSS_INFORMATION_ELEMENTS])
		print_ies(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]),
			  nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]),
			  arg);

	return NL_SKIP;
}

static struct scan_params scan_params;

static int handle_scan_dump(struct nl80211_state *state,
			    struct nl_cb *cb,
			    struct nl_msg *msg,
			    int argc, char **argv)
{
	if (argc > 1)
		return 1;

	scan_params.unknown = false;
	if (argc == 1 && !strcmp(argv[0], "-u"))
		scan_params.unknown = true;

	nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, print_bss_handler,
		  &scan_params);
	return 0;
}
COMMAND(scan, dump, "[-u]",
	NL80211_CMD_GET_SCAN, NLM_F_DUMP, CIB_NETDEV, handle_scan_dump);

static int handle_scan_combined(struct nl80211_state *state,
				struct nl_cb *cb,
				struct nl_msg *msg,
				int argc, char **argv)
{
	static char *trig_argv[] = {
		NULL,
		"scan",
		"trigger",
	};
	static char *dump_argv[] = {
		NULL,
		"scan",
		"dump",
	};
	static const __u32 cmds[] = {
		NL80211_CMD_NEW_SCAN_RESULTS,
		NL80211_CMD_SCAN_ABORTED,
	};
	int err;

	trig_argv[0] = argv[0];
	err = handle_cmd(state, II_NETDEV, ARRAY_SIZE(trig_argv), trig_argv);
	if (err)
		return err;

	if (listen_events(state, ARRAY_SIZE(cmds), cmds) ==
					NL80211_CMD_SCAN_ABORTED) {
		printf("scan aborted!\n");
		return 0;
	}

	dump_argv[0] = argv[0];
	return handle_cmd(state, II_NETDEV, ARRAY_SIZE(dump_argv), dump_argv);
}
TOPLEVEL(scan, NULL, 0, 0, CIB_NETDEV, handle_scan_combined);
