/*	
 *	clipnet.c
 *
 *	ネットワーク関連のサポートルーチン
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Sun Jul 24 04:08:00 JST 2005 Naoyuki Sawa
 *	- 作成開始。
 *	* Tue Aug 02 06:25:00 JST 2005 Naoyuki Sawa
 *	- update_internet_checksum()、ip_unpack_header()、ip_pack_header()を追加しました。
 *	* Sun Aug 07 04:10:00 JST 2005 Naoyuki Sawa
 *	- ip_pack_header()、ip_unpack_header()をpack_ip_header()、unpack_ip_header()に改名しました。
 *	* Thu Aug 25 12:46:00 JST 2005 Naoyuki Sawa
 *	- address_class_mask()を追加しました。
 *	* Fri Aug 26 04:09:00 JST 2005 Naoyuki Sawa
 *	- IP_ADDRESS()マクロを追加しました。
 *	* Mon Sep 12 05:27:00 JST 2005 Naoyuki Sawa
 *	- internet_checksum_pseudo_header()、update_internet_checksum_pseudo_header()を追加しました。
 *	* Tue Sep 13 22:28:00 JST 2005 Naoyuki Sawa
 *	- address_class_mask()をip_address_class_mask()に改名しました。
 *	- unpack_tcp_header()、pack_tcp_header()を追加しました。
 */
#include "clip.h"

/****************************************************************************
 *	
 ****************************************************************************/

int
internet_checksum(const void* _data, int size)
{
	const unsigned char* data = _data;
	int sum = 0;

			//////////////////////////////////////////////////////////////////
			// 例として、最大値のケースの演算をトレースした様子を示します。	//
	 		//////////////////////////	-------	-------	-------		//
				                //	  sum	data[0]	data[1]		//
	 					//	-------	-------	-------		//
	while(size) {				//	0FFFF	  FF__	  __FF		//
		sum += *data++ << 8;		//	1FEFF <---+	    |		//
		size--;				//					//
		if(size) {			//			    |		//
			sum += *data++;		//	1FFFE <-------------+		//
			size--;			//					//
		}				//	+-------+			//
		sum += sum >> 16;		//	1FFFF <-+			//
		sum = (unsigned short)sum;	//	0FFFF				//
	}					//////////////////////////////////////////

	return (unsigned short)~sum;
}

void
update_internet_checksum(void* _data, int size, int sum_ofs)
{
	unsigned char* data = _data;
	//
	int sum;

	if((sum_ofs < 0) ||
	   (sum_ofs > size - 2)) {
		DIE();
	}

	/* チェックサム計算時は、チェックサムフィールドを0としておきます。 */
	PUT_BEHALF(data + sum_ofs, 0);

	/* チェックサムを計算します。 */
	sum = internet_checksum(data, size);

	/* チェックサムを格納します。
	 * * PUT_BEHALF()は引数を二度評価するので、
	 *	PUT_BEHALF(data + sum_ofs, internet_checksum(data, size));
	 *   としてはいけません!!
	 */
	PUT_BEHALF(data + sum_ofs, sum);
}

int
internet_checksum_pseudo_header(int source_ip_address, int destination_ip_address, int protocol, const void* packet, int packet_length)
{
	int checksum;
	int tmpbuf_length;
	unsigned char* tmpbuf;

	/* 疑似ヘッダとパケットを格納するための、一時バッファを確保します。 */
	tmpbuf_length = 12/*疑似ヘッダ*/ + packet_length;
	tmpbuf = malloc(tmpbuf_length);
	if(!tmpbuf) {
		DIE();
	}

	/* 擬似ヘッダを構築します。 */
	PUT_BEWORD(tmpbuf +  0, source_ip_address);		/* + 0,4: 送信元IPアドレス */
	PUT_BEWORD(tmpbuf +  4, destination_ip_address);	/* + 4,4: 宛て先IPアドレス */
	PUT_BEBYTE(tmpbuf +  8, 0);				/* + 8,1: パディング */
	PUT_BEBYTE(tmpbuf +  9, protocol);			/* + 9,1: プロトコル */
	PUT_BEHALF(tmpbuf + 10, packet_length);			/* +10,2: パケット長 */

	/* パケットをコピーします。 */
	memcpy(tmpbuf + 12, packet, packet_length);		/* +12,?: パケット */

	/* チェックサムを計算します。 */
	checksum = internet_checksum(tmpbuf, tmpbuf_length);

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

	return checksum;
}

