/*	$NetBSD: ieee80211_input.c,v 1.115 2018/12/22 13:11:37 maxv Exp $	*/

/*
 * Copyright (c) 2001 Atsushi Onoe
 * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * Alternatively, this software may be distributed under the terms of the
 * GNU General Public License ("GPL") version 2 as published by the Free
 * Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifdef __FreeBSD__
__FBSDID("$FreeBSD: src/sys/net80211/ieee80211_input.c,v 1.81 2005/08/10 16:22:29 sam Exp $");
#endif
#ifdef __NetBSD__
__KERNEL_RCSID(0, "$NetBSD: ieee80211_input.c,v 1.115 2018/12/22 13:11:37 maxv Exp $");
#endif

#ifdef _KERNEL_OPT
#include "opt_inet.h"
#endif

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/malloc.h>
#include <sys/endian.h>
#include <sys/kernel.h>

#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/endian.h>
#include <sys/errno.h>
#include <sys/proc.h>
#include <sys/sysctl.h>
#include <sys/cpu.h>

#include <net/if.h>
#include <net/if_media.h>
#include <net/if_arp.h>
#include <net/if_ether.h>
#include <net/if_llc.h>

#include <net80211/ieee80211_var.h>

#include <net/bpf.h>

#ifdef INET
#include <netinet/in.h>
#include <net/if_ether.h>
#endif

const struct timeval ieee80211_merge_print_intvl = {.tv_sec = 1, .tv_usec = 0};

#ifdef IEEE80211_DEBUG

/*
 * Decide if a received management frame should be
 * printed when debugging is enabled.  This filters some
 * of the less interesting frames that come frequently
 * (e.g. beacons).
 */
static __inline int
doprint(struct ieee80211com *ic, int subtype)
{
	switch (subtype) {
	case IEEE80211_FC0_SUBTYPE_BEACON:
		return (ic->ic_flags & IEEE80211_F_SCAN);
	case IEEE80211_FC0_SUBTYPE_PROBE_REQ:
		return (ic->ic_opmode == IEEE80211_M_IBSS);
	}
	return 1;
}

/*
 * Emit a debug message about discarding a frame or information
 * element.  One format is for extracting the mac address from
 * the frame header; the other is for when a header is not
 * available or otherwise appropriate.
 */
#define	IEEE80211_DISCARD(_ic, _m, _wh, _type, _fmt, ...) do {		\
	if ((_ic)->ic_debug & (_m))					\
		ieee80211_discard_frame(_ic, _wh, _type, _fmt, __VA_ARGS__);\
} while (0)
#define	IEEE80211_DISCARD_IE(_ic, _m, _wh, _type, _fmt, ...) do {	\
	if ((_ic)->ic_debug & (_m))					\
		ieee80211_discard_ie(_ic, _wh, _type, _fmt, __VA_ARGS__);\
} while (0)
#define	IEEE80211_DISCARD_MAC(_ic, _m, _mac, _type, _fmt, ...) do {	\
	if ((_ic)->ic_debug & (_m))					\
		ieee80211_discard_mac(_ic, _mac, _type, _fmt, __VA_ARGS__);\
} while (0)
#define	IEEE80211_DEBUGVAR(a) a

static const u_int8_t *ieee80211_getbssid(struct ieee80211com *,
	const struct ieee80211_frame *);
static void ieee80211_discard_frame(struct ieee80211com *,
	const struct ieee80211_frame *, const char *type, const char *fmt, ...);
static void ieee80211_discard_ie(struct ieee80211com *,
	const struct ieee80211_frame *, const char *type, const char *fmt, ...);
static void ieee80211_discard_mac(struct ieee80211com *,
	const u_int8_t mac[IEEE80211_ADDR_LEN], const char *type,
	const char *fmt, ...);
#else
#define	IEEE80211_DISCARD(_ic, _m, _wh, _type, _fmt, ...)
#define	IEEE80211_DISCARD_IE(_ic, _m, _wh, _type, _fmt, ...)
#define	IEEE80211_DISCARD_MAC(_ic, _m, _mac, _type, _fmt, ...)
#define	IEEE80211_DEBUGVAR(a)
#endif /* IEEE80211_DEBUG */

static struct mbuf *ieee80211_defrag(struct ieee80211_node *,
    struct mbuf *, int);
static struct mbuf *ieee80211_decap(struct mbuf *, int);
static void ieee80211_send_error(struct ieee80211com *, struct ieee80211_node *,
	const u_int8_t *mac, int subtype, int arg);
static void ieee80211_deliver_data(struct ieee80211com *,
	struct ieee80211_node *, struct mbuf *);
#ifndef IEEE80211_NO_HOSTAP
static void ieee80211_node_pwrsave(struct ieee80211_node *, int enable);
static void ieee80211_recv_pspoll(struct ieee80211com *,
	struct ieee80211_node *, struct mbuf *);
#endif /* !IEEE80211_NO_HOSTAP */
static void ieee80211_update_adhoc_node(struct ieee80211com *,
    struct ieee80211_node *, struct ieee80211_frame *,
    struct ieee80211_scanparams *, int, u_int32_t);

/* -------------------------------------------------------------------------- */

/*
 * Input code for a DATA frame.
 */
static int
ieee80211_input_data(struct ieee80211com *ic, struct mbuf **mp,
    struct ieee80211_node *ni)
{
	struct ifnet *ifp = ic->ic_ifp;
	struct ieee80211_key *key;
	struct ieee80211_frame *wh;
	u_int8_t dir, subtype;
	struct ether_header *eh;
	struct mbuf *m = *mp;
	int hdrspace;

	wh = mtod(m, struct ieee80211_frame *);
	dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK;
	subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;

	hdrspace = ieee80211_hdrspace(ic, wh);

	if (m->m_len < hdrspace &&
	    (m = m_pullup(m, hdrspace)) == NULL) {
		ic->ic_stats.is_rx_tooshort++;
		goto out;
	}
	wh = mtod(m, struct ieee80211_frame *);

	switch (ic->ic_opmode) {
	case IEEE80211_M_STA:
		if (dir != IEEE80211_FC1_DIR_FROMDS) {
			IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
			    wh, "data", "%s", "unknown dir 0x%x", dir);
			ic->ic_stats.is_rx_wrongdir++;
			goto out;
		}
		if ((ifp->if_flags & IFF_SIMPLEX) &&
		    IEEE80211_IS_MULTICAST(wh->i_addr1) &&
		    IEEE80211_ADDR_EQ(wh->i_addr3, ic->ic_myaddr)) {
			/*
			 * In IEEE802.11 network, multicast packet
			 * sent from me is broadcast from AP.
			 * It should be silently discarded for
			 * SIMPLEX interface.
			 */
			IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
			    wh, NULL, "%s", "multicast echo");
			ic->ic_stats.is_rx_mcastecho++;
			goto out;
		}
		break;

	case IEEE80211_M_IBSS:
	case IEEE80211_M_AHDEMO:
		if (dir != IEEE80211_FC1_DIR_NODS) {
			IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
			    wh, "data", "%s", "unknown dir 0x%x", dir);
			ic->ic_stats.is_rx_wrongdir++;
			goto out;
		}
		/* XXX no power-save support */
		break;

	case IEEE80211_M_HOSTAP:
#ifndef IEEE80211_NO_HOSTAP
		if (dir != IEEE80211_FC1_DIR_TODS) {
			IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
			    wh, "data", "%s", "unknown dir 0x%x", dir);
			ic->ic_stats.is_rx_wrongdir++;
			goto out;
		}
		/* check if source STA is associated */
		if (ni == ic->ic_bss) {
			IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
			    wh, "data", "%s", "unknown src");
			ieee80211_send_error(ic, ni, wh->i_addr2,
			    IEEE80211_FC0_SUBTYPE_DEAUTH,
			    IEEE80211_REASON_NOT_AUTHED);
			ic->ic_stats.is_rx_notassoc++;
			goto err;
		}
		if (ni->ni_associd == 0) {
			IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
			    wh, "data", "%s", "unassoc src");
			IEEE80211_SEND_MGMT(ic, ni,
			    IEEE80211_FC0_SUBTYPE_DISASSOC,
			    IEEE80211_REASON_NOT_ASSOCED);
			ic->ic_stats.is_rx_notassoc++;
			goto err;
		}

		/*
		 * Check for power save state change.
		 */
		if (((wh->i_fc[1] & IEEE80211_FC1_PWR_MGT) ^
		    (ni->ni_flags & IEEE80211_NODE_PWR_MGT)))
			ieee80211_node_pwrsave(ni,
				wh->i_fc[1] & IEEE80211_FC1_PWR_MGT);
#endif /* !IEEE80211_NO_HOSTAP */
		break;

	default:
		/* XXX here to keep compiler happy */
		goto out;
	}

	/*
	 * Handle privacy requirements.  Note that we
	 * must not be preempted from here until after
	 * we (potentially) call ieee80211_crypto_demic;
	 * otherwise we may violate assumptions in the
	 * crypto cipher modules used to do delayed update
	 * of replay sequence numbers.
	 */
	if (wh->i_fc[1] & IEEE80211_FC1_WEP) {
		if ((ic->ic_flags & IEEE80211_F_PRIVACY) == 0) {
			/*
			 * Discard encrypted frames when privacy is off.
			 */
			IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
			    wh, "WEP", "%s", "PRIVACY off");
			ic->ic_stats.is_rx_noprivacy++;
			IEEE80211_NODE_STAT(ni, rx_noprivacy);
			goto out;
		}
		key = ieee80211_crypto_decap(ic, ni, &m, hdrspace);
		if (key == NULL) {
			/* NB: stats+msgs handled in crypto_decap */
			IEEE80211_NODE_STAT(ni, rx_wepfail);
			goto out;
		}
		wh = mtod(m, struct ieee80211_frame *);
		wh->i_fc[1] &= ~IEEE80211_FC1_WEP;
	} else {
		key = NULL;
	}

	/*
	 * Next up, any fragmentation.
	 */
	if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
		m = ieee80211_defrag(ni, m, hdrspace);
		if (m == NULL) {
			/* Fragment dropped or frame not complete yet */
			goto out;
		}
	}

	/*
	 * Next, strip any MSDU crypto bits.
	 */
	if (key != NULL && !ieee80211_crypto_demic(ic, key, m, 0)) {
		IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT,
		    ni->ni_macaddr, "data", "%s", "demic error");
		IEEE80211_NODE_STAT(ni, rx_demicfail);
		goto out;
	}

	/* copy to listener after decrypt */
	bpf_mtap3(ic->ic_rawbpf, m, BPF_D_IN);

	/*
	 * Finally, strip the 802.11 header.
	 */
	m = ieee80211_decap(m, hdrspace);
	if (m == NULL) {
		/* don't count Null data frames as errors */
		if (subtype == IEEE80211_FC0_SUBTYPE_NODATA)
			goto out;
		IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT,
		    ni->ni_macaddr, "data", "%s", "decap error");
		ic->ic_stats.is_rx_decap++;
		IEEE80211_NODE_STAT(ni, rx_decap);
		goto err;
	}

	eh = mtod(m, struct ether_header *);
	if (!ieee80211_node_is_authorized(ni)) {
		/*
		 * Deny any non-PAE frames received prior to
		 * authorization.  For open/shared-key
		 * authentication the port is mark authorized
		 * after authentication completes.  For 802.1x
		 * the port is not marked authorized by the
		 * authenticator until the handshake has completed.
		 */
		if (eh->ether_type != htons(ETHERTYPE_PAE)) {
			IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT,
			    eh->ether_shost, "data",
			    "unauthorized port: ether type 0x%x len %u",
			    eh->ether_type, m->m_pkthdr.len);
			ic->ic_stats.is_rx_unauth++;
			IEEE80211_NODE_STAT(ni, rx_unauth);
			goto err;
		}
	} else {
		/*
		 * When denying unencrypted frames, discard
		 * any non-PAE frames received without encryption.
		 */
		if ((ic->ic_flags & IEEE80211_F_DROPUNENC) &&
		    key == NULL && eh->ether_type != htons(ETHERTYPE_PAE)) {
			/*
			 * Drop unencrypted frames.
			 */
			ic->ic_stats.is_rx_unencrypted++;
			IEEE80211_NODE_STAT(ni, rx_unencrypted);
			goto out;
		}
	}

	ifp->if_ipackets++;
	IEEE80211_NODE_STAT(ni, rx_data);
	IEEE80211_NODE_STAT_ADD(ni, rx_bytes, m->m_pkthdr.len);

	ieee80211_deliver_data(ic, ni, m);

	*mp = NULL;
	return 0;

err:
	ifp->if_ierrors++;
out:
	*mp = m;
	return -1;
}

/*
 * Input code for a MANAGEMENT frame.
 */
static int
ieee80211_input_management(struct ieee80211com *ic, struct mbuf **mp,
    struct ieee80211_node *ni, int rssi, u_int32_t rstamp)
{
	IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
	struct ifnet *ifp = ic->ic_ifp;
	struct ieee80211_key *key;
	struct ieee80211_frame *wh;
	u_int8_t dir, subtype;
	struct mbuf *m = *mp;
	int hdrspace;

	wh = mtod(m, struct ieee80211_frame *);
	dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK;
	subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;

	IEEE80211_NODE_STAT(ni, rx_mgmt);
	if (dir != IEEE80211_FC1_DIR_NODS) {
		IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
		    wh, "data", "%s", "unknown dir 0x%x", dir);
		ic->ic_stats.is_rx_wrongdir++;
		goto err;
	}
	if (m->m_len < sizeof(struct ieee80211_frame)) {
		IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_ANY, ni->ni_macaddr,
		    "mgt", "too short: len %u", m->m_len);
		ic->ic_stats.is_rx_tooshort++;
		goto out;
	}
#ifdef IEEE80211_DEBUG
	if ((ieee80211_msg_debug(ic) && doprint(ic, subtype)) ||
	    ieee80211_msg_dumppkts(ic)) {
		if_printf(ic->ic_ifp, "received %s from %s rssi %d\n",
		    ieee80211_mgt_subtype_name[subtype >>
			IEEE80211_FC0_SUBTYPE_SHIFT],
		    ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2),
		    rssi);
	}
