/*********************************************************************************
 * 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_cUipService_protected.h"
#include "NyLPC_cTcpListener_protected.h"
#include "NyLPC_cTcpSocket_protected.h"
#include "NyLPC_stdlib.h"

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

#include "NyLPC_uip.h"
#include "../driver/uip/uip_arp.h"





/****************************************************
 * UipServiceに関する宣言:タスクメッセージ
 ***************************************************/

#define TTaskMsg_NOP 0
#define TTaskMsg_START 1
#define TTaskMsg_STOP 2

/**
 * サービスの起動メッセージです。
 */
struct TTaskMsg{
	NyLPC_TUInt16 msg;
	union{
		struct{
			const NyLPC_TcIPv4Config_t* ref_config;
		}start;
	};
};

/****************************************************
 * UipServiceに関する宣言:その他
 ***************************************************/
/**
 * イーサネットフレームの読み出し構造体
 */
struct TEthPacket
{
	struct NyLPC_TEthernetIIHeader header;
	union{
		struct NyLPC_TArpHeader arp;
		struct NyLPC_TIPv4Header ipv4;
	}data;
};



/**
 * サービスインスタンスのポインタ。サービスが稼働中はインスタンスのポインタが有効です。
 */
NyLPC_TcUipService_t* _NyLPC_TcUipService_inst=NULL;

/**
 * 唯一のインスタンス
 */
static NyLPC_TcUipService_t _service_instance;




/**
 * uipタスク
 */
static void uipTask(void *pvParameters);

//--------------------------------------------------------------


static void startEther();
static void stopEther();
static NyLPC_TBool sendIPv4Tx(struct NyLPC_TTxBufferHeader* i_eth_buf);
static void copyAndSendIPv4Tx(const struct TEthPacket* i_buf);
static void sendArpReqest(const struct TEthPacket* i_eth_packet);
static void sendRawEthFrame(void* i_buf,NyLPC_TUInt16 i_len);

/**メッセージなし*/
#define TTaskMessage_MSG_NULL    0x0000
/**uipコアタスクに、開始要求する*/
#define TTaskMessage_MSG_RUN     0x0001
/**uipコアタスクに、停止要求する*/
#define TTaskMessage_MSG_STOP    0x0002




NyLPC_TBool NyLPC_cUipService_initialize(void)
{

	NyLPC_TcUipService_t* inst=&_service_instance;
	//サービスは停止している事。 - Service must be uninitialized.
	NyLPC_Assert(!NyLPC_TcUipService_isInitService());
	//IP処理部分の初期化
	NyLPC_cIPv4_initialize(&(inst->_tcpv4));
	//EMAC割込セマフォ
	inst->_emac_semapho=xSemaphoreCreateMutex();
	NyLPC_Assert(inst->_emac_semapho!=NULL);
	inst->_task_cmd=NULL;
	inst->_status=NyLPC_TcUipService_STATUS_STOP;
	NyLPC_cStopwatch_initialize(&(inst->_arp_sw));
	NyLPC_cStopwatch_initialize(&(inst->_periodic_sw));
	NyLPC_cIPv4_initialize(&(inst->_tcpv4));
	NyLPC_AbortIfNot(NyLPC_cMutex_initialize(&(inst->_mutex)));

	_NyLPC_TcUipService_inst=inst;
	inst->stx.h.is_lock=NyLPC_TUInt8_FALSE;
	inst->stx.h.ref=0;

	//タスク起動
	NyLPC_AbortIfNot(pdPASS==xTaskCreate(
		uipTask,
		(signed char*)NyLPC_TcUipService_config_TASK_NAME,
		NyLPC_TcUipService_config_STACK_SIZE,
		( void * ) NULL,
		NyLPC_TcUipService_config_TASK_PRIORITY,
		(void*)_NyLPC_TcUipService_inst));
	return NyLPC_TBool_TRUE;
}







/**
 * UIP処理を開始します。
 * この関数はリエントラントではありません。複数のタスクから共有するときには、排他ロックを掛けてください。
 * @param i_ref_config
 * このコンフィギュレーションは、stopを実行するまでの間、インスタンスから参照します。外部で保持してください。
 */
