/* p80211.c: 802.11 type handling.
 *
 * Copyright (C) 2003 David S. Miller (davem@redhat.com)
 * Copyright (C) 2003 Arlando Carvalho de Melo (acme@conectiva.com.br)
 */

#ifndef EXPORT_SYMTAB
#define	EXPORT_SYMTAB
#endif

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/init.h>
#include <net/neighbour.h>
#include <net/arp.h>

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

MODULE_AUTHOR("David S. Miller (davem@redhat.com) and Arnaldo Carvalho de Melo (acme@conectiva.com.br)");
MODULE_DESCRIPTION("IEEE 802.11 protocol module");
MODULE_LICENSE("GPL");

/* Actually, 802.11 + LLC header.  */
struct p80211_data_header {
	/* 802.11 */
	struct ieee802_11_hdr	hdr;
	/* LLC */
	struct ieee802_11_snap	snap;
};

/* Create the 802.11 MAC header for an arbitrary protocol layer.
 *
 * saddr == NULL means use device source address
 * daddr == NULL means leave destination address (eg unresolved arp)
 */

static int p80211_header(struct sk_buff *skb, struct net_device *dev,
			 unsigned short type, void *daddr, void *saddr,
			 unsigned int len)
{
	struct p80211_data_header *p = (struct p80211_data_header *) skb_push(skb, sizeof(*p));
	void *s_ptr, *d_ptr, *bss_ptr;
	u16 fctrl;
	int ret, mode;

	/* We fill in shost/dhost, wireless driver fills in self-node address.
	 * This layout works for station mode only.
	 */
#if 1 /* XXX get this from device's wireless info */
	mode = WIRELESS_MODE_STATION;
#endif
	fctrl = cpu_to_le16(IEEE802_11_FCTL_VERS_0 |
			    IEEE802_11_FTYPE_DATA |
			    IEEE802_11_STYPE_DATA);
	switch (mode) {
	case WIRELESS_MODE_STATION:
		fctrl |= cpu_to_le16(IEEE802_11_FCTL_TODS);
		s_ptr = p->hdr.addr3;
		d_ptr = p->hdr.addr1;
		bss_ptr = p->hdr.addr2;
		break;

	case WIRELESS_MODE_AP:
		fctrl |= cpu_to_le16(IEEE802_11_FCTL_FROMDS);
		s_ptr = p->hdr.addr2;
		d_ptr = p->hdr.addr3;
		bss_ptr = p->hdr.addr1;
		break;

	case WIRELESS_MODE_IBSS:
		fctrl |= cpu_to_le16(0);
		s_ptr = p->hdr.addr2;
		d_ptr = p->hdr.addr1;
		bss_ptr = p->hdr.addr3;
		break;

	default:
		/* Unexpected... */
		return -1;
	};
	p->hdr.frame_ctl = fctrl;

	/* Filled in by driver, if necessary. */
	p->hdr.duration_id = 0;

	if (daddr) {
		memcpy(d_ptr, daddr, ETH_ALEN);
		ret = dev->hard_header_len;
	} else
		ret = -dev->hard_header_len;

	/* XXX Get this from wireless_info */
	memset(bss_ptr, 0, ETH_ALEN);

	memcpy(s_ptr, (saddr ? saddr : dev->dev_addr), ETH_ALEN);

	/* Filled in by driver. */
	p->hdr.seq_ctl = 0;

	p->snap.dsap = 0xaa;
	p->snap.ssap = 0xaa;
	p->snap.control = 0x03;
	p->snap.oui[0] = 0x00;
	p->snap.oui[1] = 0x00;
	p->snap.oui[2] = 0x00;

	if (type != ETH_P_802_3)
		p->snap.ethertype = htons(type);
	else
		p->snap.ethertype = htons(len);

	return ret;
}

