/*********************************************************************************
 * PROJECT: MiMic
 * --------------------------------------------------------------------------------
 *
 * This file is part of MiMic
 * Copyright (C)2011 Ryo Iizuka
 *
 * MiMic is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by　the Free Software Foundation, either version 3 of the　License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * For further information please contact.
 *	http://nyatla.jp/
 *	<airmail(at)ebony.plala.or.jp> or <nyatla(at)nyatla.jp>
 *
 *
 * Parts of this file were leveraged from uIP:
 *
 * Copyright (c) 2001-2003, Adam Dunkels.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "NyLPC_stdlib.h"
#include "NyLPC_cIPv4.h"
#include "NyLPC_cIPv4Payload_protected.h"
#include "NyLPC_cTcpSocket_protected.h"
#include "NyLPC_cTcpListener_protected.h"
#include "NyLPC_cIPv4IComp_protected.h"
#include "NyLPC_cUipService_protected.h"



/****************************************************
 * Listenerテーブルに関する宣言
 ***************************************************/




#define cListenerTbl_initialize(i_inst,buf) NyLPC_cPtrTbl_initialize(i_inst,buf,NyLPC_cIPv4_MAX_TCP_LISTENER)

/**
 * i_peer番号に一致するリスナを返します。
 */
static NyLPC_TcTcpListener_t* cListenerTbl_getByPeerPort(NyLPC_TcPtrTbl_t* i_inst,NyLPC_TUInt16 i_peer_port)
{
	NyLPC_TcTcpListener_t** p=(NyLPC_TcTcpListener_t**)(i_inst->buf);
	int i;
	//一致するポートを検索して、acceptをコールする。
	for(i=i_inst->size-1;i>=0;i--){
		if(p[i]==NULL){
			continue;
		}
		if(p[i]->_port!=i_peer_port){
			continue;
		}
		return p[i];
	}
	return NULL;
}

/****************************************************
 * Socketテーブルに関する宣言
 ***************************************************/

#define cSocketTbl_initialize(i_inst,buf) NyLPC_cPtrTbl_initialize(i_inst,buf,NyLPC_cIPv4_MAX_TCP_SOCKET)


/**
 * 条件に一致する、アクティブなソケットオブジェクトを取得します。
 * この関数は、ローカルIPが一致していると仮定して検索をします。
 * @param i_rip
 * リモートIPアドレスを指定します。
 */
static NyLPC_TcTcpSocket_t* cSocketTbl_getMatchSocket(
	NyLPC_TcPtrTbl_t* i_inst,
	NyLPC_TUInt16 i_lport,
	struct NyLPC_TIPv4Addr i_rip,
	NyLPC_TUInt16 i_rport)
{
	NyLPC_TcTcpSocket_t** p=(NyLPC_TcTcpSocket_t**)(i_inst->buf);
	int i;
	//一致するポートを検索して、acceptをコールする。
	for(i=i_inst->size-1;i>=0;i--){
		if(p[i]==NULL){
			continue;
		}
		if(p[i]->tcpstateflags==UIP_CLOSED){
			continue;
		}
		//パラメータの一致チェック
		if(i_lport!=p[i]->uip_connr.lport || i_rport!= p[i]->uip_connr.rport || i_rip.v!=p[i]->uip_connr.ripaddr.v)
		{
			continue;
		}
		return p[i];
	}
	return NULL;
}
/**
 * テーブルにある有効なソケットのperiodicをすべて呼び出します。
 */
static void cSocketTbl_callPeriodic(
	NyLPC_TcPtrTbl_t* i_inst)
{
	NyLPC_TcTcpSocket_t** p=(NyLPC_TcTcpSocket_t**)(i_inst->buf);
	int i;
	for(i=i_inst->size-1;i>=0;i--){
		if(p[i]==NULL){
			continue;
		}
		NyLPC_cTcpSocket_periodic(p[i]);
	}
}

/**
 * Static関数
 */

static NyLPC_TBool tcp_rx(
	NyLPC_TcIPv4_t* i_inst,
	NyLPC_TcIPv4Payload_t* i_ipp);