void NyLPC_cUipService_start(const NyLPC_TcIPv4Config_t* i_ref_config)
{
	struct TTaskMsg msg;
	NyLPC_Assert(NyLPC_TcUipService_isInitService());
	if(!NyLPC_cUipService_isRun())
	{
		//コマンドセット
		msg.msg=TTaskMsg_START;
		msg.start.ref_config=i_ref_config;
		_NyLPC_TcUipService_inst->_task_cmd=&msg;
		//状態が変わるまでループ
		while(!NyLPC_cUipService_isRun()){
			vTaskDelay(10);
		}
	}
	//コマンドクリア
	_NyLPC_TcUipService_inst->_task_cmd=NULL;
	return;
}
/**
 * UIP処理を停止します。
 * この関数はリエントラントではありません。複数のタスクから共有するときには、排他ロックを掛けてください。
 * いまのところ、ストップシーケンスの実装は良くありません。
 * 再設計が必要。
 */
void NyLPC_cUipService_stop(void)
{
	struct TTaskMsg msg;
	NyLPC_TcUipService_t* inst=&_service_instance;
	NyLPC_Assert(NyLPC_TcUipService_isInitService());
	if(NyLPC_cUipService_isRun())
	{
		//コマンドセット
		msg.msg=TTaskMsg_STOP;
		_NyLPC_TcUipService_inst->_task_cmd=&msg;
		//状態が変わるまでループ
		while(NyLPC_cUipService_isRun()){
			vTaskDelay(10);
		}
		NyLPC_cIPv4_stop(&(inst->_tcpv4));

	}
	//コマンドクリア
	_NyLPC_TcUipService_inst->_task_cmd=NULL;
	return;
}






/**********************************************************************
 *
 * </HWコールバックに関わる宣言>
 *
 *********************************************************************/

#define PERIODIC_TIMER (1*1000)
#define ARP_TIMER (60*1000*10)

/**
 * 操作キューを確認して、タスクのステータスをアップデートします。
 * 高速化のため、Proc-Callerを使用していません。複雑なタスク操作をするときには、書き換えてください。
 */
static void updateTaskStatus()
{
	NyLPC_TcUipService_t* inst=_NyLPC_TcUipService_inst;

	struct TTaskMsg* msg=(struct TTaskMsg*)(inst->_task_cmd);

	//コマンドが設定されていない時は、何もしない。
	if(msg==NULL){
		return;
	}
	switch(msg->msg)
	{
	//何もしない。
	case TTaskMsg_NOP:
		break;
	//開始操作
	case TTaskMsg_START:
		//状態チェック
		if(NyLPC_cUipService_isRun()){
			break;
		}
		inst->_ref_config=msg->start.ref_config;
		//TCP,ICOMPの初期化
		NyLPC_cIPv4_start(&(inst->_tcpv4),inst->_ref_config);
		NyLPC_cIPv4IComp_initialize(&(inst->_icomp),inst->_ref_config);
		uip_arp_init(msg->start.ref_config);
		NyLPC_cStopwatch_startExpire(&(inst->_arp_sw),1);//1度ARPを起動するため。
		NyLPC_cStopwatch_startExpire(&(inst->_periodic_sw),PERIODIC_TIMER);
		//開始操作
		startEther();
		inst->_status=NyLPC_TcUipService_STATUS_RUN;
		break;
	//終了操作
	case TTaskMsg_STOP:
		//状態チェック
		if(!NyLPC_cUipService_isRun()){
			break;
		}
		//停止操作
		stopEther();
		NyLPC_cIPv4_stop(&(inst->_tcpv4));
		NyLPC_cIPv4IComp_finalize(&(inst->_icomp));
		inst->_status=NyLPC_TcUipService_STATUS_STOP;
		break;
	default:
		//実行してはいけない。
		NyLPC_Abort();
	}
	//コマンドを無効化
	inst->_task_cmd=TTaskMsg_NOP;
	return;
}

/**
 * uipタスクのメインループ
 */
