/*
 * FIXME:
 * PROTOTYPE: add nl80211 calls to iw_if. Mostly copied/stolen from iw
 */
#include "wavemon.h"
#include <net/if.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

#include "iw_nl80211.h"

/**
 * handle_cmd: process @cmd
 * Returns 0 if ok, -errno < 0 on failure
 * stolen/modified from iw:iw.c 
 */
int handle_cmd(struct cmd *cmd)
{
	struct nl_cb *cb;
	struct nl_msg *msg;
	static int nl80211_id = -1;
	int ret;
	uint32_t ifindex, idx;

	/*
	 * Initialization of static components:
	 * - per-cmd socket
	 * - global nl80211 ID
	 * - per-cmd interface index (in case conf_ifname() changes)
	 */
	if (!cmd->sk) {
		cmd->sk = nl_socket_alloc();
		if (!cmd->sk)
			err_sys("failed to allocate netlink socket");

		/* NB: not setting sk buffer size, using default 32Kb */
		if (genl_connect(cmd->sk))
			err_sys("failed to connect to GeNetlink");
	}

	if (nl80211_id < 0) {
		nl80211_id = genl_ctrl_resolve(cmd->sk, "nl80211");
		if (nl80211_id < 0)
			err_sys("nl80211 not found");
	}

	ifindex = if_nametoindex(conf_ifname());
	if (ifindex == 0 && errno)
		err_sys("failed to look up interface %s", conf_ifname());

	/*
	 * Message Preparation
	 */
	msg = nlmsg_alloc();
	if (!msg)
		err_sys("failed to allocate netlink message");

	cb = nl_cb_alloc(IW_NL_CB_DEBUG ? NL_CB_DEBUG : NL_CB_DEFAULT);
	if (!cb)
		err_sys("failed to allocate netlink callback");

	genlmsg_put(msg, 0, 0, nl80211_id, 0, cmd->flags, cmd->cmd, 0);

	/* netdev identifier: interface index */
	NLA_PUT(msg, NL80211_ATTR_IFINDEX, sizeof(ifindex), &ifindex);

	/* Additional attributes */
	if (cmd->msg_args) {
		for (idx = 0; idx < cmd->msg_args_len; idx++)
			NLA_PUT(msg, cmd->msg_args[idx].type,
				     cmd->msg_args[idx].len,
				     cmd->msg_args[idx].data);
	}

	ret = nl_send_auto_complete(cmd->sk, msg);
	if (ret < 0)
		err_sys("failed to send netlink message");

	/*-------------------------------------------------------------------------
	 * Receive loop
	 *-------------------------------------------------------------------------*/
	nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret);
	nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &ret);
	nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret);
	if (cmd->handler)
		nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cmd->handler, cmd->handler_arg);

	while (ret > 0)
		nl_recvmsgs(cmd->sk, cb);

	nl_cb_put(cb);
	nlmsg_free(msg);
	goto out;

nla_put_failure:
	err_quit("failed to add attribute to netlink message");
out:
	return ret;
}

/*
 * STATION COMMANDS
 */
