/*
 * Original code based Host AP (software wireless LAN access point) driver 
 * for Intersil Prism2/2.5/3 - hostap.o module, common routines
 *
 * Copyright (c) 2001-2002, SSH Communications Security Corp and Jouni Malinen
 * <jkmaline@cc.hut.fi>
 * Copyright (c) 2002-2003, Jouni Malinen <jkmaline@cc.hut.fi>
 * Copyright (c) 2004, Intel Corporation
 * Association logic, scanning WX and logics, some management frame handling
 * ans misc modifications for the rtl8180-sa2400 driver added by Andrea Merello 
 * <andreamrl@tiscali.it>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation. See README and COPYING for
 * more details.
 */

#include <linux/compiler.h>
#include <linux/config.h>
#include <linux/errno.h>
#include <linux/if_arp.h>
#include <linux/in6.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/pci.h>
#include <linux/proc_fs.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/tcp.h>
#include <linux/types.h>
#include <linux/version.h>
#include <linux/wireless.h>
#include <linux/etherdevice.h>
#include <asm/uaccess.h>
#include <linux/ctype.h>

#include "ieee80211.h"

#define BEACON_SOURCE_OFF 10
#define BEACON_BSSID_OFF 16
#define BEACON_TAG_OFF 36
#define BEACON_CAPA_OFF 34
#define BEACON_INTERVAL_OFF 32

#define IEEE80211_DEBUG_MASTER_MODE

static inline u16 auth_parse(struct sk_buff *skb)
{
	struct ieee80211_authentication *a;
	if(skb->len <  sizeof(struct ieee80211_authentication)){ 
		IEEE80211DMESG("invalid len in auth resp:%d should be %d",skb->len,
			sizeof(struct ieee80211_authentication));
		return 0xcafe;
	}
	a = (struct ieee80211_authentication*) skb->data;
	return cpu_to_le32(a->status);
}


int auth_rq_parse(struct sk_buff *skb,u8* dest)
{
struct ieee80211_authentication *a;
	if(skb->len <  sizeof(struct ieee80211_authentication)){ 
		IEEE80211DMESG("invalid len in auth request:%d should be %d",skb->len,
			sizeof(struct ieee80211_authentication));	
		return -1;
	}
	a = (struct ieee80211_authentication*) skb->data;
	
	memcpy(dest,a->header.addr2,ETH_ALEN);
	
	if (le16_to_cpu(a->algorithm) != WLAN_AUTH_OPEN) 
		return  WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG;
	
	return WLAN_STATUS_SUCCESS;
}

static short probe_rq_parse(struct ieee80211_device *ieee, struct sk_buff *skb, u8 *src)
{
	u8 *tag;
	u8 *skbend;
	u8 *ssid=NULL;
	u8 ssidlen=0;
	struct ieee80211_header_data *header =
		(struct ieee80211_header_data *) skb->data;
	if (skb->len < sizeof (struct ieee80211_header_data)) 
		return -1; /* corrupted */
	memcpy(src,header->addr2, ETH_ALEN);
	skbend = (u8*)skb->data + skb->len;
	tag = skb->data + sizeof (struct ieee80211_header_data);
	while (tag+1 < skbend){
		if (*tag == 0){ 
			ssid = tag+2;
			ssidlen = *(tag+1);
			break;
		}
		tag++; /* point to the len field */
		tag = tag + *(tag); /* point to the last data byte of the tag */
		tag++; /* point to the next tag */
	}
	
	//IEEE80211DMESG("Card MAC address is "MACSTR, MAC2STR(src));
	if(ssidlen == 0) return 1;
	
	if(!ssid) return 1; /* ssid not found in tagged param */
	return (!strncmp(ssid, ieee->master_essid, ssidlen));
		
}

int assoc_rq_parse(struct sk_buff *skb,u8* dest)
{
	struct ieee80211_association_request_hdr *a;
	
	if(skb->len <  sizeof(struct ieee80211_association_request_hdr)){ 
		IEEE80211DMESG("invalid len in auth request:%d should be at least %d",skb->len,
			sizeof(struct ieee80211_association_response_hdr));
		return -1;
	}
	
	a = (struct ieee80211_association_request_hdr*) skb->data;
	memcpy(dest,a->addr2,ETH_ALEN);
	
	return 0;
}

static inline u16 assoc_parse(struct sk_buff *skb)
{
	struct ieee80211_association_response_hdr *a;
	if(skb->len <  sizeof(struct ieee80211_association_response_hdr)){ 
		IEEE80211DMESG("invalid len in auth resp:%d should be at least %d",skb->len,
			sizeof(struct ieee80211_association_response_hdr));
		return 0xcafe;
	}
	a = (struct ieee80211_association_response_hdr*) skb->data;
	return cpu_to_le32(a->status);
}

