/*
 * ip.c
 *
 * Copyright 2004, Minoru Murashima. All rights reserved.
 * Distributed under the terms of the BSD License.
 *
 * Internet Protocol.
 */


#include<types.h>
#include<lib.h>
#include<proc.h>
#include<lock.h>
#include<mm.h>
#include<time.h>
#include<errno.h>
#include<net/net.h>
#include<net/netlib.h>
#include<net/ether.h>
#include<net/arp.h>
#include<net/icmp.h>
#include<net/udp.h>
#include<net/tcp.h>
#include<net/ip.h>


/**************************************************************************
 *
 * IPヘッダー
 *
 **************************************************************************/


//================================== パブリック ======================================


/*
 * IPデータ部のサイズを取得する
 */
static inline int getDataSize(IP_HEADER *ip)
{
	return swapWord(ip->len) - sizeof(IP_HEADER);
}


/*
 * IPデータ部のフラグメントオフセットを取得する
 */
static inline uint16_t getFragOffset(IP_HEADER *ip)
{
	return swapWord(ip->fragOffset) & FRAG_OFFSET_MASK;
}


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


enum{
	IP_HEAD_VERSION=4<<4,

	IP_HEAD_FRAG_ON= 0x2000,	/* フラグメントデータありフラグ。 */
	IP_HEAD_FRAG_NOT=0x4000,	/* フラグメント不可フラグ。 */
};


typedef struct{
	int    lock;
	ushort id;
}HEAD_ID;


//================================== プライベート ======================================


#define GET_ID(a)	enter_spinlock(&headId.lock);\
			 		a = swapWord(headId.id++);\
					exit_spinlock(&headId.lock)


static HEAD_ID headId;
/************************************************************************************************
static void viewHead(IP_HEADER *head)
{
	printk("TRANS IP version=%x,head length=%x,TOS=%x,total length=%x,id=%x,fragment=%x\n"
	       "TTL=%x,protocol=%x,sourceIP=%x,destIP=%x\n",
	       head->verhead>>4,head->verhead&0xf,head->tos,swapWord(head->len),head->id,head->frag,
	       head->ttl,head->prot,head->srcip,head->dstip);
}
*************************************************************************************************/


//================================== グローバル ======================================


/*
 *《注意》引数"sock"に"NULL"あり
 * Transfer IP datagram.
 * return : 0 or error number
 */