/* stolen from iw:station.c */
void parse_bitrate(struct nlattr *bitrate_attr, char *buf, int buflen)
{
	int rate = 0;
	char *pos = buf;
	struct nlattr *rinfo[NL80211_RATE_INFO_MAX + 1];
	static struct nla_policy rate_policy[NL80211_RATE_INFO_MAX + 1] = {
		[NL80211_RATE_INFO_BITRATE] = { .type = NLA_U16 },
		[NL80211_RATE_INFO_BITRATE32] = { .type = NLA_U32 },
		[NL80211_RATE_INFO_MCS] = { .type = NLA_U8 },
		[NL80211_RATE_INFO_40_MHZ_WIDTH] = { .type = NLA_FLAG },
		[NL80211_RATE_INFO_SHORT_GI] = { .type = NLA_FLAG },
	};

	if (nla_parse_nested(rinfo, NL80211_RATE_INFO_MAX,
			     bitrate_attr, rate_policy)) {
		snprintf(buf, buflen, "failed to parse nested rate attributes!");
		return;
	}

	if (rinfo[NL80211_RATE_INFO_BITRATE32])
		rate = nla_get_u32(rinfo[NL80211_RATE_INFO_BITRATE32]);
	else if (rinfo[NL80211_RATE_INFO_BITRATE])
		rate = nla_get_u16(rinfo[NL80211_RATE_INFO_BITRATE]);
	if (rate > 0)
		pos += snprintf(pos, buflen - (pos - buf),
				"%d.%d Mbit/s", rate / 10, rate % 10);

	if (rinfo[NL80211_RATE_INFO_MCS])
		pos += snprintf(pos, buflen - (pos - buf),
				" MCS %d", nla_get_u8(rinfo[NL80211_RATE_INFO_MCS]));
	if (rinfo[NL80211_RATE_INFO_VHT_MCS])
		pos += snprintf(pos, buflen - (pos - buf),
				" VHT-MCS %d", nla_get_u8(rinfo[NL80211_RATE_INFO_VHT_MCS]));
	if (rinfo[NL80211_RATE_INFO_40_MHZ_WIDTH])
		pos += snprintf(pos, buflen - (pos - buf), " 40MHz");
	if (rinfo[NL80211_RATE_INFO_80_MHZ_WIDTH])
		pos += snprintf(pos, buflen - (pos - buf), " 80MHz");
	if (rinfo[NL80211_RATE_INFO_80P80_MHZ_WIDTH])
		pos += snprintf(pos, buflen - (pos - buf), " 80P80MHz");
	if (rinfo[NL80211_RATE_INFO_160_MHZ_WIDTH])
		pos += snprintf(pos, buflen - (pos - buf), " 160MHz");
	if (rinfo[NL80211_RATE_INFO_SHORT_GI])
		pos += snprintf(pos, buflen - (pos - buf), " short GI");
	if (rinfo[NL80211_RATE_INFO_VHT_NSS])
		pos += snprintf(pos, buflen - (pos - buf),
				" VHT-NSS %d", nla_get_u8(rinfo[NL80211_RATE_INFO_VHT_NSS]));
}

/*
 * INTERFACE COMMANDS
 */
void print_ssid_escaped(char *buf, const size_t buflen,
			const uint8_t *data, const size_t datalen)
{
	int i, l;

	memset(buf, '\0', buflen);
	/* Treat zeroed-out SSIDs separately */
	for (i = 0; i < datalen && data[i] == '\0'; i++)
		;
	if (i == datalen)
		return;

	for (i = l= 0; i < datalen; i++) {
		if (l + 4 >= buflen)
			return;
		else if (isprint(data[i]) && data[i] != ' ' && data[i] != '\\')
			l += sprintf(buf + l, "%c", data[i]);
		else if (data[i] == ' ' && i != 0 && i != datalen -1)
			l += sprintf(buf + l, " ");
		else
			l += sprintf(buf + l, "\\x%.2x", data[i]);
	}
}

/* stolen from iw:interface.c */
static int iface_handler(struct nl_msg *msg, void *arg)
{
	struct iw_nl80211_ifstat *ifs = (struct iw_nl80211_ifstat *)arg;
	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
	struct nlattr *tb_msg[NL80211_ATTR_MAX + 1];

	assert(ifs != NULL);

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

	if (tb_msg[NL80211_ATTR_WIPHY])
		ifs->phy = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY]);

	if (tb_msg[NL80211_ATTR_IFINDEX])
		ifs->ifindex = nla_get_u32(tb_msg[NL80211_ATTR_IFINDEX]);

	if (tb_msg[NL80211_ATTR_WDEV])
		ifs->wdev = nla_get_u64(tb_msg[NL80211_ATTR_WDEV]);
	if (tb_msg[NL80211_ATTR_SSID])
		print_ssid_escaped(ifs->ssid, sizeof(ifs->ssid),
				   nla_data(tb_msg[NL80211_ATTR_SSID]),
				   nla_len(tb_msg[NL80211_ATTR_SSID]));

	if (tb_msg[NL80211_ATTR_IFTYPE])
		ifs->iftype = nla_get_u32(tb_msg[NL80211_ATTR_IFTYPE]);

	ifs->chan_width = -1;
	ifs->chan_type  = -1;
	if (tb_msg[NL80211_ATTR_WIPHY_FREQ]) {
		ifs->freq = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY_FREQ]);

		if (tb_msg[NL80211_ATTR_CHANNEL_WIDTH]) {
			ifs->chan_width = nla_get_u32(tb_msg[NL80211_ATTR_CHANNEL_WIDTH]);

			if (tb_msg[NL80211_ATTR_CENTER_FREQ1])
				ifs->freq_ctr1 = nla_get_u32(tb_msg[NL80211_ATTR_CENTER_FREQ1]);
			if (tb_msg[NL80211_ATTR_CENTER_FREQ2])
				ifs->freq_ctr2 = nla_get_u32(tb_msg[NL80211_ATTR_CENTER_FREQ2]);

		}
		if (tb_msg[NL80211_ATTR_WIPHY_CHANNEL_TYPE])
			ifs->chan_type = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY_CHANNEL_TYPE]);
	}

	return NL_SKIP;
}

