/*	
 *	clipip.c
 *
 *	IPプロトコルドライバ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Wed Aug 25 05:43:00 JST 2005 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sat Aug 16 15:46:20 JST 2014 Naoyuki Sawa
 *	- get_psr(),set_psr()の実装変更によりENABLEDマクロが無くなったため、ENABLEDマクロを使用していた箇所を新しいget_psr()に合わせて修正しました。
 */
#include "clip.h"

#define DEFAULT_TIMEOUT	1000	/* タイムアウト時間の既定値[ミリ秒] */

/****************************************************************************
 *	ARPプロトコルドライバ
 ****************************************************************************/

#define ARP_TABLE_SIZE	16	/* ARPテーブルの要素数 (調整可) */

/* ARPプロトコルドライバ構造体 */
typedef struct _ARPDRIVER {
	ETHERNETCLIENT ethernet_client;		/* Ethernetクライアント登録用 */
	ARPENTRY table[ARP_TABLE_SIZE];		/* ARPテーブル */
} ARPDRIVER;
static ARPDRIVER arp_driver;

static void arp_start();
static void arp_stop();
static void arp_recv(ETHERNETCLIENT* ethernet_client, const unsigned char* address/*[6]*/, const unsigned char* data/*[length]*/, int length);
static void arp_conf(ETHERNETCLIENT* ethernet_client);
static int arp_get_address(int ip_address, unsigned char ethernet_address[6]);
static void arp_set_address(int ip_address, const unsigned char ethernet_address[6]);

/*--------------------------------------------------------------------------*/

static void
arp_start()
{
	/* ドライバ構造体をクリアします。 */
	memset(&arp_driver, 0, sizeof arp_driver);

	/* ARPプロトコルドライバを開始します。 */
	arp_driver.ethernet_client.type = 0x0806/*ARP*/;
	arp_driver.ethernet_client.recv = arp_recv;
	arp_driver.ethernet_client.conf = arp_conf;
	ethernet_open(&arp_driver.ethernet_client);
}

static void
arp_stop()
{
	/* ARPプロトコルドライバを停止します。 */
	ethernet_close(&arp_driver.ethernet_client);

	/* ドライバ構造体をクリアします。 */
	memset(&arp_driver, 0, sizeof arp_driver);
}