#endif

	if (wh->i_fc[1] & IEEE80211_FC1_WEP) {
		if (subtype != IEEE80211_FC0_SUBTYPE_AUTH) {
			/*
			 * Only shared key auth frames with a challenge
			 * should be encrypted, discard all others.
			 */
			IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
			    wh, ieee80211_mgt_subtype_name[subtype >>
				IEEE80211_FC0_SUBTYPE_SHIFT],
			    "%s", "WEP set but not permitted");
			ic->ic_stats.is_rx_mgtdiscard++; /* XXX */
			goto out;
		}
		if ((ic->ic_flags & IEEE80211_F_PRIVACY) == 0) {
			/*
			 * Discard encrypted frames when privacy is off.
			 */
			IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
			    wh, "mgt", "%s", "WEP set but PRIVACY off");
			ic->ic_stats.is_rx_noprivacy++;
			goto out;
		}
		hdrspace = ieee80211_hdrspace(ic, wh);
		key = ieee80211_crypto_decap(ic, ni, &m, hdrspace);
		if (key == NULL) {
			/* NB: stats+msgs handled in crypto_decap */
			goto out;
		}
		wh = mtod(m, struct ieee80211_frame *);
		wh->i_fc[1] &= ~IEEE80211_FC1_WEP;
	}

	bpf_mtap3(ic->ic_rawbpf, m, BPF_D_IN);
	(*ic->ic_recv_mgmt)(ic, m, ni, subtype, rssi, rstamp);
	m_freem(m);

	*mp = NULL;
	return 0;

err:
	ifp->if_ierrors++;
out:
	*mp = m;
	return -1;
}

/*
 * Input code for a CONTROL frame.
 */
static void
ieee80211_input_control(struct ieee80211com *ic, struct mbuf *m,
    struct ieee80211_node *ni)
{
	IEEE80211_NODE_STAT(ni, rx_ctrl);
	ic->ic_stats.is_rx_ctl++;

#ifndef IEEE80211_NO_HOSTAP
	if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
		struct ieee80211_frame *wh;
		u_int8_t subtype;

		wh = mtod(m, struct ieee80211_frame *);
		subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;

		switch (subtype) {
		case IEEE80211_FC0_SUBTYPE_PS_POLL:
			ieee80211_recv_pspoll(ic, ni, m);
			break;
		}
	}
#endif
}

/* -------------------------------------------------------------------------- */

/*
 * Process a received frame.  The node associated with the sender
 * should be supplied.  If nothing was found in the node table then
 * the caller is assumed to supply a reference to ic_bss instead.
 * The RSSI and a timestamp are also supplied.  The RSSI data is used
 * during AP scanning to select a AP to associate with; it can have
 * any units so long as values have consistent units and higher values
 * mean ``better signal''.  The receive timestamp is currently not used
 * by the 802.11 layer.
 */
int
ieee80211_input(struct ieee80211com *ic, struct mbuf *m,
	struct ieee80211_node *ni, int rssi, u_int32_t rstamp)
{
#define	SEQ_LEQ(a,b)	((int)((a)-(b)) <= 0)
#define	HAS_SEQ(type)	((type & 0x4) == 0)
	struct ifnet *ifp = ic->ic_ifp;
	struct ieee80211_frame *wh;
	u_int8_t dir, type;
	u_int16_t rxseq;
	IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
	int ret;

	KASSERT(!cpu_intr_p());

	IASSERT(ni != NULL, ("null node"));
	ni->ni_inact = ni->ni_inact_reload;

	/* trim CRC here so WEP can find its own CRC at the end of packet. */
	if (m->m_flags & M_HASFCS) {
		m_adj(m, -IEEE80211_CRC_LEN);
		m->m_flags &= ~M_HASFCS;
	}
	type = -1;			/* undefined */

	/*
	 * In monitor mode, send everything directly to bpf.
	 * XXX may want to include the CRC
	 */
	if (ic->ic_opmode == IEEE80211_M_MONITOR)
		goto out;

	if (m->m_len < sizeof(struct ieee80211_frame_min)) {
		IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_ANY,
		    ni->ni_macaddr, NULL,
		    "too short (1): len %u", m->m_len);
		ic->ic_stats.is_rx_tooshort++;
		goto out;
	}

	/*
	 * Bit of a cheat here, we use a pointer for a 3-address
	 * frame format but don't reference fields past outside
	 * ieee80211_frame_min w/o first validating the data is
	 * present.
	 */
	wh = mtod(m, struct ieee80211_frame *);

	if ((wh->i_fc[0] & IEEE80211_FC0_VERSION_MASK) !=
	    IEEE80211_FC0_VERSION_0) {
		IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_ANY,
		    ni->ni_macaddr, NULL, "wrong version %x", wh->i_fc[0]);
		ic->ic_stats.is_rx_badversion++;
		goto err;
	}

	dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK;
	type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;

	if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) {
		u_int8_t *bssid;

		switch (ic->ic_opmode) {
		case IEEE80211_M_STA:
			bssid = wh->i_addr2;
			if (!IEEE80211_ADDR_EQ(bssid, ni->ni_bssid)) {
				/* not interested in */
				IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT,
				    bssid, NULL, "node %s, %s",
				    ether_snprintf(ebuf, sizeof(ebuf),
				    ni->ni_bssid), "not to bss");
				ic->ic_stats.is_rx_wrongbss++;
				goto out;
			}

			/*
			 * Filter out packets not directed to us in case the
			 * device is in promiscuous mode
			 */
			if (!IEEE80211_IS_MULTICAST(wh->i_addr1) &&
			    !IEEE80211_ADDR_EQ(wh->i_addr1, ic->ic_myaddr)) {
				IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT,
				    bssid, NULL, "not to cur sta: lladdr=%6D, addr1=%6D",
				    ic->ic_myaddr, ":", wh->i_addr1, ":");
				ic->ic_stats.is_rx_wrongbss++;
				goto out;
			}
			break;

		case IEEE80211_M_IBSS:
		case IEEE80211_M_AHDEMO:
		case IEEE80211_M_HOSTAP:
			if (dir != IEEE80211_FC1_DIR_NODS)
				bssid = wh->i_addr1;
			else if (type == IEEE80211_FC0_TYPE_CTL)
				bssid = wh->i_addr1;
			else {
				if (m->m_len < sizeof(struct ieee80211_frame)) {
					IEEE80211_DISCARD_MAC(ic,
					    IEEE80211_MSG_ANY, ni->ni_macaddr,
					    NULL, "too short (2): len %u",
					    m->m_len);
					ic->ic_stats.is_rx_tooshort++;
					goto out;
				}
				bssid = wh->i_addr3;
			}
			if (type != IEEE80211_FC0_TYPE_DATA)
				break;

			/*
			 * Data frame, validate the bssid.
			 */
			if (!IEEE80211_ADDR_EQ(bssid, ic->ic_bss->ni_bssid) &&
			    !IEEE80211_ADDR_EQ(bssid, ifp->if_broadcastaddr)) {
				/* not interested in */
				IEEE80211_DEBUGVAR(
				    char bbuf[3 * ETHER_ADDR_LEN]);
				IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT,
				    bssid, NULL, "bss %s, broadcast %s, %s",
				    ether_snprintf(ebuf, sizeof(ebuf),
				    ic->ic_bss->ni_bssid),
				    ether_snprintf(bbuf, sizeof(bbuf),
				    ifp->if_broadcastaddr), "not to bss");
				ic->ic_stats.is_rx_wrongbss++;
				goto out;
			}

			/*
			 * For adhoc mode we cons up a node when it doesn't
			 * exist. This should probably be done after an ACL
			 * check.
			 */
			if (ni == ic->ic_bss &&
			    ic->ic_opmode != IEEE80211_M_HOSTAP &&
			    !IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) {
				/*
				 * Fake up a node for this newly
				 * discovered member of the IBSS.
				 */
				ni = ieee80211_fakeup_adhoc_node(&ic->ic_sta,
				    wh->i_addr2);
				if (ni == NULL) {
					/* NB: stat kept for alloc failure */
					goto err;
				}
			}
			break;

		default:
			goto out;
		}

		ni->ni_rssi = rssi;
		ni->ni_rstamp = rstamp;

		if (HAS_SEQ(type) && (ic->ic_opmode != IEEE80211_M_STA ||
		    !IEEE80211_IS_MULTICAST(wh->i_addr1))) {
			u_int8_t tid, retry;
			u_int16_t rxno, orxno;

			if (ieee80211_has_qos(wh)) {
				struct ieee80211_qosframe *qosf;

				if (m->m_len < sizeof(struct ieee80211_qosframe)) {
					IEEE80211_DISCARD_MAC(ic,
					    IEEE80211_MSG_ANY,
					    ni->ni_macaddr, NULL,
					    "too short (1): len %u", m->m_len);
					ic->ic_stats.is_rx_tooshort++;
					goto out;
				}
				qosf = mtod(m, struct ieee80211_qosframe *);

				tid = qosf->i_qos[0] & IEEE80211_QOS_TID;
				if (TID_TO_WME_AC(tid) >= WME_AC_VI)
					ic->ic_wme.wme_hipri_traffic++;
				tid++;
			} else {
				tid = 0;
			}

			rxseq = le16toh(*(u_int16_t *)wh->i_seq);
			retry = wh->i_fc[1] & IEEE80211_FC1_RETRY;
			rxno = rxseq >> IEEE80211_SEQ_SEQ_SHIFT;
			orxno = ni->ni_rxseqs[tid] >> IEEE80211_SEQ_SEQ_SHIFT;

			if (retry && (
			    (orxno == 4095 && rxno == orxno) ||
			    (orxno != 4095 &&
			     SEQ_LEQ(rxseq, ni->ni_rxseqs[tid]))
			    )) {
				/* duplicate, discard */
				IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT,
				    bssid, "duplicate",
				    "seqno <%u,%u> fragno <%u,%u> tid %u",
				    rxno,
				    orxno,
				    rxseq & IEEE80211_SEQ_FRAG_MASK,
				    ni->ni_rxseqs[tid] &
					IEEE80211_SEQ_FRAG_MASK,
				    tid);
				ic->ic_stats.is_rx_dup++;
				IEEE80211_NODE_STAT(ni, rx_dup);
				goto out;
			}
			ni->ni_rxseqs[tid] = rxseq;
		}
	}

	switch (type) {
	case IEEE80211_FC0_TYPE_DATA:
		ret = ieee80211_input_data(ic, &m, ni);
		if (ret == -1) {
			goto out;
		}
		return IEEE80211_FC0_TYPE_DATA;

	case IEEE80211_FC0_TYPE_MGT:
		ret = ieee80211_input_management(ic, &m, ni, rssi, rstamp);
		if (ret == -1) {
			goto out;
		}
		return IEEE80211_FC0_TYPE_MGT;

	case IEEE80211_FC0_TYPE_CTL:
		ieee80211_input_control(ic, m, ni);
		goto out;

	default:
		IEEE80211_DISCARD(ic, IEEE80211_MSG_ANY,
		    wh, NULL, "bad frame type 0x%x", type);
		/* should not come here */
		break;
	}

err:
	ifp->if_ierrors++;

out:
	if (m != NULL) {
		bpf_mtap3(ic->ic_rawbpf, m, BPF_D_IN);
		m_freem(m);
	}
	return type;
#undef SEQ_LEQ
}

/*
 * This function reassembles fragments.
 */
static struct mbuf *
ieee80211_defrag(struct ieee80211_node *ni, struct mbuf *m, int hdrspace)
{
	struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *);
	struct ieee80211_frame *lwh;
	u_int16_t rxseq, iseq;
	u_int8_t fragno;
	const u_int8_t more_frag = wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG;
	struct mbuf *mfrag;

	IASSERT(!IEEE80211_IS_MULTICAST(wh->i_addr1), ("multicast fragm?"));

	iseq = *(u_int16_t *)wh->i_seq;
	rxseq = le16toh(iseq);
	fragno = rxseq & IEEE80211_SEQ_FRAG_MASK;

	/* Quick way out, if there's nothing to defragment */
	if (!more_frag && fragno == 0 && ni->ni_rxfrag[0] == NULL)
		return m;

	/*
	 * Remove frag to insure it doesn't get reaped by timer.
	 */
	if (ni->ni_table == NULL) {
		/*
		 * Should never happen.  If the node is orphaned (not in
		 * the table) then input packets should not reach here.
		 * Otherwise, a concurrent request that yanks the table
		 * should be blocked by other interlocking and/or by first
		 * shutting the driver down.  Regardless, be defensive
		 * here and just bail
		 */
		/* XXX need msg+stat */
		m_freem(m);
		return NULL;
	}
	IEEE80211_NODE_LOCK(ni->ni_table);
	mfrag = ni->ni_rxfrag[0];
	ni->ni_rxfrag[0] = NULL;
	IEEE80211_NODE_UNLOCK(ni->ni_table);

	/*
	 * Validate new fragment is in order and
	 * related to the previous ones.
	 */
	if (mfrag != NULL) {
		u_int16_t last_rxseq;

		lwh = mtod(mfrag, struct ieee80211_frame *);
		last_rxseq = le16toh(*(u_int16_t *)lwh->i_seq);
		/* NB: check seq # and frag together */
		if (rxseq != last_rxseq+1 ||
		    !IEEE80211_ADDR_EQ(wh->i_addr1, lwh->i_addr1) ||
		    !IEEE80211_ADDR_EQ(wh->i_addr2, lwh->i_addr2)) {
			/*
			 * Unrelated fragment or no space for it,
			 * clear current fragments.
			 */
			m_freem(mfrag);
			mfrag = NULL;
		}
	}

	if (mfrag == NULL) {
		if (fragno != 0) {		/* !first fragment, discard */
			IEEE80211_NODE_STAT(ni, rx_defrag);
			m_freem(m);
			return NULL;
		}
		mfrag = m;
	} else {
		int mlen;

		/* Strip header and concatenate */
		m_adj(m, hdrspace);
		mlen = m->m_pkthdr.len;
		m_cat(mfrag, m);

		/* NB: m_cat doesn't update the packet header */
		mfrag->m_pkthdr.len += mlen;

		/* track last seqnum and fragno */
		lwh = mtod(mfrag, struct ieee80211_frame *);
		*(u_int16_t *)lwh->i_seq = iseq;
	}

	if (more_frag) {
		/* more to come, save */
		ni->ni_rxfragstamp = ticks;
		ni->ni_rxfrag[0] = mfrag;
		mfrag = NULL;
	}

	return mfrag;
}