/****************************************************
 * NyLPC_cIPv4
 ***************************************************/

void NyLPC_cIPv4_initialize(
	NyLPC_TcIPv4_t* i_inst)
{
	//内部テーブルの初期化
	cListenerTbl_initialize(&(i_inst->_listener_tbl),i_inst->_listener_array_buf);
	cSocketTbl_initialize(&(i_inst->_socket_tbl),i_inst->_socket_array_buf);
	//instanceの初期化
	i_inst->_ref_config=NULL;
	return;
}
void NyLPC_cIPv4_finalize(
	NyLPC_TcIPv4_t* i_inst)
{
	return;
}
void NyLPC_cIPv4_start(
	NyLPC_TcIPv4_t* i_inst,
	const NyLPC_TcIPv4Config_t* i_ref_configlation)
{
	NyLPC_ArgAssert(i_ref_configlation!=NULL);
	i_inst->_ref_config=i_ref_configlation;
	return;
}

void NyLPC_cIPv4_stop(
	NyLPC_TcIPv4_t* i_inst)
{
	//実行タイミングが未設計。当分は使わないこと。
	//新規ソケットの生成を打ち切って、すべてのソケットが停止するのを待つ？
	i_inst->_ref_config=NULL;
	return;
}

NyLPC_TBool NyLPC_cIPv4_addSocket(
	NyLPC_TcIPv4_t* i_inst,
	NyLPC_TcTcpSocket_t* i_sock)
{
	//当面、stop中しか成功しない。
	NyLPC_Assert(!NyLPC_cUipService_isRun());
	return NyLPC_cPtrTbl_add(&(i_inst->_socket_tbl),i_sock)>=0;
}

NyLPC_TBool NyLPC_cIPv4_removeSocket(
	NyLPC_TcIPv4_t* i_inst,
	NyLPC_TcTcpSocket_t* i_sock)
{
	NyLPC_TUInt16 i;
	NyLPC_Assert(!NyLPC_cUipService_isRun());
	i=NyLPC_cPtrTbl_getIndex(&(i_inst->_socket_tbl),i_sock);
	if(i>=0){
		NyLPC_cPtrTbl_remove(&(i_inst->_socket_tbl),i);
		return NyLPC_TBool_TRUE;
	}
	return NyLPC_TBool_FALSE;
}
NyLPC_TBool NyLPC_cIPv4_addListener(
	NyLPC_TcIPv4_t* i_inst,
	NyLPC_TcTcpListener_t* i_listener)
{
	//当面、stop中しか成功しない。
	NyLPC_Assert(!NyLPC_cUipService_isRun());
	return NyLPC_cPtrTbl_add(&(i_inst->_listener_tbl),i_listener)>=0;
}
NyLPC_TBool NyLPC_cIPv4_removeListener(
	NyLPC_TcIPv4_t* i_inst,
	NyLPC_TcTcpListener_t* i_listener)
{
	NyLPC_TUInt16 i;
	NyLPC_Assert(!NyLPC_cUipService_isRun());
	i=NyLPC_cPtrTbl_getIndex(&(i_inst->_listener_tbl),i_listener);
	if(i>=0){
		NyLPC_cPtrTbl_remove(&(i_inst->_listener_tbl),i);
		return NyLPC_TBool_TRUE;
	}
	return NyLPC_TBool_FALSE;
}





#define IS_START(i_inst) ((i_inst)->_ref_config!=NULL)

/**
 * 稼動時に、1s置きに呼び出す関数です。
 */
void NyLPC_cIPv4_periodec(NyLPC_TcIPv4_t* i_inst)
{
	cSocketTbl_callPeriodic(&(i_inst->_socket_tbl));
}


/**
 * IPv4ペイロードを処理する関数。
 * この関数は、パケット受信タスクから実行します。
 * @param i_rx
 * 先頭ポインタ。
 * @return
 * TRUEなら、i_rxに応答パケットをセットして返します。
 */