int transIp(SOCKET *sock, TRANS_BUF_INFO *tbi, uint dstip, uint16_t frag)
{
	char dstmac[6];
	uchar tos;
	uint transip;
	int num;
	int rest;
	IP_HEADER head;
	int max,total,trans;

	/* Get device number. */
	num = searchDevnum(dstip, &transip);

	/* 送信先 MACの取得 */
	if ((rest = getMac(num, transip, dstmac)) < 0){
		return rest;
	}

	// IPオプションの設定
	tos = 0;
	if (sock != NULL){
		if (sock->ipOpt.name == IP_TOS){
			tos = sock->ipOpt.value;
		}
	}

	/* Set IP head. */
	head.verhead = IP_HEAD_VERSION | (sizeof(IP_HEADER) / 4);
	head.tos = tos;
	GET_ID(head.id);
	head.ttl = 255;
	head.prot = tbi->ipType;
	head.srcip = ethDev[num].ip;
	head.dstip = dstip;
	head.chksum = 0;

	tbi->type = DIX_TYPE_IP;
	tbi->data[0] = (char*)&head;
	tbi->size[0] = sizeof(IP_HEADER);			/* IP header size. */
	max = ethDev[num].mtu - sizeof(IP_HEADER);	/* データ最大送信サイズ。 */
	total = tbi->size[1] + tbi->size[2];		/* データトータルサイズ。 */

	if (total <= max){
		head.len = swapWord(tbi->size[0] + total);
		head.fragOffset = swapWord(frag);
		head.chksum = calcSum((uint*)&head, tbi->size[0]);
/**********************************************
viewHead(&head);
**********************************************/
		startEmu(ethDev[num].ifnet, tbi, dstmac);
	}
	else{
		head.len = swapWord(tbi->size[0] + max);
		head.fragOffset = swapWord(IP_HEAD_FRAG_ON);
		tbi->size[2] = max-tbi->size[1];
		head.chksum = calcSum((uint*)&head, tbi->size[0]);
/**********************************************
viewHead(&head);
**********************************************/
		startEmu(ethDev[num].ifnet, tbi, dstmac);

		trans = max;						/* 送信済サイズ。 */
		tbi->data[2] += max-tbi->size[1];
		tbi->size[1] = 0;

		for(;;){
			head.chksum = 0;

			if(total - trans <= max){
				head.len = swapWord(tbi->size[0] + total - trans);
				head.fragOffset = swapWord(trans);
				GET_ID(head.id);
				head.chksum = calcSum((uint*)&head, tbi->size[0]);
				tbi->size[2] = total - trans;
/**********************************************
viewHead(&head);
**********************************************/
				startEmu(ethDev[num].ifnet, tbi, dstmac);
				break;
			}
			else{
				head.len = swapWord(tbi->size[0] + max);
				head.fragOffset = swapWord(trans | IP_HEAD_FRAG_ON);
				GET_ID(head.id);
				head.chksum = calcSum((uint*)&head, tbi->size[0]);
				tbi->size[2] = max;
/**********************************************
viewHead(&head);
**********************************************/
				startEmu(ethDev[num].ifnet, tbi, dstmac);
			}

			trans += max;
			tbi->data[2] += max;
		}
	}

	return 0;
}


/**************************************************************************
 *
 * フラグメンテーションデータの結合処理
 *
 **************************************************************************/


/* フラグメント管理ヘッダー */
enum{
	DATA_LEN_MAX		= 0x10000,	// IPデータ最大サイズ
	FRAG_HEAD_USE		= 1,		// フラグメントヘッダ使用中
	FRAG_HEAD_NOT_USE	= 0,		// フラグメントヘッダ未使用
	REASSENBLED			= 1,		// 再構築完了
	FRAG_LIVE_TIME		= 5,		// フラグメントデータ生存時間（秒）
	FRAG_HEAD_NUM		= 1,		// フラグメントヘッダ数
};

typedef struct FRAG_HEAD{
	int 	 use;		// 使用済みフラグ
	int		 curLen;	// 現在の全データ長
	uint 	 creatTime; // 作成時間
	char	*data;		// データ格納バッファ
}FRAG_HEAD;


//================================== プライベート ======================================


static char fragData[DATA_LEN_MAX];

static FRAG_HEAD fragHead[FRAG_HEAD_NUM] = {
	{FRAG_HEAD_NOT_USE,
	0,
	0,
	fragData}
};


/*
 * 送信元IPアドレスを取得する
 */
static inline uint32_t getDstIp(FRAG_HEAD *fragHead)
{
	return ((IP_HEADER*)fragHead->data)->dstip;
}


/*
 * 送信元IP識別番号を得る
 */
static inline uint16_t getId(FRAG_HEAD *fragHead)
{
	return ((IP_HEADER*)fragHead->data)->id;
}


/*
 * フラグメントヘッダの開放
 */
static void releaseFragHead(FRAG_HEAD *fragHead)
{
	fragHead->use = FRAG_HEAD_NOT_USE;
	fragHead->curLen = 0;
	fragHead->creatTime = 0;
}


/*
 * 保留中のフラグメントデータの検索
 * フラグメントヘッダアドレス or NULL
 */