static void
ieee80211_deliver_data(struct ieee80211com *ic,
	struct ieee80211_node *ni, struct mbuf *m)
{
	struct ether_header *eh = mtod(m, struct ether_header *);
	struct ifnet *ifp = ic->ic_ifp;
	int error;

	/* perform as a bridge within the AP */
	if (ic->ic_opmode == IEEE80211_M_HOSTAP &&
	    (ic->ic_flags & IEEE80211_F_NOBRIDGE) == 0) {
		struct mbuf *m1 = NULL;

		if (ETHER_IS_MULTICAST(eh->ether_dhost)) {
			m1 = m_copypacket(m, M_DONTWAIT);
			if (m1 == NULL)
				ifp->if_oerrors++;
			else
				m1->m_flags |= M_MCAST;
		} else {
			/*
			 * Check if the destination is known; if so
			 * and the port is authorized dispatch directly.
			 */
			struct ieee80211_node *sta =
			    ieee80211_find_node(&ic->ic_sta, eh->ether_dhost);
			if (sta != NULL) {
				if (ieee80211_node_is_authorized(sta)) {
					/*
					 * Beware of sending to ourself; this
					 * needs to happen via the normal
					 * input path.
					 */
					if (sta != ic->ic_bss) {
						m1 = m;
						m = NULL;
					}
				} else {
					ic->ic_stats.is_rx_unauth++;
					IEEE80211_NODE_STAT(sta, rx_unauth);
				}
				ieee80211_free_node(sta);
			}
		}

		if (m1 != NULL) {
			int len;
#ifdef ALTQ
			if (ALTQ_IS_ENABLED(&ifp->if_snd)) {
				altq_etherclassify(&ifp->if_snd, m1);
			}
#endif
			len = m1->m_pkthdr.len;
			IFQ_ENQUEUE(&ifp->if_snd, m1, error);
			if (error) {
				ifp->if_oerrors++;
				m_freem(m);
				m = NULL;
			}
			ifp->if_obytes += len;
		}
	}

	if (m != NULL) {
		if (ni->ni_vlan != 0)
			vlan_set_tag(m, ni->ni_vlan);

		/*
		 * XXX once ieee80211_input (or rxintr itself) runs in softint
		 * we have to change here too to use if_input.
		 */
		KASSERT(ifp->if_percpuq);
		if_percpuq_enqueue(ifp->if_percpuq, m);
	}

	return;
}

static struct mbuf *
ieee80211_decap(struct mbuf *m, int hdrlen)
{
	struct ieee80211_qosframe_addr4 wh; /* Max size address frames */
	struct ether_header *eh;
	struct llc *llc;

	if (m->m_len < hdrlen + sizeof(*llc) &&
	    (m = m_pullup(m, hdrlen + sizeof(*llc))) == NULL) {
		return NULL;
	}

	memcpy(&wh, mtod(m, void *), hdrlen);

	llc = (struct llc *)(mtod(m, char *) + hdrlen);
	if (llc->llc_dsap == LLC_SNAP_LSAP &&
	    llc->llc_ssap == LLC_SNAP_LSAP &&
	    llc->llc_control == LLC_UI &&
	    llc->llc_snap.org_code[0] == 0 &&
	    llc->llc_snap.org_code[1] == 0 &&
	    llc->llc_snap.org_code[2] == 0) {
		m_adj(m, hdrlen + sizeof(struct llc) - sizeof(*eh));
		llc = NULL;
	} else {
		/* Keep the LLC after the Ethernet header. */
		m_adj(m, hdrlen - sizeof(*eh));
	}

	eh = mtod(m, struct ether_header *);

	switch (wh.i_fc[1] & IEEE80211_FC1_DIR_MASK) {
	case IEEE80211_FC1_DIR_NODS:
		IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr1);
		IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr2);
		break;
	case IEEE80211_FC1_DIR_TODS:
		IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr3);
		IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr2);
		break;
	case IEEE80211_FC1_DIR_FROMDS:
		IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr1);
		IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr3);
		break;
	case IEEE80211_FC1_DIR_DSTODS:
		IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr3);
		IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr4);
		break;
	}

#ifdef ALIGNED_POINTER
	if (!ALIGNED_POINTER(mtod(m, char *) + sizeof(*eh), u_int32_t)) {
		struct mbuf *n, *n0, **np;
		char *newdata;
		int off, pktlen;

		n0 = NULL;
		np = &n0;
		off = 0;
		pktlen = m->m_pkthdr.len;
		while (pktlen > off) {
			if (n0 == NULL) {
				MGETHDR(n, M_DONTWAIT, MT_DATA);
				if (n == NULL) {
					m_freem(m);
					return NULL;
				}
				m_move_pkthdr(n, m);
				n->m_len = MHLEN;
			} else {
				MGET(n, M_DONTWAIT, MT_DATA);
				if (n == NULL) {
					m_freem(m);
					m_freem(n0);
					return NULL;
				}
				n->m_len = MLEN;
			}
			if (pktlen - off >= MINCLSIZE) {
				MCLGET(n, M_DONTWAIT);
				if (n->m_flags & M_EXT)
					n->m_len = n->m_ext.ext_size;
			}
			if (n0 == NULL) {
				newdata =
				    (char *)ALIGN(n->m_data + sizeof(*eh)) -
				    sizeof(*eh);
				n->m_len -= newdata - n->m_data;
				n->m_data = newdata;
			}
			if (n->m_len > pktlen - off)
				n->m_len = pktlen - off;
			m_copydata(m, off, n->m_len, mtod(n, void *));
			off += n->m_len;
			*np = n;
			np = &n->m_next;
		}
		m_freem(m);
		m = n0;
	}
#endif /* ALIGNED_POINTER */

	if (llc != NULL) {
		eh = mtod(m, struct ether_header *);
		eh->ether_type = htons(m->m_pkthdr.len - sizeof(*eh));
	}

	return m;
}

/*
 * Install received rate set information in the node's state block.
 */
int
ieee80211_setup_rates(struct ieee80211_node *ni, const u_int8_t *rates,
    const u_int8_t *xrates, int flags)
{
	struct ieee80211com *ic = ni->ni_ic;
	struct ieee80211_rateset *rs = &ni->ni_rates;

	memset(rs, 0, sizeof(*rs));

	rs->rs_nrates = rates[1];
	memcpy(rs->rs_rates, rates + 2, rs->rs_nrates);

	if (xrates != NULL) {
		u_int8_t nxrates;
		size_t totalrate;

		/*
		 * Tack on 11g extended supported rate element.
		 */
		nxrates = xrates[1];
		totalrate = (size_t)rs->rs_nrates + (size_t)nxrates;

		if (totalrate > IEEE80211_RATE_MAXSIZE) {
			IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
			nxrates = IEEE80211_RATE_MAXSIZE - rs->rs_nrates;
			IEEE80211_DPRINTF(ic, IEEE80211_MSG_XRATE,
			     "[%s] extended rate set too large;"
			     " only using %u of %u rates\n",
			     ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr),
			     nxrates, xrates[1]);
			ic->ic_stats.is_rx_rstoobig++;
		}

		memcpy(rs->rs_rates + rs->rs_nrates, xrates+2, nxrates);
		rs->rs_nrates += nxrates;
	}

	return ieee80211_fix_rate(ni, flags);
}

static void
ieee80211_auth_open(struct ieee80211com *ic, struct ieee80211_frame *wh,
    struct ieee80211_node *ni, int rssi, u_int32_t rstamp,
    u_int16_t seq, u_int16_t status)
{
	IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);

	if (ni->ni_authmode == IEEE80211_AUTH_SHARED) {
		IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH,
		    ni->ni_macaddr, "open auth",
		    "bad sta auth mode %u", ni->ni_authmode);
		ic->ic_stats.is_rx_bad_auth++;	/* XXX */

		if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
			/* XXX hack to workaround calling convention */
			ieee80211_send_error(ic, ni, wh->i_addr2,
			    IEEE80211_FC0_SUBTYPE_AUTH,
			    (seq + 1) | (IEEE80211_STATUS_ALG<<16));
		}
		return;
	}

	switch (ic->ic_opmode) {
	case IEEE80211_M_IBSS:
	case IEEE80211_M_AHDEMO:
	case IEEE80211_M_MONITOR:
		/* should not come here */
		IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH,
		    ni->ni_macaddr, "open auth",
		    "bad operating mode %u", ic->ic_opmode);
		break;

	case IEEE80211_M_HOSTAP:
#ifndef IEEE80211_NO_HOSTAP
		if (ic->ic_state != IEEE80211_S_RUN ||
		    seq != IEEE80211_AUTH_OPEN_REQUEST) {
			ic->ic_stats.is_rx_bad_auth++;
			return;
		}

		/* always accept open authentication requests */
		if (ni == ic->ic_bss) {
			ni = ieee80211_dup_bss(&ic->ic_sta, wh->i_addr2);
			if (ni == NULL)
				return;
		} else if ((ni->ni_flags & IEEE80211_NODE_AREF) == 0) {
			(void)ieee80211_ref_node(ni);
		}

		/*
		 * Mark the node as referenced to reflect that its
		 * reference count has been bumped to insure it remains
		 * after the transaction completes.
		 */
		ni->ni_flags |= IEEE80211_NODE_AREF;

		IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_AUTH,
		    seq + 1);
		IEEE80211_DPRINTF(ic, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH,
		    "[%s] station authenticated (open)\n",
		    ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr));

		/*
		 * When 802.1x is not in use mark the port
		 * authorized at this point so traffic can flow.
		 */
		if (ni->ni_authmode != IEEE80211_AUTH_8021X)
			ieee80211_node_authorize(ni);
#endif /* !IEEE80211_NO_HOSTAP */
		break;

	case IEEE80211_M_STA:
		if (ic->ic_state != IEEE80211_S_AUTH ||
		    seq != IEEE80211_AUTH_OPEN_RESPONSE) {
			ic->ic_stats.is_rx_bad_auth++;
			return;
		}
		if (status != 0) {
			IEEE80211_DPRINTF(ic,
			    IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH,
			    "[%s] open auth failed (reason %d)\n",
			    ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr),
			    status);

			/* XXX can this happen? */
			if (ni != ic->ic_bss)
				ni->ni_fails++;

			ic->ic_stats.is_rx_auth_fail++;
			ieee80211_new_state(ic, IEEE80211_S_SCAN, 0);
		} else {
			ieee80211_new_state(ic, IEEE80211_S_ASSOC,
			    wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK);
		}
		break;
	}
}

/*
 * Send a management frame error response to the specified
 * station.  If ni is associated with the station then use
 * it; otherwise allocate a temporary node suitable for
 * transmitting the frame and then free the reference so
 * it will go away as soon as the frame has been transmitted.
 */
static void
ieee80211_send_error(struct ieee80211com *ic, struct ieee80211_node *ni,
    const u_int8_t *mac, int subtype, int arg)
{
	bool istmp;

	if (ni == ic->ic_bss) {
		ni = ieee80211_tmp_node(ic, mac);
		if (ni == NULL) {
			/* XXX msg */
			return;
		}
		istmp = true;
	} else {
		istmp = false;
	}

	IEEE80211_SEND_MGMT(ic, ni, subtype, arg);
	if (istmp)
		ieee80211_free_node(ni);
}

static int
alloc_challenge(struct ieee80211com *ic, struct ieee80211_node *ni)
{
	if (ni->ni_challenge == NULL) {
		ni->ni_challenge = malloc(IEEE80211_CHALLENGE_LEN,
		    M_DEVBUF, M_NOWAIT);
	}
	if (ni->ni_challenge == NULL) {
		IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);

		IEEE80211_DPRINTF(ic, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH,
		    "[%s] shared key challenge alloc failed\n",
		    ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr));
		/* XXX statistic */
	}
	return (ni->ni_challenge != NULL);
}

/* XXX TODO: add statistics */
static void
ieee80211_auth_shared(struct ieee80211com *ic, struct ieee80211_frame *wh,
    u_int8_t *frm, u_int8_t *efrm, struct ieee80211_node *ni, int rssi,
    u_int32_t rstamp, u_int16_t seq, u_int16_t status)
{
	u_int8_t *challenge;
	int estatus;
	IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);

	/*
	 * NB: this can happen as we allow pre-shared key
	 * authentication to be enabled w/o wep being turned
	 * on so that configuration of these can be done
	 * in any order.  It may be better to enforce the
	 * ordering in which case this check would just be
	 * for sanity/consistency.
	 */
	if ((ic->ic_flags & IEEE80211_F_PRIVACY) == 0) {
		IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH,
		    ni->ni_macaddr, "shared key auth",
		    "%s", " PRIVACY is disabled");
		estatus = IEEE80211_STATUS_ALG;
		goto bad;
	}

	/*
	 * Pre-shared key authentication is evil; accept
	 * it only if explicitly configured (it is supported
	 * mainly for compatibility with clients like OS X).
	 */
	if (ni->ni_authmode != IEEE80211_AUTH_AUTO &&
	    ni->ni_authmode != IEEE80211_AUTH_SHARED) {
		IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH,
		    ni->ni_macaddr, "shared key auth",
		    "bad sta auth mode %u", ni->ni_authmode);
		ic->ic_stats.is_rx_bad_auth++;	/* XXX maybe a unique error? */
		estatus = IEEE80211_STATUS_ALG;
		goto bad;
	}

	challenge = NULL;
	if (frm + 1 < efrm) {
		if ((frm[1] + 2) > (efrm - frm)) {
			IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH,
			    ni->ni_macaddr, "shared key auth",
			    "ie %d/%d too long",
			    frm[0], (frm[1] + 2) - (efrm - frm));
			ic->ic_stats.is_rx_bad_auth++;
			estatus = IEEE80211_STATUS_CHALLENGE;
			goto bad;
		}
		if (*frm == IEEE80211_ELEMID_CHALLENGE)
			challenge = frm;
		frm += frm[1] + 2;
	}

	switch (seq) {
	case IEEE80211_AUTH_SHARED_CHALLENGE:
	case IEEE80211_AUTH_SHARED_RESPONSE:
		if (challenge == NULL) {
			IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH,
			    ni->ni_macaddr, "shared key auth",
			    "%s", "no challenge");
			ic->ic_stats.is_rx_bad_auth++;
			estatus = IEEE80211_STATUS_CHALLENGE;
			goto bad;
		}
		if (challenge[1] != IEEE80211_CHALLENGE_LEN) {
			IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH,
			    ni->ni_macaddr, "shared key auth",
			    "bad challenge len %d", challenge[1]);
			ic->ic_stats.is_rx_bad_auth++;
			estatus = IEEE80211_STATUS_CHALLENGE;
			goto bad;
		}
	default:
		break;
	}

	switch (ic->ic_opmode) {
	case IEEE80211_M_MONITOR:
	case IEEE80211_M_AHDEMO:
	case IEEE80211_M_IBSS:
		IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH,
		    ni->ni_macaddr, "shared key auth",
		    "bad operating mode %u", ic->ic_opmode);
		return;

	case IEEE80211_M_HOSTAP:
#ifndef IEEE80211_NO_HOSTAP
	{
		int allocbs;
		if (ic->ic_state != IEEE80211_S_RUN) {
			IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH,
			    ni->ni_macaddr, "shared key auth",
			    "bad state %u", ic->ic_state);
			estatus = IEEE80211_STATUS_ALG;	/* XXX */
			goto bad;
		}
		switch (seq) {
		case IEEE80211_AUTH_SHARED_REQUEST:
			if (ni == ic->ic_bss) {
				ni = ieee80211_dup_bss(&ic->ic_sta, wh->i_addr2);
				if (ni == NULL) {
					/* NB: no way to return an error */
					return;
				}
				allocbs = 1;
			} else {
				if ((ni->ni_flags & IEEE80211_NODE_AREF) == 0)
					(void)ieee80211_ref_node(ni);
				allocbs = 0;
			}
			__USE(allocbs);

			/*
			 * Mark the node as referenced to reflect that its
			 * reference count has been bumped to insure it remains
			 * after the transaction completes.
			 */
			ni->ni_flags |= IEEE80211_NODE_AREF;
			ni->ni_rssi = rssi;
			ni->ni_rstamp = rstamp;
			if (!alloc_challenge(ic, ni)) {
				/* NB: don't return error so they rexmit */
				return;
			}

			get_random_bytes(ni->ni_challenge,
				IEEE80211_CHALLENGE_LEN);

			IEEE80211_DPRINTF(ic,
				IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH,
				"[%s] shared key %sauth request\n",
				ether_snprintf(ebuf, sizeof(ebuf),
				ni->ni_macaddr),
				allocbs ? "" : "re");
			break;

		case IEEE80211_AUTH_SHARED_RESPONSE:
			if (ni == ic->ic_bss) {
				IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH,
				    ni->ni_macaddr, "shared key response",
				    "%s", "unknown station");
				/* NB: don't send a response */
				return;
			}

			if (ni->ni_challenge == NULL) {
				IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH,
				    ni->ni_macaddr, "shared key response",
				    "%s", "no challenge recorded");
				ic->ic_stats.is_rx_bad_auth++;
				estatus = IEEE80211_STATUS_CHALLENGE;
				goto bad;
			}

			if (memcmp(ni->ni_challenge, &challenge[2],
			    challenge[1]) != 0) {
				IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH,
				    ni->ni_macaddr, "shared key response",
				    "%s", "challenge mismatch");
				ic->ic_stats.is_rx_auth_fail++;
				estatus = IEEE80211_STATUS_CHALLENGE;
				goto bad;
			}

			IEEE80211_DPRINTF(ic,
			    IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH,
			    "[%s] station authenticated (shared key)\n",
			    ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr));

			ieee80211_node_authorize(ni);
			break;

		default:
			IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH,
			    ni->ni_macaddr, "shared key auth",
			    "bad seq %d", seq);
			ic->ic_stats.is_rx_bad_auth++;
			estatus = IEEE80211_STATUS_SEQUENCE;
			goto bad;
		}

		IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_AUTH,
		    seq + 1);
	}
#endif /* !IEEE80211_NO_HOSTAP */
		break;

	case IEEE80211_M_STA:
		if (ic->ic_state != IEEE80211_S_AUTH)
			return;
		switch (seq) {
		case IEEE80211_AUTH_SHARED_PASS:
			if (ni->ni_challenge != NULL) {
				free(ni->ni_challenge, M_DEVBUF);
				ni->ni_challenge = NULL;
			}
			if (status != 0) {
				IEEE80211_DPRINTF(ic,
				    IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH,
				    "[%s] shared key auth failed (reason %d)\n",
				    ether_snprintf(ebuf, sizeof(ebuf),
				    ieee80211_getbssid(ic, wh)),
				    status);

				/* XXX can this happen? */
				if (ni != ic->ic_bss)
					ni->ni_fails++;

				ic->ic_stats.is_rx_auth_fail++;
				return;
			}

			ieee80211_new_state(ic, IEEE80211_S_ASSOC,
			    wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK);
			break;

		case IEEE80211_AUTH_SHARED_CHALLENGE:
			if (!alloc_challenge(ic, ni))
				return;
			/* XXX could optimize by passing recvd challenge */
			memcpy(ni->ni_challenge, &challenge[2], challenge[1]);

			IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_AUTH,
			    seq + 1);
			break;

		default:
			IEEE80211_DISCARD(ic, IEEE80211_MSG_AUTH,
			    wh, "shared key auth", "bad seq %d", seq);
			ic->ic_stats.is_rx_bad_auth++;
			return;
		}
		break;
	}
	return;

bad:
#ifndef IEEE80211_NO_HOSTAP
	/*
	 * Send an error response; but only when operating as an AP.
	 */
	if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
		/* XXX hack to workaround calling convention */
		ieee80211_send_error(ic, ni, wh->i_addr2,
		    IEEE80211_FC0_SUBTYPE_AUTH,
		    (seq + 1) | (estatus<<16));
	} else if (ic->ic_opmode == IEEE80211_M_STA) {
		/*
		 * Kick the state machine.  This short-circuits
		 * using the mgt frame timeout to trigger the
		 * state transition.
		 */
		if (ic->ic_state == IEEE80211_S_AUTH)
			ieee80211_new_state(ic, IEEE80211_S_SCAN, 0);
	}
#else
	;
#endif /* !IEEE80211_NO_HOSTAP */
}

#ifdef IEEE80211_DEBUG
static void
ieee80211_ssid_mismatch(struct ieee80211com *ic, const char *tag,
	u_int8_t mac[IEEE80211_ADDR_LEN], u_int8_t *ssid)
{
	char ebuf[3 * ETHER_ADDR_LEN];

	printf("[%s] discard %s frame, ssid mismatch: ",
	    ether_snprintf(ebuf, sizeof(ebuf), mac), tag);
	ieee80211_print_essid(ssid + 2, ssid[1]);
	printf("\n");
}

#define	IEEE80211_VERIFY_SSID(_ni, _ssid) do {				\
	if ((_ssid)[1] != 0 &&						\
	    ((_ssid)[1] != (_ni)->ni_esslen ||				\
	    memcmp((_ssid) + 2, (_ni)->ni_essid, (_ssid)[1]) != 0)) {	\
		if (ieee80211_msg_input(ic))				\
			ieee80211_ssid_mismatch(ic, 			\
			    ieee80211_mgt_subtype_name[subtype >>	\
				IEEE80211_FC0_SUBTYPE_SHIFT],		\
				wh->i_addr2, _ssid);			\
		ic->ic_stats.is_rx_ssidmismatch++;			\
		return;							\
	}								\
} while (0)
#else /* !IEEE80211_DEBUG */
#define	IEEE80211_VERIFY_SSID(_ni, _ssid) do {				\
	if ((_ssid)[1] != 0 &&						\
	    ((_ssid)[1] != (_ni)->ni_esslen ||				\
	    memcmp((_ssid) + 2, (_ni)->ni_essid, (_ssid)[1]) != 0)) {	\
		ic->ic_stats.is_rx_ssidmismatch++;			\
		return;							\
	}								\
} while (0)
#endif /* !IEEE80211_DEBUG */

/* unaligned little endian access */
#define LE_READ_2(p)					\
	((u_int16_t)					\
	 ((((const u_int8_t *)(p))[0]      ) |		\
	  (((const u_int8_t *)(p))[1] <<  8)))
#define LE_READ_4(p)					\
	((u_int32_t)					\
	 ((((const u_int8_t *)(p))[0]      ) |		\
	  (((const u_int8_t *)(p))[1] <<  8) |		\
	  (((const u_int8_t *)(p))[2] << 16) |		\
	  (((const u_int8_t *)(p))[3] << 24)))

static __inline int
iswpaoui(const u_int8_t *frm)
{
	return frm[1] > 3 && LE_READ_4(frm+2) == ((WPA_OUI_TYPE<<24)|WPA_OUI);
}

static __inline int
iswmeoui(const u_int8_t *frm)
{
	return frm[1] > 3 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI);
}

static __inline int
iswmeparam(const u_int8_t *frm)
{
	return frm[1] > 5 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI) &&
		frm[6] == WME_PARAM_OUI_SUBTYPE;
}

static __inline int
iswmeinfo(const u_int8_t *frm)
{
	return frm[1] > 5 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI) &&
		frm[6] == WME_INFO_OUI_SUBTYPE;
}

/*
 * Convert a WPA cipher selector OUI to an internal
 * cipher algorithm.  Where appropriate we also
 * record any key length.
 */
static int
wpa_cipher(u_int8_t *sel, u_int8_t *keylen)
{
#define	WPA_SEL(x)	(((x)<<24)|WPA_OUI)
	u_int32_t w = LE_READ_4(sel);

	switch (w) {
	case WPA_SEL(WPA_CSE_NULL):
		return IEEE80211_CIPHER_NONE;
	case WPA_SEL(WPA_CSE_WEP40):
		if (keylen)
			*keylen = 40 / NBBY;
		return IEEE80211_CIPHER_WEP;
	case WPA_SEL(WPA_CSE_WEP104):
		if (keylen)
			*keylen = 104 / NBBY;
		return IEEE80211_CIPHER_WEP;
	case WPA_SEL(WPA_CSE_TKIP):
		return IEEE80211_CIPHER_TKIP;
	case WPA_SEL(WPA_CSE_CCMP):
		return IEEE80211_CIPHER_AES_CCM;
	}
	return 32;		/* NB: so 1<< is discarded */
#undef WPA_SEL
}

/*
 * Convert a WPA key management/authentication algorithm
 * to an internal code.
 */
static int
wpa_keymgmt(u_int8_t *sel)
{
#define	WPA_SEL(x)	(((x)<<24)|WPA_OUI)
	u_int32_t w = LE_READ_4(sel);

	switch (w) {
	case WPA_SEL(WPA_ASE_8021X_UNSPEC):
		return WPA_ASE_8021X_UNSPEC;
	case WPA_SEL(WPA_ASE_8021X_PSK):
		return WPA_ASE_8021X_PSK;
	case WPA_SEL(WPA_ASE_NONE):
		return WPA_ASE_NONE;
	}
	return 0;		/* NB: so is discarded */
#undef WPA_SEL
}

/*
 * Parse a WPA information element to collect parameters
 * and validate the parameters against what has been
 * configured for the system.
 */
static int
ieee80211_parse_wpa(struct ieee80211com *ic, u_int8_t *frm,
    struct ieee80211_rsnparms *rsn, const struct ieee80211_frame *wh)
{
	u_int8_t len = frm[1];
	u_int32_t w;
	int n;

	if ((ic->ic_flags & IEEE80211_F_WPA1) == 0) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
		    wh, "WPA", "not WPA, flags 0x%x", ic->ic_flags);
		return IEEE80211_REASON_IE_INVALID;
	}

	/*
	 * Check the length once for fixed parts: OUI, type,
	 * version, mcast cipher, and 2 selector counts.
	 * Other, variable-length data, must be checked separately.
	 */
	if (len < 14) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
		    wh, "WPA", "too short, len %u", len);
		return IEEE80211_REASON_IE_INVALID;
	}

	frm += 2; /* beginning of payload */
	frm += 4, len -= 4;

	/* NB: iswapoui already validated the OUI and type */
	w = LE_READ_2(frm);
	if (w != WPA_VERSION) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
		    wh, "WPA", "bad version %u", w);
		return IEEE80211_REASON_IE_INVALID;
	}
	frm += 2, len -= 2;

	/* multicast/group cipher */
	w = wpa_cipher(frm, &rsn->rsn_mcastkeylen);
	if (w != rsn->rsn_mcastcipher) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
		    wh, "WPA", "mcast cipher mismatch; got %u, expected %u",
		    w, rsn->rsn_mcastcipher);
		return IEEE80211_REASON_IE_INVALID;
	}
	frm += 4, len -= 4;

	/* unicast ciphers */
	n = LE_READ_2(frm);
	frm += 2, len -= 2;
	if (len < n*4+2) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
		    wh, "WPA", "ucast cipher data too short; len %u, n %u",
		    len, n);
		return IEEE80211_REASON_IE_INVALID;
	}
	w = 0;
	for (; n > 0; n--) {
		w |= 1 << wpa_cipher(frm, &rsn->rsn_ucastkeylen);
		frm += 4, len -= 4;
	}
	w &= rsn->rsn_ucastcipherset;
	if (w == 0) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
		    wh, "WPA", "%s", "ucast cipher set empty");
		return IEEE80211_REASON_IE_INVALID;
	}
	if (w & (1 << IEEE80211_CIPHER_TKIP))
		rsn->rsn_ucastcipher = IEEE80211_CIPHER_TKIP;
	else
		rsn->rsn_ucastcipher = IEEE80211_CIPHER_AES_CCM;

	/* key management algorithms */
	n = LE_READ_2(frm);
	frm += 2, len -= 2;
	if (len < n*4) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
		    wh, "WPA", "key mgmt alg data too short; len %u, n %u",
		    len, n);
		return IEEE80211_REASON_IE_INVALID;
	}
	w = 0;
	for (; n > 0; n--) {
		w |= wpa_keymgmt(frm);
		frm += 4, len -= 4;
	}
	w &= rsn->rsn_keymgmtset;
	if (w == 0) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
		    wh, "WPA", "%s", "no acceptable key mgmt alg");
		return IEEE80211_REASON_IE_INVALID;
	}
	if (w & WPA_ASE_8021X_UNSPEC)
		rsn->rsn_keymgmt = WPA_ASE_8021X_UNSPEC;
	else
		rsn->rsn_keymgmt = WPA_ASE_8021X_PSK;

	if (len > 2)		/* optional capabilities */
		rsn->rsn_caps = LE_READ_2(frm);

	return 0;
}

