/*
 * arp.c
 *
 * Copyright 2004, Minoru Murashima. All rights reserved.
 * Distributed under the terms of the BSD License.
 *
 * ARP protocol.
 */


#include<sys/user_config.h>
#include<types.h>
#include<lib.h>
#include<lock.h>
#include<device.h>
#include<mm.h>
#include<proc.h>
#include<errno.h>
#include<net/net.h>
#include<net/ether.h>
#include<net/ip.h>
#include<net/arp.h>


enum{
	/* Hard type. */
	HARD_TYPE_ETHER= 1,		/* Ethernet. */

	/* Protocol type. */
	PROT_TYPE_IP=0x0800,	/* IP. */

	/* Operation code. */
	OPE_CODE_ARP_REQ= 1,	/* ARP request. */
	OPE_CODE_ARP_REP= 2,	/* ARP repry. */
	OPE_CODE_RARP_REQ=3,	/* RARP request. */
	OPE_CODE_RARP_REP=4,	/* RARP repry. */
};


/**************************************************************************
 *
 * ARPキャッシュ
 *
 **************************************************************************/


enum{
	ARP_CACHE_NUM=64,				/* ARPキャッシュ数。 */
	ARP_CACHE_HASH=ARP_CACHE_NUM/2,	/* ARPキャッシュハッシュ数。 */
};


typedef struct{
	uint ip;
	char mac[6];
	char next;
}ARP_CACHE;


//================================== PRIVATE ============================================


static ARP_CACHE arpCache[ARP_CACHE_NUM];
static char cacheEmpty=0;
static char hashTbl[ARP_CACHE_HASH];
static int cacheLock=0;


/*
 * Search previous index address.
 * parameters : IP address
 * return : prev pointer or NULL
 */
static inline char *searchPrevIndex(uint ip)
{
	uchar tbl;
	int i;


	tbl=(ip>>24)%ARP_CACHE_HASH;
	if(hashTbl[tbl]==-1)return &hashTbl[tbl];
	else
		for(i=hashTbl[tbl];;i=arpCache[i].next)
		{
			if(arpCache[i].ip==ip)return NULL;
			if(arpCache[i].next==-1)return &arpCache[i].next;
		}
}


/*
 * キャッシュの横取り。
 */
static inline int snatchCache()
{
	int num;
	int i;


	for(i=0;hashTbl[i]==-1;++i);
	num=hashTbl[i];
	hashTbl[i]=arpCache[num].next;

	return num;
}


//================================== PROTECTED ==========================================


/*
 * Search cache.
 * parameters : IP address
 * return : Mac address or failed=NULL
 */
static inline char *searchCache(uint ip)
{
	uchar tbl;
	int i;


	tbl=(ip>>24)%ARP_CACHE_HASH;
	for(i=hashTbl[tbl];i!=-1;i=arpCache[i].next)
		if(arpCache[i].ip==ip)return arpCache[i].mac;

	return NULL;
}


/*
 * Add cache.
 */
static void addCache(uint ip,char *mac)
{
	char *prev;
	char num;


	enter_spinlock(&cacheLock);
	{
		if((prev=searchPrevIndex(ip))==NULL)
			memcpy(searchCache(ip),mac,6);
		else
		{
			if(cacheEmpty==-1)num=snatchCache();
			else
			{
				num=cacheEmpty;
				cacheEmpty=arpCache[(int)num].next;
			}

			arpCache[(int)num].ip=ip;
			memcpy(arpCache[(int)num].mac,mac,6);
			arpCache[(int)num].next=-1;
			*prev=num;
		}
	}
	exit_spinlock(&cacheLock);
}


/*
 * Init arp cache.
 */
static void initCache()
{
	int i;


	/* キャッシュを空きリンクに連結。 */
	for(i=0;i<ARP_CACHE_NUM;++i)arpCache[i].next=i+1;
	arpCache[ARP_CACHE_NUM-1].next=-1;

	/* ハッシュテーブルの初期化。 */
	memset(hashTbl,0xff,ARP_CACHE_HASH);
}


/**************************************************************************
 *
 * 送信
 *
 **************************************************************************/


//================================== PROTECTED ==========================================


static char broadcastMac[]={0xff,0xff,0xff,0xff,0xff,0xff};


/*
 * Transfer ARP frame.
 */
static void transArp(int num, uint dstip, char *dstmac, ushort opecode)
{
	ARP_HEADER head;
	TRANS_BUF_INFO tbi;

	/* Make ARP head. */
	head.hardType = swapWord(HARD_TYPE_ETHER);
	head.protType = swapWord(PROT_TYPE_IP);
	head.hardAddrLen = 6;
	head.protAddrLen = 4;
	head.opeCode = swapWord(opecode);
	memcpy(head.srcMac, ethDev[num].mac, 6);
	head.srcIp = ethDev[num].ip;
	if (dstmac == broadcastMac){
		memset(head.dstMac,0,6);
	}
	else{
		memcpy(head.dstMac, dstmac, 6);
	}
	head.dstIp = dstip;

	/* Set transmission buffer structure. */
	tbi.data[0] = (char*)&head;
	tbi.size[0] = sizeof(ARP_HEADER);
	tbi.size[1] = 0;
	tbi.size[2] = 0;
	tbi.type = DIX_TYPE_ARP;

	startEmu(ethDev[num].ifnet, &tbi, dstmac);
}


/**************************************************************************
 *
 * リクエスト
 *
 **************************************************************************/


