/*
* (c) Copyright 2002, Wiznet.
*
* Filename      : ping.c
* Version       : 1.5 
* Created       : 2002/10/16
* Description   : Implemation of Ping
		  To send 'ping-request' to peer & To receive 'ping-reply' from peer.
*/

#include <stdio.h>
#include <string.h>
#include "../mcu/serial.h"                     // serial related functions
#include "../mcu/delay.h"
#include "../util/util.h"			// Useful function
#include "../util/sockutil.h"			// Util of iinChip
#include "../util/myprintf.h"
#include "../iinchip/socket.h"
#include "../iinchip/w5100.h"

#include "ping.h"                       // header file


void SendPingReply(SOCKET s, PINGMSG * pingrequest,u_int len, u_long destaddr);


/*
Description   :  Send ping-request to the specified peer and receive ping-reply from the specified peer.
Argument      :  count - Ping reqeust count. 
			 If count is -1 then to send ping request to the specified peer infinitely.
                 size  - Data size to be sent, The range is greater than 0 and less than PINGBUF_LEN
                 time  - wait ping reply time (unit : ms)
                 addr  - Peer Ip Address(32bit network odering address)
                 log   - result of ping
Return Value  :  1 - success, 0 - fail because free socket is not found or can't be opened.
Note          :  
*/
char ping(int count, u_int size, u_int time, char* addr, PINGLOG* log)
{
	PINGMSG  PingRequest;		// Variable for Ping Request
	PINGMSG  PingReply;		// Variable for Ping Reply		
	u_long peerip;                  // 32 bit Peer IP Address
	u_long tempip;			// ip address received from a destination
	char  addrstr[16];        	// Decimal dotted-notation IP address string
	u_int  port;               	// port number received from a destination
	int i,len;
	SOCKET s = 0;		// socket variable for pinging
	char bLoop = 0;                 // Infinite = 1, finite = 0
	char IsReceived = 0;            // Received packet = 1, not received packet = 0
	u_int  RemainTime=0;       	// Remained Time for waiting to ping-reply
	
	/* Initialize PingRequest */
	static u_int  RandomID = 0x1234;			// Ping-Request ID
	static u_int  RandomSeqNum = 0x4321;			// Ping-Request packet's sequence number
	PingRequest.Type = 8;           			// Ping-Request 
	PingRequest.Code = 0;					// always 0
	PingRequest.CheckSum = 0;				// value of checksum before calucating checksum of ping-request packet
	PingRequest.ID = htons(RandomID++);			// set ping-request's ID to random integer value
	PingRequest.SeqNum = htons(RandomSeqNum);		// set ping-request's sequence number to ramdom integer value
	for(i = 0 ; i < PINGBUF_LEN; i++)	
		PingRequest.Data[i] = 'a' + i % 23;		// fill 'a'~'w' characters into ping-request's data 

	/* Initialize result of pinging */
	memset((void*)log,0,sizeof(PINGLOG));

        /* Verify arguments */	
	if(count == -1) bLoop = 1;				// if it is infinite 
	else if(!count) count = 4;				// set count to defalut value
	if(size < 1 || size > PINGBUF_LEN) size = PINGBUF_LEN;  // set size to default value
	if(!time) time = 1000;					// set time to default value

	/* Create a ping socket */
	s = getSocket(SOCK_CLOSED,0);
	if(s == (SOCKET)-1) return 0;				// if it isn't exist free socket, Error
	setSn_PROTO(s,IPPROTO_ICMP);                         	// Set upper-protocol of IP proctol
	if(socket(s,Sn_MR_IPRAW,3000,0) == 0) return 0;	// Open IP_RAW Mode , if fail then Error
	peerip = htonl(inet_addr(addr));			// convert address's string into 32bit address

	printf("\r\nPinging %s with %d bytes of data :\r\n",addr,size);

	/* Ping Service */
	while(bLoop || count-- > 0)				// if Infinite or count > 0, Loop 
	{
		if(uart_keyhit(0))
		{						// Is pressed "Ctrl-C" or "Ctrl-Break"?
			if(uart0_getchar()==0x03)
			{
				bLoop=count=0;
				printf("User canceled.\r\n");
				break;
			}
		}
		IsReceived = 0;
		RemainTime = time/2;
		PingRequest.SeqNum = htons(RandomSeqNum++);                           // Increase Sequence number for next ping-request packet
		PingRequest.CheckSum = 0;
		PingRequest.CheckSum = htons(checksum((u_char*)&PingRequest,size+8));	// update checksum field

		(*log).PingRequest++;				// Increase PingRequest's value

		if(sendto(s, (u_char *)&PingRequest,size+8,(u_char*)&peerip,3000)==-1)	// Send Ping-Request to the specified peer. If fail, then it is occurred ARP Error.
		{
			(*log).ARPErr++;			// Increase ARPErr
			close(s);                               // close the pinging socket
			/* Reopen pinging socket */
			setSn_PROTO(s,IPPROTO_ICMP);          
			if(socket(s,Sn_MR_IPRAW,3000,0)==0) return 0;
			continue;
		}
		while(RemainTime-- > 0)	// until wait_time is remaining 
		{
			wait_1ms(2);
			if((len = getSn_RX_RSR(s)) > 0)		// Is pinging socket  received a packet?
			{
				len = recvfrom(s,(u_char *)&PingReply,len,(uint8*)&tempip,&port);	// receive a packet from unknown peer
				strcpy(addrstr,inet_ntoa(ntohl(tempip)));                            // convert 32 bit unknown peer IP address into string of IP Address.
				printf("Reply from %s",addrstr);
				if(checksum((u_char*)&PingReply,len) != 0)			// if the packet's checksum value is correct
				{                                                               // not correct
					(*log).CheckSumErr++;                                   // checksum error
					if(tempip == peerip) IsReceived = 1;       
					printf(" : Checksum Error.\r\n"); 
				}
				else if(PingReply.Type == 0)					// if the received packet is ping-reply 
				{
										  		
					if((PingReply.ID!=PingRequest.ID) || (PingReply.SeqNum!=PingRequest.SeqNum) || (tempip!=peerip)) // verify id,sequence nubmer, and ip address
					{							
						printf(" : Unknown peer.\r\n");			// fail to verify 
						(*log).UnknownMSG++;
					}
					else                                                    // success
					{
						IsReceived = 1;
						printf(" : bytes=%d, time<=%dms\r\n",len-8,(time/2-RemainTime)*2);
						(*log).PingReply++;
					}
				}
				else if( PingReply.Type == 3)  					// If the packet is unreachable message
				{                                                               
					IsReceived = 1;
					printf(" : Destination Unreachable.\r\n");
					(*log).UnreachableMSG++;
				}
				else if( PingReply.Type == 11)                 			// If the packet is time exceeded message
				{
				        IsReceived = 1;
					printf(" : TTL expired in transit.\r\n");
					(*log).TimeExceedMSG++;
				}
				else if( PingReply.Type == 8)					// Send ping reply to a peer 
				{
					printf(" : Ping Request Message.\r\n");
					SendPingReply(s,&PingReply,len,tempip);
				}				
				else                                                            // if the packet is unknown message
				{
					printf(" : Unknown Message. (type = 0x%02X)\r\n",PingReply.Type);
					(*log).UnknownMSG++;
				}
			}
			else if(getSn_SR(s)==SOCK_CLOSED) 				// if it is occurred to fail to send arp packet
			{
				(*log).ARPErr++;
				close(s);							// close the pinging socket
				setSn_PROTO(s,IPPROTO_ICMP);                                  // Reopen the pinging socket
				if(socket(s,Sn_MR_IPRAW,3000,0)==0) return 0;
				break;
			}
			if(RemainTime == 0 && IsReceived == 0)					// If it is not received packet from the specified peer during waiting ping-reply packet.
			{
				(*log).Loss++;
				printf("Request timed out.\r\n");
			}
		}
	}
	printf("\r\n");

	/* Release pinging socket */
	setSn_PROTO(s,0);
	close(s);
	return 1;
}


