/*	
 *	clipudp.c
 *
 *	UDPプロトコルドライバ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Wed Sep 07 03:55:00 JST 2005 Naoyuki Sawa
 *	- 1st リリース。
 *	* Tue Sep 20 19:51:00 JST 2005 Naoyuki Sawa
 *	- udp_open()時にlocal_port=0ならば、エフェメラルポートを割り当てるよう変更しました。
 *	* Fri Sep 23 02:34:00 JST 2005 Naoyuki Sawa
 *	- udp_socket_open()の受信キュー容量を、少なくとも1480バイト以上に切り上げるよう変更しました。
 */
#include "clip.h"

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

typedef struct _UDPDRIVER {
	IPCLIENT ip_client;		/* IPクライアント登録用 */
	LIST_ENTRY client_list;		/* クライアントリスト */
} UDPDRIVER;
static UDPDRIVER udp_driver;

static void udp_recv(IPCLIENT* ip_client, int source_ip_address, int destination_ip_address, const unsigned char* data/*[length]*/, int length);
static UDPCLIENT* udp_find_client(int local_port);

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

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

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

	return NULL;
}

/* 未使用のエフェメラルポートを探します。
 * ------------------------------------------------------------------------------------------------------------------------------------------------------------
 * IT用語辞典 e-Words ： エフェメラルポートとは 【ephemeral ports】 - 意味・解説
 * 特定の用途(プロトコルでの使用が推奨されておらず、一時的な通信のために自由に利用できるポート。ダイナミックポートとも言われる。「ephemeral」は「短命な」の意。
 * これに対して、HTTPの80番のように、特定のプロトコルが利用することが広く推奨されているポートはウェルノウンポート(well-known port)と呼ばれる。
 * エフェメラルポートは主にクライアント側で利用される。クライアントからサーバにリクエストを送出する際、
 * サーバ側のポートは目的のサービスが稼動しているポート(多くの場合、ウェルノウンポート)である必要があるが、クライアント側はどんなポートを使ってももかまわない。
 * このとき、クライアント側ではOSが空いているエフェメラルポートの中から適当なものを選んで割り当てる。通信が終了するとそのポートは解放される。
 * エフェメラルポートは、基本的には1024番以降が用いられるが、OSにより異なる。
 * Windowsを始め多くのOSでは1024番から5000番をエフェメラルポートとして割り当てているが、例えば、FreeBSD 5.0では49152番から65535番を割り当てている。
 * ------------------------------------------------------------------------------------------------------------------------------------------------------------
 * 本ドライバは、FreeBSDに倣い、49152番から65535番($C000〜$FFFF)をエフェメラルポートとして割り当てることにします。
 */
/* ★割り込み禁止状態で呼び出してください!!★ */
static int
udp_allocate_ephemeral_port()
{
	LIST_ENTRY* list_head = &udp_driver.client_list;	/* リストのルート要素 */
	LIST_ENTRY* list_entry = list_head->Flink;		/* 先頭の要素(or終端) */
	UDPCLIENT* client;
	int ephemeral_port;

	for(ephemeral_port = 0xc000; ephemeral_port <= 0xffff; ephemeral_port++) {
		while(list_entry != list_head) {
			if(!list_entry) {
				DIE(); /* 未初期化、または、クライアントリスト破損 */
			}
			client = CONTAINING_RECORD(list_entry, UDPCLIENT, list_entry);
			if(client->local_port == ephemeral_port) {
				break; /* 使用済み */
			}
			list_entry = list_entry->Flink;
		}
		if(list_entry == list_head) {
			break; /* 未使用ポート発見 */
		}
	}

	if(ephemeral_port > 0xffff) {
		DIE(); /* 未使用ポート無し */
	}

	return ephemeral_port;
}

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

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

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

	/* UDPプロトコルドライバを開始します。 */
	udp_driver.ip_client.protocol = 17/*UDP*/;
	udp_driver.ip_client.recv = udp_recv;
	ip_open(&udp_driver.ip_client);
}

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

	/* UDPプロトコルドライバを停止します。 */
	ip_close(&udp_driver.ip_client);

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

void
udp_open(UDPCLIENT* client)
{
ENTER_CS;


	/* ローカルポートが指定されていなければ、エフェメラルポートを確保します。 */
	if(!client->local_port) {
		client->local_port = udp_allocate_ephemeral_port(); /* $C000-$FFFF */

	/* ローカルポートが指定されていたら、不正や、登録済みでないか、確認します。 */
	} else {
		if(client->local_port & ~0xffff) {
			DIE(); /* 不正なポート */
		}
		if(udp_find_client(client->local_port)) {
			DIE(); /* 同じポートが登録済み */
		}
	}

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

LEAVE_CS;
}