/* Rebuild the MAC header.  This is invoked after ARP completes.  */
static int p80211_rebuild_header(struct sk_buff *skb)
{
	struct p80211_data_header *p = (struct p80211_data_header *) skb->data;
	struct net_device *dev = skb->dev;

	switch (p->snap.ethertype) {
#ifdef CONFIG_INET
	case __constant_htons(ETH_P_IP):
		return arp_find(p->hdr.addr3, skb);
#endif
	default:
		printk(KERN_DEBUG
		       "%s: unable to resolve type %X addresses.\n", 
		       dev->name, (int) p->snap.ethertype);
	};

	return 0;
}

/* Determine the RX packet's protocol ID.  */
unsigned short p80211_type_trans(struct sk_buff *skb, struct net_device *dev)
{
	struct p80211_data_header *p;
	u8 *daddr;

	skb->mac.raw = skb->data;
	p = (struct p80211_data_header *) skb->mac.raw;

	/* First, check the type, if it isn't data we want to
	 * keep the 802.11 header in place.
	 */
	if (((p->hdr.frame_ctl & cpu_to_le16(IEEE802_11_FCTL_FTYPE))
	     != cpu_to_le16(IEEE802_11_FTYPE_DATA)) ||
	    ((p->hdr.frame_ctl & cpu_to_le16(IEEE802_11_FCTL_STYPE))
	     != cpu_to_le16(IEEE802_11_STYPE_DATA))) {
		return htons(ETH_P_802_11);
	}

	skb_pull(skb, dev->hard_header_len);
	skb_trim(skb, skb->len - 4); /* Zap trailing CRC */

	switch (p->hdr.frame_ctl & cpu_to_le16(IEEE802_11_FCTL_DIRMASK)) {
	case cpu_to_le16(IEEE802_11_FCTL_NODS):
	case cpu_to_le16(IEEE802_11_FCTL_TODS):
		daddr = p->hdr.addr1;
		break;

	case cpu_to_le16(IEEE802_11_FCTL_FROMDS):
	case cpu_to_le16(IEEE802_11_FCTL_DSTODS):
	default:
		daddr = p->hdr.addr3;
		break;
	};

	if (daddr[0] & 0x1) {
		if (memcmp(daddr, dev->broadcast, ETH_ALEN) == 0)
			skb->pkt_type = PACKET_BROADCAST;
		else
			skb->pkt_type = PACKET_MULTICAST;
	} else {
		if (memcmp(daddr, dev->dev_addr, ETH_ALEN))
			skb->pkt_type = PACKET_OTHERHOST;
	}
	if (p->snap.dsap == 0xaa &&
	    p->snap.ssap == 0xaa &&
	    p->snap.control == 0x03 &&
	    p->snap.oui[0] == 0 && p->snap.oui[1] == 0 && p->snap.oui[2] == 0) {
		if (ntohs(p->snap.ethertype) >= 1536)
			return p->snap.ethertype;
		if (*(unsigned short *)skb->data == 0xffff)
			return htons(ETH_P_802_3);

		/* XXX LLC-in-LLC, is it legal? -DaveM */
		goto out_llc;
	}

	/* Put the LLC header back. */
	skb_push(skb, sizeof(struct ieee802_11_snap));

out_llc:
	return htons(ETH_P_802_2);
}
EXPORT_SYMBOL(p80211_type_trans);

static int p80211_header_parse(struct sk_buff *skb, unsigned char *haddr)
{
	struct p80211_data_header *p = (struct p80211_data_header *) skb->mac.raw;
	u8 *saddr;

	switch (p->hdr.frame_ctl & cpu_to_le16(IEEE802_11_FCTL_DIRMASK)) {
	case cpu_to_le16(IEEE802_11_FCTL_NODS):
	case cpu_to_le16(IEEE802_11_FCTL_FROMDS):
		saddr = p->hdr.addr2;
		break;

	case cpu_to_le16(IEEE802_11_FCTL_TODS):
		saddr = p->hdr.addr3;
		break;

	case cpu_to_le16(IEEE802_11_FCTL_DSTODS):
	default: {
		struct ieee802_11_hdr4 *p4 = (struct ieee802_11_hdr4 *) p;
		saddr = p4->addr4;
		break;
	}
	};

	memcpy(haddr, saddr, ETH_ALEN);
	return ETH_ALEN;
}