/**
 * survey_handler - channel survey data
 * This handler will be called multiple times, for each channel.
 * stolen from iw:survey.c
 */
static int survey_handler(struct nl_msg *msg, void *arg)
{
	struct iw_nl80211_survey *sd = (struct iw_nl80211_survey *)arg;
	struct nlattr *tb[NL80211_ATTR_MAX + 1];
	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
	struct nlattr *sinfo[NL80211_SURVEY_INFO_MAX + 1];

	static struct nla_policy survey_policy[NL80211_SURVEY_INFO_MAX + 1] = {
		[NL80211_SURVEY_INFO_FREQUENCY]     = { .type = NLA_U32 },
		[NL80211_SURVEY_INFO_NOISE]         = { .type = NLA_U8 },
		[NL80211_SURVEY_INFO_IN_USE]        = { .type = NLA_FLAG },
		[NL80211_SURVEY_INFO_TIME]          = { .type = NLA_U64 },
		[NL80211_SURVEY_INFO_TIME_BUSY]     = { .type = NLA_U64 },
		[NL80211_SURVEY_INFO_TIME_EXT_BUSY] = { .type = NLA_U64 },
		[NL80211_SURVEY_INFO_TIME_RX]       = { .type = NLA_U64 },
		[NL80211_SURVEY_INFO_TIME_TX]       = { .type = NLA_U64 },
		[NL80211_SURVEY_INFO_TIME_SCAN]     = { .type = NLA_U64 },
	};

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

	if (!tb[NL80211_ATTR_SURVEY_INFO])
		return NL_SKIP;

	if (nla_parse_nested(sinfo, NL80211_SURVEY_INFO_MAX,
			     tb[NL80211_ATTR_SURVEY_INFO], survey_policy))
		return NL_SKIP;

	/* The frequency is needed to match up with the associated station */
	if (!sinfo[NL80211_SURVEY_INFO_FREQUENCY])
		return NL_SKIP;

	/* We are only interested in the data of the operating channel */
	if (!sinfo[NL80211_SURVEY_INFO_IN_USE])
		return NL_SKIP;

	sd->freq  = nla_get_u32(sinfo[NL80211_SURVEY_INFO_FREQUENCY]);

	if (sinfo[NL80211_SURVEY_INFO_NOISE])
		sd->noise = (int8_t)nla_get_u8(sinfo[NL80211_SURVEY_INFO_NOISE]);

	if (sinfo[NL80211_SURVEY_INFO_TIME])
		sd->time.active = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME]);

	if (sinfo[NL80211_SURVEY_INFO_TIME_BUSY])
		sd->time.busy = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME_BUSY]);

	if (sinfo[NL80211_SURVEY_INFO_TIME_EXT_BUSY])
		sd->time.ext_busy = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME_EXT_BUSY]);

	if (sinfo[NL80211_SURVEY_INFO_TIME_RX])
		sd->time.rx = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME_RX]);

	if (sinfo[NL80211_SURVEY_INFO_TIME_TX])
		sd->time.tx = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME_TX]);

	if (sinfo[NL80211_SURVEY_INFO_TIME_SCAN])
		sd->time.scan = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME_SCAN]);

	return NL_SKIP;
}

