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


#include<types.h>
#include<lib.h>
#include<errno.h>
#include<mm.h>
#include<proc.h>
#include<time.h>
#include<lock.h>
#include<signal.h>
#include<net/net.h>
#include<net/ip.h>
#include<net/icmp.h>
#include<net/netlib.h>
#include<test.h>


//#define DEBUG_ICMP 1

#ifdef DEBUG_ICMP
	#define STATIC
	#define INLINE
#else
	#define STATIC	static
	#define INLINE	inline
#endif


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


/*
 * PUBLIC
 * Transmit ICMP frame.
 * parameters : destination IP,type,code,ICMP head address,ICMP data size
 */
STATIC void transIcmp(uint dstip,uchar type,uchar code,ICMP_HEADER *head,int size)
{
	TRANS_BUF_INFO tbi;


	/* ヘッダーの設定。 */
	head->type=type;
	head->code=code;
	head->chksum=0;
	head->chksum=calcSum((uint*)head,size);

	/* バッファテーブルの設定 */
	tbi.data[2]=NULL;
	tbi.size[2]=0;
	tbi.data[1]=(char*)head;
	tbi.size[1]=size;
	tbi.ipType=IPPROTO_ICMP;
/****************************************************************************
printk("TRANS ICMP type=%d,code=%d\n",head->type,head->code);
****************************************************************************/
	transIp(NULL, &tbi, dstip, 0);
}


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


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


static SOCKET waitSocket;	/* ウエイトソケットリンク */
static int waitLock;		/* レシーブ処理用スピンゲート */

/*
 * 受信待ちソケットリンクに接続する。
 * すでに接続済でも切り離してから接続する。
 */
STATIC void setWaitLink(SOCKET *sock)
{
	cli();
	enter_spinlock(&waitLock);
	{
		sock->next->prev = sock->prev;
		sock->prev->next = sock->next;
		sock->next = waitSocket.next;
		sock->prev = &waitSocket;
		waitSocket.next->prev = sock;
		waitSocket.next = sock;
	}
	exit_spinlock(&waitLock);
	sti();
}

/*
 * 受信待ちソケットリンクから切り離す。
 */
STATIC void resetWaitLink(SOCKET *sock)
{
	cli();
	enter_spinlock(&waitLock);
	{
		sock->next->prev = sock->prev;
		sock->prev->next = sock->next;
		sock->next = sock->prev = sock;
	}
	exit_spinlock(&waitLock);
	sti();
}

/*
 * ＊＊＊ 割り込み処理関数 ＊＊＊
 * 受信したIPデータをセーブする。
 * ウエイトプロセスは起こす。
 * return : 0 or 1 = task switch
 */
STATIC int saveRecv(IP_HEADER *ip)
{
	SOCKET *sock;
	IP_BUFFER *ipBuf;

	for (sock = waitSocket.next; sock != &waitSocket; sock = sock->next){
		/* 受信ソケットあり */
//		if (sock->srcip == ip->dstip){
			if (allocIpBuf(ip,&ipBuf) == ERR){
				return 0;
			}
			attachRecvBuf(ipBuf, (IP_BUFFER**)&sock->recvBuf, &sock->lockGate);
			if (sock->waitProc != NULL){
				sys_wake(sock->waitProc);
				return 1;
			}
//		}
	}

	return 0;
}


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


/*
 * 受信データを取得する
 * return : 0 or error number
 */
STATIC int waitReceive(SOCKET *sock,uint timeout)
{
	for (;;){
		/* シグナル割り込みが起きたか？ */
		doSignalRestart();
		if(isSigint() == TRUE){
			return -EINTR;
		}

		if (sock->recvBuf == NULL){
			sock->waitProc = get_current_task();
			if (sys_sleep(timeout) == 0){		/* Time out. */
				if (sock->waitProc == NULL){
					return -ETIMEDOUT;
				}
			}
		}
		else{
			break;
		}
	}

	return 0;
}

/*
 * 初期化
 */
STATIC void initRecv()
{
	waitSocket.next = waitSocket.prev = &waitSocket;
}


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


/*
 * ＊＊＊ 割り込み処理関数 ＊＊＊
 * Receive ICMP frame.
 * return : 0 or task switch=1
 */
