/* input.c: 802.11 mgmt packet output processing.
 *
 * Copyright (C) 2003 David S. Miller (davem@redhat.com)
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/types.h>
#include <linux/init.h>

#include "if_80211.h"
#include "p80211_impl.h"

typedef int (*p80211_send_t)(struct wireless_info *, u16);

static int p80211_send_invalid(struct wireless_info *, u16);

/* management */
static int p80211_send_assoc_req(struct wireless_info *, u16);
static int p80211_send_assoc_resp(struct wireless_info *, u16);
static int p80211_send_reassoc_req(struct wireless_info *, u16);
static int p80211_send_reassoc_resp(struct wireless_info *, u16);
static int p80211_send_probe_req(struct wireless_info *, u16);
static int p80211_send_probe_resp(struct wireless_info *, u16);
static int p80211_send_beacon(struct wireless_info *, u16);
static int p80211_send_atim(struct wireless_info *, u16);
static int p80211_send_disassoc(struct wireless_info *, u16);
static int p80211_send_auth(struct wireless_info *, u16);
static int p80211_send_deauth(struct wireless_info *, u16);

/* control */
static int p80211_send_pspoll(struct wireless_info *, u16);
static int p80211_send_rts(struct wireless_info *, u16);
static int p80211_send_cts(struct wireless_info *, u16);
static int p80211_send_ack(struct wireless_info *, u16);
static int p80211_send_cfend(struct wireless_info *, u16);
static int p80211_send_cfendack(struct wireless_info *, u16);

/* data */
static int p80211_send_data(struct wireless_info *, u16);
static int p80211_send_data_cfack(struct wireless_info *, u16);
static int p80211_send_data_cfpoll(struct wireless_info *, u16);
static int p80211_send_data_cfackpoll(struct wireless_info *, u16);
static int p80211_send_nullfunc(struct wireless_info *, u16);
static int p80211_send_cfack(struct wireless_info *, u16);
static int p80211_send_cfpoll(struct wireless_info *, u16);
static int p80211_send_cfackpoll(struct wireless_info *, u16);