/* Regulatory domain, stolen from iw:reg.c */
static int reg_handler(struct nl_msg *msg, void *arg)
{
	struct iw_nl80211_reg *ir = (struct iw_nl80211_reg *)arg;
	struct nlattr *tb_msg[NL80211_ATTR_MAX + 1];
	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
	char *alpha2;

	ir->region = -1;

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

	if (!tb_msg[NL80211_ATTR_REG_ALPHA2])
		return NL_SKIP;

	if (!tb_msg[NL80211_ATTR_REG_RULES])
		return NL_SKIP;

	if (tb_msg[NL80211_ATTR_DFS_REGION])
		ir->region = nla_get_u8(tb_msg[NL80211_ATTR_DFS_REGION]);
	else
		ir->region = NL80211_DFS_UNSET;

	alpha2 = nla_data(tb_msg[NL80211_ATTR_REG_ALPHA2]);
	ir->country[0] = alpha2[0];
	ir->country[1] = alpha2[1];

	return NL_SKIP;
}

static int link_handler(struct nl_msg *msg, void *arg)
{
	struct iw_nl80211_linkstat *ls = arg;
	struct nlattr *tb[NL80211_ATTR_MAX + 1];
	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
	struct nlattr *bss[NL80211_BSS_MAX + 1];
	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 },
		[NL80211_BSS_STATUS] = { .type = NLA_U32 },
	};

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

	if (!tb[NL80211_ATTR_BSS])
		return NL_SKIP;

	if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], bss_policy))
		return NL_SKIP;

	if (!bss[NL80211_BSS_BSSID])
		return NL_SKIP;

	if (!bss[NL80211_BSS_STATUS])
		return NL_SKIP;

	if (bss[NL80211_BSS_SIGNAL_UNSPEC])
		ls->bss_signal_qual = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]);

	if (bss[NL80211_BSS_SIGNAL_MBM]) {
		int s = nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM]);
		ls->bss_signal = s / 100;
	}

	ls->status = nla_get_u32(bss[NL80211_BSS_STATUS]);
	switch (ls->status) {
	case NL80211_BSS_STATUS_ASSOCIATED:	/* apparently no longer used */
	case NL80211_BSS_STATUS_AUTHENTICATED:
	case NL80211_BSS_STATUS_IBSS_JOINED:
		memcpy(&ls->bssid, nla_data(bss[NL80211_BSS_BSSID]), ETH_ALEN);
	}

	return NL_SKIP;
}