static void uipTask(void *pvParameters)
{
	NyLPC_TUInt16 rx_len,tx_len;
	struct TEthPacket* ethbuf;
	NyLPC_TcUipService_t* inst=_NyLPC_TcUipService_inst;
	NyLPC_TBool r;
	(void)pvParameters;
	for( ;; )
	{
		//タスク状態の更新
		updateTaskStatus();
		if(inst->_status!=NyLPC_TcUipService_STATUS_RUN)
		{
			//RUNステータス以外の時は、ここで終了する。
			vTaskDelay(100/portTICK_RATE_MS);
			continue;
		}
		//イーサネットフレームの取得
		//Ethernet Device Lock(ARPを含む)
		NyLPC_cMutex_lock(&(inst->_mutex));
		ethbuf= (struct TEthPacket*)getRxEthFrame(&rx_len);
		tx_len=0;
		while(ethbuf != NULL){
			if(rx_len>0)
			{
				//ペイロードサイズを計算
				rx_len-=sizeof(struct NyLPC_TEthernetIIHeader);
				switch(ethbuf->header.type)
				{
				case NyLPC_HTONS(NyLPC_TEthernetIIHeader_TYPE_IP):
					//ARPテーブルの更新
					uip_arp_ipin(&(ethbuf->header),ethbuf->data.ipv4.srcipaddr);
					//Ethernet Device UnLock(パケット解析の為に一時的な解除)
					NyLPC_cMutex_unlock(&(inst->_mutex));
					//IPパケットの処理
					r=NyLPC_cIPv4_rx(&(inst->_tcpv4),&(ethbuf->data.ipv4),rx_len);
					//ロックの復帰
					NyLPC_cMutex_lock(&(inst->_mutex));
					if(!r){
						//応答データは存在しない。
						break;
					}
					//IPパケットをTxバッファに転写して送信
					copyAndSendIPv4Tx(ethbuf);
					ethbuf=NULL;
					break;
				case NyLPC_HTONS(NyLPC_TEthernetIIHeader_TYPE_ARP):
					if(uip_arp_arpin(&(ethbuf->data.arp),rx_len)){
						tx_len=NyLPC_TEthernetIIHeader_setArpTx(&(ethbuf->header),&(inst->_ref_config->eth_mac));
					}
					if(tx_len>0){
						sendRawEthFrame(ethbuf,tx_len);
					}
					break;
				case NyLPC_HTONS(NyLPC_TEthernetIIHeader_TYPE_IPV6):
					//uip_process_ipv6();
					break;
				default:
					break;
				}
			}
			//受信キューを進行。
			nextRxEthFrame();
			//受信処理
			ethbuf= (struct TEthPacket*)getRxEthFrame(&rx_len);
		}
		//データが無い。
		if(NyLPC_cStopwatch_isExpired(&(inst->_arp_sw))){
			uip_arp_timer();
			NyLPC_cStopwatch_startExpire(&(inst->_arp_sw),ARP_TIMER);
		}
		if(NyLPC_cStopwatch_isExpired(&(inst->_periodic_sw))){
			NyLPC_cIPv4_periodec(&(inst->_tcpv4));
			NyLPC_cStopwatch_startExpire(&(inst->_periodic_sw),PERIODIC_TIMER);
		}
		//リソースロックの解除
		NyLPC_cMutex_unlock(&(inst->_mutex));
		//割込によるセマフォの解除か、タイムアウトで再開する。
		xSemaphoreTake(_NyLPC_TcUipService_inst->_emac_semapho, configTICK_RATE_HZ / 2 );
	}
}




/**
 * allocTxBufで取得したメモリを"IPパケットとして"送信します。
 * @param i_eth_payload
 * [NyLPC_TTxBufferHeader][NyLPC_TEthernetIIHeader][payload]メモリの、[payload]のアドレスを指定します。
 * 通常は、NyLPC_cUipService_allocTxBufの返却したメモリを指定します。
 */