static inline int
ieee80211_rx_beacon(struct ieee80211_device *ieee ,struct sk_buff *skb ,struct ieee80211_rx_stats *rx_stats)
{
	
	
	struct ieee80211_beacon *btmp;
	struct ieee80211_beacon *beacon;
	//struct ieee80211_beacon *beacon2;
	struct list_head *b,*next,*prev,*delprev;
	//IEEE80211DMESG("beacon");
	u8 *ptag;
	u8 unk_len;
	u8 *skbend;
	int i;
	
	skbend = skb->data+skb->len;
	delprev=NULL;
	
	beacon = kmalloc(sizeof(struct ieee80211_beacon),GFP_ATOMIC);
	
	beacon->last_scanned=rx_stats->mac_time;
	beacon->rssi = rx_stats->signal;
	beacon->quality = rx_stats->noise; //FIXME: we use noise to carry quality info
	
	if(skb->len <BEACON_TAG_OFF) return -1;//corrupted
	beacon->interval = skb->data[BEACON_INTERVAL_OFF];
	beacon->interval |= skb->data[BEACON_INTERVAL_OFF+1]<<8;
	beacon->capability = skb->data[BEACON_CAPA_OFF];
	beacon->capability |= (skb->data[BEACON_CAPA_OFF+1])<<8;
	
	memcpy(beacon->bssid,skb->data+BEACON_BSSID_OFF,ETH_ALEN); 
	
	for(ptag=skb->data+BEACON_TAG_OFF;ptag+1 < skbend;){
		
		switch(*ptag){
		
		case 0: //ssid
			beacon->ssid_len = ptag[1];
		
			if(skbend < ptag+1+beacon->ssid_len) return -1;//corrupted
			
			memcpy(beacon->ssid,ptag+2,beacon->ssid_len);
			beacon->ssid[beacon->ssid_len]='\0';
			
			ptag+=beacon->ssid_len+2;
			break;		
		
		case 1:	 //rates
			beacon->rates_len = ptag[1];
			
			if(skbend < ptag+1+beacon->rates_len) return -1;//corrupted
			for(i=0;i<beacon->rates_len;i++)
				beacon->rates[i]=ptag[2+i] - 0x80;
			ptag+=beacon->rates_len+2;
			break;
			
			
		case 3:
			if(ptag[1] != 1)  return -1;//corrupted
			if(skbend < ptag+1+1)  return -1;//corrupted
			beacon->channel=ptag[2];
			ptag+=3;
			break;
			
		default: //unk
	
			unk_len = ptag[1];
			if(skbend < ptag+1+unk_len) return -1;//corrupted
//			IEEE80211DMESG("unk tag %x len %x ",ptag[0],ptag[1]);
			ptag+=unk_len+2;
			break;
			
		}
			
	
	}
	
	///IEEE80211DMESG("b %s",beacon->ssid);
	list_for_each(b,&ieee->beacons){
		
		btmp=list_entry(b,struct ieee80211_beacon,list);
		
		if(delprev){
			//if(b->prev != &ieee->beacons) 
			kfree(list_entry(delprev,struct ieee80211_beacon,list));
			delprev=NULL;
		}
		
		
		
		if(0==memcmp( btmp->bssid, beacon->bssid,ETH_ALEN)){
		//printk("duplicate beacon\n");
			btmp->last_scanned=beacon->last_scanned;
			btmp->rssi=beacon->rssi;
			btmp->channel = beacon->channel;
			btmp->capability = beacon->capability;
			btmp->rates_len = beacon->rates_len;
			btmp->quality = beacon->quality;
			memcpy(btmp->rates,beacon->rates,beacon->rates_len);
			/* Beacons and Probe Responses go through here, so
			 * we should keep the name from the Probe instead of
			 * overwriting it.  I was going to check btmp, but an
			 * AP could change ESSID on the fly, and that should
			 * be caught.
			 */
			/*if (beacon->ssid[0] != '\0') {
				memcpy(btmp->ssid, beacon->ssid,
				       beacon->ssid_len);
				       }*/
			memcpy(btmp->ssid, beacon->ssid, beacon->ssid_len);
			kfree(beacon);
			return 0;
		}
		
		
		/* 
		*	this has to be done on scan request, and on association attempt to ensure old beacons aren't reported,
		*	but we do it here too to assure that the list is never too large
		*/
		if(btmp->last_scanned + SCAN_JIFFI < jiffies){ 
		
			  //!!!!!using list_del will result in oops due to list_for_each!!!!
		  	prev = b->prev;
		  	next = b->next;
		  	next->prev = prev;
		  	prev->next = next;
			  delprev=b;
			  //kfree(btmp);
		}
	}
	
	if(ieee->link_state==WLAN_LINK_NONE){
	
		/*beacon2 =  kmalloc(sizeof(struct ieee80211_beacon),GFP_ATOMIC);
		memcpy(beacon2,beacon,sizeof(struct ieee80211_beacon));*/
		//IEEE80211DMESG("Found network");
		ieee80211_new_net(ieee,beacon);
	}
	list_add(&beacon->list,&ieee->beacons);

	return 0;
}


static inline void ieee80211_monitor_rx(struct ieee80211_device *ieee, 
					struct sk_buff *skb, 
					struct ieee80211_rx_stats *rx_stats)
{
	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
	u16 fc = le16_to_cpu(hdr->frame_control);

	skb->dev = ieee->dev;
	skb->mac.raw = skb->data;
	skb_pull(skb, ieee80211_get_hdrlen(fc));
	skb->pkt_type = PACKET_OTHERHOST;
	skb->protocol = __constant_htons(ETH_P_80211_RAW);
	memset(skb->cb, 0, sizeof(skb->cb));
	netif_rx(skb);
}


/* Called only as a tasklet (software IRQ) */
static struct ieee80211_frag_entry *
ieee80211_frag_cache_find(struct ieee80211_device *ieee, unsigned int seq,
			  unsigned int frag, u8 *src, u8 *dst)
{
	struct ieee80211_frag_entry *entry;
	int i;

	for (i = 0; i < IEEE80211_FRAG_CACHE_LEN; i++) {
		entry = &ieee->frag_cache[i];
		if (entry->skb != NULL &&
		    time_after(jiffies, entry->first_frag_time + 2 * HZ)) {
			printk(KERN_DEBUG "%s: expiring fragment cache entry "
			       "seq=%u last_frag=%u\n",
			       ieee->dev->name, entry->seq, entry->last_frag);
			dev_kfree_skb_any(entry->skb);
			entry->skb = NULL;
		}

		if (entry->skb != NULL && entry->seq == seq &&
		    (entry->last_frag + 1 == frag || frag == -1) &&
		    memcmp(entry->src_addr, src, ETH_ALEN) == 0 &&
		    memcmp(entry->dst_addr, dst, ETH_ALEN) == 0)
			return entry;
	}

	return NULL;
}