static void
arp_recv(ETHERNETCLIENT* ethernet_client, const unsigned char* address/*[6]*/, const unsigned char* _data/*[length]*/, int length)
{
	unsigned char* data = (unsigned char*)_data;
	//
	unsigned char dummy[6];
	IPCONFIG ip_config;
	ip_get_config(&ip_config);

	if(length < 28) {
		return;
	}
	if(BEHALF(data + 0) != 1) {			/* + 0,2: ハードウェアアドレスの種類 (Ethernet) */
		return;
	}
	if(BEHALF(data + 2) != 0x0800) {		/* + 2,2: 論理アドレスの種類 (IP) */
		return;
	}
	if(BEBYTE(data + 4) != 6) {			/* + 4,1: ハードウェアアドレス長 */
		return;
	}
	if(BEBYTE(data + 5) != 4) {			/* + 5,1: 論理アドレス長 */
		return;
	}

	switch(BEHALF(data + 6)) {			/* + 6,2: オペレーションコード */
	case 1: /* ARP要求 */

		/* 宛て先IPアドレスが、自分のIPアドレスに一致したら... */
		if(BEWORD(data + 24) == ip_config.ip_address) {			/* +24,  4: 宛て先論理アドレス */

			/* 送信元IPアドレスとEthernetアドレスの対応を、IP->Ethernetアドレス変換表に登録します。 */
			arp_set_address(BEWORD(data + 14),			/* +14,  4: 送信元論理アドレス */
					       data +  8);			/* + 8,  6: 送信元ハードウェアアドレス */

			/* ARP応答パケットを返送します。 */
			          PUT_BEHALF(data +  6, 2);			/* + 6,  2: オペレーションコード (ARP応答) */
			              memcpy(data + 18, data + 8, 6 + 4);	/* +18,6+4: 宛て先ハードウェアアドレス、宛て先論理アドレス */
			ethernet_get_address(data +  8);			/* + 8,  6: 送信元ハードウェアアドレス */
			          PUT_BEWORD(data + 14, ip_config.ip_address);	/* +14,  4: 送信元論理アドレス */
			ethernet_send(&arp_driver.ethernet_client, address, data, 28);

		/* 宛て先IPアドレスが、自分のIPアドレスに一致しなければ... */
		} else {

			/* 送信元IPアドレスが、既にARPテーブルに登録されている場合のみ、更新します。(Gratuitous ARP) */
			if(arp_get_address(BEWORD(data + 14), dummy)) {		/* +14,  4: 送信元論理アドレス */
			   arp_set_address(BEWORD(data + 14),			/* +14,  4: 送信元論理アドレス */
						  data +  8);			/* + 8,  6: 送信元ハードウェアアドレス */
			}
		}

		return;

	case 2: /* ARP応答 */
	case 3: /* RARP要求 */
	case 4: /* RARP応答 */

		/* 宛て先IPアドレスが、自分のIPアドレスに一致したら... */
		if(BEWORD(data + 24) == ip_config.ip_address) {			/* +24,  4: 宛て先論理アドレス */

			/* 送信元IPアドレスとEthernetアドレスの対応を、IP->Ethernetアドレス変換表に登録します。 */
			arp_set_address(BEWORD(data + 14),			/* +14,  4: 送信元論理アドレス */
					       data +  8);			/* + 8,  6: 送信元ハードウェアアドレス */

		/* 宛て先IPアドレスが、自分のIPアドレスに一致しなければ... */
		} else {

			/* 送信元IPアドレスが、既にARPテーブルに登録されている場合のみ、更新します。(Gratuitous ARP) */
			if(arp_get_address(BEWORD(data + 14), dummy)) {		/* +14,  4: 送信元論理アドレス */
			   arp_set_address(BEWORD(data + 14),			/* +14,  4: 送信元論理アドレス */
						  data +  8);			/* + 8,  6: 送信元ハードウェアアドレス */
			}
		}

		return;
	}
}

static void
arp_conf(ETHERNETCLIENT* ethernet_client)
{
	/* Ethernetアドレスが変化しました。
	 * ネットワーク設定の条件が整っていれば、Gratuitous ARPを送出します。
	 */
	arp_gratuitous();

	/* 2005/08/27現在、残念ながら、ここで送出するGratuitous ARPはLAN側ホストへ届きません。
	 * 詳しくは、arp_gratuitous()関数宣言のコメントを参照してください。
	 * Windows XP側のネットワークブリッジ初期化タイミングが遅いために、LAN側ホストへ届かないのですが、
	 * ここでGratuitous ARPを送出すること自体は間違っていないので、このまま残しておくことにします。(無害です)
	 */
}