void
udp_close(UDPCLIENT* client)
{
ENTER_CS;

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

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

LEAVE_CS;
}

int
udp_send(UDPCLIENT* client, int remote_ip_address, int remote_port, const void* data/*[length]*/, int length)
{
	int retval;
	//
	int tmpbuf_length;
	unsigned char* tmpbuf = NULL; /* 要解放 */
	//
	IPCONFIG ip_config;
	ip_get_config(&ip_config);

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

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

	/* UDPヘッダを構\築します。
	 * - UDPは、送信元ポートが無指定(0)でも構\いません。
	 *   本関数は、client=NULLで呼び出された場合に、送信元ポートを無指定(0)とします。
	 */
	PUT_BEHALF(tmpbuf + 0, client ? client->local_port : 0);/* + 0,2: 送信元ポート */
	PUT_BEHALF(tmpbuf + 2, remote_port);			/* + 2,2: 宛て先ポート */
	PUT_BEHALF(tmpbuf + 4, tmpbuf_length);			/* + 4,2: パケット長 */
	//PUT_BEHALF(tmpbuf + 6, 0);				/* + 6,2: チェックサム → 後で求めます */

	/* データをコピーします。 */
	memcpy(tmpbuf + 8, data, length);

	/* チェックサムを求めます。 */
	update_internet_checksum_pseudo_header(
		ip_config.ip_address,	/* 送信元IPアドレス */
		remote_ip_address,	/* 宛て先IPアドレス */
		17/*UDP*/,		/* プロトコル */
		tmpbuf,			/* パケット */
		tmpbuf_length,		/* パケット長 */
		6);			/* チェックサム位置 */

	/* UDPチェックサムの計算結果が0x0000ならば、0xffffに置き換えます。
	 * - UDPでは、チェックサムはオプションであり、0x0000はチェックサム無しを意味します。
	 *   チェックサム無しでも構\わないのですが、勉強のため、きちんと計算することにしました。
	 */
	if(!BEHALF(tmpbuf + 6)) {
		PUT_BEHALF(tmpbuf + 6, 0xffff);
	}

	/* UDPデータグラムを送信します。 */
	retval = ip_send(&udp_driver.ip_client, remote_ip_address, tmpbuf, tmpbuf_length);
	if(retval < 0) {
		length = -1; /* 送信失敗 */
		goto L_EXIT;
	}

L_EXIT:

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

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

static void
udp_recv(IPCLIENT* ip_client, int source_ip_address, int destination_ip_address, const unsigned char* data/*[length]*/, int length)
{
	UDPCLIENT* client;

	if(length < 8) {
		return; /* 受信パケット長が、UDPヘッダ長よりも小さい */
	}

	/* UDPヘッダに記述されている、パケット長を取得します。 */
	if(length < BEHALF(data + 4)) {			/* + 4,2: パケット長 */
		return; /* 受信パケット長が、UDPヘッダに記述されているパケット長よりも小さい */
	}
	length = BEHALF(data + 4);			/* + 4,2: パケット長 */

	/* チェックサム無し(0x0000)でなければ、チェックサムを検査します。
	 * - チェックサムが0xffffならば、本当のチェックサムは0x0000で、送信時に0xffffに置き換えたことを意味します。
	 *   しかしここで、0xffff->0x0000に戻す必要はありません。(戻しても構\いませんが、無駄な処理です)
	 *   1の補数和において、非0値に対する0x0000の加算と0xffffの加算は、同じ結果になるからです。
	 *   <例> 0x1234 + 0x0000 =  0x1234
	 *        0x1234 + 0xffff = 0x11233 -> キャリーアウトを最下位ビットに加算する -> 0x1234
	 *   0に対する加算の場合のみ、0x0000の加算と0xffffの加算は異なる結果となりますが、
	 *   UDPチェックサムの計算は、擬似ヘッダやUDPヘッダを含めて計算するため、
	 *   チェックサムフィールド以外の1の補数和が、0となることは有り得ません。
	 *   (1の補数和が0となるのは、対象データが空であるか、またはすべて0の場合だけです)
	 *   従って、チェックサムが0xffffの場合、本当のチェックサム0x0000に戻さず、そのまま計算することが可能\です。
	 */
	if(BEHALF(data + 6)) {				/* + 6,2: チェックサム */
		if(internet_checksum_pseudo_header(
			source_ip_address,		/*        送信元IPアドレス */
			destination_ip_address,		/*        宛て先IPアドレス */
			17/*UDP*/,			/*        プロトコル */
			data,				/*        パケット */
			length)) {			/*        パケット長 */
			return; /* チェックサム不正 */
		}
	}

	/* 宛て先ポートに対応するクライアントへ送ります。 */
	client = udp_find_client(BEHALF(data + 2));	/* + 2,2: 宛て先ポート */
	if(client) {
		if(client->recv) {
			client->recv(client,
				source_ip_address,	/*        送信元IPアドレス */
				BEHALF(data + 0),	/* + 0,2: 送信元ポート */
				data + 8,		/*        データ */
				length - 8);		/*        データ長 */
		}
	}
}

/****************************************************************************
 *	UDPソ\ケット
 ****************************************************************************/

static void
internal_udp_socket_recv(UDPCLIENT* udp_client, int remote_ip_address, int remote_port, const unsigned char* data/*[length]*/, int length)
{
	UDPSOCKET* udp_socket = CONTAINING_RECORD(udp_client, UDPSOCKET, udp_client);
	//
	int tmpbuf_length;
	unsigned char* tmpbuf = NULL; /* 要解放 */

	/* 一時バッファを確保します。 */
	tmpbuf_length = 8/*送信元IPアドレス+送信元ポート*/ + length/*データ*/;
	tmpbuf = malloc(tmpbuf_length);
	if(!tmpbuf) {
		DIE();
	}

	/* 送信元IPアドレス、送信元ポート、データを受信キューに格納します。 */
	PUT_LEWORD(tmpbuf + 0, remote_ip_address);	/* +0,4: 送信元IPアドレス */
	PUT_LEWORD(tmpbuf + 4, remote_port);		/* +4,4: 送信元ポート */
	    memcpy(tmpbuf + 8, data, length);		/* +8,?: データ */
	write_queue(udp_socket->rxque, tmpbuf, tmpbuf_length); /* 失敗は無視 */

	/* 一時バッファを解放します。 */
	free(tmpbuf);
}

UDPSOCKET*
udp_socket_open(int local_port, int rxque_capacity)
{
	UDPSOCKET* udp_socket;

	/* 受信キュー容量を、少なくとも1480バイト以上とします。 */
	if(rxque_capacity < 1480) {
		rxque_capacity = 1480;
	}

	/* UDPソ\ケット構\造体のメモリを確保します。 */
	udp_socket = malloc(sizeof(UDPSOCKET));
	if(!udp_socket) {
		DIE(); /* メモリ不足 */
	}
	memset(udp_socket, 0, sizeof(UDPSOCKET));

	/* 受信キューを作成します。 */
	udp_socket->rxque = create_queue(rxque_capacity);

	/* UDPクライアントを登録します。 */
	udp_socket->udp_client.local_port = local_port;
	udp_socket->udp_client.recv = internal_udp_socket_recv;
	udp_open(&udp_socket->udp_client);

	return udp_socket;
}

void
udp_socket_close(UDPSOCKET* udp_socket)
{
	/* UDPクライアントを登録解除します。 */
	udp_close(&udp_socket->udp_client);

	/* 受信キューを削除します。 */
	delete_queue(udp_socket->rxque);

	/* UDPソ\ケット構\造体のメモリを解放します。 */
	free(udp_socket);
}

int
udp_socket_recv(UDPSOCKET* udp_socket, int* remote_ip_address, int* remote_port, void* data/*[length]*/, int length)
{
	int tmpbuf_length;
	unsigned char* tmpbuf = NULL; /* 要解放 */

	/* 一時バッファを確保します。 */
	tmpbuf_length = 8/*送信元IPアドレス+送信元ポート*/ + length/*バッファ*/;
	tmpbuf = malloc(tmpbuf_length);
	if(!tmpbuf) {
		DIE();
	}

	/* 受信キューから、送信元IPアドレス、送信元ポート、データを取り出します。 */
	length = read_queue(udp_socket->rxque, tmpbuf, tmpbuf_length);
	if(length < 0) {
		goto L_EXIT; /* データ無し、または、バッファ不足 → (戻り値=負数) */
	}
	if(length < 4/*remote_ip_address*/ + 4/*remote_port*/) {
		DIE(); /* 受信キュー破損 */
	}

	/* 送信元IPアドレス、送信元ポート、データを格納します。 */
	if(remote_ip_address) {
		*remote_ip_address = LEWORD(tmpbuf + 0);/* +0,4: 送信元IPアドレス */
	}
	if(remote_port) {
		*remote_port = LEWORD(tmpbuf + 4);	/* +4,4: 送信元ポート */
	}
	length -= 8/*remote_ip_address+remote_port*/;
	memcpy(data, tmpbuf + 8, length);		/* +8,?: データ */

L_EXIT:

	/* 一時バッファを解放します。 */
	free(tmpbuf);

	return length;
}

int
udp_socket_send(UDPSOCKET* udp_socket, int remote_ip_address, int remote_port, const void* data/*[length]*/, int length)
{
	return udp_send(udp_socket ? &udp_socket->udp_client : NULL, remote_ip_address, remote_port, data, length);
}