void NyLPC_cUipService_sendIPv4Tx(void* i_eth_payload)
{
	NyLPC_TcUipService_t* inst=_NyLPC_TcUipService_inst;
	NyLPC_cMutex_lock(&(inst->_mutex));
	//IPパケットの送信を試行
	if(!sendIPv4Tx(((struct NyLPC_TTxBufferHeader*)(((struct NyLPC_TEthernetIIHeader*)i_eth_payload)-1))-1)){
		//ARPリクエストを代わりに送信
		sendArpReqest(((struct TEthPacket*)i_eth_payload)-1);
	}
	NyLPC_cMutex_unlock(&(inst->_mutex));
	return;
}




/**
 * 送信ペイロードメモリを返します。
 * この関数は、リエントラントを許容します。
 * @param i_hint
 * 取得したいメモリサイズを指定します。(このサイズは、イーサネットヘッダのサイズを含みません。)
 * このサイズよりも小さなサイズが割り当てられることがあります。
 * @return
 * IPペイロードのためのメモリブロックを返します。
 */
void* NyLPC_cUipService_allocTxBuf(NyLPC_TUInt16 i_hint,NyLPC_TUInt16* o_size)
{
	//排他処理をして、メモリを取得する。
	NyLPC_TcUipService_t* inst=_NyLPC_TcUipService_inst;
	struct NyLPC_TTxBufferHeader* ethbuf;
	NyLPC_cMutex_lock(&(inst->_mutex));
	ethbuf=(struct NyLPC_TTxBufferHeader*)allocTxBuf(i_hint+sizeof(struct NyLPC_TEthernetIIHeader),o_size);
	NyLPC_cMutex_unlock(&(inst->_mutex));
	if(ethbuf!=NULL){
		//イーサネットバッファのサイズを計算
		*o_size-=sizeof(struct NyLPC_TEthernetIIHeader);
		//イーサネットバッファのアドレスを計算
		return &(((struct TEthPacket*)(ethbuf+1))->data);
	}
	return NULL;
}


void* NyLPC_cUipService_releaseTxBuf(void* i_buf)
{
	//排他処理をして、メモリを開放する。
	NyLPC_TcUipService_t* inst=_NyLPC_TcUipService_inst;
	NyLPC_cMutex_lock(&(inst->_mutex));
	//ペイロードの位置から、メモリブロックを再生。
	releaseTxBuf(((struct NyLPC_TTxBufferHeader*)(((struct NyLPC_TEthernetIIHeader*)i_buf)-1))-1);
	NyLPC_cMutex_unlock(&(inst->_mutex));
	return NULL;
}








/**********
 * イーサネットHWのコントロール関数
 */

/**
 * 新たにメモリを確保して、"IPv4パケットを格納した"イーサフレームを送信します。
 * コール前に、必ずロックしてから呼び出してください。
 */
static void copyAndSendIPv4Tx(const struct TEthPacket* i_buf)
{
	NyLPC_TcUipService_t* inst=_NyLPC_TcUipService_inst;
	NyLPC_TUInt16 s;
	//IPタスクの送信バッファが未使用になるまで待つ。
	while(inst->stx.h.is_lock){
		waitForTxEthFrameEmpty();
	}
	//送信する。
	s=NyLPC_htons(i_buf->data.ipv4.len16)+sizeof(struct NyLPC_TEthernetIIHeader);
	memcpy(inst->stx.buf,i_buf,s);
	if(!sendIPv4Tx(&(inst->stx.h))){
		//失敗した場合はARPリクエストに変換して再送
		sendArpReqest(i_buf);
	}
	return;
}
/**
 * IPv4パケットのpeerIPを問い合わせるARPパケットを送信します。
 */
static void sendArpReqest(const struct TEthPacket* i_eth_packet)
{
	NyLPC_TcUipService_t* inst=_NyLPC_TcUipService_inst;
	NyLPC_TUInt16 tx_len;
	struct TEthPacket* ethbuf;
	//IPタスクの送信バッファが未使用になるまで待つ。
	while(inst->stx.h.is_lock){
		waitForTxEthFrameEmpty();
	}
	//ARPパケットを作る。
	ethbuf=(struct TEthPacket*)(inst->stx.buf);
	NyLPC_TArpHeader_setArpRequest(&(ethbuf->data.arp),inst->_ref_config->ip_addr,&(inst->_ref_config->eth_mac),i_eth_packet->data.ipv4.destipaddr);
	tx_len=NyLPC_TEthernetIIHeader_setArpTx(&(ethbuf->header),&(inst->_ref_config->eth_mac));
	//送信
	sendTxEthFrame(&(inst->stx.h),tx_len);
}