int
arp_gratuitous()
{
	int retval;
	unsigned char ethernet_address[6];
	IPCONFIG ip_config;
	unsigned char data[28];

	/* Ethernetアドレスが未設定ならば、Gratuitous ARPは発行しません。 */
	ethernet_get_address(ethernet_address);
	if(memcmp(ethernet_address, ethernet_null_address, 6) == 0) {
		return -1;
	}

	/* IPアドレスが未設定ならば、Gratuitous ARPは発行しません。 */
	ip_get_config(&ip_config);
	if(!ip_config.ip_address) {
		return -1;
	}

#if 1	/*----------------------------------------------------------------------------------------------*/

	/* Gratuitous ARPを発行します。(自分のIPアドレスのARP要求をブロードキャスト) */
	PUT_BEHALF(data +  0, 1);			/* + 0,2: ハードウェアアドレスの種類 (Ethernet) */
	PUT_BEHALF(data +  2, 0x0800);			/* + 2,2: 論理アドレスの種類 (IP) */
	PUT_BEBYTE(data +  4, 6);			/* + 4,1: ハードウェアアドレス長 */
	PUT_BEBYTE(data +  5, 4);			/* + 5,1: 論理アドレス長 */
	PUT_BEHALF(data +  6, 1);			/* + 6,2: オペレーションコード (ARP要求) */
	    memcpy(data +  8, ethernet_address, 6);	/* + 8,6: 送信元ハードウェアアドレス (自分) */
	PUT_BEWORD(data + 14, ip_config.ip_address);	/* +14,4: 送信元論理アドレス         (自分) */
	    memcpy(data + 18, ethernet_null_address, 6);/* +18,6: 宛て先ハードウェアアドレス (Null) */
	PUT_BEWORD(data + 24, ip_config.ip_address);	/* +24,4: 宛て先論理アドレス         (自分) */
	retval = ethernet_send(&arp_driver.ethernet_client, ethernet_broadcast_address, data, 28);
	if(retval < 0) {
		return -1; /* 送信キューがいっぱい */
	}

#else	/*-------------------- Gratuitous ARPの仕様では、↑↓どちらでもOKみたいです --------------------*/

	/* Gratuitous ARPを発行します。(自分のIPアドレスのARP応答をブロードキャスト) */
	PUT_BEHALF(data +  0, 1);			/* + 0,2: ハードウェアアドレスの種類 (Ethernet) */
	PUT_BEHALF(data +  2, 0x0800);			/* + 2,2: 論理アドレスの種類 (IP) */
	PUT_BEBYTE(data +  4, 6);			/* + 4,1: ハードウェアアドレス長 */
	PUT_BEBYTE(data +  5, 4);			/* + 5,1: 論理アドレス長 */
	PUT_BEHALF(data +  6, 2);			/* + 6,2: オペレーションコード (ARP応答) */
	    memcpy(data +  8, ethernet_address, 6);	/* + 8,6: 送信元ハードウェアアドレス (自分) */
	PUT_BEWORD(data + 14, ip_config.ip_address);	/* +14,4: 送信元論理アドレス         (自分) */
	    memcpy(data + 18, ethernet_address, 6);	/* +18,6: 宛て先ハードウェアアドレス (自分) */
	PUT_BEWORD(data + 24, ip_config.ip_address);	/* +24,4: 宛て先論理アドレス         (自分) */
	retval = ethernet_send(&arp_driver.ethernet_client, ethernet_broadcast_address, data, 28);
	if(retval < 0) {
		return -1; /* 送信キューがいっぱい */
	}

#endif	/*----------------------------------------------------------------------------------------------*/

	return 0;
}

/* ARPテーブルから、指定されたIPアドレスに対応する、Ethernetアドレスを取得します。
 * 取得した要素を、ARPテーブルの先頭へ移動します。(MRU)
 */
/* ★割り込み禁止状態で呼び出してください!!★ */
static int
arp_get_address(int ip_address, unsigned char* ethernet_address/*[6]*/)
{
	int i;

	/* 指定されたIPアドレスに一致する要素を検索します。 */
	for(i = 0; i < ARP_TABLE_SIZE; i++) {
		if(arp_driver.table[i].ip_address == ip_address) {
			/* 一致した要素のEthernetアドレスを、指定されたバッファへ格納します。 */
			memcpy(ethernet_address, arp_driver.table[i].ethernet_address, 6);
			/* 一致した要素よりも前の要素を、ひとつづつ後ろへずらします。 */
			memmove(&arp_driver.table[1], &arp_driver.table[0], sizeof(ARPENTRY) * i);
			/* 一致した要素と同じ内容を、先頭の要素に格納します。 */
			arp_driver.table[0].ip_address = ip_address;
			memcpy(arp_driver.table[0].ethernet_address, ethernet_address, 6);
			return 0; /* 成功 */
		}
	}

	return -1; /* 失敗 */
}

/* ARPテーブルに、IPアドレスとEthernetアドレスの組合せを登録、または、更新します。
 * 登録、または、更新した要素を、ARPテーブルの先頭へ移動します。(MRU)
 */