static FRAG_HEAD *searchFragHead(IP_HEADER *ip)
{
	int i;

	for (i = 0; i < FRAG_HEAD_NUM; ++i){
		/* 送信元IPと識別番号が一致 */
		if ((ip->dstip == getDstIp(&fragHead[i])) && (ip->id == getId(&fragHead[i]))){
			return &fragHead[i];
		}
	}

	return NULL;
}


/*
 * フラグメントヘッダの初期化
 * return : NOERR or ERR
 */
static int initFragHead(IP_HEADER *ip)
{
	uint curTime = sys_time(NULL);
	int freeNum;
	IP_HEADER *fragIp;
	
	// 未使用のヘッダを探す
	for (freeNum = 0; freeNum < FRAG_HEAD_NUM; ++freeNum){
		if (FRAG_HEAD_NUM <= freeNum){
			return ERR;
		}
		if (fragHead[freeNum].use == FRAG_HEAD_NOT_USE){
			break;
		}
		else if (FRAG_LIVE_TIME < (curTime - fragHead[freeNum].creatTime)){
			/* 生存時間を過ぎたヘッダを再利用 */
			break;
		}
	}

	/* IPヘッダをコピー */
	fragIp = (IP_HEADER*)&fragHead[freeNum].data;
	memcpy(fragIp, ip, sizeof(IP_HEADER));
	if (swapWord(ip->fragOffset) & FRAG_MORE){
		fragIp->len = 0;
	}
	else{
		fragIp->len = swapWord(getFragOffset(ip) + getDataSize(ip) + sizeof(IP_HEADER));
	}
	
	// IPデータをコピー
	memcpy(fragIp->data + getFragOffset(ip), ip->data, getDataSize(ip));

	fragHead->curLen = getDataSize(ip);		// 現在の全データ長
	fragHead->creatTime = curTime;			// 作成時刻
	fragHead[freeNum].use = FRAG_HEAD_USE;
	
	return NOERR;
}


/*
 * フラグメントの再構築
 * return : NOERR or REASSENBLED or error number
 */
static int reassemble(IP_HEADER *ip, FRAG_HEAD *fragHead)
{
	IP_HEADER *fragIp = (IP_HEADER*)fragHead->data;

	/* IPデータをコピー */
	memcpy(fragIp->data + getFragOffset(ip), ip->data, getDataSize(ip));
	fragHead->curLen += getDataSize(ip);

	/* IPトータル長を代入する */
	if ((swapWord(ip->fragOffset) & FRAG_MORE) == 0){
		fragIp->len = swapWord(getFragOffset(ip) + getDataSize(ip) + sizeof(IP_HEADER));
	}

	/* すべて結合したか？ */
	if (getDataSize(fragIp) == fragHead->curLen){
		return REASSENBLED;
	}
	
	return NOERR;
}


//================================== パブリック ======================================


/*
 * フラグメントデータサイズをチェックする
 * return : NOERR or ERR
 */
static int checkFragSize(IP_HEADER *ip)
{
	/* 最大サイズのチェック */
	if (DATA_LEN_MAX < getFragOffset(ip) + getDataSize(ip) + sizeof(IP_HEADER)){
		return  ERR;
	}
	return NOERR;
}


/*
 * フラグメントデータの再構築
 * return : NOERR or REASSENBLED or error number
 */
static int reassembleFragment(IP_HEADER *ip,void **o_fragHead)
{
	FRAG_HEAD *fragHead;
	
	/* 保留中のフラグメントデータの検索 */
	fragHead = searchFragHead(ip);
	if (fragHead == NULL){
		/* フラグメントヘッダーの新規作成 */
		return initFragHead(ip);
	}
	else{
		/* フラグメントデータの結合 */
		if (reassemble(ip, fragHead) == REASSENBLED){
			*o_fragHead = fragHead;
			return REASSENBLED;
		}
	}
	
	return NOERR;
}


/*
 * フラグメントデータの開放
 */
static void freeFragment(FRAG_HEAD *fragHead)
{
	releaseFragHead(fragHead);
}