/* Called only as a tasklet (software IRQ) */
static struct sk_buff *
ieee80211_frag_cache_get(struct ieee80211_device *ieee, 
			 struct ieee80211_hdr *hdr)
{
	struct sk_buff *skb = NULL;
	u16 sc;
	unsigned int frag, seq;
	struct ieee80211_frag_entry *entry;
	
	sc = le16_to_cpu(hdr->seq_ctrl);
	frag = WLAN_GET_SEQ_FRAG(sc);
	seq = WLAN_GET_SEQ_SEQ(sc);

	if (frag == 0) {
		/* Reserve enough space to fit maximum frame length */
		skb = dev_alloc_skb(ieee->dev->mtu +
				    sizeof(struct ieee80211_hdr) +
				    8 /* LLC */ +
				    2 /* alignment */ +
				    8 /* WEP */ + ETH_ALEN /* WDS */);
		if (skb == NULL)
			return NULL;

		entry = &ieee->frag_cache[ieee->frag_next_idx];
		ieee->frag_next_idx++;
		if (ieee->frag_next_idx >= IEEE80211_FRAG_CACHE_LEN)
			ieee->frag_next_idx = 0;

		if (entry->skb != NULL)
			dev_kfree_skb_any(entry->skb);

		entry->first_frag_time = jiffies;
		entry->seq = seq;
		entry->last_frag = frag;
		entry->skb = skb;
		memcpy(entry->src_addr, hdr->addr2, ETH_ALEN);
		memcpy(entry->dst_addr, hdr->addr1, ETH_ALEN);
	} else {
		/* received a fragment of a frame for which the head fragment
		 * should have already been received */
		entry = ieee80211_frag_cache_find(ieee, seq, frag, hdr->addr2,
						  hdr->addr1);
		if (entry != NULL) {
			entry->last_frag = frag;
			skb = entry->skb;
		}
	}

	return skb;
}


/* Called only as a tasklet (software IRQ) */
static int ieee80211_frag_cache_invalidate(struct ieee80211_device *ieee,
					   struct ieee80211_hdr *hdr)
{
	u16 sc;
	unsigned int seq;
	struct ieee80211_frag_entry *entry;

	sc = le16_to_cpu(hdr->seq_ctrl);
	seq = WLAN_GET_SEQ_SEQ(sc);

	entry = ieee80211_frag_cache_find(ieee, seq, -1, hdr->addr2, 
					  hdr->addr1);

	if (entry == NULL) {
		printk(KERN_DEBUG "%s: could not invalidate fragment cache "
		       "entry (seq=%u)\n",
		       ieee->dev->name, seq);
		return -1;
	}

	entry->skb = NULL;
	return 0;
}
 
static inline void
ieee80211_rx_probe_rq(struct ieee80211_device *ieee, struct sk_buff *skb)
{
	u8 dest[ETH_ALEN];
	
	//IEEE80211DMESG("Rx probe");
	ieee->ieee_stats.rx_probe++;
	//DMESG("Dest is "MACSTR, MAC2STR(dest));
	if (probe_rq_parse(ieee, skb, dest)){
		//IEEE80211DMESG("Was for me!");
		ieee->ieee_stats.tx_probe++;
		ieee80211_resp_to_probe(ieee, dest);
		
	}
}

static inline void
ieee80211_rx_auth_rq(struct ieee80211_device *ieee, struct sk_buff *skb)
{
	u8 dest[ETH_ALEN];
	int status;
	//IEEE80211DMESG("Rx probe");
	ieee->ieee_stats.rx_auth_rq++;
	
	if((status = auth_rq_parse(skb,dest))!= -1){
		ieee80211_resp_to_auth(ieee,status,dest);
	}
	//DMESG("Dest is "MACSTR, MAC2STR(dest));
	
}

static inline void
ieee80211_rx_assoc_rq(struct ieee80211_device *ieee, struct sk_buff *skb)
{
	u8 dest[ETH_ALEN];
	unsigned long flags;
	
	ieee->ieee_stats.rx_assoc_rq++;
	if(assoc_rq_parse(skb,dest)!=-1){
		ieee80211_resp_to_assoc_rq(ieee,dest);
	}
	
	IEEE80211DMESG("New client associated: "MACSTR, MAC2STR(dest));
	
	spin_lock_irqsave(&ieee->lock,flags);
	add_associate(ieee,dest);
	spin_unlock_irqrestore(&ieee->lock,flags);
}