/* ★割り込み禁止状態で呼び出してください!!★ */
static void
arp_set_address(int ip_address, const unsigned char* ethernet_address/*[6]*/)
{
	int i;

	/* 指定されたIPアドレスに一致する要素、または、末尾の要素を削除し、
	 * 削除した要素よりも前の要素を、ひとつづつ後ろへずらします。
	 */
	for(i = 0; i < ARP_TABLE_SIZE - 1/*←注目!!*/; i++) {
		if(arp_driver.table[i].ip_address == ip_address) {
			break;
		}
	}
	memmove(&arp_driver.table[1], &arp_driver.table[0], sizeof(ARPENTRY) * i);

	/* 先頭の要素に、IPアドレスとEthernetアドレスの対応を格納します。 */
	arp_driver.table[0].ip_address = ip_address;
	memcpy(arp_driver.table[0].ethernet_address, ethernet_address, 6);
}

int
arp_request(int ip_address, unsigned char* ethernet_address/*[6]*/, int timeout)
{
	int T0 = pceTimerGetCount();	/* タイムアウト判定の基準時刻 */
	int send_ok = 0;		/* ARP要求パケットを送信したら、1になります */
	//
	int retval;

	/* 割り込み許可状態で呼ばれた場合は、ARP要求パケット送信後に、ARP応答パケットを待ちます。 */
//{{2014/08/16変更:get_psr(),set_psr()の実装変更に伴い削除
//	if(ENABLED) {
//↓2014/08/16変更:get_psr(),set_psr()の実装変更に伴い削除
	if(get_psr().ie) {
//}}2014/08/16変更:get_psr(),set_psr()の実装変更に伴い削除
		if(timeout < 0) {	/* 負数のタイムアウト値が指定された場合は、既定値とします */
			timeout = DEFAULT_TIMEOUT;
		}
	} else {
		timeout = 0;		/* 割り込み禁止で呼ばれた場合は、即座にタイムアウトします */
	}

	do {
ENTER_CS;
		/* ARPテーブルから、指定されたIPアドレスに対応するEthernetアドレスを取得します。 */
		retval = arp_get_address(ip_address, ethernet_address);
LEAVE_CS;
		/* Ethernetアドレスを取得できたら、成功(0)を返します。 */
		if(retval == 0) {
			return 0;
		}

		/* ARP要求パケットをブロードキャストします。 */
		if(!send_ok) {
			unsigned char data[28];
			IPCONFIG ip_config;
			ip_get_config(&ip_config);
			          PUT_BEHALF(data +  0, 1);			/* + 0,2: ハードウェアアドレスの種類 (Ethernet) */
			          PUT_BEHALF(data +  2, 0x0800);		/* + 2,2: 論理アドレスの種類 (IP) */
			          PUT_BEBYTE(data +  4, 6);			/* + 4,1: ハードウェアアドレス長 */
			          PUT_BEBYTE(data +  5, 4);			/* + 5,1: 論理アドレス長 */
			          PUT_BEHALF(data +  6, 1);			/* + 6,2: オペレーションコード (ARP要求) */
			ethernet_get_address(data +  8);			/* + 8,6: 送信元ハードウェアアドレス */
			          PUT_BEWORD(data + 14, ip_config.ip_address);	/* +14,4: 送信元論理アドレス */
			              memset(data + 18, 0, 6);			/* +18,6: 宛て先ハードウェアアドレス (0) */
			          PUT_BEWORD(data + 24, ip_address);		/* +24,4: 宛て先論理アドレス */
										/* =28 */
			retval = ethernet_send(&arp_driver.ethernet_client, ethernet_broadcast_address, data, 28);
			if(retval >= 0) {
				send_ok = 1; /* 成功したら、次回以降は送信しません */
			}
		}
	} while(pceTimerGetCount() - T0 < timeout); /* タイムアウトするまで、ARP応答パケットを待ちます */

	/* Ethernetアドレスを取得できなければ、失敗(負数)を返します。 */
	return -1;
}

int
arp_table(ARPENTRY* table/*[table_size]*/, int table_size)
{
	int i;

	if(table_size > ARP_TABLE_SIZE) {
		table_size = ARP_TABLE_SIZE;
	}

ENTER_CS;

	for(i = 0; i < table_size; i++) {
		if(!arp_driver.table[i].ip_address) {
			break;
		}
		table[i] = arp_driver.table[i];
	}

LEAVE_CS;

	return i;
}

/****************************************************************************
 *	ICMPプロトコルドライバ
 ****************************************************************************/