/**
 * uipタスクが所有するTXバッファを使用してデータを送信します。
 * この関数は、i_bufをコピーします。
 * @i_buf
 * イーサネットフレームを格納したメモリです。
 * @i_len
 * イーサネットペイロードのサイズです。
 */
static void sendRawEthFrame(void* i_buf,NyLPC_TUInt16 i_len)
{
	NyLPC_TcUipService_t* inst=_NyLPC_TcUipService_inst;

	//自分の送信バッファが空くまで待つ。
	while(inst->stx.h.is_lock){
		waitForTxEthFrameEmpty();
	}
	//64バイトを超えるとかありえない。
	if(i_len+sizeof(struct NyLPC_TEthernetIIHeader)>NyLPC_TcUipService_SIZE_OF_REPLY_BUF){
		return;
	}
	//送信する。
	memcpy(inst->stx.buf,i_buf,i_len);
	sendTxEthFrame(&(inst->stx.h),i_len);
	return;
}


/**
 * ペイロードをIPパケットとしてネットワークへ送出します。
 * コール前に、必ずロックしてから呼び出してください。
 * @param i_eth_payload
 * allocTxBufで確保したメモリを指定してください。
 * ペイロードには、TCP/IPパケットを格納します。
 */
static NyLPC_TBool sendIPv4Tx(struct NyLPC_TTxBufferHeader* i_eth_buf)
{
	NyLPC_TcUipService_t* inst=_NyLPC_TcUipService_inst;
	NyLPC_TUInt16 tx_len;
	struct TEthPacket* ethbuf=(struct TEthPacket*)(i_eth_buf+1);
NyLPC_Trace();
	//ペイロードのアドレスから、イーサネットフレームバッファのアドレスを復元
	const struct NyLPC_TEthAddr* eth_dest=uip_arp_IPv4toEthAddr(ethbuf->data.ipv4.destipaddr);
	//IP->MAC変換をテスト。
	if(eth_dest==NULL){
		//失敗
NyLPC_Trace();
		return NyLPC_TBool_FALSE;
	}
NyLPC_Trace();
	//変換可能なら、イーサネットヘッダを更新して、送信処理へ。
	tx_len=NyLPC_TEthernetIIHeader_setIPv4Tx(&(ethbuf->header),&(inst->_ref_config->eth_mac),eth_dest);
	sendTxEthFrame(i_eth_buf,tx_len);
NyLPC_Trace();
	return NyLPC_TBool_TRUE;
}



static void startEther()
{
	NyLPC_TcUipService_t* inst=_NyLPC_TcUipService_inst;

	//Ethernetの起動待ち
	while(lEMACInit(_NyLPC_TcUipService_inst->_emac_semapho,&(inst->_ref_config->eth_mac))!= pdPASS )
    {
        vTaskDelay( 100 / portTICK_RATE_MS );
    }
	//Ethernetの割込み開始設定
	portENTER_CRITICAL();
	{
		LPC_EMAC->IntEnable = ( INT_RX_DONE | INT_TX_DONE );
		/* Set the interrupt priority to the max permissible to cause some
		interrupt nesting. */
		NVIC_SetPriority( ENET_IRQn, configEMAC_INTERRUPT_PRIORITY );

		/* Enable the interrupt. */
		NVIC_EnableIRQ( ENET_IRQn );
	}
	portEXIT_CRITICAL();
}

static void stopEther()
{
	portENTER_CRITICAL();
	{
		LPC_EMAC->IntEnable = (~(INT_RX_DONE|INT_TX_DONE))&LPC_EMAC->IntEnable;
		NVIC_DisableIRQ( ENET_IRQn );
	}
	portEXIT_CRITICAL();
}