/*
 * Convert an RSN cipher selector OUI to an internal
 * cipher algorithm.  Where appropriate we also
 * record any key length.
 */
static int
rsn_cipher(u_int8_t *sel, u_int8_t *keylen)
{
#define	RSN_SEL(x)	(((x)<<24)|RSN_OUI)
	u_int32_t w = LE_READ_4(sel);

	switch (w) {
	case RSN_SEL(RSN_CSE_NULL):
		return IEEE80211_CIPHER_NONE;
	case RSN_SEL(RSN_CSE_WEP40):
		if (keylen)
			*keylen = 40 / NBBY;
		return IEEE80211_CIPHER_WEP;
	case RSN_SEL(RSN_CSE_WEP104):
		if (keylen)
			*keylen = 104 / NBBY;
		return IEEE80211_CIPHER_WEP;
	case RSN_SEL(RSN_CSE_TKIP):
		return IEEE80211_CIPHER_TKIP;
	case RSN_SEL(RSN_CSE_CCMP):
		return IEEE80211_CIPHER_AES_CCM;
	case RSN_SEL(RSN_CSE_WRAP):
		return IEEE80211_CIPHER_AES_OCB;
	}
	return 32;		/* NB: so 1<< is discarded */
#undef WPA_SEL
}

/*
 * Convert an RSN key management/authentication algorithm
 * to an internal code.
 */
static int
rsn_keymgmt(u_int8_t *sel)
{
#define	RSN_SEL(x)	(((x)<<24)|RSN_OUI)
	u_int32_t w = LE_READ_4(sel);

	switch (w) {
	case RSN_SEL(RSN_ASE_8021X_UNSPEC):
		return RSN_ASE_8021X_UNSPEC;
	case RSN_SEL(RSN_ASE_8021X_PSK):
		return RSN_ASE_8021X_PSK;
	case RSN_SEL(RSN_ASE_NONE):
		return RSN_ASE_NONE;
	}
	return 0;		/* NB: so is discarded */
#undef RSN_SEL
}

/*
 * Parse a WPA/RSN information element to collect parameters
 * and validate the parameters against what has been
 * configured for the system.
 */
static int
ieee80211_parse_rsn(struct ieee80211com *ic, u_int8_t *frm,
    struct ieee80211_rsnparms *rsn, const struct ieee80211_frame *wh)
{
	u_int8_t len = frm[1];
	u_int32_t w;
	int n;

	if ((ic->ic_flags & IEEE80211_F_WPA2) == 0) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
		    wh, "WPA", "not RSN, flags 0x%x", ic->ic_flags);
		return IEEE80211_REASON_IE_INVALID;
	}

	/*
	 * Check the length once for fixed parts:
	 * version, mcast cipher, and 2 selector counts.
	 * Other, variable-length data, must be checked separately.
	 */
	if (len < 10) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
		    wh, "RSN", "too short, len %u", len);
		return IEEE80211_REASON_IE_INVALID;
	}

	frm += 2; /* beginning of payload */
	w = LE_READ_2(frm);
	if (w != RSN_VERSION) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
		    wh, "RSN", "bad version %u", w);
		return IEEE80211_REASON_IE_INVALID;
	}
	frm += 2, len -= 2;

	/* multicast/group cipher */
	w = rsn_cipher(frm, &rsn->rsn_mcastkeylen);
	if (w != rsn->rsn_mcastcipher) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
		    wh, "RSN", "mcast cipher mismatch; got %u, expected %u",
		    w, rsn->rsn_mcastcipher);
		return IEEE80211_REASON_IE_INVALID;
	}
	frm += 4, len -= 4;

	/* unicast ciphers */
	n = LE_READ_2(frm);
	frm += 2, len -= 2;
	if (len < n*4+2) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
		    wh, "RSN", "ucast cipher data too short; len %u, n %u",
		    len, n);
		return IEEE80211_REASON_IE_INVALID;
	}
	w = 0;
	for (; n > 0; n--) {
		w |= 1 << rsn_cipher(frm, &rsn->rsn_ucastkeylen);
		frm += 4, len -= 4;
	}
	w &= rsn->rsn_ucastcipherset;
	if (w == 0) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
		    wh, "RSN", "%s", "ucast cipher set empty");
		return IEEE80211_REASON_IE_INVALID;
	}
	if (w & (1 << IEEE80211_CIPHER_TKIP))
		rsn->rsn_ucastcipher = IEEE80211_CIPHER_TKIP;
	else
		rsn->rsn_ucastcipher = IEEE80211_CIPHER_AES_CCM;

	/* key management algorithms */
	n = LE_READ_2(frm);
	frm += 2, len -= 2;
	if (len < n*4) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
		    wh, "RSN", "key mgmt alg data too short; len %u, n %u",
		    len, n);
		return IEEE80211_REASON_IE_INVALID;
	}
	w = 0;
	for (; n > 0; n--) {
		w |= rsn_keymgmt(frm);
		frm += 4, len -= 4;
	}
	w &= rsn->rsn_keymgmtset;
	if (w == 0) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA,
		    wh, "RSN", "%s", "no acceptable key mgmt alg");
		return IEEE80211_REASON_IE_INVALID;
	}
	if (w & RSN_ASE_8021X_UNSPEC)
		rsn->rsn_keymgmt = RSN_ASE_8021X_UNSPEC;
	else
		rsn->rsn_keymgmt = RSN_ASE_8021X_PSK;

	/* optional RSN capabilities */
	if (len > 2)
		rsn->rsn_caps = LE_READ_2(frm);
	/* XXXPMKID */

	return 0;
}

static int
ieee80211_parse_wmeparams(struct ieee80211com *ic, u_int8_t *frm,
    const struct ieee80211_frame *wh)
{
#define	MS(_v, _f)	(((_v) & _f) >> _f##_S)
	struct ieee80211_wme_state *wme = &ic->ic_wme;
	u_int len = frm[1], qosinfo;
	int i;

	if (len < sizeof(struct ieee80211_wme_param)-2) {
		IEEE80211_DISCARD_IE(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_WME,
		    wh, "WME", "too short, len %u", len);
		return -1;
	}

	qosinfo = frm[offsetof(struct ieee80211_wme_param, param_qosInfo)];
	qosinfo &= WME_QOSINFO_COUNT;

	/* XXX do proper check for wraparound */
	if (qosinfo == wme->wme_wmeChanParams.cap_info)
		return 0;

	frm += offsetof(struct ieee80211_wme_param, params_acParams);
	for (i = 0; i < WME_NUM_AC; i++) {
		struct wmeParams *wmep =
			&wme->wme_wmeChanParams.cap_wmeParams[i];
		/* NB: ACI not used */
		wmep->wmep_acm = MS(frm[0], WME_PARAM_ACM);
		wmep->wmep_aifsn = MS(frm[0], WME_PARAM_AIFSN);
		wmep->wmep_logcwmin = MS(frm[1], WME_PARAM_LOGCWMIN);
		wmep->wmep_logcwmax = MS(frm[1], WME_PARAM_LOGCWMAX);
		wmep->wmep_txopLimit = LE_READ_2(frm+2);
		frm += 4;
	}

	wme->wme_wmeChanParams.cap_info = qosinfo;
	return 1;
#undef MS
}

void
ieee80211_saveie(u_int8_t **iep, const u_int8_t *ie)
{
	u_int ielen = ie[1]+2;

	/* Record information element for later use. */
	if (*iep == NULL || (*iep)[1] != ie[1]) {
		if (*iep != NULL)
			free(*iep, M_DEVBUF);
		*iep = malloc(ielen, M_DEVBUF, M_NOWAIT);
	}
	if (*iep != NULL)
		memcpy(*iep, ie, ielen);
	/* XXX note failure */
}

static void
ieee80211_update_adhoc_node(struct ieee80211com *ic, struct ieee80211_node *ni,
    struct ieee80211_frame *wh, struct ieee80211_scanparams *scan, int rssi,
    u_int32_t rstamp)
{
	if (!IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) {
		/*
		 * Create a new entry in the neighbor table.
		 * Records the TSF.
		 */
		if ((ni = ieee80211_add_neighbor(ic, wh, scan)) == NULL)
			return;
	} else if (ni->ni_capinfo == 0) {
		/*
		 * Initialize a node that was "faked up."  Records
		 * the TSF.
		 *
		 * No need to check for a change of BSSID: ni could
		 * not have been the IBSS (ic_bss)
		 */
		ieee80211_init_neighbor(ic, ni, wh, scan, 0);
	} else {
		/* Record TSF for potential resync. */
		memcpy(ni->ni_tstamp.data, scan->sp_tstamp, sizeof(ni->ni_tstamp));
	}

	ni->ni_rssi = rssi;
	ni->ni_rstamp = rstamp;

	/* Mark a neighbor's change of BSSID. */
	if (IEEE80211_ADDR_EQ(wh->i_addr3, ni->ni_bssid))
		return;

	IEEE80211_ADDR_COPY(ni->ni_bssid, wh->i_addr3);

	if (ni != ic->ic_bss)
		return;
	else if (ic->ic_flags & IEEE80211_F_DESBSSID) {
		/*
		 * Now, ni does not represent a network we
		 * want to belong to, so start a scan.
		 */
		ieee80211_new_state(ic, IEEE80211_S_SCAN, 0);
		return;
	} else {
		/*
		 * A RUN->RUN transition lets the driver
		 * reprogram its BSSID filter.
		 *
		 * No need to SCAN, we already belong to
		 * an IBSS that meets our criteria: channel,
		 * SSID, etc.  It could be harmful to scan,
		 * too: if a scan does not detect nodes
		 * belonging to my current IBSS, then we
		 * will create a new IBSS at the end of
		 * the scan, needlessly splitting the
		 * network.
		 */
		ieee80211_new_state(ic, IEEE80211_S_RUN, 0);
	}
}

/* -------------------------------------------------------------------------- */

#define IEEE80211_VERIFY_ELEMENT(__elem, __maxlen) do {			\
	if ((__elem) == NULL) {						\
		IEEE80211_DISCARD(ic, IEEE80211_MSG_ELEMID,		\
		    wh, ieee80211_mgt_subtype_name[subtype >>		\
			IEEE80211_FC0_SUBTYPE_SHIFT],			\
		    "%s", "no " #__elem );				\
		ic->ic_stats.is_rx_elem_missing++;			\
		return;							\
	}								\
	if ((__elem)[1] > (__maxlen)) {					\
		IEEE80211_DISCARD(ic, IEEE80211_MSG_ELEMID,		\
		    wh, ieee80211_mgt_subtype_name[subtype >>		\
			IEEE80211_FC0_SUBTYPE_SHIFT],			\
		    "bad " #__elem " len %d", (__elem)[1]);		\
		ic->ic_stats.is_rx_elem_toobig++;			\
		return;							\
	}								\
} while (0)

#define	IEEE80211_VERIFY_LENGTH(_len, _minlen) do {			\
	if ((size_t)(_len) < (size_t)(_minlen)) {			\
		IEEE80211_DISCARD(ic, IEEE80211_MSG_ELEMID,		\
		    wh, ieee80211_mgt_subtype_name[subtype >>		\
			IEEE80211_FC0_SUBTYPE_SHIFT],			\
		    "%s", "ie too short");				\
		ic->ic_stats.is_rx_elem_toosmall++;			\
		return;							\
	}								\
} while (0)