/* ICMPプロトコルドライバ構造体 */
typedef struct _ICMPDRIVER {
	IPCLIENT ip_client;		/* IPクライアント登録用 */
	/*{{icmp_echo_request()*/
	int echo_address;		/* エコー要求を送信した、宛て先アドレス */
	int echo_identifier;		/* エコー要求メッセージに設定した、識別子 */
	int echo_sequence;		/* エコー要求メッセージに設定した、順序番号 */
	/*}}icmp_echo_request()*/
	ICMPSTAT stat;			/* 通信状態 */
} ICMPDRIVER;
static ICMPDRIVER icmp_driver;

/*{{icmp_echo_request()*/
#define ICMP_ECHO_DATA_LENGTH	8
static const unsigned char icmp_echo_data[ICMP_ECHO_DATA_LENGTH + 1] = "PIECE-ME";
/*}}icmp_echo_request()*/

static void icmp_start();
static void icmp_stop();
static void icmp_recv(IPCLIENT* ip_client, int source_ip_address, int destination_ip_address, const unsigned char* data/*[length]*/, int length);

/*--------------------------------------------------------------------------*/

static void
icmp_start()
{
	/* ドライバ構造体をクリアします。 */
	memset(&icmp_driver, 0, sizeof icmp_driver);

	/* ICMPプロトコルドライバを開始します。 */
	icmp_driver.ip_client.protocol = 1/*ICMP*/;
	icmp_driver.ip_client.recv = icmp_recv;
	ip_open(&icmp_driver.ip_client);
}

static void
icmp_stop()
{
	/* ICMPプロトコルドライバを停止します。 */
	ip_close(&icmp_driver.ip_client);

	/* ドライバ構造体をクリアします。 */
	memset(&icmp_driver, 0, sizeof icmp_driver);
}

static void
icmp_recv(IPCLIENT* ip_client, int source_ip_address, int destination_ip_address, const unsigned char* _data/*[length]*/, int length)
{
	unsigned char* data = (unsigned char*)_data;

	if(length < 4) {
		return;
	}
	if(internet_checksum(data, length) != 0) {			/* +2,2: チェックサム */
		return;
	}

	switch(BEBYTE(data + 0)) {					/* +0,1: タイプ */
	case 0: /* エコー応答 */

		/* 正しいエコー応答を受信したら、カウントアップして、icmp_echo_request()へ通知します。
		 * icmp_echo_request()が既にタイムアウトしていたら、無意味な通知となります。(安全です)
		 */
		if(length != 8 + ICMP_ECHO_DATA_LENGTH) {
			return;
		}
		if(source_ip_address != icmp_driver.echo_address) {
			return;
		}
		if(BEHALF(data + 4) != icmp_driver.echo_identifier) {	/* +4,2: 識別子 */
			return;
		}
		if(BEHALF(data + 6) != icmp_driver.echo_sequence) {	/* +6,2: 順序番号 */
			return;
		}
		if(memcmp(data + 8, icmp_echo_data, ICMP_ECHO_DATA_LENGTH) != 0) {	/* +8,8: データ */
			return;
		}
		icmp_driver.stat.echo_reply++;	/* カウントアップ (icmp_echo_request()への通知を兼ねる) */

		return;

	case 8: /* エコー要求 */

		/* 正しいエコー要求を受信したら、カウントアップして、エコー応答メッセージを返送します。 */
		PUT_BEBYTE(data + 0, 0);				/* +0,1: タイプ (エコー応答) */
		update_internet_checksum(data, length, 2);		/* +2,2: チェックサム */
		ip_send(ip_client, source_ip_address, data, length);
		icmp_driver.stat.echo_request++; /* カウントアップ */

		return;
	}
}