static inline int
ieee80211_rx_frame_mgmt(struct ieee80211_device *ieee, struct sk_buff *skb,
			struct ieee80211_rx_stats *rx_stats, u16 type,
			u16 stype)
{
	u16 errcode;
	#if 0
	if (ieee->iw_mode == IW_MODE_MASTER) {
		printk(KERN_DEBUG "%s: Master mode not yet suppported.\n",
		       ieee->dev->name);
		return 0;
	
/*
  hostap_update_sta_ps(ieee, (struct hostap_ieee80211_hdr *)
  skb->data);*/
	}
	#endif

	if (type == WLAN_FC_TYPE_MGMT) {
		if (stype == IEEE802_11_STYPE_BEACON ||
		    stype == IEEE802_11_STYPE_PROBE_RESP){
		//	struct sk_buff *skb2;
			/* Process beacon frames also in kernel driver to
			 * update STA(AP) table statistics */
		//	skb2 = skb_clone(skb, GFP_ATOMIC);
		//	if (skb2)
			/*if(ieee->link_state!=WLAN_LINK_ASSOCIATED)*/
			
			if(ieee80211_rx_beacon(ieee, skb,rx_stats))
				return -1;
			else
				return 1;
		}
		if(ieee->iw_mode == IW_MODE_MASTER && 
			(stype == IEEE802_11_STYPE_ASSOC_REQ || stype == IEEE802_11_STYPE_REASSOC_REQ))
		
			ieee80211_rx_assoc_rq(ieee, skb);
		
		if(ieee->iw_mode == IW_MODE_MASTER && 
			stype == IEEE802_11_STYPE_AUTH)
			ieee80211_rx_auth_rq(ieee, skb);
			
		if(((ieee->iw_mode == IW_MODE_ADHOC 
			|| ieee->iw_mode == IW_MODE_MASTER)&&
			ieee->link_state == WLAN_LINK_ASSOCIATED) && 
				stype == IEEE802_11_STYPE_PROBE_REQ)
			ieee80211_rx_probe_rq(ieee, skb);
			
		if(ieee->link_state == WLAN_LINK_ASSOCIATED && 
			ieee->iw_mode == IW_MODE_INFRA){
			if(stype == IEEE802_11_STYPE_DISASSOC ||
			   stype == IEEE802_11_STYPE_DEAUTH){
				
				ieee->func->data_poll_hard_stop(ieee->dev);
				
				ieee->link_state = WLAN_LINK_ASSOCIATING;
				ieee->ieee_stats.reassoc++;
				
				ieee80211_associate_step1(ieee);
				//queue_work(ieee->workqueue ,&ieee->associate_tasklet);
			}
		}
		if(ieee->link_state == WLAN_LINK_ASSOCIATING && 
			ieee->iw_mode == IW_MODE_INFRA){
		
			if(ieee->associate_state==ASSOC_AUTH)
				if(stype == IEEE802_11_STYPE_AUTH){
					IEEE80211DMESG("Received authentication response");
					if(0 == (errcode=auth_parse(skb))){
						ieee->associate_state=ASSOC_REQ;
						ieee->ieee_stats.rx_aut_ok++;
						
						ieee80211_associate_step2(ieee);
					}else{
						ieee->ieee_stats.rx_aut_err++;
						IEEE80211DMESG("Authentication respose status code 0x%x",errcode);
						ieee80211_associate_abort(ieee);
					}
				}
			if(ieee->associate_state==ASSOC_REQ)
				if(stype == IEEE802_11_STYPE_ASSOC_RESP){
					IEEE80211DMESG("Received association response");
					if(0 == (errcode=assoc_parse(skb)))
					{
						ieee->associate_state=ASSOC_SUCCESS;
						ieee->ieee_stats.rx_ass_ok++;
						
						ieee80211_associate_complete(ieee);
					}else{
						ieee->ieee_stats.rx_ass_err++;
						IEEE80211DMESG("Association response status code 0x%x",errcode);
						ieee80211_associate_abort(ieee); 
					}
				}
		
		}
		/* send management frames to the user space daemon for
		 * processing */
		//ieee->apdevstats.rx_packets++;
		//ieee->apdevstats.rx_bytes += skb->len;
		//prism2_rx_80211(ieee->apdev, skb, rx_stats, PRISM2_RX_MGMT);
		return 0;
	}
#if 0	    
	    if (ieee->iw_mode == IW_MODE_MASTER) {
		if (type != WLAN_FC_TYPE_MGMT && type != WLAN_FC_TYPE_CTRL) {
			printk(KERN_DEBUG "%s: unknown management frame "
			       "(type=0x%02x, stype=0x%02x) dropped\n",
			       skb->dev->name, type, stype);
			return -1;
		}

		hostap_rx(skb->dev, skb, rx_stats);
		return 0;
	} 
#endif
	printk(KERN_DEBUG "%s: hostap_rx_frame_mgmt: management frame "
	       "received in non-Host AP mode\n", skb->dev->name);
	return -1;
}


/* See IEEE 802.1H for LLC/SNAP encapsulation/decapsulation */
/* Ethernet-II snap header (RFC1042 for most EtherTypes) */
static unsigned char rfc1042_header[] =
{ 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00 };
/* Bridge-Tunnel header (for EtherTypes ETH_P_AARP and ETH_P_IPX) */
static unsigned char bridge_tunnel_header[] =
{ 0xaa, 0xaa, 0x03, 0x00, 0x00, 0xf8 };
/* No encapsulation header if EtherType < 0x600 (=length) */

#ifndef CONFIG_IEEE80211_NOWEP
/* Called by ieee80211_rx_frame_decrypt */
static int ieee80211_is_eapol_frame(struct ieee80211_device *ieee, 
				    struct sk_buff *skb)
{
	struct net_device *dev = ieee->dev;
	u16 fc, ethertype;
	struct ieee80211_hdr *hdr;
	u8 *pos;

	if (skb->len < 24)
		return 0;

	hdr = (struct ieee80211_hdr *) skb->data;
	fc = le16_to_cpu(hdr->frame_control);