/*
 * IPデータを取得する
 */
static IP_HEADER *getIpFromFragment(FRAG_HEAD *fragHead)
{
	return (IP_HEADER*)fragHead->data;
}


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


//=================================== グローバル =======================================


/*
 * Receive IP datagram.
 * parameters : IP header
 * return : 0 or task switch=1
 */
int receiveIp(IP_HEADER *ip)
{
	int (*receive)(IP_HEADER*);

/*****************************************************************************************
TCP_HEADER *tcp = (TCP_HEADER*)ip->data;

printk("IP version=%x,ip length=%x,TOS=%x,total length=%x,id=%x,fragment=%x "
       "TTL=%x,protocol=%x,sourceIP=%x,destIP=%x\n",
       ip->verhead >> 4,ip->verhead & 0xf,ip->tos,swapWord(ip->len),ip->id,
       swapWord(ip->fragOffset),ip->ttl,ip->prot,ip->srcip,ip->dstip);
*******************************************************************************************/
	// チェックサムの確認
	if (calcSum((uint*)ip,(ip->verhead&0xf) * 4)){
		return 0;
	}

	// 上位プロトコル関数をセット
	switch(ip->prot){
		case IPPROTO_TCP:
			receive = receiveTcp;
			break;
		case IPPROTO_UDP:
			receive = receiveUdp;
			break;
		case IPPROTO_ICMP:
			receive = receiveIcmp;
			break;
		default:
			return 0;
	}

	// フラグメントIPデータの処理
	if ((swapWord(ip->fragOffset) & FRAG_MORE) || (0 < getFragOffset(ip))){
		void *fragment;
		int rest;

		// サイズのチェック
		if (checkFragSize(ip) == ERR){
			return 0;
		}
		
		// フラグメントの結合
		if (reassembleFragment(ip,&fragment) != REASSENBLED){
			return 0;
		}

		rest = receive(getIpFromFragment(fragment));
		freeFragment(fragment);
		return rest;
	}

	return receive(ip);
}


/**************************************************************************
 *
 * IPスレッド
 *
 **************************************************************************/


//================================== プライベート ========================================


static PROC *ipTask;
static IP_BUFFER *recvBuf;
static int recvLock;


/*
 * IPスレッド
 */
static inline void IpTask()
{
	IP_BUFFER *ipBuf;

	ipBuf = getRecvBuf(&recvBuf, &recvLock);
	if (ipBuf == NULL){
		sys_sleep(-1);
	}
	else{
		switch (ipBuf->ip.prot){
			case IPPROTO_TCP:
				doTcpService(ipBuf->sock, &ipBuf->ip);
				break;
			case IPPROTO_UDP:
				doUdpService(ipBuf->sock, &ipBuf->ip);
				break;
			case IPPROTO_ICMP:
				// 未対応
				break;
			default:
				break;
		}
		freeIpBuf(ipBuf);
	}
}


//================================== パブリック ========================================


/*
 * IPスレッドの起動
 */
static void startIpTask()
{
	switch(sys_fork())
    {
		case -1:
			printk("Faild startIpTask()\n");
			break;
		case 0:
			ipTask = get_current_task();
			del_from_schedule(TASK_WAIT, PROC_SCHEDULE_DEL);
			for (;;){
				IpTask();
			}
			break;
	}
}


//================================== グローバル ========================================


/*
 * ! 注意！受信割り込み時のみに使用すること
 * IPスレッドの起床
 */
void setIpTask(SOCKET *sock, IP_HEADER *ip)
{
	IP_BUFFER *ipBuf;

	if (allocIpBuf(ip, &ipBuf) == ERR){
		return;
	}
	ipBuf->sock = sock;
	attachRecvBuf(ipBuf, &recvBuf, &recvLock);
	sys_wake(ipTask);
}


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


//================================== グローバル ========================================


int initIp()
{
	startIpTask();
	
	return 0;
}