static p80211_send_t output_methods[0x4][0x10] = {
/* management */
[IEEE802_11_FTYPE_MGMT >> IEEE802_11_FTYPE_SHIFT] = {
	[IEEE802_11_STYPE_ASSOC_REQ >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_assoc_req,
	[IEEE802_11_STYPE_ASSOC_RESP >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_assoc_resp,
	[IEEE802_11_STYPE_REASSOC_REQ >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_reassoc_req,
	[IEEE802_11_STYPE_REASSOC_RESP >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_reassoc_resp,
	[IEEE802_11_STYPE_PROBE_REQ >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_probe_req,
	[IEEE802_11_STYPE_PROBE_RESP >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_probe_resp,
	[0x6] = p80211_send_invalid,
	[0x7] = p80211_send_invalid,
	[IEEE802_11_STYPE_BEACON >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_beacon,
	[IEEE802_11_STYPE_ATIM >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_atim,
	[IEEE802_11_STYPE_DISASSOC >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_disassoc,
	[IEEE802_11_STYPE_AUTH >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_auth,
	[IEEE802_11_STYPE_DEAUTH >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_deauth,
	[0xd] = p80211_send_invalid,
	[0xe] = p80211_send_invalid,
	[0xf] = p80211_send_invalid,
},
/* control */
[IEEE802_11_FTYPE_CTL >> IEEE802_11_FTYPE_SHIFT] = {
	[0x0] = p80211_send_invalid,
	[0x1] = p80211_send_invalid,
	[0x2] = p80211_send_invalid,
	[0x3] = p80211_send_invalid,
	[0x4] = p80211_send_invalid,
	[0x5] = p80211_send_invalid,
	[0x6] = p80211_send_invalid,
	[0x7] = p80211_send_invalid,
	[0x8] = p80211_send_invalid,
	[0x9] = p80211_send_invalid,
	[IEEE802_11_STYPE_PSPOLL >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_pspoll,
	[IEEE802_11_STYPE_RTS >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_rts,
	[IEEE802_11_STYPE_CTS >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_cts,
	[IEEE802_11_STYPE_ACK >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_ack,
	[IEEE802_11_STYPE_CFEND >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_cfend,
	[IEEE802_11_STYPE_CFENDACK >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_cfendack,
},
/* data */
[IEEE802_11_FTYPE_DATA >> IEEE802_11_FTYPE_SHIFT] = {
	[IEEE802_11_STYPE_DATA >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_data,
	[IEEE802_11_STYPE_DATA_CFACK >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_data_cfack,
	[IEEE802_11_STYPE_DATA_CFPOLL >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_data_cfpoll,
	[IEEE802_11_STYPE_DATA_CFACKPOLL >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_data_cfackpoll,
	[IEEE802_11_STYPE_NULLFUNC >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_nullfunc,
	[IEEE802_11_STYPE_CFACK >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_cfack,
	[IEEE802_11_STYPE_CFPOLL >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_cfpoll,
	[IEEE802_11_STYPE_CFACKPOLL >> IEEE802_11_STYPE_SHIFT] =
		p80211_send_cfackpoll,
	[0x8] = p80211_send_invalid,
	[0x9] = p80211_send_invalid,
	[0xa] = p80211_send_invalid,
	[0xb] = p80211_send_invalid,
	[0xc] = p80211_send_invalid,
	[0xd] = p80211_send_invalid,
	[0xe] = p80211_send_invalid,
	[0xf] = p80211_send_invalid,
},
/* invalid ftype */
[IEEE802_11_FTYPE_INVALID >> 2] = {
	[0x0] = p80211_send_invalid,
	[0x1] = p80211_send_invalid,
	[0x2] = p80211_send_invalid,
	[0x3] = p80211_send_invalid,
	[0x4] = p80211_send_invalid,
	[0x5] = p80211_send_invalid,
	[0x6] = p80211_send_invalid,
	[0x7] = p80211_send_invalid,
	[0x8] = p80211_send_invalid,
	[0x9] = p80211_send_invalid,
	[0xa] = p80211_send_invalid,
	[0xb] = p80211_send_invalid,
	[0xc] = p80211_send_invalid,
	[0xd] = p80211_send_invalid,
	[0xe] = p80211_send_invalid,
	[0xf] = p80211_send_invalid,
},
};

int p80211_output(struct wireless_info *wp, u16 ctl)
{
	int type, subtype;

	type = (ctl & IEEE802_11_FCTL_FTYPE) >> IEEE802_11_FTYPE_SHIFT;
	subtype = (ctl & IEEE802_11_FCTL_STYPE) >> IEEE802_11_STYPE_SHIFT;

	return output_methods[type][subtype](wp, ctl);
}

/* Packet building helpers. */
static void mgmt_build_and_send(struct wireless_info *wp,
				struct wireless_node *np,
				struct sk_buff *skb,
				u16 stype)
{
	struct ieee802_11_hdr *p;

	p = (struct ieee802_11_hdr *) skb_push(skb, sizeof(*p));

	p->frame_ctl = cpu_to_le16(IEEE802_11_FCTL_VERS_0 |
				   IEEE802_11_FTYPE_MGMT |
				   stype |
				   IEEE802_11_FCTL_NODS);
	p->duration_id = 0; /* Set by driver, if necessary */

	memcpy(p->addr1, np->mac, ETH_ALEN);
	memcpy(p->addr2, wp->dev->dev_addr, ETH_ALEN);
	memcpy(p->addr3, np->bssid, ETH_ALEN);

	p->seq_ctl = 0; /* Set by driver, if necessary */

	dev_queue_xmit(skb);
}

static void put_essid(struct sk_buff *skb, struct wireless_info *wp)
{
	u8 *p;

	if (wp->ssid_len > WIRELESS_SSID_MAX)
		BUG();

	p = (u8 *) skb_put(skb, IEEE802_11_TLV_SIZE + wp->ssid_len);
	*p++ = IEEE802_11_TLV_TYPE_SSID;
	*p++ = wp->ssid_len;
	if (wp->ssid_len) {
		memcpy(p, wp->ssid, wp->ssid_len);
		p += wp->ssid_len;
	}
	if (p != skb->tail)
		BUG();
}

static int size_rates(struct wireless_rtdesc *rd, int *base_num_p, int *ext_num_p)
{
	int base_num, ext_num, pkt_len;

	base_num = rd->num;
	ext_num = 0;
	if (!base_num || (base_num > WIRELESS_MAX_RATES))
		BUG();
	if (base_num > IEEE802_11_MAX_RATES) {
		ext_num = base_num - IEEE802_11_MAX_RATES;
		base_num = IEEE802_11_MAX_RATES;
	}
	pkt_len = IEEE802_11_TLV_SIZE + base_num;
	if (ext_num)
		pkt_len += IEEE802_11_TLV_SIZE + ext_num;

	if (base_num_p)
		*base_num_p = base_num;
	if (ext_num_p)
		*ext_num_p = ext_num;

	return pkt_len;
}

static void put_rates(struct sk_buff *skb, struct wireless_rtdesc *rd)
{
	u8 *p;
	int base_num, ext_num, pkt_len;

	pkt_len = size_rates(rd, &base_num, &ext_num);

	p = (u8 *) skb_put(skb, pkt_len);
	*p++ = IEEE802_11_TLV_TYPE_RATES;
	*p++ = base_num;
	memcpy(p, rd->rates, base_num);
	p += base_num;

	if (ext_num) {
		*p++ = IEEE802_11_TLV_TYPE_XRATES;
		*p++ = ext_num;
		memcpy(p, rd->rates + base_num, ext_num);
		p += ext_num;
	}

	if (p != skb->tail)
		BUG();
}

static struct sk_buff *mgmt_alloc_skb(int len)
{
	struct sk_buff *skb = alloc_skb(len, GFP_ATOMIC);

	if (skb)
		skb_reserve(skb, sizeof(struct ieee802_11_hdr));

	return skb;
}

int p80211_send_invalid(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

/* management */
int p80211_send_assoc_req(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_assoc_resp(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_reassoc_req(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_reassoc_resp(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_probe_req(struct wireless_info *wp, u16 ctl)
{
	struct ieee802_11_hdr *p;
	struct wireless_rtdesc *rd;
	struct wireless_node *np;
	struct sk_buff *skb;
	int len;

	/* XXX Verify phy_cur is sane... */
	rd = &wp->rate_caps[wp->phy_cur];
	np = &wp->self;

	len = sizeof(*p) +
		(IEEE802_11_TLV_SIZE + wp->ssid_len) +
		(IEEE802_11_TLV_SIZE + size_rates(rd, NULL, NULL));
	skb = mgmt_alloc_skb(len);
	if (skb) {
		put_essid(skb, wp);
		put_rates(skb, rd);

		mgmt_build_and_send(wp, np, skb, IEEE802_11_STYPE_PROBE_REQ);
		return 0;
	}

	return -ENOMEM;
}

int p80211_send_probe_resp(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_beacon(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_atim(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_disassoc(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_auth(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_deauth(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

/* control */
int p80211_send_pspoll(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_rts(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_cts(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_ack(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_cfend(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_cfendack(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

/* data */
int p80211_send_data(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_data_cfack(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_data_cfpoll(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_data_cfackpoll(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_nullfunc(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_cfack(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_cfpoll(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}

int p80211_send_cfackpoll(struct wireless_info *wp, u16 ctl)
{
	return -ENOSYS;
}