	/* check that the frame is unicast frame to us */
	if ((fc & (IEEE802_11_FCTL_TODS | IEEE802_11_FCTL_FROMDS)) == 
	    IEEE802_11_FCTL_TODS &&
	    memcmp(hdr->addr1, dev->dev_addr, ETH_ALEN) == 0 &&
	    memcmp(hdr->addr3, dev->dev_addr, ETH_ALEN) == 0) {
		/* ToDS frame with own addr BSSID and DA */
	} else if ((fc & (IEEE802_11_FCTL_TODS | IEEE802_11_FCTL_FROMDS)) == 
		   IEEE802_11_FCTL_FROMDS &&
		   memcmp(hdr->addr1, dev->dev_addr, ETH_ALEN) == 0) {
		/* FromDS frame with own addr as DA */
	} else
		return 0;

	if (skb->len < 24 + 8)
		return 0;

	/* check for port access entity Ethernet type */
	pos = skb->data + 24;
	ethertype = (pos[6] << 8) | pos[7];
	if (ethertype == ETH_P_PAE)
		return 1;

	return 0;
}

/* Called only as a tasklet (software IRQ), by ieee80211_rx */
static inline int
ieee80211_rx_frame_decrypt(struct ieee80211_device* ieee, struct sk_buff *skb,
			struct ieee80211_crypt_data *crypt)
{
	struct ieee80211_hdr *hdr;
	int res, hdrlen;

	if (crypt == NULL || crypt->ops->decrypt_mpdu == NULL)
		return 0;

	hdr = (struct ieee80211_hdr *) skb->data;
	hdrlen = ieee80211_get_hdrlen(le16_to_cpu(hdr->frame_control));

#ifdef NOT_YET
	if (ieee->tkip_countermeasures &&
	    strcmp(crypt->ops->name, "TKIP") == 0) {
		if (net_ratelimit()) {
			printk(KERN_DEBUG "%s: TKIP countermeasures: dropped "
			       "received packet from " MACSTR "\n",
			       ieee->dev->name, MAC2STR(hdr->addr2));
		}
		return -1;
	}
#endif

	atomic_inc(&crypt->refcnt);
	res = crypt->ops->decrypt_mpdu(skb, hdrlen, crypt->priv);
	atomic_dec(&crypt->refcnt);
	if (res < 0) {
		printk(KERN_DEBUG "%s: decryption failed (SA=" MACSTR
		       ") res=%d\n",
		       ieee->dev->name, MAC2STR(hdr->addr2), res);
		if (res == -2)
			printk(KERN_DEBUG "%s: WEP decryption failed ICV mismatch\n",
				ieee->dev->name);
		ieee->ieee_stats.rx_discards_wep_undecryptable++;
		return -1;
	}

	return res;
}


/* Called only as a tasklet (software IRQ), by ieee80211_rx */
static inline int
ieee80211_rx_frame_decrypt_msdu(struct ieee80211_device* ieee, struct sk_buff *skb,
			     int keyidx, struct ieee80211_crypt_data *crypt)
{
	struct ieee80211_hdr *hdr;
	int res, hdrlen;

	if (crypt == NULL || crypt->ops->decrypt_msdu == NULL)
		return 0;

	hdr = (struct ieee80211_hdr *) skb->data;
	hdrlen = ieee80211_get_hdrlen(le16_to_cpu(hdr->frame_control));

	atomic_inc(&crypt->refcnt);
	res = crypt->ops->decrypt_msdu(skb, keyidx, hdrlen, crypt->priv);
	atomic_dec(&crypt->refcnt);
	if (res < 0) {
		printk(KERN_DEBUG "%s: MSDU decryption/MIC verification failed"
		       " (SA=" MACSTR " keyidx=%d)\n",
		       ieee->dev->name, MAC2STR(hdr->addr2), keyidx);
		return -1;
	}

	return 0;
}
#endif /* CONFIG_IEEE80211_NOWEP */


/* All received frames are sent to this function. @skb contains the frame in
 * IEEE 802.11 format, i.e., in the format it was sent over air.
 * This function is called only as a tasklet (software IRQ). */
int ieee80211_r8180_rx(struct ieee80211_device *ieee, struct sk_buff *skb,
		 struct ieee80211_rx_stats *rx_stats)
{
	struct net_device *dev = ieee->dev;
	struct ieee80211_hdr *hdr;
	size_t hdrlen;
	u16 fc, type, stype, sc;
	struct net_device_stats *stats;
	unsigned int frag;
	u8 *payload;
	u16 ethertype;
	int err;
	struct sk_buff *skb2 = NULL;
	short unknow;
	unsigned long flags;
#ifdef NOT_YET
	struct net_device *wds = NULL;
	
	struct net_device *wds = NULL;
	int frame_authorized = 0;
	int from_assoc_ap = 0;
	void *sta = NULL;
#endif
	u8 dst[ETH_ALEN];
	u8 src[ETH_ALEN];
#ifndef CONFIG_IEEE80211_NOWEP
	struct ieee80211_crypt_data *crypt = NULL;
	int keyidx = 0;
#endif /* CONFIG_IEEE80211_NOWEP */

	hdr = (struct ieee80211_hdr *)skb->data;
	stats = &ieee->stats;

	if (skb->len < 10) {
		printk(KERN_INFO "%s: SKB length < 10\n",
		       dev->name);
		goto rx_dropped;
	}
	