static int link_sta_handler(struct nl_msg *msg, void *arg)
{
	struct iw_nl80211_linkstat *ls = arg;
	struct nlattr *tb[NL80211_ATTR_MAX + 1];
	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
	struct nlattr *sinfo[NL80211_STA_INFO_MAX + 1];
	struct nlattr *binfo[NL80211_STA_BSS_PARAM_MAX + 1];
	struct nl80211_sta_flag_update *sta_flags;
	static struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1] = {
		[NL80211_STA_INFO_CONNECTED_TIME] = { .type = NLA_U32 },
		[NL80211_STA_INFO_INACTIVE_TIME] = { .type = NLA_U32 },
		[NL80211_STA_INFO_RX_BYTES] = { .type = NLA_U32 },
		[NL80211_STA_INFO_TX_BYTES] = { .type = NLA_U32 },
		[NL80211_STA_INFO_RX_PACKETS] = { .type = NLA_U32 },
		[NL80211_STA_INFO_TX_PACKETS] = { .type = NLA_U32 },
		[NL80211_STA_INFO_SIGNAL] = { .type = NLA_U8 },
		[NL80211_STA_INFO_SIGNAL_AVG] = { .type = NLA_U8 },
		[NL80211_STA_INFO_T_OFFSET] = { .type = NLA_U64 },
		[NL80211_STA_INFO_TX_BITRATE] = { .type = NLA_NESTED },
		[NL80211_STA_INFO_RX_BITRATE] = { .type = NLA_NESTED },
		[NL80211_STA_INFO_RX_DROP_MISC] = { .type = NLA_U64 },
		[NL80211_STA_INFO_BEACON_RX] = { .type = NLA_U64 },
		[NL80211_STA_INFO_BEACON_LOSS] = { .type = NLA_U32 },
		[NL80211_STA_INFO_BEACON_SIGNAL_AVG] = { .type = NLA_U8 },
		[NL80211_STA_INFO_LLID] = { .type = NLA_U16 },
		[NL80211_STA_INFO_PLID] = { .type = NLA_U16 },
		[NL80211_STA_INFO_PLINK_STATE] = { .type = NLA_U8 },
		[NL80211_STA_INFO_TX_RETRIES] = { .type = NLA_U32 },
		[NL80211_STA_INFO_TX_FAILED] = { .type = NLA_U32 },
		[NL80211_STA_INFO_STA_FLAGS] =
			{ .minlen = sizeof(struct nl80211_sta_flag_update) },
		[NL80211_STA_INFO_LOCAL_PM] = { .type = NLA_U32},
		[NL80211_STA_INFO_PEER_PM] = { .type = NLA_U32},
		[NL80211_STA_INFO_NONPEER_PM] = { .type = NLA_U32},
		[NL80211_STA_INFO_CHAIN_SIGNAL] = { .type = NLA_NESTED },
		[NL80211_STA_INFO_CHAIN_SIGNAL_AVG] = { .type = NLA_NESTED },
	};
	static struct nla_policy bss_policy[NL80211_STA_BSS_PARAM_MAX + 1] = {
		[NL80211_STA_BSS_PARAM_CTS_PROT] = { .type = NLA_FLAG },
		[NL80211_STA_BSS_PARAM_SHORT_PREAMBLE] = { .type = NLA_FLAG },
		[NL80211_STA_BSS_PARAM_SHORT_SLOT_TIME] = { .type = NLA_FLAG },
		[NL80211_STA_BSS_PARAM_DTIM_PERIOD] = { .type = NLA_U8 },
		[NL80211_STA_BSS_PARAM_BEACON_INTERVAL] = { .type = NLA_U16 },
	};

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

	if (!tb[NL80211_ATTR_STA_INFO])
		return NL_SKIP;

	if (nla_parse_nested(sinfo, NL80211_STA_INFO_MAX,
			     tb[NL80211_ATTR_STA_INFO],
			     stats_policy))
		return NL_SKIP;

	if (sinfo[NL80211_STA_INFO_TX_RETRIES])
		ls->tx_retries = nla_get_u32(sinfo[NL80211_STA_INFO_TX_RETRIES]);
	if (sinfo[NL80211_STA_INFO_TX_FAILED])
		ls->tx_failed = nla_get_u32(sinfo[NL80211_STA_INFO_TX_FAILED]);


	if (sinfo[NL80211_STA_INFO_EXPECTED_THROUGHPUT]) {
		ls->expected_thru = nla_get_u32(sinfo[NL80211_STA_INFO_EXPECTED_THROUGHPUT]);
		/* convert in Mbps but scale by 1000 to save kbps units */
		ls->expected_thru = ls->expected_thru * 1000 / 1024;
	}
	if (sinfo[NL80211_STA_INFO_INACTIVE_TIME])
		ls->inactive_time = nla_get_u32(sinfo[NL80211_STA_INFO_INACTIVE_TIME]);
	if (sinfo[NL80211_STA_INFO_CONNECTED_TIME])
		ls->connected_time = nla_get_u32(sinfo[NL80211_STA_INFO_CONNECTED_TIME]);

	if (sinfo[NL80211_STA_INFO_RX_BYTES])
		ls->rx_bytes = nla_get_u32(sinfo[NL80211_STA_INFO_RX_BYTES]);
	if (sinfo[NL80211_STA_INFO_RX_PACKETS])
		ls->rx_packets = nla_get_u32(sinfo[NL80211_STA_INFO_RX_PACKETS]);
	if (sinfo[NL80211_STA_INFO_RX_DROP_MISC])
		ls->rx_drop_misc = nla_get_u64(sinfo[NL80211_STA_INFO_RX_DROP_MISC]);

	if (sinfo[NL80211_STA_INFO_TX_BYTES])
		ls->tx_bytes = nla_get_u32(sinfo[NL80211_STA_INFO_TX_BYTES]);
	if (sinfo[NL80211_STA_INFO_TX_PACKETS])
		ls->tx_packets = nla_get_u32(sinfo[NL80211_STA_INFO_TX_PACKETS]);

	if (sinfo[NL80211_STA_INFO_SIGNAL])
		ls->signal = (int8_t)nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL]);
	if (sinfo[NL80211_STA_INFO_SIGNAL_AVG])
		ls->signal_avg = (int8_t)nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL_AVG]);


	if (sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG])
		ls->beacon_avg_sig = nla_get_u8(sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG]);
	if (sinfo[NL80211_STA_INFO_BEACON_RX])
		ls->beacons = nla_get_u64(sinfo[NL80211_STA_INFO_BEACON_RX]);
	if (sinfo[NL80211_STA_INFO_BEACON_LOSS])
		ls->beacon_loss = nla_get_u32(sinfo[NL80211_STA_INFO_BEACON_LOSS]);

	if (sinfo[NL80211_STA_INFO_TX_BITRATE])
		parse_bitrate(sinfo[NL80211_STA_INFO_TX_BITRATE], ls->tx_bitrate, sizeof(ls->tx_bitrate));

	if (sinfo[NL80211_STA_INFO_RX_BITRATE])
		parse_bitrate(sinfo[NL80211_STA_INFO_RX_BITRATE], ls->rx_bitrate, sizeof(ls->rx_bitrate));

	if (sinfo[NL80211_STA_INFO_STA_FLAGS]) {
		sta_flags = (struct nl80211_sta_flag_update *)
			    nla_data(sinfo[NL80211_STA_INFO_STA_FLAGS]);

		if (sta_flags->mask & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE) &&
		    sta_flags->set & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE))
			ls->long_preamble = true;

		if (sta_flags->mask & BIT(NL80211_STA_FLAG_WME) &&
		    sta_flags->set & BIT(NL80211_STA_FLAG_WME))
			ls->wme = true;

		if (sta_flags->mask & BIT(NL80211_STA_FLAG_MFP) &&
		    sta_flags->set & BIT(NL80211_STA_FLAG_MFP))
			ls->mfp = true;

		if (sta_flags->mask & BIT(NL80211_STA_FLAG_TDLS_PEER) &&
		    sta_flags->set & BIT(NL80211_STA_FLAG_TDLS_PEER))
			ls->tdls = true;
	}

	/* BSS Flags */
	if (sinfo[NL80211_STA_INFO_BSS_PARAM]) {
		if (nla_parse_nested(binfo, NL80211_STA_BSS_PARAM_MAX,
				     sinfo[NL80211_STA_INFO_BSS_PARAM],
				     bss_policy) == 0) {
			if (binfo[NL80211_STA_BSS_PARAM_CTS_PROT]) {
				ls->cts_protection = true;
			}
			if (binfo[NL80211_STA_BSS_PARAM_SHORT_PREAMBLE])
				ls->long_preamble = false;
			if (binfo[NL80211_STA_BSS_PARAM_SHORT_SLOT_TIME])
				ls->short_slot_time = true;

			ls->beacon_int  = nla_get_u16(binfo[NL80211_STA_BSS_PARAM_BEACON_INTERVAL]);
			ls->dtim_period = nla_get_u8(binfo[NL80211_STA_BSS_PARAM_DTIM_PERIOD]);
		}
	}

	return NL_SKIP;
}