int receiveIcmp(IP_HEADER *ip)
{
	int icmpSize = getIpSize(ip) - sizeof(IP_HEADER);
	ICMP_HEADER *icmp = (ICMP_HEADER*)ip->data;

	/* チェックサムの確認。 */
	if (calcSum((uint*)icmp,icmpSize))
		return 0;

	switch (icmp->type)
	{
		case ICMP_TYPE_ECHOREQ:
			setIpTask(NULL, ip);
			break;
		case ICMP_TYPE_ECHOREP:
			return saveRecv(ip);
	}

	return 0;
}


/**************************************************************************
 *
 * UDPサービス関数
 *
 **************************************************************************/


/*
 * IPスレッドから呼び出される
 */
void doIcmpService(SOCKET *sock, IP_HEADER *ip)
{
	ICMP_HEADER *icmp = (ICMP_HEADER*)ip->data;

	if (sock == NULL){
		transIcmp(ip->srcip, ICMP_TYPE_ECHOREP, 0, icmp,getIpSize(ip) - sizeof(IP_HEADER));
	}
}


/**************************************************************************
 *
 * ソケットサービス関数
 *
 **************************************************************************/


void releaseSockIcmp(SOCKET *sock)
{
	resetWaitLink(sock);
}

STATIC int send(SOCKET *sock, void *msg, size_t len, int flags, struct sockaddr *to)
{
	TRANS_BUF_INFO tbi;


	/* バッファテーブルの設定 */
	tbi.data[2]=NULL;
	tbi.size[2]=0;
	tbi.data[1]=(char*)msg;
	tbi.size[1]=len;
	tbi.ipType=IPPROTO_ICMP;
/****************************************************************************
{
	ICMP_HEADER *head=(ICMP_HEADER*)msg;
	printk("TRANS ICMP type=%d,code=%d\n",head->type,head->code);
}
****************************************************************************/
	transIp(sock, &tbi, ((struct sockaddr_in*)to)->sin_addr.s_addr, 0);

	return len;
}


/*
 * return : copy data size or error number
 */
STATIC int recv(SOCKET *sock,struct msghdr *msg)
{
	enum{REPLY_TIMEOUT = 2000};	/* 返答タイムアウトミリ秒。 */
	int dataSize,cpSize,iovArrey,iovOffset;
	IP_BUFFER *ipBuf;
	IP_HEADER *ip;
	int rest;

	/* 受信データを取得する */
	setWaitLink(sock);
	rest = waitReceive(sock,REPLY_TIMEOUT);
	resetWaitLink(sock);
	if (rest < 0)
		return rest;
	ipBuf = sock->recvBuf;

	/* データをバッファにコピー */
	ip = &ipBuf->ip;
	dataSize = getIpSize(ip) - ipBuf->offset;
	iovArrey = iovOffset = 0;
	cpSize = copyIovec((char*)ip + ipBuf->offset,dataSize,msg->msg_iov,msg->msg_iovlen,&iovArrey,&iovOffset);
	if (iovArrey == msg->msg_iovlen)
	{
		/*
		 * ユーザーバッファがいっぱいだったら再コピーまたはEOFを送るためにIPバッファは残しておく。
		 */
		ipBuf->offset += cpSize;
		return cpSize;
	}

	sock->dstip = ip->srcip;

	/* ソケットオプションを実行する */
	if (msg->msg_control != NULL)
		doSockOpt(sock,ipBuf,msg->msg_control,msg->msg_controllen);

	/* IPバッファを開放 */
	detachRecvBuf(ipBuf, (IP_BUFFER**)&sock->recvBuf, &sock->lockGate);
	freeIpBuf(ipBuf);

	return dataSize;
}

STATIC int connect(SOCKET *a)
{
	return -EOPNOTSUPP;
}

STATIC int shutdown(SOCKET *sock,int flag)
{
	return -EOPNOTSUPP;
}

STATIC int listen(SOCKET *a,int fd)
{
	return -EOPNOTSUPP;
}

STATIC int accept(SOCKET *sock,SOCKET **newsock,uint32_t *srcip)
{
	return -EOPNOTSUPP;
}

STATIC int poll(SOCKET *sock)
{
	if (sock->recvBuf != NULL){
		return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
	}
	else{
		sock->waitProc = get_current_task();
		setWaitLink(sock);
		return POLLOUT | POLLWRNORM;
	}
}


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


STATIC SOCKET_INFO sockInfo = {send, recv, connect, shutdown, listen, accept, poll};


/*
 * GLOBAL
 * Init ICMP.
 */
int initIcmp()
{
	registSocket(&sockInfo,IPPROTO_ICMP);
	initRecv();

	return 0;
}