	fc = le16_to_cpu(hdr->frame_control);
	type = WLAN_FC_GET_TYPE(fc);
	stype = WLAN_FC_GET_STYPE(fc);
	sc = le16_to_cpu(hdr->seq_ctrl);
	frag = WLAN_GET_SEQ_FRAG(sc);
	hdrlen = ieee80211_get_hdrlen(fc);

#ifdef NOT_YET
#if WIRELESS_EXT > 15
	/* Put this code here so that we avoid duplicating it in all
	 * Rx paths. - Jean II */
#ifdef IW_WIRELESS_SPY		/* defined in iw_handler.h */
	/* If spy monitoring on */
	if (iface->spy_data.spy_number > 0) {
		struct iw_quality wstats;
		wstats.level = rx_stats->signal;
		wstats.noise = rx_stats->noise;
		wstats.updated = 6;	/* No qual value */
		/* Update spy records */
		wireless_spy_update(dev, hdr->addr2, &wstats);
	}
#endif /* IW_WIRELESS_SPY */
#endif /* WIRELESS_EXT > 15 */
	hostap_update_rx_stats(local->ap, hdr, rx_stats);
#endif

#if WIRELESS_EXT > 15
	if (ieee->iw_mode == IW_MODE_MONITOR) {
		ieee80211_monitor_rx(ieee, skb, rx_stats);
		stats->rx_packets++;
		stats->rx_bytes += skb->len;
		return 1;
	}
#endif

#ifndef CONFIG_IEEE80211_NOWEP
	if (ieee->host_decrypt) {
		int idx = 0;
		if (skb->len >= hdrlen + 3)
			idx = skb->data[hdrlen + 3] >> 6;
		crypt = ieee->crypt[idx];
#ifdef NOT_YET
		sta = NULL;

		/* Use station specific key to override default keys if the
		 * receiver address is a unicast address ("individual RA"). If
		 * bcrx_sta_key parameter is set, station specific key is used
		 * even with broad/multicast targets (this is against IEEE
		 * 802.11, but makes it easier to use different keys with
		 * stations that do not support WEP key mapping). */

		if (!(hdr->addr1[0] & 0x01) || local->bcrx_sta_key)
			(void) hostap_handle_sta_crypto(local, hdr, &crypt,
							&sta);
#endif

		/* allow NULL decrypt to indicate an station specific override
		 * for default encryption */
		if (crypt && (crypt->ops == NULL ||
			      crypt->ops->decrypt_mpdu == NULL))
			crypt = NULL;

		if (!crypt && (fc & IEEE802_11_FCTL_WEP)) {
#if 0
			/* This seems to be triggered by some (multicast?)
			 * frames from other than current BSS, so just drop the
			 * frames silently instead of filling system log with
			 * these reports. */
			printk(KERN_DEBUG "%s: WEP decryption failed (not set)"
			       " (SA=" MACSTR ")\n",
			       ieee->dev->name, MAC2STR(hdr->addr2));
#endif
			ieee->ieee_stats.rx_discards_wep_undecryptable++;
			goto rx_dropped;
		}
	}
#endif /* CONFIG_IEEE80211_NOWEP */


	if (type != WLAN_FC_TYPE_DATA) {
		if (type == WLAN_FC_TYPE_MGMT){
		#ifndef  CONFIG_IEEE80211_NOWEP
			if(fc & WLAN_FC_ISWEP && ieee->host_decrypt &&
			    	ieee80211_rx_frame_decrypt(ieee,skb,crypt)){ 
					printk(KERN_DEBUG "%s: failed to decrypt mgmt::auth "
			       			"from " MACSTR "\n", dev->name,	MAC2STR(hdr->addr2));
				/* TODO: could inform hostapd about this so that it
			 	* could send auth failure report */
				goto rx_dropped;
			}
		#endif
			err = ieee80211_rx_frame_mgmt(ieee, skb, rx_stats, type, stype);
			if (err<0)
				goto rx_dropped;
			if (err==0){
				ieee->stats.rx_bytes+=skb->len;
				ieee->stats.rx_packets++;
			}
			
			dev_kfree_skb_any(skb);
			goto rx_exit;
			
		}

	}


	/* Data frame - extract src/dst addresses */
	if (skb->len < IEEE80211_DATA_HDR3_LEN)
		goto rx_dropped;

	switch (fc & (IEEE802_11_FCTL_FROMDS | IEEE802_11_FCTL_TODS)) {
	case IEEE802_11_FCTL_FROMDS:
		memcpy(dst, hdr->addr1, ETH_ALEN);
		memcpy(src, hdr->addr3, ETH_ALEN);
		break;
	case IEEE802_11_FCTL_TODS:
		memcpy(dst, hdr->addr3, ETH_ALEN);
		memcpy(src, hdr->addr2, ETH_ALEN);
		break;
	case IEEE802_11_FCTL_FROMDS | IEEE802_11_FCTL_TODS:
		if (skb->len < IEEE80211_DATA_HDR4_LEN)
			goto rx_dropped;
		memcpy(dst, hdr->addr3, ETH_ALEN);
		memcpy(src, hdr->addr4, ETH_ALEN);
		break;
	case 0:
		memcpy(dst, hdr->addr1, ETH_ALEN);
		memcpy(src, hdr->addr2, ETH_ALEN);
		break;
	}
	//IEEE80211DMESG("Frame from "MACSTR,MAC2STR(src));
#ifdef NOT_YET
	if (hostap_rx_frame_wds(ieee, hdr, fc, &wds))
		goto rx_dropped;
	if (wds) {
		skb->dev = dev = wds;
		stats = hostap_get_stats(dev);
	}

	if (ieee->iw_mode == IW_MODE_MASTER && !wds &&
	    (fc & (IEEE802_11_FCTL_TODS | IEEE802_11_FCTL_FROMDS)) == IEEE802_11_FCTL_FROMDS &&
	    ieee->stadev &&
	    memcmp(hdr->addr2, ieee->assoc_ap_addr, ETH_ALEN) == 0) {
		/* Frame from BSSID of the AP for which we are a client */
		skb->dev = dev = ieee->stadev;
		stats = hostap_get_stats(dev);
		from_assoc_ap = 1;
	}
#endif