/*
Description   :  Display result of ping 
Argument      :  log - result of ping
Return Value  :  None.
Note          :  
*/
void DisplayPingStatistics(PINGLOG log)
{
	printf("Ping statistics :\r\n");
    	printf("\tPackets: Sent = %d, Received = %d, Lost = %d\r\n",
    		log.PingRequest,log.PingReply+log.CheckSumErr+log.UnknownMSG+log.UnreachableMSG+log.TimeExceedMSG,log.Loss+log.ARPErr);
	if(log.CheckSumErr > 0)
		printf("\t\tChecksum Error Packets = %d\r\n",log.CheckSumErr);
	if(log.UnreachableMSG > 0)
		printf("\t\tUnreachable Message Packets = %d\r\n",log.UnreachableMSG);
	if(log.TimeExceedMSG > 0)
		printf("\t\tTime Exceeded Messsage Packets = %d\r\n",log.TimeExceedMSG);
	if(log.UnknownMSG > 0)
		printf("\t\tUnknown Message Packets = %d\r\n",log.UnknownMSG);
	if(log.ARPErr > 0)
		printf("\t\tFail To Send ARP Packets = %d\r\n",log.ARPErr);
	if(log.Loss > 0)
		printf("\t\tRequest timed out = %d\r\n",log.Loss);
	if(log.PingReply > 0)
		printf("\t\tPing Reply Packets = %d\r\n\r\n",log.PingReply);
}

/*
Description   :  Send ping reply packet to the specified peer
Argument      :  s - socket handle of receiving ICMP Message
		 pingrequest - received pingreply packet from the specified peer
                 len - size of received pingrequest packet
		 destaddr - 32bit peer ip address (network ordering)
Return Value  :  None.
Note          :  If Error is occurred in sending pingreply packet, then ignored
                 Because it is not need to send ping-reply packet to the specified peer everytime.
*/
void SendPingReply(SOCKET s, PINGMSG * pingrequest,u_int len, u_long destaddr)
{
	SOCKET PingReplySocket;
	u_int size = len;
        if(size > PINGBUF_LEN) size = PINGBUF_LEN;   					// Set size to MSS size(1460)
	PingReplySocket = getSocket(SOCK_CLOSED,s);			// Find free socket number
	if(PingReplySocket != (SOCKET)-1 && PingReplySocket > s)        // Is it exist free socket?
	{
		setSn_PROTO(PingReplySocket,IPPROTO_ICMP); 	      		// Set upper protocol of IP_RAW mode
		if(socket(PingReplySocket,Sn_MR_IPRAW,3001,0) != 0)	// Open ICMP socket
		{
			(*pingrequest).Type = 0;				// Ping-Reply
			(*pingrequest).Code = 0;                                // Always 0
  			(*pingrequest).CheckSum = 0;                            
			(*pingrequest).CheckSum = htons(checksum((u_char*)pingrequest,size));	// Calculate checksum
			if(sendto(PingReplySocket,(u_char*)pingrequest,size,(uint8*)&destaddr,3001)==-1)  // sent ping-reply packet to the specified peer
				printf("Fail to send ping-reply packet to %s\r\n",inet_ntoa(ntohl(destaddr)));
			close(PingReplySocket);				// Close ICMP socket
		}
		setSn_PROTO(PingReplySocket,0);		// Clear IP protocol register.
	}
	else	printf("Fail to send ping-reply packet to %s\r\n",inet_ntoa(ntohl(destaddr)));
}