NyLPC_TBool NyLPC_cIPv4_rx(NyLPC_TcIPv4_t* i_inst,void* i_rx,NyLPC_TUInt16 i_rx_size)
{
	NyLPC_TcUipService_t* inst=_NyLPC_TcUipService_inst;
	NyLPC_TcIPv4Payload_t ipv4;
	//NOT開始状態なら受け付けないよ。
	if(!IS_START(i_inst)){
		NyLPC_OnErrorGoto(ERROR_DROP);
	}

	NyLPC_cIPv4Payload_initialize(&ipv4);
	//IPフラグメントを読出し用にセットする。
	if(!NyLPC_cIPv4Payload_setRxBuf(&ipv4,i_rx,i_rx_size))
	{
		NyLPC_OnErrorGoto(ERROR_DROP);
	}
	switch(ipv4.header->proto)
	{
	case UIP_PROTO_TCP:
		//TCP受信処理
		return tcp_rx(i_inst,&ipv4);
	case UIP_PROTO_UDP:
		//	//uip_process_UDP();//実装したら解除いして
		NyLPC_OnErrorGoto(ERROR_DROP);
	case UIP_PROTO_ICMP:
		if(!NyLPC_cIPv4IComp_rx(&(inst->_icomp),&ipv4)){
			NyLPC_OnErrorGoto(ERROR_DROP);
		}
		return NyLPC_TBool_TRUE;
	}
	return NyLPC_TBool_FALSE;
ERROR_DROP:
	return NyLPC_TBool_FALSE;
}


/**********************************************************************
 *
 * public function
 *
 **********************************************************************/


static NyLPC_TBool tcp_rx(
	NyLPC_TcIPv4_t* i_inst,
	NyLPC_TcIPv4Payload_t* i_ipp)
{
	NyLPC_TcTcpSocket_t* sock;
	NyLPC_TcTcpListener_t* listener;

	//自分自身のIPに対する呼び出し？
	if(!NyLPC_TIPv4Addr_isEqual(&(i_ipp->header->destipaddr),&(i_inst->_ref_config->ip_addr)))
	{
		//自分以外のパケットはドロップ
		goto DROP;
	}
	//チェックサムの計算
	if((NyLPC_TIPv4Header_makeTcpChecksum(i_ipp->header) != 0xffff))
	{
		//受信エラーのあるパケットはドロップ
		goto DROP;
	}
	//アクティブなソケットを探す。
	sock=cSocketTbl_getMatchSocket(&(i_inst->_socket_tbl),i_ipp->payload.tcp->destport,i_ipp->header->srcipaddr,i_ipp->payload.tcp->srcport);
	if(sock!=NULL)
	{
		//既存の接続を処理
		return NyLPC_cTcpSocket_parseRx(sock,i_ipp);

	}else{
		//未知の接続
		if(!NyLPC_cPtrTbl_hasEmpty(&(i_inst->_socket_tbl))){
			//ソケットテーブルが不十分。RST送信
			NyLPC_cIPv4Payload_setTcpReverseRstAck(i_ipp);
			return NyLPC_TBool_TRUE;
		}
		//このポートに対応したListenerを得る。
		listener=cListenerTbl_getByPeerPort(&(i_inst->_listener_tbl),i_ipp->payload.tcp->destport);
		if(listener==NULL){
			//Listen対象ではない。RST送信
			NyLPC_cIPv4Payload_setTcpReverseRstAck(i_ipp);
			return NyLPC_TBool_TRUE;
		}
		//リスナにソケットのバインドを依頼する。
		sock=NyLPC_cTcpListener_makeSynRcvdSocket(listener,i_inst->_ref_config,i_ipp);
		if(sock==NULL){
			//Listen失敗。ドロップ
			goto DROP;
		}
//		//Socketの割当に成功。管理リストへ追加
//		NyLPC_AbortIfNot(NyLPC_cPtrTbl_add(&(i_inst->_socket_tbl),sock)>=0);
		return NyLPC_TBool_FALSE;//LISTEN成功。送信データなし
	}
	return NyLPC_TBool_TRUE;
DROP:
	return NyLPC_TBool_FALSE;
}