	dev->last_rx = jiffies;

#ifdef NOT_YET
	if ((ieee->iw_mode == IW_MODE_MASTER ||
	     ieee->iw_mode == IW_MODE_REPEAT) &&
	    !from_assoc_ap) {
		switch (hostap_handle_sta_rx(ieee, dev, skb, rx_stats,
					     wds != NULL)) {
		case AP_RX_CONTINUE_NOT_AUTHORIZED:
			frame_authorized = 0;
			break;
		case AP_RX_CONTINUE:
			frame_authorized = 1;
			break;
		case AP_RX_DROP:
			goto rx_dropped;
		case AP_RX_EXIT:
			goto rx_exit;
		}
	}
#endif

	/* Nullfunc frames may have PS-bit set, so they must be passed to
	 * hostap_handle_sta_rx() before being dropped here. */
	if (stype != IEEE802_11_STYPE_DATA &&
	    stype != IEEE802_11_STYPE_DATA_CFACK &&
	    stype != IEEE802_11_STYPE_DATA_CFPOLL &&
	    stype != IEEE802_11_STYPE_DATA_CFACKPOLL) {
		if (stype != IEEE802_11_STYPE_NULLFUNC)
			printk(KERN_DEBUG "%s: RX: dropped data frame "
			       "with no data (type=0x%02x, subtype=0x%02x, len=%d)\n",
			       dev->name, type, stype, skb->len);
		goto rx_dropped;
	}

	/* skb: hdr + (possibly fragmented, possibly encrypted) payload */

#ifndef CONFIG_IEEE80211_NOWEP
	if (ieee->host_decrypt && (fc & IEEE802_11_FCTL_WEP) &&
	    (keyidx = ieee80211_rx_frame_decrypt(ieee, skb, crypt)) < 0)
		goto rx_dropped;
#endif
	hdr = (struct ieee80211_hdr *) skb->data;

	/* skb: hdr + (possibly fragmented) plaintext payload */
	// PR: FIXME: hostap has additional conditions in the "if" below:
	// ieee->host_decrypt && (fc & IEEE802_11_FCTL_WEP) &&
	if ((frag != 0 || (fc & IEEE802_11_FCTL_MOREFRAGS))) {
		int flen;
		struct sk_buff *frag_skb = ieee80211_frag_cache_get(ieee, hdr);
		if (!frag_skb) {
			printk(KERN_DEBUG "%s: RX: cannot get skb from "
			       "fragment cache (morefrag=%d seq=%u frag=%u)\n",
			       dev->name, 
			       (fc & IEEE802_11_FCTL_MOREFRAGS) != 0,
			       WLAN_GET_SEQ_SEQ(sc), frag);
			goto rx_dropped;
		}

		flen = skb->len;
		if (frag != 0)
			flen -= hdrlen;

		if (frag_skb->tail + flen > frag_skb->end) {
			printk(KERN_WARNING "%s: host decrypted and "
			       "reassembled frame did not fit skb\n",
			       dev->name);
			ieee80211_frag_cache_invalidate(ieee, hdr);
			goto rx_dropped;
		}

		if (frag == 0) {
			/* copy first fragment (including full headers) into
			 * beginning of the fragment cache skb */
			memcpy(skb_put(frag_skb, flen), skb->data, flen);
		} else {
			/* append frame payload to the end of the fragment
			 * cache skb */
			memcpy(skb_put(frag_skb, flen), skb->data + hdrlen,
			       flen);
		}
		dev_kfree_skb_any(skb);
		skb = NULL;

		if (fc & IEEE802_11_FCTL_MOREFRAGS) {
			/* more fragments expected - leave the skb in fragment
			 * cache for now; it will be delivered to upper layers
			 * after all fragments have been received */
			goto rx_exit;
		}

		/* this was the last fragment and the frame will be
		 * delivered, so remove skb from fragment cache */
		skb = frag_skb;
		hdr = (struct ieee80211_hdr *) skb->data;
		ieee80211_frag_cache_invalidate(ieee, hdr);
	}

	/* skb: hdr + (possible reassembled) full MSDU payload; possibly still
	 * encrypted/authenticated */
#ifndef CONFIG_IEEE80211_NOWEP
	if (ieee->host_decrypt && (fc & IEEE802_11_FCTL_WEP) &&
	    ieee80211_rx_frame_decrypt_msdu(ieee, skb, keyidx, crypt))
		goto rx_dropped;

	hdr = (struct ieee80211_hdr *) skb->data;
	if (crypt && !(fc & IEEE802_11_FCTL_WEP) && !ieee->open_wep) {
		if (/*ieee->ieee_802_1x &&*/
		    ieee80211_is_eapol_frame(ieee, skb)) {
			/* pass unencrypted EAPOL frames even if encryption is
			 * configured */
			printk(KERN_DEBUG "%s: RX: IEEE 802.1X - passing "
			       "unencrypted EAPOL frame\n", ieee->dev->name);
		} else {
			printk(KERN_DEBUG "%s: encryption configured, but RX "
			       "frame not encrypted (SA=" MACSTR ")\n",
			       ieee->dev->name, MAC2STR(hdr->addr2));
			goto rx_dropped;
		}
	}

	if (/*ieee->drop_unencrypted*/ crypt && !(fc & IEEE802_11_FCTL_WEP) && !ieee->open_wep &&
	    !ieee80211_is_eapol_frame(ieee, skb)) {
		if (net_ratelimit()) {
			printk(KERN_DEBUG "%s: dropped unencrypted RX data "
			       "frame from " MACSTR " (drop_unencrypted=1)\n",
			       dev->name, MAC2STR(hdr->addr2));
		}
		goto rx_dropped;
	}
#endif /* CONFIG_IEEE80211_NOWEP */