extern void hh_data_is_too_small(void);

static int p80211_header_cache(struct neighbour *neigh, struct hh_cache *hh)
{
	unsigned short type = hh->hh_type;
	struct p80211_data_header *p =
		(struct p80211_data_header *) hh->hh_data;
	struct net_device *dev = neigh->dev;
	void *s_ptr, *d_ptr, *bss_ptr;
	u16 fctrl;
	int mode;

	if (sizeof(hh->hh_data) < sizeof(*p))
		hh_data_is_too_small();

	if (type == __constant_htons(ETH_P_802_3))
		return -1;

#if 1 /* XXX get this from device's wireless info */
	mode = WIRELESS_MODE_STATION;
#endif
	fctrl = cpu_to_le16(IEEE802_11_FCTL_VERS_0 |
			    IEEE802_11_FTYPE_DATA |
			    IEEE802_11_STYPE_DATA);
	switch (mode) {
	case WIRELESS_MODE_STATION:
		fctrl |= cpu_to_le16(IEEE802_11_FCTL_TODS);
		s_ptr = p->hdr.addr3;
		d_ptr = p->hdr.addr1;
		bss_ptr = p->hdr.addr2;
		break;

	case WIRELESS_MODE_AP:
		fctrl |= cpu_to_le16(IEEE802_11_FCTL_FROMDS);
		s_ptr = p->hdr.addr2;
		d_ptr = p->hdr.addr3;
		bss_ptr = p->hdr.addr1;
		break;

	case WIRELESS_MODE_IBSS:
		fctrl |= cpu_to_le16(0);
		s_ptr = p->hdr.addr2;
		d_ptr = p->hdr.addr1;
		bss_ptr = p->hdr.addr3;
		break;

	default:
		/* Unexpected... */
		return -1;
	};
	p->hdr.frame_ctl = fctrl;

	/* Filled in by driver, if necessary. */
	p->hdr.duration_id = 0;

	memcpy(d_ptr, neigh->ha, ETH_ALEN);

	/* XXX Get this from wireless_info */
	memset(bss_ptr, 0, ETH_ALEN);

	memcpy(s_ptr, dev->dev_addr, ETH_ALEN);

	/* Filled in by driver. */
	p->hdr.seq_ctl = 0;

	p->snap.dsap = 0xaa;
	p->snap.ssap = 0xaa;
	p->snap.control = 0x03;
	p->snap.oui[0] = 0x00;
	p->snap.oui[1] = 0x00;
	p->snap.oui[2] = 0x00;
	p->snap.ethertype = type;

	hh->hh_len = sizeof(*p);
	return 0;
}

static void p80211_header_cache_update(struct hh_cache *hh, struct net_device *dev, unsigned char *haddr)
{
	struct p80211_data_header *p = (struct p80211_data_header *) hh->hh_data;

	memcpy(p->hdr.addr3, haddr, dev->addr_len);
}

void p80211_setup(struct net_device *dev)
{
	ether_setup(dev);

	dev->hard_header	= p80211_header;
	dev->rebuild_header 	= p80211_rebuild_header;
	dev->hard_header_cache	= p80211_header_cache;
	dev->header_cache_update= p80211_header_cache_update;
	dev->hard_header_parse	= p80211_header_parse;
	dev->hard_header_len 	= sizeof(struct p80211_data_header);
}
EXPORT_SYMBOL(p80211_setup);

static int __init p80211_init(void)
{
	int err;

	printk(KERN_INFO "IEEE 802.11 protocol stack loading.\n");
	err = p80211_input_init();
	if (err) {
		printk(KERN_ERR "p80211_init: Failed to init input processing.\n");
		goto out;
	}

out:
	return err;
}

static void __exit p80211_cleanup(void)
{
	p80211_input_cleanup();
}

module_init(p80211_init);
module_exit(p80211_cleanup);