static void
ieee80211_recv_mgmt_beacon(struct ieee80211com *ic, struct mbuf *m0,
    struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp)
{
	struct ieee80211_frame *wh;
	u_int8_t *frm, *efrm;
	IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
	struct ieee80211_scanparams scan;

	wh = mtod(m0, struct ieee80211_frame *);
	frm = (u_int8_t *)(wh + 1);
	efrm = mtod(m0, u_int8_t *) + m0->m_len;

	/*
	 * We process beacon/probe response frames:
	 *    o when scanning, or
	 *    o station mode when associated (to collect state
	 *      updates such as 802.11g slot time), or
	 *    o adhoc mode (to discover neighbors)
	 * Frames otherwise received are discarded.
	 */
	if (!((ic->ic_flags & IEEE80211_F_SCAN) ||
	      (ic->ic_opmode == IEEE80211_M_STA && ni->ni_associd) ||
	       ic->ic_opmode == IEEE80211_M_IBSS)) {
		ic->ic_stats.is_rx_mgtdiscard++;
		return;
	}

	/*
	 * beacon/probe response frame format
	 *	[8] time stamp
	 *	[2] beacon interval
	 *	[2] capability information
	 *	[tlv] ssid
	 *	[tlv] supported rates
	 *	[tlv] country information
	 *	[tlv] parameter set (FH/DS)
	 *	[tlv] erp information
	 *	[tlv] extended supported rates
	 *	[tlv] WME
	 *	[tlv] WPA or RSN
	 */
	IEEE80211_VERIFY_LENGTH(efrm - frm, 12);
	memset(&scan, 0, sizeof(scan));
	scan.sp_tstamp  = frm;				frm += 8;
	scan.sp_bintval = le16toh(*(u_int16_t *)frm);	frm += 2;
	scan.sp_capinfo = le16toh(*(u_int16_t *)frm);	frm += 2;
	scan.sp_bchan = ieee80211_chan2ieee(ic, ic->ic_curchan);
	scan.sp_chan = scan.sp_bchan;

	while (frm + 1 < efrm) {
		IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2);

		switch (*frm) {
		case IEEE80211_ELEMID_SSID:
			/* no length check needed */
			scan.sp_ssid = frm;
			break;
		case IEEE80211_ELEMID_RATES:
			/* no length check needed */
			scan.sp_rates = frm;
			break;
		case IEEE80211_ELEMID_COUNTRY:
			/* XXX: we don't do anything with this? */
			scan.sp_country = frm;
			break;
		case IEEE80211_ELEMID_FHPARMS:
			IEEE80211_VERIFY_LENGTH(frm[1], 5);
			if (ic->ic_phytype == IEEE80211_T_FH) {
				scan.sp_fhdwell = LE_READ_2(&frm[2]);
				scan.sp_chan = IEEE80211_FH_CHAN(frm[4], frm[5]);
				scan.sp_fhindex = frm[6];
			}
			break;
		case IEEE80211_ELEMID_DSPARMS:
			/*
			 * XXX hack this since depending on phytype
			 * is problematic for multi-mode devices.
			 */
			IEEE80211_VERIFY_LENGTH(frm[1], 1);
			if (ic->ic_phytype != IEEE80211_T_FH)
				scan.sp_chan = frm[2];
			break;
		case IEEE80211_ELEMID_TIM:
			/* XXX ATIM? */
			IEEE80211_VERIFY_LENGTH(frm[1], 4);
			scan.sp_tim = frm;
			scan.sp_timoff = frm - mtod(m0, u_int8_t *);
			break;
		case IEEE80211_ELEMID_IBSSPARMS:
			break;
		case IEEE80211_ELEMID_XRATES:
			scan.sp_xrates = frm;
			break;
		case IEEE80211_ELEMID_ERP:
			if (frm[1] != 1) {
				IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID,
				    wh, "ERP", "bad len %u", frm[1]);
				ic->ic_stats.is_rx_elem_toobig++;
				break;
			}
			scan.sp_erp = frm[2];
			break;
		case IEEE80211_ELEMID_RSN:
			/* no length check needed */
			scan.sp_wpa = frm;
			break;
		case IEEE80211_ELEMID_VENDOR:
			/* no length check needed */
			if (iswpaoui(frm))
				scan.sp_wpa = frm;
			else if (iswmeparam(frm) || iswmeinfo(frm))
				scan.sp_wme = frm;
			/* XXX Atheros OUI support */
			break;
		default:
			IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID,
			    wh, "unhandled", "id %u, len %u", *frm, frm[1]);
			ic->ic_stats.is_rx_elem_unknown++;
			break;
		}

		frm += frm[1] + 2;
	}

	IEEE80211_VERIFY_ELEMENT(scan.sp_rates, IEEE80211_RATE_MAXSIZE);
	IEEE80211_VERIFY_ELEMENT(scan.sp_ssid, IEEE80211_NWID_LEN);

	if (
#if IEEE80211_CHAN_MAX < 255
	    scan.sp_chan > IEEE80211_CHAN_MAX ||
#endif
	    isclr(ic->ic_chan_active, scan.sp_chan)) {
		IEEE80211_DISCARD(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT,
		    wh, ieee80211_mgt_subtype_name[subtype >>
			IEEE80211_FC0_SUBTYPE_SHIFT],
		    "invalid channel %u", scan.sp_chan);
		ic->ic_stats.is_rx_badchan++;
		return;
	}

	if (scan.sp_chan != scan.sp_bchan &&
	    ic->ic_phytype != IEEE80211_T_FH) {
		/*
		 * Frame was received on a channel different from the
		 * one indicated in the DS params element id;
		 * silently discard it.
		 *
		 * NB: this can happen due to signal leakage.
		 *     But we should take it for FH phy because
		 *     the rssi value should be correct even for
		 *     different hop pattern in FH.
		 */
		IEEE80211_DISCARD(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT,
		    wh, ieee80211_mgt_subtype_name[subtype >>
			IEEE80211_FC0_SUBTYPE_SHIFT],
		    "for off-channel %u", scan.sp_chan);
		ic->ic_stats.is_rx_chanmismatch++;
		return;
	}

	if (!(IEEE80211_BINTVAL_MIN <= scan.sp_bintval &&
	      scan.sp_bintval <= IEEE80211_BINTVAL_MAX)) {
		IEEE80211_DISCARD(ic,
		    IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT,
		    wh, ieee80211_mgt_subtype_name[subtype >>
			IEEE80211_FC0_SUBTYPE_SHIFT],
		    "bogus beacon interval", scan.sp_bintval);
		ic->ic_stats.is_rx_badbintval++;
		return;
	}

	if (ni != ic->ic_bss) {
		ni = ieee80211_refine_node_for_beacon(ic, ni,
		    &ic->ic_channels[scan.sp_chan], scan.sp_ssid);
	}

	/*
	 * Count frame now that we know it's to be processed.
	 */
	if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) {
		ic->ic_stats.is_rx_beacon++;
		IEEE80211_NODE_STAT(ni, rx_beacons);
	} else {
		IEEE80211_NODE_STAT(ni, rx_proberesp);
	}

	/*
	 * When operating in station mode, check for state updates.
	 * Be careful to ignore beacons received while doing a
	 * background scan.  We consider only 11g/WMM stuff right now.
	 */
	if (ic->ic_opmode == IEEE80211_M_STA && ni->ni_associd != 0 &&
	    ((ic->ic_flags & IEEE80211_F_SCAN) == 0 ||
	     IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_bssid))) {
		/* record tsf of last beacon */
		memcpy(ni->ni_tstamp.data, scan.sp_tstamp, sizeof(ni->ni_tstamp));

		if (ni->ni_erp != scan.sp_erp) {
			IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC,
			    "[%s] erp change: was 0x%x, now 0x%x\n",
			    ether_snprintf(ebuf, sizeof(ebuf),
			    wh->i_addr2), ni->ni_erp, scan.sp_erp);

			if (ic->ic_curmode == IEEE80211_MODE_11G &&
			    (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) {
				ic->ic_flags |= IEEE80211_F_USEPROT;
			} else {
				ic->ic_flags &= ~IEEE80211_F_USEPROT;
			}
			ni->ni_erp = scan.sp_erp;
		}

		if ((ni->ni_capinfo ^ scan.sp_capinfo) & IEEE80211_CAPINFO_SHORT_SLOTTIME) {
			IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC,
			    "[%s] capabilities change: before 0x%x,"
			     " now 0x%x\n",
			     ether_snprintf(ebuf, sizeof(ebuf),
			     wh->i_addr2),
			     ni->ni_capinfo, scan.sp_capinfo);
			/*
			 * NB: we assume short preamble doesn't
			 *     change dynamically
			 */
			ieee80211_set_shortslottime(ic,
			    ic->ic_curmode == IEEE80211_MODE_11A ||
			    (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME));
			ni->ni_capinfo = scan.sp_capinfo;
		}

		if (scan.sp_wme != NULL && (ni->ni_flags & IEEE80211_NODE_QOS) &&
		    ieee80211_parse_wmeparams(ic, scan.sp_wme, wh) > 0) {
			ieee80211_wme_updateparams(ic);
		}

		if (scan.sp_tim != NULL) {
			struct ieee80211_tim_ie *ie =
			    (struct ieee80211_tim_ie *)scan.sp_tim;

			ni->ni_dtim_count = ie->tim_count;
			ni->ni_dtim_period = ie->tim_period;
		}

		if (ic->ic_flags & IEEE80211_F_SCAN) {
			ieee80211_add_scan(ic, &scan, wh, subtype, rssi,
			    rstamp);
		}

		ic->ic_bmiss_count = 0;
		return;
	}

	/*
	 * If scanning, just pass information to the scan module.
	 */
	if (ic->ic_flags & IEEE80211_F_SCAN) {
		if (ic->ic_flags_ext & IEEE80211_FEXT_PROBECHAN) {
			/*
			 * Actively scanning a channel marked passive;
			 * send a probe request now that we know there
			 * is 802.11 traffic present.
			 *
			 * XXX check if the beacon we recv'd gives
			 * us what we need and suppress the probe req
			 */
			ieee80211_probe_curchan(ic, 1);
			ic->ic_flags_ext &= ~IEEE80211_FEXT_PROBECHAN;
		}
		ieee80211_add_scan(ic, &scan, wh, subtype, rssi, rstamp);
		return;
	}

	if (scan.sp_capinfo & IEEE80211_CAPINFO_IBSS) {
		ieee80211_update_adhoc_node(ic, ni, wh, &scan, rssi, rstamp);
	}
}

static void
ieee80211_recv_mgmt_probe_req(struct ieee80211com *ic, struct mbuf *m0,
    struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp)
{
	struct ieee80211_frame *wh;
	u_int8_t *frm, *efrm;
	u_int8_t *ssid, *rates, *xrates;
	bool need_free = false;
	u_int8_t rate;
	IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);

	wh = mtod(m0, struct ieee80211_frame *);
	frm = (u_int8_t *)(wh + 1);
	efrm = mtod(m0, u_int8_t *) + m0->m_len;

	if (ic->ic_opmode == IEEE80211_M_STA ||
	    ic->ic_state != IEEE80211_S_RUN) {
		ic->ic_stats.is_rx_mgtdiscard++;
		return;
	}
	if (IEEE80211_IS_MULTICAST(wh->i_addr2)) {
		/* frame must be directed */
		ic->ic_stats.is_rx_mgtdiscard++;
		return;
	}

	/*
	 * prreq frame format
	 *	[tlv] ssid
	 *	[tlv] supported rates
	 *	[tlv] extended supported rates
	 */
	ssid = rates = xrates = NULL;
	while (frm + 1 < efrm) {
		IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2);

		switch (*frm) {
		case IEEE80211_ELEMID_SSID:
			ssid = frm;
			break;
		case IEEE80211_ELEMID_RATES:
			rates = frm;
			break;
		case IEEE80211_ELEMID_XRATES:
			xrates = frm;
			break;
		}

		frm += frm[1] + 2;
	}

	IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE);
	IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN);
	IEEE80211_VERIFY_SSID(ic->ic_bss, ssid);

	if ((ic->ic_flags & IEEE80211_F_HIDESSID) && ssid[1] == 0) {
		IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT,
		    wh, ieee80211_mgt_subtype_name[subtype >>
		    IEEE80211_FC0_SUBTYPE_SHIFT],
		    "%s", "no ssid with ssid suppression enabled");
		ic->ic_stats.is_rx_ssidmismatch++; /*XXX*/
		return;
	}

	if (ni == ic->ic_bss) {
		if (ic->ic_opmode != IEEE80211_M_IBSS) {
			ni = ieee80211_tmp_node(ic, wh->i_addr2);
			need_free = true;
		} else if (IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) {
			;
		} else {
			/*
			 * XXX Cannot tell if the sender is operating
			 * in ibss mode.  But we need a new node to
			 * send the response so blindly add them to the
			 * neighbor table.
			 */
			ni = ieee80211_fakeup_adhoc_node(&ic->ic_sta,
				wh->i_addr2);
		}
		if (ni == NULL)
			return;
	}

	IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, "[%s] recv probe req\n",
	    ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2));

	ni->ni_rssi = rssi;
	ni->ni_rstamp = rstamp;
	rate = ieee80211_setup_rates(ni, rates, xrates,
	    IEEE80211_R_DOSORT | IEEE80211_R_DOFRATE |
	    IEEE80211_R_DONEGO | IEEE80211_R_DODEL);

	if (rate & IEEE80211_RATE_BASIC) {
		IEEE80211_DISCARD(ic, IEEE80211_MSG_XRATE,
		    wh, ieee80211_mgt_subtype_name[subtype >>
		    IEEE80211_FC0_SUBTYPE_SHIFT],
		    "%s", "recv'd rate set invalid");
	} else {
		IEEE80211_SEND_MGMT(ic, ni,
		    IEEE80211_FC0_SUBTYPE_PROBE_RESP, 0);
	}

	if (need_free) {
		/* reclaim immediately */
		ieee80211_free_node(ni);
	}
}

static void
ieee80211_recv_mgmt_auth(struct ieee80211com *ic, struct mbuf *m0,
    struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp)
{
	struct ieee80211_frame *wh;
	u_int8_t *frm, *efrm;
	IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
	u_int16_t algo, seq, status;

	wh = mtod(m0, struct ieee80211_frame *);
	frm = (u_int8_t *)(wh + 1);
	efrm = mtod(m0, u_int8_t *) + m0->m_len;

	/*
	 * auth frame format
	 *	[2] algorithm
	 *	[2] sequence
	 *	[2] status
	 *	[tlv*] challenge
	 */
	IEEE80211_VERIFY_LENGTH(efrm - frm, 6);
	algo   = le16toh(*(u_int16_t *)frm);
	seq    = le16toh(*(u_int16_t *)(frm + 2));
	status = le16toh(*(u_int16_t *)(frm + 4));

	IEEE80211_DPRINTF(ic, IEEE80211_MSG_AUTH,
	    "[%s] recv auth frame with algorithm %d seq %d\n",
	    ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2), algo, seq);

	/*
	 * Consult the ACL policy module if setup.
	 */
	if (ic->ic_acl != NULL && !ic->ic_acl->iac_check(ic, wh->i_addr2)) {
		IEEE80211_DISCARD(ic, IEEE80211_MSG_ACL,
		    wh, "auth", "%s", "disallowed by ACL");
		ic->ic_stats.is_rx_acl++;
		if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
			IEEE80211_SEND_MGMT(ic, ni,
			    IEEE80211_FC0_SUBTYPE_AUTH,
			    (seq+1) | (IEEE80211_STATUS_UNSPECIFIED<<16));
		}
		return;
	}

	if (ic->ic_flags & IEEE80211_F_COUNTERM) {
		IEEE80211_DISCARD(ic,
		    IEEE80211_MSG_AUTH | IEEE80211_MSG_CRYPTO,
		    wh, "auth", "%s", "TKIP countermeasures enabled");
		ic->ic_stats.is_rx_auth_countermeasures++;
#ifndef IEEE80211_NO_HOSTAP
		if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
			IEEE80211_SEND_MGMT(ic, ni,
				IEEE80211_FC0_SUBTYPE_AUTH,
				IEEE80211_REASON_MIC_FAILURE);
		}
#endif
		return;
	}

	if (algo == IEEE80211_AUTH_ALG_SHARED) {
		ieee80211_auth_shared(ic, wh, frm + 6, efrm, ni, rssi,
		    rstamp, seq, status);
	} else if (algo == IEEE80211_AUTH_ALG_OPEN) {
		ieee80211_auth_open(ic, wh, ni, rssi, rstamp, seq, status);
	} else {
		IEEE80211_DISCARD(ic, IEEE80211_MSG_ANY,
		    wh, "auth", "unsupported alg %d", algo);
		ic->ic_stats.is_rx_auth_unsupported++;
#ifndef IEEE80211_NO_HOSTAP
		if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
			/* XXX not right */
			IEEE80211_SEND_MGMT(ic, ni,
				IEEE80211_FC0_SUBTYPE_AUTH,
				(seq+1) | (IEEE80211_STATUS_ALG<<16));
		}