void
update_internet_checksum_pseudo_header(int source_ip_address, int destination_ip_address, int protocol, void* _packet, int packet_length, int checksum_offset)
{
	unsigned char* packet = _packet;
	//
	int checksum;

	if((checksum_offset < 0) ||
	   (checksum_offset > packet_length - 2)) {
		DIE();
	}

	/* チェックサム計算時は、チェックサムフィールドを0としておきます。 */
	PUT_BEHALF(packet + checksum_offset, 0);

	/* チェックサムを計算します。 */
	checksum = internet_checksum_pseudo_header(source_ip_address, destination_ip_address, protocol, packet, packet_length);

	/* チェックサムを格納します。 */
	PUT_BEHALF(packet + checksum_offset, checksum);
}

/****************************************************************************
 *	
 ****************************************************************************/

int
ip_address_class_mask(int ip_address)
{
	/* 0xxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx ; Class A */
	if(!(ip_address & (1<<31))) {
		return 0xff000000;
	}

	/* 10xxxxxx xxxxxxxx xxxxxxxx xxxxxxxx ; Class B */
	if(!(ip_address & (1<<30))) {
		return 0xffff0000;
	}

	/* 110xxxxx xxxxxxxx xxxxxxxx xxxxxxxx ; Class C */
	if(!(ip_address & (1<<29))) {
		return 0xffffff00;
	}

	/* 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx ; Class D
	 * 1111xxxx xxxxxxxx xxxxxxxx xxxxxxxx ; Class E
	 */
	return -1; /* 要検討 */
}

int
unpack_ip_header(IPHEADER* header, const void* _buffer, int buffer_length)
{
	const unsigned char* buffer = _buffer;
	//
	int version;
	int header_length;
	int type_of_service;
	int total_length;
	int identification;
	int flags;
	int fragment_offset;
	int time_to_live;
	int protocol;
	int header_checksum;
	int source_ip_address;
	int destination_ip_address;

	/* 最小ヘッダ長は20バイトです。 */
	if(buffer_length < 20) {
		return -1;
	}

	/* フィールド値を取り出します。 */
	version			= (BEHALF(buffer +  0) & 0xf000) >> 12;
	header_length		= (BEHALF(buffer +  0) & 0x0f00) >>  6;
	type_of_service		= (BEHALF(buffer +  0) & 0x00ff) >>  0;
	total_length		=  BEHALF(buffer +  2);
	identification		=  BEHALF(buffer +  4);
	flags			= (BEHALF(buffer +  6) & 0xe000) >> 13;
	fragment_offset		= (BEHALF(buffer +  6) & 0x1fff) <<  3;
	time_to_live		= (BEHALF(buffer +  8) & 0xff00) >>  8;
	protocol		= (BEHALF(buffer +  8) & 0x00ff) >>  0;
	header_checksum		=  BEHALF(buffer + 10);
	source_ip_address	=  BEWORD(buffer + 12);
	destination_ip_address	=  BEWORD(buffer + 16);

	/* フィールド値を検査します。 */
	if(version != 4) {
		return -1;
	}
	if(header_length < 20) {
		return -1;
	}
	if((total_length < header_length) ||
	   (total_length > buffer_length)) {
		return -1;
	}
	if(internet_checksum(buffer, header_length) != 0) {
		return -1;
	}

	/* IPHEADER構造体へ、フィールド値を格納します。 */
	header->identification		= identification;
	header->flags			= flags;
	header->fragment_offset		= fragment_offset;
	header->protocol		= protocol;
	header->source_ip_address	= source_ip_address;
	header->destination_ip_address	= destination_ip_address;
	//
	header->data			= (unsigned char*)buffer + header_length;
	header->data_length		= total_length           - header_length;

	/* 成功ならば、パケット長を返します。 */
	return total_length;
}