	/* skb: hdr + (possible reassembled) full plaintext payload */

	payload = skb->data + hdrlen;
	ethertype = (payload[6] << 8) | payload[7];

#ifdef NOT_YET
	/* If IEEE 802.1X is used, check whether the port is authorized to send
	 * the received frame. */
	if (ieee->ieee_802_1x && ieee->iw_mode == IW_MODE_MASTER) {
		if (ethertype == ETH_P_PAE) {
			printk(KERN_DEBUG "%s: RX: IEEE 802.1X frame\n",
			       dev->name);
			if (ieee->hostapd && ieee->apdev) {
				/* Send IEEE 802.1X frames to the user
				 * space daemon for processing */
				prism2_rx_80211(ieee->apdev, skb, rx_stats,
						PRISM2_RX_MGMT);
				ieee->apdevstats.rx_packets++;
				ieee->apdevstats.rx_bytes += skb->len;
				goto rx_exit;
			}
		} else if (!frame_authorized) {
			printk(KERN_DEBUG "%s: dropped frame from "
			       "unauthorized port (IEEE 802.1X): "
			       "ethertype=0x%04x\n",
			       dev->name, ethertype);
			goto rx_dropped;
		}
	}
#endif

	/* convert hdr + possible LLC headers into Ethernet header */
	if (skb->len - hdrlen >= 8 &&
	    ((memcmp(payload, rfc1042_header, SNAP_SIZE) == 0 &&
	      ethertype != ETH_P_AARP && ethertype != ETH_P_IPX) ||
	     memcmp(payload, bridge_tunnel_header, SNAP_SIZE) == 0)) {
		/* remove RFC1042 or Bridge-Tunnel encapsulation and
		 * replace EtherType */
		skb_pull(skb, hdrlen + SNAP_SIZE);
		memcpy(skb_push(skb, ETH_ALEN), src, ETH_ALEN);
		memcpy(skb_push(skb, ETH_ALEN), dst, ETH_ALEN);
	} else {
		u16 len;
		/* Leave Ethernet header part of hdr and full payload */
		skb_pull(skb, hdrlen);
		len = htons(skb->len);
		memcpy(skb_push(skb, 2), &len, 2);
		memcpy(skb_push(skb, ETH_ALEN), src, ETH_ALEN);
		memcpy(skb_push(skb, ETH_ALEN), dst, ETH_ALEN);
	}

#ifdef NOT_YET
	if (wds && ((fc & (IEEE802_11_FCTL_TODS | IEEE802_11_FCTL_FROMDS)) == 
		    IEEE802_11_FCTL_TODS) &&
	    skb->len >= ETH_HLEN + ETH_ALEN) {
		/* Non-standard frame: get addr4 from its bogus location after
		 * the payload */
		memcpy(skb->data + ETH_ALEN,
		       skb->data + skb->len - ETH_ALEN, ETH_ALEN);
		skb_trim(skb, skb->len - ETH_ALEN);
	}
#endif

	stats->rx_packets++;
	stats->rx_bytes += skb->len;
	
	skb2=NULL;
	
	/* here, by default, the frame will be sent on the 
	 * ethernet media. If in master mode we have to check
	 * if we need to send back on the wireless media or both
	 */
	 
	if (ieee->iw_mode == IW_MODE_MASTER) {
	spin_lock_irqsave(&ieee->lock,flags);
		unknow = (!is_associated(ieee,dst) && !is_bridged(ieee,dst));
		//if (memcmp(ieee->dev->dev_addr ,dst,ETH_ALEN)==0) unknow = 0;
		if (is_broadcast(dst) || is_multicast(dst) || unknow){
		 	/* enable sending also to wireless media*/
			skb2 = skb_clone(skb, GFP_ATOMIC);
			if (skb2 == NULL){
				printk(KERN_DEBUG "%s: skb_clone failed for "
				       "multicast frame\n", dev->name);
			}
			
		}else if (is_associated(ieee,dst)){
			/* send only to the wireless media */
			skb2=skb;
			skb=NULL;
		}
#ifdef  IEEE80211_DEBUG_MASTER_MODE
	IEEE80211DMESG("adr "MACSTR" assoc: %d unk:%d",
		MAC2STR(dst),is_associated(ieee,dst),unknow);
#endif
	spin_unlock_irqrestore(&ieee->lock,flags);
	} 

	if (skb2 != NULL) {
		/* send to wireless media */
		skb2->protocol = __constant_htons(ETH_P_802_3);
		skb2->mac.raw = skb2->nh.raw = skb2->data;
		/* skb2->nh.raw = skb2->data + ETH_HLEN; */
		skb2->dev = dev;
		dev_queue_xmit(skb2);
	}



	if (skb) {
		skb->protocol = eth_type_trans(skb, dev);
		memset(skb->cb, 0, sizeof(skb->cb));
		skb->dev = dev;
		skb->ip_summed = CHECKSUM_NONE; /* 802.11 crc not sufficient */
		netif_rx(skb);
	}

 rx_exit:
#ifdef NOT_YET
	if (sta)
		hostap_handle_sta_release(sta);
#endif
	return 1;

 rx_dropped:
	stats->rx_dropped++;

	/* Returning 0 indicates to caller that we have not handled the SKB--
	 * so it is still allocated and can be used again by underlying
	 * hardware as a DMA target */
	return 0;
}

EXPORT_SYMBOL(ieee80211_r8180_rx);