#endif
	}
}

static void
ieee80211_recv_mgmt_assoc_req(struct ieee80211com *ic, struct mbuf *m0,
    struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp)
{
	struct ieee80211_frame *wh;
	u_int8_t *frm, *efrm;
	u_int8_t *ssid, *rates, *xrates, *wpa, *wme;
	int reassoc, resp;
	u_int8_t rate;
	IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
	u_int16_t capinfo, lintval;
	struct ieee80211_rsnparms rsn;
	u_int8_t reason;

	wh = mtod(m0, struct ieee80211_frame *);
	frm = (u_int8_t *)(wh + 1);
	efrm = mtod(m0, u_int8_t *) + m0->m_len;

	if (ic->ic_opmode != IEEE80211_M_HOSTAP ||
	    ic->ic_state != IEEE80211_S_RUN) {
		ic->ic_stats.is_rx_mgtdiscard++;
		return;
	}

	if (subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) {
		reassoc = 1;
		resp = IEEE80211_FC0_SUBTYPE_REASSOC_RESP;
	} else {
		reassoc = 0;
		resp = IEEE80211_FC0_SUBTYPE_ASSOC_RESP;
	}

	/*
	 * asreq frame format
	 *	[2] capability information
	 *	[2] listen interval
	 *	[6*] current AP address (reassoc only)
	 *	[tlv] ssid
	 *	[tlv] supported rates
	 *	[tlv] extended supported rates
	 *	[tlv] WPA or RSN
	 */
	IEEE80211_VERIFY_LENGTH(efrm - frm, (reassoc ? 10 : 4));

	if (!IEEE80211_ADDR_EQ(wh->i_addr3, ic->ic_bss->ni_bssid)) {
		IEEE80211_DISCARD(ic, IEEE80211_MSG_ANY,
		    wh, ieee80211_mgt_subtype_name[subtype >>
			IEEE80211_FC0_SUBTYPE_SHIFT],
		    "%s", "wrong bssid");
		ic->ic_stats.is_rx_assoc_bss++;
		return;
	}

	capinfo = le16toh(*(u_int16_t *)frm);	frm += 2;
	lintval = le16toh(*(u_int16_t *)frm);	frm += 2;
	if (reassoc)
		frm += 6;	/* ignore current AP info */

	ssid = rates = xrates = wpa = wme = NULL;
	while (frm + 1 < efrm) {
		IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2);

		switch (*frm) {
		case IEEE80211_ELEMID_SSID:
			ssid = frm;
			break;
		case IEEE80211_ELEMID_RATES:
			rates = frm;
			break;
		case IEEE80211_ELEMID_XRATES:
			xrates = frm;
			break;
		/* XXX verify only one of RSN and WPA ie's? */
		case IEEE80211_ELEMID_RSN:
			wpa = frm;
			break;
		case IEEE80211_ELEMID_VENDOR:
			if (iswpaoui(frm))
				wpa = frm;
			else if (iswmeinfo(frm))
				wme = frm;
			/* XXX Atheros OUI support */
			break;
		}

		frm += frm[1] + 2;
	}

	IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE);
	IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN);
	IEEE80211_VERIFY_SSID(ic->ic_bss, ssid);

	if (ni == ic->ic_bss) {
		IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY,
		    "[%s] deny %s request, sta not authenticated\n",
		    ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2),
		    reassoc ? "reassoc" : "assoc");
		ieee80211_send_error(ic, ni, wh->i_addr2,
		    IEEE80211_FC0_SUBTYPE_DEAUTH,
		    IEEE80211_REASON_ASSOC_NOT_AUTHED);
		ic->ic_stats.is_rx_assoc_notauth++;
		return;
	}

	/* assert right association security credentials */
	if (wpa == NULL && (ic->ic_flags & IEEE80211_F_WPA)) {
		IEEE80211_DPRINTF(ic,
		    IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA,
		    "[%s] no WPA/RSN IE in association request\n",
		    ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2));

		IEEE80211_SEND_MGMT(ic, ni,
		    IEEE80211_FC0_SUBTYPE_DEAUTH,
		    IEEE80211_REASON_RSN_REQUIRED);
		ieee80211_node_leave(ic, ni);

		/* XXX distinguish WPA/RSN? */
		ic->ic_stats.is_rx_assoc_badwpaie++;
		return;
	}

	if (wpa != NULL) {
		/*
		 * Parse WPA information element.  Note that
		 * we initialize the param block from the node
		 * state so that information in the IE overrides
		 * our defaults.  The resulting parameters are
		 * installed below after the association is assured.
		 */
		rsn = ni->ni_rsn;
		if (wpa[0] != IEEE80211_ELEMID_RSN)
			reason = ieee80211_parse_wpa(ic, wpa, &rsn, wh);
		else
			reason = ieee80211_parse_rsn(ic, wpa, &rsn, wh);

		if (reason != 0) {
			IEEE80211_SEND_MGMT(ic, ni,
			    IEEE80211_FC0_SUBTYPE_DEAUTH, reason);
			ieee80211_node_leave(ic, ni);

			/* XXX distinguish WPA/RSN? */
			ic->ic_stats.is_rx_assoc_badwpaie++;
			return;
		}

		IEEE80211_DPRINTF(ic,
		    IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA,
		    "[%s] %s ie: mc %u/%u uc %u/%u key %u caps 0x%x\n",
		    ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2),
		    wpa[0] != IEEE80211_ELEMID_RSN ?  "WPA" : "RSN",
		    rsn.rsn_mcastcipher, rsn.rsn_mcastkeylen,
		    rsn.rsn_ucastcipher, rsn.rsn_ucastkeylen,
		    rsn.rsn_keymgmt, rsn.rsn_caps);
	}

	/* discard challenge after association */
	if (ni->ni_challenge != NULL) {
		free(ni->ni_challenge, M_DEVBUF);
		ni->ni_challenge = NULL;
	}

	/* NB: 802.11 spec says to ignore station's privacy bit */
	if ((capinfo & IEEE80211_CAPINFO_ESS) == 0) {
		IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY,
		    "[%s] deny %s request, capability mismatch 0x%x\n",
		    ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2),
		    reassoc ? "reassoc" : "assoc", capinfo);
		IEEE80211_SEND_MGMT(ic, ni, resp,
			IEEE80211_STATUS_CAPINFO);
		ieee80211_node_leave(ic, ni);
		ic->ic_stats.is_rx_assoc_capmismatch++;
		return;
	}

	rate = ieee80211_setup_rates(ni, rates, xrates,
	    IEEE80211_R_DOSORT | IEEE80211_R_DOFRATE |
	    IEEE80211_R_DONEGO | IEEE80211_R_DODEL);

	/*
	 * If constrained to 11g-only stations reject an
	 * 11b-only station.  We cheat a bit here by looking
	 * at the max negotiated xmit rate and assuming anyone
	 * with a best rate <24Mb/s is an 11b station.
	 */
	if ((rate & IEEE80211_RATE_BASIC) ||
	    ((ic->ic_flags & IEEE80211_F_PUREG) && rate < 48)) {
		IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY,
		    "[%s] deny %s request, rate set mismatch\n",
		    ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2),
		    reassoc ? "reassoc" : "assoc");
		IEEE80211_SEND_MGMT(ic, ni, resp,
			IEEE80211_STATUS_BASIC_RATE);
		ieee80211_node_leave(ic, ni);
		ic->ic_stats.is_rx_assoc_norate++;
		return;
	}

	ni->ni_rssi = rssi;
	ni->ni_rstamp = rstamp;
	ni->ni_intval = lintval;
	ni->ni_capinfo = capinfo;
	ni->ni_chan = ic->ic_bss->ni_chan;
	ni->ni_fhdwell = ic->ic_bss->ni_fhdwell;
	ni->ni_fhindex = ic->ic_bss->ni_fhindex;

	if (wpa != NULL) {
		/*
		 * Record WPA/RSN parameters for station, mark
		 * node as using WPA and record information element
		 * for applications that require it.
		 */
		ni->ni_rsn = rsn;
		ieee80211_saveie(&ni->ni_wpa_ie, wpa);
	} else if (ni->ni_wpa_ie != NULL) {
		/*
		 * Flush any state from a previous association.
		 */
		free(ni->ni_wpa_ie, M_DEVBUF);
		ni->ni_wpa_ie = NULL;
	}

	if (wme != NULL) {
		/*
		 * Record WME parameters for station, mark node
		 * as capable of QoS and record information
		 * element for applications that require it.
		 */
		ieee80211_saveie(&ni->ni_wme_ie, wme);
		ni->ni_flags |= IEEE80211_NODE_QOS;
	} else if (ni->ni_wme_ie != NULL) {
		/*
		 * Flush any state from a previous association.
		 */
		free(ni->ni_wme_ie, M_DEVBUF);
		ni->ni_wme_ie = NULL;
		ni->ni_flags &= ~IEEE80211_NODE_QOS;
	}

	ieee80211_node_join(ic, ni, resp);
}

#define	ISREASSOC(_st)	((_st) == IEEE80211_FC0_SUBTYPE_REASSOC_RESP)

static void
ieee80211_recv_mgmt_assoc_resp(struct ieee80211com *ic, struct mbuf *m0,
    struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp)
{
	struct ieee80211_frame *wh;
	u_int8_t *frm, *efrm;
	u_int8_t *rates, *xrates, *wme;
	u_int8_t rate;
	IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
	u_int16_t capinfo, associd;
	u_int16_t status;

	wh = mtod(m0, struct ieee80211_frame *);
	frm = (u_int8_t *)(wh + 1);
	efrm = mtod(m0, u_int8_t *) + m0->m_len;

	if (ic->ic_opmode != IEEE80211_M_STA ||
	    ic->ic_state != IEEE80211_S_ASSOC) {
		ic->ic_stats.is_rx_mgtdiscard++;
		return;
	}

	/*
	 * asresp frame format
	 *	[2] capability information
	 *	[2] status
	 *	[2] association ID
	 *	[tlv] supported rates
	 *	[tlv] extended supported rates
	 *	[tlv] WME
	 */
	IEEE80211_VERIFY_LENGTH(efrm - frm, 6);
	ni = ic->ic_bss;
	capinfo = le16toh(*(u_int16_t *)frm);
	frm += 2;
	status = le16toh(*(u_int16_t *)frm);
	frm += 2;
	if (status != 0) {
		IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC,
		    "[%s] %sassoc failed (reason %d)\n",
		    ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2),
		    ISREASSOC(subtype) ?  "re" : "", status);
		if (ni != ic->ic_bss)	/* XXX never true? */
			ni->ni_fails++;
		ic->ic_stats.is_rx_auth_fail++;	/* XXX */
		return;
	}
	associd = le16toh(*(u_int16_t *)frm);
	frm += 2;

	rates = xrates = wme = NULL;
	while (frm + 1 < efrm) {
		IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2);

		switch (*frm) {
		case IEEE80211_ELEMID_RATES:
			rates = frm;
			break;
		case IEEE80211_ELEMID_XRATES:
			xrates = frm;
			break;
		case IEEE80211_ELEMID_VENDOR:
			if (iswmeoui(frm))
				wme = frm;
			/* XXX Atheros OUI support */
			break;
		}

		frm += frm[1] + 2;
	}

	IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE);

	rate = ieee80211_setup_rates(ni, rates, xrates,
	    IEEE80211_R_DOSORT | IEEE80211_R_DOFRATE |
	    IEEE80211_R_DONEGO | IEEE80211_R_DODEL);

	if (rate & IEEE80211_RATE_BASIC) {
		IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC,
		    "[%s] %sassoc failed (rate set mismatch)\n",
		    ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2),
		    ISREASSOC(subtype) ?  "re" : "");
		if (ni != ic->ic_bss)	/* XXX never true? */
			ni->ni_fails++;
		ic->ic_stats.is_rx_assoc_norate++;
		ieee80211_new_state(ic, IEEE80211_S_SCAN, 0);
		return;
	}

	ni->ni_capinfo = capinfo;
	ni->ni_associd = associd;
	if (wme != NULL && ieee80211_parse_wmeparams(ic, wme, wh) >= 0) {
		ni->ni_flags |= IEEE80211_NODE_QOS;
		ieee80211_wme_updateparams(ic);
	} else {
		ni->ni_flags &= ~IEEE80211_NODE_QOS;
	}

	/*
	 * Configure state now that we are associated.
	 *
	 * XXX may need different/additional driver callbacks?
	 */
	if (ic->ic_curmode == IEEE80211_MODE_11A ||
	    (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE)) {
		ic->ic_flags |= IEEE80211_F_SHPREAMBLE;
		ic->ic_flags &= ~IEEE80211_F_USEBARKER;
	} else {
		ic->ic_flags &= ~IEEE80211_F_SHPREAMBLE;
		ic->ic_flags |= IEEE80211_F_USEBARKER;
	}
	ieee80211_set_shortslottime(ic,
	    ic->ic_curmode == IEEE80211_MODE_11A ||
	    (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME));

	/*
	 * Honor ERP protection.
	 *
	 * NB: ni_erp should zero for non-11g operation.
	 * XXX check ic_curmode anyway?
	 */
	if (ic->ic_curmode == IEEE80211_MODE_11G &&
	    (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) {
		ic->ic_flags |= IEEE80211_F_USEPROT;
	} else {
		ic->ic_flags &= ~IEEE80211_F_USEPROT;
	}

	IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC,
	    "[%s] %sassoc success: %s preamble, %s slot time%s%s\n",
	    ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2),
	    ISREASSOC(subtype) ? "re" : "",
	    ic->ic_flags&IEEE80211_F_SHPREAMBLE ? "short" : "long",
	    ic->ic_flags&IEEE80211_F_SHSLOT ? "short" : "long",
	    ic->ic_flags&IEEE80211_F_USEPROT ? ", protection" : "",
	    ni->ni_flags & IEEE80211_NODE_QOS ? ", QoS" : ""
	);

	ieee80211_new_state(ic, IEEE80211_S_RUN, subtype);
}