typedef struct REPLY_WAIT{
	struct REPLY_WAIT *next;
	uint               ip;			/* Request IP address. */
	int volatile       repFlg;		/* Reply flag. */
	WAIT_INTR          wait;
	char               mac[6];		/* Reply mac buffer. */
}REPLY_WAIT;


//================================== PRIVATE ============================================


static REPLY_WAIT *replyWait;


//================================== PROTECTED ==========================================


/*
 * Receive reply.
 * parameters : source IP,source MAC
 * return : 0 or task switch=1
 */
static int receiveReply(uint srcip,char *mac)
{
	REPLY_WAIT *p;


	/* replyWaitの検索。 */
	if ((p = replyWait) == NULL){
		return 0;
	}
	while (p->ip != srcip){
		if ((p = p->next) == NULL){
			return 0;
		}
	}
	memcpy(p->mac, mac, 6);
	p->repFlg = 1;

	return wake_intr(&p->wait,TASK_SIGNAL_WAIT);		/* 待ちプロセスを起こす。 */
}


//================================== PUBLIC =============================================


/*
 * Get destination MAC.
 * parameters : device number,destination ip address,mac address buffer
 * return : 0 or error number
 */
int getMac(int num,uint ip,char *mac)
{
	enum{
		RETRY_COUNT = 3,		/* リトライ送信回数。 */
		REQUEST_TIMEOUT = 2000,	/* リクエストタイムアウトミリ秒。 */
	};
	char *retmac;
	int rest;
	REPLY_WAIT *wait;
	REPLY_WAIT *p;
	int i;

	/* キャッシュを検索。 */
	retmac = searchCache(ip);
	if (retmac != NULL){
		memcpy(mac, retmac, 6);
		return 0;
	}

	/* 送信準備。 */
	wait = kmalloc(sizeof(REPLY_WAIT));
	if (wait == NULL){
		return -ENOMEM;
	}
	memset(wait, 0, sizeof(*wait));
	wait->ip = ip;
	wait->next = replyWait;
	replyWait = wait;

	/* リクエストARPを送信。 */
	for (i = 0;;){
		transArp(num, ip, broadcastMac, OPE_CODE_ARP_REQ);
		wait_intr(&wait->wait, REQUEST_TIMEOUT, TASK_SIGNAL_WAIT);
		if (wait->repFlg){
			addCache(ip, wait->mac);
			memcpy(mac, wait->mac, 6);
			rest = 0;
			break;
		}
		if (RETRY_COUNT <= ++i){
			rest = -ENETUNREACH;
			break;
		}
	}

	/* 送信後処理。 */
	if (replyWait == wait){
		replyWait = wait->next;
	}
	else{
		for (p = replyWait; p->next != wait; p = p->next){;
		}
		p->next = wait->next;
	}

	kfree(wait);

	return rest;
}


/**************************************************************************
 *
 * 受信
 *
 **************************************************************************/


//================================== PUBLIC =============================================


/*
 * Receive ARP frame.
 * parameters : device number,ARP_HEADER address
 * return : task switch on=1,off=0
 */
int receiveArp(int num,ARP_HEADER *head)
{
	int rest = 0;
/***************************************************************************************************************
uchar *srcip,*dstip;

srcip=(uchar*)&head->srcIp;
dstip=(uchar*)&head->dstIp;
printk("ARP_HEADER htype=%x,ptype=%x,hsize=%x,psize=%x,opecode=%x\n"
		"source mac=%d-%d-%d-%d-%d-%d,IP=%d-%d-%d-%d\n"
		"dest   mac=%d-%d-%d-%d-%d-%d,IP=%d-%d-%d-%d\n",
       swapWord(head->hardType),swapWord(head->protType),head->hardAddrLen,head->protAddrLen,
       swapWord(head->opeCode),
       head->srcMac[0],head->srcMac[1],head->srcMac[2],head->srcMac[3],head->srcMac[4],head->srcMac[5],
       srcip[0],srcip[1],srcip[2],srcip[3],
       head->dstMac[0],head->dstMac[1],head->dstMac[2],head->dstMac[3],head->dstMac[4],head->dstMac[5],
       dstip[0],dstip[1],dstip[2],dstip[3]);
***************************************************************************************************************/
	/* IPの確認。 */
	if (head->dstIp != ethDev[num].ip){
		return 0;
	}

	/* オペレーションコードの確認。 */
	switch (swapWord(head->opeCode)){
		case OPE_CODE_ARP_REQ:
			transArp(num, head->srcIp, head->srcMac, OPE_CODE_ARP_REP);
			addCache(head->srcIp, head->srcMac);
			break;
		case OPE_CODE_ARP_REP:
			rest = receiveReply(head->srcIp, head->srcMac);
			break;
		case OPE_CODE_RARP_REQ:
		case OPE_CODE_RARP_REP:
			break;
	}

	return rest;
}


/**************************************************************************
 *
 * 初期化
 *
 **************************************************************************/


//================================== PUBLIC =============================================


/*
 * Init arp protocol.
 */
void initArp()
{
	initCache();
}

/***************************************************************************/
#define IP_ADDR(a,b,c,d) (a+(b<<8)+(c<<16)+(d<<24))

int test_arp()
{
	char mac[6];

	printk("test_arp\n");
	if(getMac(0,IP_ADDR(172,25,0,2),mac)<0)
		printk("Failed getMac!\n");
	else
		printk("MAC=%d-%d-%d-%d-%d-%d\n",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);

	return 0;
}
/***************************************************************************/