/*
 * COMMAND HANDLERS
 */
void iw_nl80211_get_linkstat(struct iw_nl80211_linkstat *ls)
{
	static struct cmd cmd_linkstat = {
		.cmd	 = NL80211_CMD_GET_SCAN,
		.flags	 = NLM_F_DUMP,
		.handler = link_handler
	};
	static struct cmd cmd_getstation = {
		.cmd	 = NL80211_CMD_GET_STATION,
		.flags	 = 0,
		.handler = link_sta_handler
	};

	struct msg_attribute station_addr = {
		.type = NL80211_ATTR_MAC,
		.len  = sizeof(ls->bssid),
		.data = &ls->bssid
	};

	cmd_linkstat.handler_arg = ls;
	memset(ls, 0, sizeof(*ls));
	handle_cmd(&cmd_linkstat);

	/* If not associated to another station, the bssid is zeroed out */
	if (ether_addr_is_zero(&ls->bssid))
		return;
	/*
	 * Details of the associated station
	 */
	cmd_getstation.handler_arg  = ls;
	cmd_getstation.msg_args     = &station_addr;
	cmd_getstation.msg_args_len = 1;

	handle_cmd(&cmd_getstation);

	/* Channel survey data */
	iw_nl80211_get_survey(&ls->survey);
}