int
icmp_echo_request(int address, int timeout)
{
	int T0 = pceTimerGetCount();	/* タイムアウト判定の基準時刻、兼、識別子シード */
	int send_ok = 0;		/* エコー要求パケットを送信したら、1になります */
	//
	unsigned char data[8 + ICMP_ECHO_DATA_LENGTH];
	int retval;
	int elapse;
	int echo_reply;

	/* 負数のタイムアウト値が指定された場合は、既定値とします。 */
	if(timeout < 0) {
		timeout = DEFAULT_TIMEOUT;
	}

	/* エコー要求メッセージを作成します。 */
	PUT_BEBYTE(data + 0, 8);					/* +0,1: タイプ (エコー要求) */
	PUT_BEBYTE(data + 1, 0);					/* +1,1: コード */
	PUT_BEHALF(data + 4, (unsigned short)T0);			/* +4,2: 識別子 */
	PUT_BEHALF(data + 6, 0);					/* +6,2: 順序番号 */
	    memcpy(data + 8, icmp_echo_data, ICMP_ECHO_DATA_LENGTH);	/* +8,8: データ */
	update_internet_checksum(data, 8 + ICMP_ECHO_DATA_LENGTH, 2);	/* +2,2: チェックサム */

ENTER_CS;

	/* エコー応答待ちの条件を格納します。 */
	icmp_driver.echo_address    = address;
	icmp_driver.echo_identifier = BEHALF(data + 4);
	icmp_driver.echo_sequence   = BEHALF(data + 6);

	/* エコー応答メッセージ受信回数カウントアップを検出するために、現在の値を記憶しておきます。 */
	echo_reply = icmp_driver.stat.echo_reply;

LEAVE_CS;

	do {
		/* エコー要求メッセージを送信します。 */
		if(!send_ok) {
			retval = ip_send(&icmp_driver.ip_client, address, data, 8 + ICMP_ECHO_DATA_LENGTH);
			if(retval >= 0) {
				send_ok = 1; /* 成功したら、次回以降は送信しません */
			}
		}

		/* エコー応答メッセージを受信したら、遅延時間(0、または、正数)を返します。 */
		elapse = pceTimerGetCount() - T0;
		if(icmp_driver.stat.echo_reply != echo_reply) { /* 1wordアクセスなので、排他制御不要 */
			return elapse;
		}
	} while(elapse < timeout); /* タイムアウトするまで、エコー応答メッセージの受信を待ちます */

	/* エコー応答メッセージを受信できなければ、失敗(負数)を返します。 */
	return -1;
}

void
icmp_stat(ICMPSTAT* stat)
{
ENTER_CS;

	*stat = icmp_driver.stat;

LEAVE_CS;
}

/****************************************************************************
 *	IPプロトコルドライバ
 ****************************************************************************/

typedef struct _IPDRIVER {
	ETHERNETCLIENT ethernet_client;	/* Ethernetクライアント登録用 */
	IPCONFIG config;		/* ネットワーク設定 */
	LIST_ENTRY client_list;		/* クライアントリスト */
} IPDRIVER;
static IPDRIVER ip_driver;

static void ip_recv(ETHERNETCLIENT* ethernet_client, const unsigned char* address/*[6]*/, const unsigned char* data/*[length]*/, int length);
static IPCLIENT* ip_find_client(int protocol);

/*--------------------------------------------------------------------------*/

/* クライアントリストから、指定した上位プロトコルに対応するクライアントを探します。
 * 指定した上位プロトコルに対応するクライアントが見つかったら、ポインタを返します。
 * 指定した上位プロトコルに対応するクライアントが見つからなければ、NULLを返します。
 */
/* ★割り込み禁止状態で呼び出してください!!★ */
static IPCLIENT*
ip_find_client(int protocol)
{
	LIST_ENTRY* list_head = &ip_driver.client_list;	/* リストのルート要素 */
	LIST_ENTRY* list_entry = list_head->Flink;	/* 先頭の要素(or終端) */
	IPCLIENT* client;

	while(list_entry != list_head) {
		if(!list_entry) {
			DIE(); /* 未初期化、または、クライアントリスト破損 */
		}
		client = CONTAINING_RECORD(list_entry, IPCLIENT, list_entry);
		if(client->protocol == protocol) {
			return client;
		}
		list_entry = list_entry->Flink;
	}

	return NULL;
}