int
pack_ip_header(const IPHEADER* header, void* _buffer, int buffer_length)
{
	unsigned char* buffer = _buffer;
	//
	int version;
	int header_length;
	int type_of_service;
	int total_length;
	int identification;
	int flags;
	int fragment_offset;
	int time_to_live;
	int protocol;
	//int header_checksum;
	int source_ip_address;
	int destination_ip_address;

	/* パラメータを検査します。 */
	if(header->identification & ~0xffff) {
		DIE();
	}
	if(header->flags & ~(IP_DF|IP_MF)) {
		DIE();
	}
	if(header->fragment_offset & ~0xfff8/*注意!*/) {
		DIE();
	}
	if(header->protocol & ~0xff) {
		DIE();
	}
	if((header->data_length < 0) ||
	  ((header->data_length > 0) && !header->data)) {
		DIE();
	}

	/* フィールド値を決定します。 */
	version			= 4;
	header_length		= 20;
	type_of_service		= 0;
	total_length		= 20 + header->data_length;
	identification		= header->identification;
	flags			= header->flags;
	fragment_offset		= header->fragment_offset;
	time_to_live		= 255;
	protocol		= header->protocol;
	//header_checksum	= あとで計算します
	source_ip_address	= header->source_ip_address;
	destination_ip_address	= header->destination_ip_address;

	/* バッファ容量が充分でなければ、失敗とします。 */
	if(total_length > buffer_length) {
		return -1;
	}

	/* ヘッダ格納位置とコピー元データがオーバーラップしている可能性があるので、
	 * 先に、コピー元データをバッファ内のデータ格納位置へコピーしておきます。
	 */
	memmove(buffer + 20, header->data, header->data_length);

	/* バッファ内のヘッダ格納位置へ、フィールド値を格納します。 */
	PUT_BEHALF(buffer +  0, (version<<12) | (header_length<<6) | (type_of_service<<0));
	PUT_BEHALF(buffer +  2, total_length);
	PUT_BEHALF(buffer +  4, identification);
	PUT_BEHALF(buffer +  6, (flags<<13) | (fragment_offset>>3));
	PUT_BEHALF(buffer +  8, (time_to_live<<8) | (protocol<<0));
	//PUT_BEHALF(buffer + 10, あとで計算します);
	PUT_BEWORD(buffer + 12, source_ip_address);
	PUT_BEWORD(buffer + 16, destination_ip_address);

	/* ヘッダチェックサムを格納します。 */
	update_internet_checksum(buffer, 20, 10);

	/* 成功ならば、パケット長を返します。 */
	return total_length;
}

/****************************************************************************
 *	
 ****************************************************************************/

int
unpack_tcp_header(TCPHEADER* header, const void* _buffer, int buffer_length, int source_ip_address, int destination_ip_address)
{
	const unsigned char* buffer = _buffer;
	//
	int source_port;
	int destination_port;
	int sequence_number;
	int acknowledgement_number;
	int data_offset;
	int reserved;
	int control_flag;
	int window;
	int urgent_pointer;

	/* 最小ヘッダ長は20バイトです。 */
	if(buffer_length < 20) {
		return -1;
	}

	/* フィールド値を取り出します。 */
	source_port		=  BEHALF(buffer +  0);
	destination_port	=  BEHALF(buffer +  2);
	sequence_number		=  BEWORD(buffer +  4);
	acknowledgement_number	=  BEWORD(buffer +  8);
	data_offset		= (BEHALF(buffer + 12) & 0xf000) >> 10;
	reserved		= (BEHALF(buffer + 12) & 0x0fc0) >>  6;
	control_flag		= (BEHALF(buffer + 12) & 0x003f) >>  0;
	window			=  BEHALF(buffer + 14);
	//checksum		=  BEHALF(buffer + 16);
	urgent_pointer		=  BEHALF(buffer + 18);

	/* フィールド値を検査します。 */
	if((data_offset < 20) ||
	   (data_offset > buffer_length)) {
		return -1;
	}
	if(internet_checksum_pseudo_header(
		source_ip_address,		/* 送信元IPアドレス */
		destination_ip_address,		/* 宛て先IPアドレス */
		6/*TCP*/,			/* プロトコル */
		buffer,				/* パケット */
		buffer_length)) {		/* パケット長 */
		return -1; /* チェックサム不正 */
	}

	/* TCPHEADER構造体へ、フィールド値を格納します。 */
	header->source_port		= source_port;
	header->destination_port	= destination_port;
	header->sequence_number		= sequence_number;
	header->acknowledgement_number	= acknowledgement_number;
	header->control_flag		= control_flag;
	header->window			= window;
	//
	header->data			= (unsigned char*)buffer + data_offset;
	header->data_length		= buffer_length          - data_offset;

	/* 成功ならば、パケット長を返します。 */
	return buffer_length;
}

