root/sys/net80211/ieee80211_input.c
/*      $NetBSD: ieee80211_input.c,v 1.118 2025/10/18 07:35:33 mlelstv 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.118 2025/10/18 07:35:33 mlelstv 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", "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", "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", "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;
                }
        } 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) {
                if (!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;
                }
                wh = mtod(m, struct ieee80211_frame *);
                wh->i_fc[1] &= ~IEEE80211_FC1_WEP;
        }

        /* 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;
                }
        }

        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:
        if_statinc(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", "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:
        if_statinc(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:
        if_statinc(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. Also check that both
                 * fragments are plaintext or that both are encrypted.
                 */
                if (rxseq != last_rxseq+1 ||
                    !IEEE80211_ADDR_EQ(wh->i_addr1, lwh->i_addr1) ||
                    !IEEE80211_ADDR_EQ(wh->i_addr2, lwh->i_addr2) ||
                    ((wh->i_fc[1] ^ lwh->i_fc[1]) & IEEE80211_FC1_WEP)) {
                        /*
                         * 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)
                                if_statinc(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) {
                                if_statinc(ifp, if_oerrors);
                                m_freem(m);
                                m = NULL;
                        }
                        if_statadd(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 */