void
ip_start(int txque_capacity)
{
	/* まず、確実に停止します。 */
	ip_stop();

	/* ドライバ構造体をクリアします。 */
	memset(&ip_driver, 0, sizeof ip_driver);

	/* クライアントリストを初期化します。 */
	InitializeListHead(&ip_driver.client_list);

ENTER_CS;

	/* IPプロトコルドライバを開始します。(Ethernetクライアント) */
	memset(&ip_driver.ethernet_client, 0, sizeof ip_driver.ethernet_client);
	ip_driver.ethernet_client.type = 0x0800/*IP*/;
	ip_driver.ethernet_client.recv = ip_recv;
	ethernet_open(&ip_driver.ethernet_client);

	/* ARPプロトコルドライバを開始します。(Ethernetクライアント) */
	arp_start();

	/* ICMPプロトコルドライバを開始します。(IPクライアント) */
	icmp_start();

LEAVE_CS;
}

void
ip_stop()
{
	/* 開始していなければ、何もしません。 */
	if(!ip_driver.client_list.Flink) {
		return;
	}

ENTER_CS;

	/* ICMPプロトコルドライバを停止します。(IPクライアント) */
	icmp_stop();

	/* ARPプロトコルドライバを停止します。(Ethernetクライアント) */
	arp_stop();

	/* IPプロトコルドライバを停止します。(Ethernetクライアント) */
	ethernet_close(&ip_driver.ethernet_client);

LEAVE_CS;

	/* ドライバ構造体をクリアします。 */
	memset(&ip_driver, 0, sizeof ip_driver);
}

void
ip_open(IPCLIENT* client)
{
ENTER_CS;

	if((client->protocol < 0x00) || (client->protocol > 0xff)) {
		DIE(); /* 不正なプロトコルID */
	}

	if(ip_find_client(client->protocol)) {
		DIE(); /* 同じプロトコルIDが登録済み */
	}

	/* クライアントリストに追加します。 */
	InsertTailList(&ip_driver.client_list, &client->list_entry);

LEAVE_CS;
}

void
ip_close(IPCLIENT* client)
{
ENTER_CS;

	if(ip_find_client(client->protocol) != client) {
		DIE(); /* 未登録のクライアント */
	}

	/* クライアントリストから取り除きます。 */
	RemoveEntryList(&client->list_entry);

LEAVE_CS;
}

int
ip_send(IPCLIENT* client, int address, const void* data/*[length]*/, int length)
{
	int retval;
	//
	int tmpbuf_length;
	unsigned char* tmpbuf = NULL; /* 要開放 */
	//
	unsigned char ethernet_address[6];
	IPHEADER* header;
	//
	IPCONFIG ip_config;
	ip_get_config(&ip_config);

	if(length < 0) {
		DIE(); /* パラメータ不正 */
	}

	/* IPデータグラム構築用バッファを確保します。 */
	tmpbuf_length = 20/*IPヘッダ*/ + length/*データ*/;
	tmpbuf = malloc(tmpbuf_length);
	if(!tmpbuf) {
		DIE(); /* メモリ不足 */
	}

	/* 宛て先IPアドレスがブロードキャストアドレスならば、Ethernetブロードキャストを行います。 */
	if(address == (ip_config.ip_address | ~ip_config.subnet_mask)) {
		memcpy(ethernet_address, ethernet_broadcast_address, 6);

	/* 宛て先IPアドレス、または、デフォルトゲートウェイに対するEthernetアドレスを取得します。 */
	} else {
		retval = arp_request((address & ip_config.subnet_mask) ==
			(ip_config.ip_address & ip_config.subnet_mask) ? address : ip_config.default_gateway,
			ethernet_address, -1/*既定のタイムアウト時間を使用*/);
		if(retval != 0) {
			length = -1; /* アドレス解決失敗 */
			goto L_EXIT;
		}
	}

	/* IPデータグラムを構築します。 */
	header = (IPHEADER*)tmpbuf;
	memset(header, 0, sizeof(IPHEADER));
	header->protocol		= client->protocol;
	header->source_ip_address	= ip_driver.config.ip_address;
	header->destination_ip_address	= address;
	header->data			= (unsigned char*)data;
	header->data_length		= length;
	retval = pack_ip_header(header, tmpbuf, tmpbuf_length);
	if(retval != tmpbuf_length) {
		DIE(); /* 有り得ない */
	}

	/* IPデータグラムを送信します。 */
	retval = ethernet_send(&ip_driver.ethernet_client, ethernet_address, tmpbuf, tmpbuf_length);
	if(retval < 0) {
		length = -1; /* 送信失敗 */
		goto L_EXIT;
	}

L_EXIT:

	/* IPデータグラム構築用バッファを解放します。 */
	free(tmpbuf);

	/* 成功時ならば、データ長をそのまま返します。 */
	return length;
}