static void
ieee80211_recv_mgmt_deauth(struct ieee80211com *ic, struct mbuf *m0,
    struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp)
{
	struct ieee80211_frame *wh;
	u_int8_t *frm, *efrm;
	IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
	u_int16_t reason;

	wh = mtod(m0, struct ieee80211_frame *);
	frm = (u_int8_t *)(wh + 1);
	efrm = mtod(m0, u_int8_t *) + m0->m_len;

	if (ic->ic_state == IEEE80211_S_SCAN) {
		ic->ic_stats.is_rx_mgtdiscard++;
		return;
	}

	/*
	 * deauth frame format
	 *	[2] reason
	 */
	IEEE80211_VERIFY_LENGTH(efrm - frm, 2);
	reason = le16toh(*(u_int16_t *)frm);
	__USE(reason);
	ic->ic_stats.is_rx_deauth++;
	IEEE80211_NODE_STAT(ni, rx_deauth);

	if (!IEEE80211_ADDR_EQ(wh->i_addr1, ic->ic_myaddr)) {
		/* Not intended for this station. */
		ic->ic_stats.is_rx_mgtdiscard++;
		return;
	}

	IEEE80211_DPRINTF(ic, IEEE80211_MSG_AUTH,
	    "[%s] recv deauthenticate (reason %d)\n",
	    ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr), reason);

	switch (ic->ic_opmode) {
	case IEEE80211_M_STA:
		ieee80211_new_state(ic, IEEE80211_S_AUTH,
		    wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK);
		break;
	case IEEE80211_M_HOSTAP:
#ifndef IEEE80211_NO_HOSTAP
		if (ni != ic->ic_bss)
			ieee80211_node_leave(ic, ni);
#endif
		break;
	default:
		ic->ic_stats.is_rx_mgtdiscard++;
		break;
	}
}

static void
ieee80211_recv_mgmt_disassoc(struct ieee80211com *ic, struct mbuf *m0,
    struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp)
{
	struct ieee80211_frame *wh;
	u_int8_t *frm, *efrm;
	IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);
	u_int16_t reason;

	wh = mtod(m0, struct ieee80211_frame *);
	frm = (u_int8_t *)(wh + 1);
	efrm = mtod(m0, u_int8_t *) + m0->m_len;

	if (ic->ic_state != IEEE80211_S_RUN &&
	    ic->ic_state != IEEE80211_S_ASSOC &&
	    ic->ic_state != IEEE80211_S_AUTH) {
		ic->ic_stats.is_rx_mgtdiscard++;
		return;
	}

	/*
	 * disassoc frame format
	 *	[2] reason
	 */
	IEEE80211_VERIFY_LENGTH(efrm - frm, 2);
	reason = le16toh(*(u_int16_t *)frm);
	__USE(reason);
	ic->ic_stats.is_rx_disassoc++;
	IEEE80211_NODE_STAT(ni, rx_disassoc);

	if (!IEEE80211_ADDR_EQ(wh->i_addr1, ic->ic_myaddr)) {
		/* Not intended for this station. */
		ic->ic_stats.is_rx_mgtdiscard++;
		return;
	}

	IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC,
	    "[%s] recv disassociate (reason %d)\n",
	    ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr), reason);

	switch (ic->ic_opmode) {
	case IEEE80211_M_STA:
		ieee80211_new_state(ic, IEEE80211_S_ASSOC,
		    wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK);
		break;
	case IEEE80211_M_HOSTAP:
#ifndef IEEE80211_NO_HOSTAP
		if (ni != ic->ic_bss)
			ieee80211_node_leave(ic, ni);
#endif
		break;
	default:
		ic->ic_stats.is_rx_mgtdiscard++;
		break;
	}
}

#undef ISREASSOC
#undef IEEE80211_VERIFY_LENGTH
#undef IEEE80211_VERIFY_ELEMENT

/* -------------------------------------------------------------------------- */

void
ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0,
    struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp)
{
	struct ieee80211_frame *wh;

	wh = mtod(m0, struct ieee80211_frame *);

	switch (subtype) {
	case IEEE80211_FC0_SUBTYPE_PROBE_RESP:
	case IEEE80211_FC0_SUBTYPE_BEACON:
		ieee80211_recv_mgmt_beacon(ic, m0, ni, subtype, rssi, rstamp);
		return;

	case IEEE80211_FC0_SUBTYPE_PROBE_REQ:
		ieee80211_recv_mgmt_probe_req(ic, m0, ni, subtype, rssi, rstamp);
		return;

	case IEEE80211_FC0_SUBTYPE_AUTH:
		ieee80211_recv_mgmt_auth(ic, m0, ni, subtype, rssi, rstamp);
		return;

	case IEEE80211_FC0_SUBTYPE_ASSOC_REQ:
	case IEEE80211_FC0_SUBTYPE_REASSOC_REQ:
		ieee80211_recv_mgmt_assoc_req(ic, m0, ni, subtype, rssi, rstamp);
		return;

	case IEEE80211_FC0_SUBTYPE_ASSOC_RESP:
	case IEEE80211_FC0_SUBTYPE_REASSOC_RESP:
		ieee80211_recv_mgmt_assoc_resp(ic, m0, ni, subtype, rssi, rstamp);
		return;

	case IEEE80211_FC0_SUBTYPE_DEAUTH:
		ieee80211_recv_mgmt_deauth(ic, m0, ni, subtype, rssi, rstamp);
		return;

	case IEEE80211_FC0_SUBTYPE_DISASSOC:
		ieee80211_recv_mgmt_disassoc(ic, m0, ni, subtype, rssi, rstamp);
		return;

	default:
		IEEE80211_DISCARD(ic, IEEE80211_MSG_ANY,
		     wh, "mgt", "subtype 0x%x not handled", subtype);
		ic->ic_stats.is_rx_badsubtype++;
		break;
	}
}

#ifndef IEEE80211_NO_HOSTAP
/*
 * Handle station power-save state change.
 */
static void
ieee80211_node_pwrsave(struct ieee80211_node *ni, int enable)
{
	struct ieee80211com *ic = ni->ni_ic;
	struct mbuf *m;
	IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);

	if (enable) {
		if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) == 0)
			ic->ic_ps_sta++;
		ni->ni_flags |= IEEE80211_NODE_PWR_MGT;
		IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER,
		    "[%s] power save mode on, %u sta's in ps mode\n",
		    ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr),
		    ic->ic_ps_sta);
		return;
	}

	if (ni->ni_flags & IEEE80211_NODE_PWR_MGT)
		ic->ic_ps_sta--;
	ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT;
	IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER,
	    "[%s] power save mode off, %u sta's in ps mode\n",
	    ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr), ic->ic_ps_sta);
	/* XXX if no stations in ps mode, flush mc frames */

	/*
	 * Flush queued unicast frames.
	 */
	if (IEEE80211_NODE_SAVEQ_QLEN(ni) == 0) {
		if (ic->ic_set_tim != NULL)
			ic->ic_set_tim(ni, 0);		/* just in case */
		return;
	}

	IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER,
	    "[%s] flush ps queue, %u packets queued\n",
	    ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr),
	    IEEE80211_NODE_SAVEQ_QLEN(ni));

	for (;;) {
		int qlen;

		IEEE80211_NODE_SAVEQ_DEQUEUE(ni, m, qlen);
		if (m == NULL)
			break;

		/*
		 * If this is the last packet, turn off the TIM bit.
		 * If there are more packets, set the more packets bit
		 * in the mbuf so ieee80211_encap will mark the 802.11
		 * head to indicate more data frames will follow.
		 */
		if (qlen != 0)
			m->m_flags |= M_MORE_DATA;

		/* XXX need different driver interface */
		/* XXX bypasses q max */
		IF_ENQUEUE(&ic->ic_ifp->if_snd, m);
	}

	if (ic->ic_set_tim != NULL)
		ic->ic_set_tim(ni, 0);
}

/*
 * Process a received ps-poll frame.
 */
static void
ieee80211_recv_pspoll(struct ieee80211com *ic, struct ieee80211_node *ni,
    struct mbuf *m0)
{
	struct ieee80211_frame_min *wh;
	struct mbuf *m;
	u_int16_t aid;
	int qlen;
	IEEE80211_DEBUGVAR(char ebuf[3 * ETHER_ADDR_LEN]);

	wh = mtod(m0, struct ieee80211_frame_min *);
	if (ni->ni_associd == 0) {
		IEEE80211_DISCARD(ic, IEEE80211_MSG_POWER | IEEE80211_MSG_DEBUG,
		    (struct ieee80211_frame *)wh, "ps-poll",
		    "%s", "unassociated station");
		ic->ic_stats.is_ps_unassoc++;
		IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DEAUTH,
			IEEE80211_REASON_NOT_ASSOCED);
		return;
	}

	aid = le16toh(*(u_int16_t *)wh->i_dur);
	if (aid != ni->ni_associd) {
		IEEE80211_DISCARD(ic, IEEE80211_MSG_POWER | IEEE80211_MSG_DEBUG,
		    (struct ieee80211_frame *)wh, "ps-poll",
		    "aid mismatch: sta aid 0x%x poll aid 0x%x",
		    ni->ni_associd, aid);
		ic->ic_stats.is_ps_badaid++;
		IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DEAUTH,
			IEEE80211_REASON_NOT_ASSOCED);
		return;
	}

	/* Okay, take the first queued packet and put it out... */
	IEEE80211_NODE_SAVEQ_DEQUEUE(ni, m, qlen);
	if (m == NULL) {
		IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER,
		    "[%s] recv ps-poll, but queue empty\n",
		    ether_snprintf(ebuf, sizeof(ebuf), wh->i_addr2));
		ieee80211_send_nulldata(ieee80211_ref_node(ni));
		ic->ic_stats.is_ps_qempty++;	/* XXX node stat */
		if (ic->ic_set_tim != NULL)
			ic->ic_set_tim(ni, 0);	/* just in case */
		return;
	}

	/*
	 * If there are more packets, set the more packets bit
	 * in the packet dispatched to the station; otherwise
	 * turn off the TIM bit.
	 */
	if (qlen != 0) {
		IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER,
		    "[%s] recv ps-poll, send packet, %u still queued\n",
		    ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr), qlen);
		m->m_flags |= M_MORE_DATA;
	} else {
		IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER,
		    "[%s] recv ps-poll, send packet, queue empty\n",
		    ether_snprintf(ebuf, sizeof(ebuf), ni->ni_macaddr));
		if (ic->ic_set_tim != NULL)
			ic->ic_set_tim(ni, 0);
	}

	m->m_flags |= M_PWR_SAV;		/* bypass PS handling */
	IF_ENQUEUE(&ic->ic_ifp->if_snd, m);
}
#endif /* !IEEE80211_NO_HOSTAP */

#ifdef IEEE80211_DEBUG
/*
 * Debugging support.
 */

/*
 * Return the bssid of a frame.
 */
static const u_int8_t *
ieee80211_getbssid(struct ieee80211com *ic, const struct ieee80211_frame *wh)
{
	if (ic->ic_opmode == IEEE80211_M_STA)
		return wh->i_addr2;
	if ((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) != IEEE80211_FC1_DIR_NODS)
		return wh->i_addr1;
	if ((wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == IEEE80211_FC0_SUBTYPE_PS_POLL)
		return wh->i_addr1;
	return wh->i_addr3;
}

void
ieee80211_note(struct ieee80211com *ic, const char *fmt, ...)
{
	char buf[128];		/* XXX */
	va_list ap;

	va_start(ap, fmt);
	vsnprintf(buf, sizeof(buf), fmt, ap);
	va_end(ap);

	if_printf(ic->ic_ifp, "%s", buf);	/* NB: no \n */
}

void
ieee80211_note_mac(struct ieee80211com *ic,
	const u_int8_t mac[IEEE80211_ADDR_LEN],
	const char *fmt, ...)
{
	char buf[128];		/* XXX */
	va_list ap;
	char ebuf[3 * ETHER_ADDR_LEN];

	va_start(ap, fmt);
	vsnprintf(buf, sizeof(buf), fmt, ap);
	va_end(ap);
	if_printf(ic->ic_ifp, "[%s] %s\n", ether_snprintf(ebuf, sizeof(ebuf),
	    mac), buf);
}

static void
ieee80211_discard_frame(struct ieee80211com *ic,
	const struct ieee80211_frame *wh,
	const char *type, const char *fmt, ...)
{
	va_list ap;
	char ebuf[3 * ETHER_ADDR_LEN];

	printf("[%s:%s] discard ", ic->ic_ifp->if_xname,
		ether_snprintf(ebuf, sizeof(ebuf), ieee80211_getbssid(ic, wh)));
	if (type != NULL)
		printf("%s frame, ", type);
	else
		printf("frame, ");
	va_start(ap, fmt);
	vprintf(fmt, ap);
	va_end(ap);
	printf("\n");
}

static void
ieee80211_discard_ie(struct ieee80211com *ic,
	const struct ieee80211_frame *wh,
	const char *type, const char *fmt, ...)
{
	va_list ap;
	char ebuf[3 * ETHER_ADDR_LEN];

	printf("[%s:%s] discard ", ic->ic_ifp->if_xname,
	    ether_snprintf(ebuf, sizeof(ebuf), ieee80211_getbssid(ic, wh)));
	if (type != NULL)
		printf("%s information element, ", type);
	else
		printf("information element, ");
	va_start(ap, fmt);
	vprintf(fmt, ap);
	va_end(ap);
	printf("\n");
}

static void
ieee80211_discard_mac(struct ieee80211com *ic,
	const u_int8_t mac[IEEE80211_ADDR_LEN],
	const char *type, const char *fmt, ...)
{
	va_list ap;
	char ebuf[3 * ETHER_ADDR_LEN];

	printf("[%s:%s] discard ", ic->ic_ifp->if_xname,
	    ether_snprintf(ebuf, sizeof(ebuf), mac));
	if (type != NULL)
		printf("%s frame, ", type);
	else
		printf("frame, ");
	va_start(ap, fmt);
	vprintf(fmt, ap);
	va_end(ap);
	printf("\n");
}
#endif /* IEEE80211_DEBUG */