int
pack_tcp_header(TCPHEADER* header, void* _buffer, int buffer_length, int source_ip_address, int destination_ip_address)
{
	unsigned char* buffer = _buffer;
	//
	int source_port;
	int destination_port;
	int sequence_number;
	int acknowledgement_number;
	int data_offset;
	int reserved;
	int control_flag;
	int window;
	int urgent_pointer;
	//
	int total_length;

	/* パラメータを検査します。 */
	if(header->source_port & ~0xffff) {
		DIE();
	}
	if(header->destination_port & ~0xffff) {
		DIE();
	}
	if(header->control_flag & ~(TCP_FIN|TCP_SYN|TCP_RST|TCP_PSH|TCP_ACK|TCP_URG)) {
		DIE();
	}
	if(header->window & ~0xffff) {
		DIE();
	}
	if((header->data_length < 0) ||
	  ((header->data_length > 0) && !header->data)) {
		DIE();
	}

	/* フィールド値を決定します。 */
	source_port		= header->source_port;
	destination_port	= header->destination_port;
	sequence_number		= header->sequence_number;
	acknowledgement_number	= header->acknowledgement_number;
	data_offset		= 20;
	reserved		= 0;
	control_flag		= header->control_flag;
	window			= header->window;
	//checksum		= 0; → あとで計算します
	urgent_pointer		= 0;

	/* パケット長を決定します。 */
	total_length = 20/*ヘッダ*/ + header->data_length;

	/* バッファ容量が充分でなければ、失敗とします。 */
	if(total_length > buffer_length) {
		return -1;
	}

	/* ヘッダ格納位置とコピー元データがオーバーラップしている可能性があるので、
	 * 先に、コピー元データをバッファ内のデータ格納位置へコピーしておきます。
	 */
	memmove(buffer + 20, header->data, header->data_length);

	/* バッファ内のヘッダ格納位置へ、フィールド値を格納します。 */
	PUT_BEHALF(buffer +  0, source_port);
	PUT_BEHALF(buffer +  2, destination_port);
	PUT_BEWORD(buffer +  4, sequence_number);
	PUT_BEWORD(buffer +  8, acknowledgement_number);
	PUT_BEHALF(buffer + 12, (data_offset<<10) | (reserved<<6) | (control_flag<<0));
	PUT_BEHALF(buffer + 14, window);
	//PUT_BEHALF(buffer + 16, あとで計算します);
	PUT_BEHALF(buffer + 18, urgent_pointer);

	/* チェックサムを格納します。 */
	update_internet_checksum_pseudo_header(
		source_ip_address,	/* 送信元IPアドレス */
		destination_ip_address,	/* 宛て先IPアドレス */
		6/*TCP*/,		/* プロトコル */
		buffer,			/* パケット */
		total_length,		/* パケット長 */
		16);			/* チェックサム位置 */

	/* 成功ならば、パケット長を返します。 */
	return total_length;
}