void iw_nl80211_getreg(struct iw_nl80211_reg *ir)
{
	static struct cmd cmd_reg = {
		.cmd	 = NL80211_CMD_GET_REG,
		.flags	 = 0,
		.handler = reg_handler
	};

	cmd_reg.handler_arg = ir;
	memset(ir, 0, sizeof(*ir));
	handle_cmd(&cmd_reg);
}

void iw_nl80211_getifstat(struct iw_nl80211_ifstat *ifs)
{
	static struct cmd cmd_ifstat = {
		.cmd	 = NL80211_CMD_GET_INTERFACE,
		.flags	 = 0,
		.handler = iface_handler
	};

	cmd_ifstat.handler_arg = ifs;
	memset(ifs, 0, sizeof(*ifs));
	handle_cmd(&cmd_ifstat);
}

void iw_nl80211_get_survey(struct iw_nl80211_survey *sd)
{
	static struct cmd cmd_survey = {
		.cmd	 = NL80211_CMD_GET_SURVEY,
		.flags	 = NLM_F_DUMP,
		.handler = survey_handler
	};

	cmd_survey.handler_arg = sd;
	memset(sd, 0, sizeof(*sd));
	handle_cmd(&cmd_survey);
}

/*
 * Multicast Handling
 */
/**
 * struct handler_args - arguments to resolve multicast group
 * @group: group name to resolve
 * @id:    ID it resolves into
 */
struct handler_args {
	const char	*group;
	int		id;
};

/* stolen from iw:genl.c */
static int family_handler(struct nl_msg *msg, void *arg)
{
	struct handler_args *grp = arg;
	struct nlattr *tb[CTRL_ATTR_MAX + 1];
	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
	struct nlattr *mcgrp;
	int rem_mcgrp;

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

	if (!tb[CTRL_ATTR_MCAST_GROUPS])
		return NL_SKIP;

	nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) {
		struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1];

		nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX,
			  nla_data(mcgrp), nla_len(mcgrp), NULL);

		if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] ||
		    !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID])
			continue;
		if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]),
			    grp->group, nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME])))
			continue;
		grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]);
		break;
	}

	return NL_SKIP;
}

/* stolen from iw:genl.c */
int nl_get_multicast_id(struct nl_sock *sock, const char *family, const char *group)
{
	struct nl_msg *msg;
	struct nl_cb *cb;
	int ret, ctrlid;
	struct handler_args grp = {
		.group = group,
		.id = -ENOENT,
	};

	msg = nlmsg_alloc();
	if (!msg)
		return -ENOMEM;

	cb = nl_cb_alloc(NL_CB_DEFAULT);
	if (!cb) {
		ret = -ENOMEM;
		goto out_fail_cb;
	}

	ctrlid = genl_ctrl_resolve(sock, "nlctrl");

	genlmsg_put(msg, 0, 0, ctrlid, 0,
		    0, CTRL_CMD_GETFAMILY, 0);

	ret = -ENOBUFS;
	NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, family);

	ret = nl_send_auto_complete(sock, msg);
	if (ret < 0)
		goto out;

	ret = 1;

	nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret);
	nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret);
	nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, family_handler, &grp);

	while (ret > 0)
		nl_recvmsgs(sock, cb);

	if (ret == 0)
		ret = grp.id;
nla_put_failure:
out:
	nl_cb_put(cb);
out_fail_cb:
	nlmsg_free(msg);
	return ret;
}

/**
 * Allocate a GeNetlink socket ready to listen for nl80211 multicast group @grp
 * @grp: identifier of an nl80211 multicast group (e.g. "scan")
 */
struct nl_sock *alloc_nl_mcast_sk(const char *grp)
{
	int mcid, ret;
	struct nl_sock *sk = nl_socket_alloc();

	if (!sk)
		err_sys("failed to allocate netlink multicast socket");

	if (genl_connect(sk))
		err_sys("failed to connect multicast socket to GeNetlink");

	mcid = nl_get_multicast_id(sk, "nl80211", grp);
	if (mcid < 0)
		err_quit("failed to resolve nl80211 '%s' multicast group", grp);

	ret = nl_socket_add_membership(sk, mcid);
	if (ret)
		err_sys("failed to join nl80211 multicast group %s", grp);

	return sk;
}