void
ip_set_config(const IPCONFIG* config)
{
ENTER_CS;

	/* ネットワーク設定が変化したら... */
	if(memcmp(&ip_driver.config, config, sizeof(IPCONFIG)) != 0) {

		/* 新しいネットワーク設定を格納します。 */
		memcpy(&ip_driver.config, config, sizeof(IPCONFIG));

		/* Ethernetアドレス設定の条件が整っていれば、Gratuitous ARPを送出します。
		 * - 厳密には、IPアドレスが変化した場合のみ、Gratuitous ARPを送出すべきですが、
		 *   現在の実装では、簡単のために、他の項目だけが変化した場合も送出しています。
		 */
		arp_gratuitous();

		/* ARPはIPクライアントで無いので、↓以下の処理と別に、↑明示的に通知しています。 */

		/* クライアントの設定変更コールバックへ、ネットワーク設定の変化を通知します。
		 * - 2005/09/05現在、この通知を利用しているIPクライアントドライバは有りません。
		 *   もしかしたら今後、TCPやUDPドライバにて、利用するかもしれません。(予定)
		 * - EthernetドライバからのEthernetアドレス変化を伝播しているのでは無いことに注意してください。
		 *   本通知は、あくまでも、ネットワーク設定(IPアドレスなど)の変化を通知するものです。
		 *   Ethernetアドレスの変化はIPドライバにて吸収されるため、上位ドライバでは考慮不要だからです。
		 *   基本的に、N層ドライバの設定変化を知る必要があるのは、次のN+1層ドライバだけです。
		 */
		{
			LIST_ENTRY* list_head = &ip_driver.client_list;	/* リストのルート要素 */
			LIST_ENTRY* list_entry = list_head->Flink;	/* 先頭の要素(or終端) */
			IPCLIENT* client;

			while(list_entry != list_head) {
				if(!list_entry) {
					DIE(); /* 未初期化、または、クライアントリスト破損 */
				}
				client = CONTAINING_RECORD(list_entry, IPCLIENT, list_entry);
				if(client->conf) {
					client->conf(client);
				}
				list_entry = list_entry->Flink;
			}
		}
	}

LEAVE_CS;
}

void
ip_get_config(IPCONFIG* config)
{
ENTER_CS;

	memcpy(config, &ip_driver.config, sizeof(IPCONFIG));

LEAVE_CS;
}

static void
ip_recv(ETHERNETCLIENT* ethernet_client, const unsigned char* address/*[6]*/, const unsigned char* data/*[length]*/, int length)
{
	int retval;
	IPHEADER header;
	IPCLIENT* client;

	/* IPデータグラムを展開します。 */
	retval = unpack_ip_header(&header, data, length);
	if(retval < 0) {
		return;
	}

	/* フラグメントパケットは未対応です。 */
	if((header.flags & (1<<2)) ||	/* 後続パケット有り */
	   (header.fragment_offset)) {	/* フラグメントオフセット指定有り */
		return;
	}

	/* 自分宛て、または、ブロードキャストパケット以外は、破棄します。 */
	if((header.destination_ip_address !=  ip_driver.config.ip_address) &&
	   (header.destination_ip_address != (ip_driver.config.ip_address | ~ip_driver.config.subnet_mask))) {
		return;
	}
	
	/* 上位プロトコルに対応するクライアントへ送ります。 */
	client = ip_find_client(header.protocol);
	if(client) {
		if(client->recv) {
			client->recv(client,
				header.source_ip_address,	/* 送信元IPアドレス */
				header.destination_ip_address,	/* 宛て先IPアドレス */
				header.data,			/* データ */
				header.data_length);		/* データ長 */
		}
	}
}

