// PROGRAMMER: Fatemah Panahi
// CS640 Project 3 Spring 2010 University of Wisconsin-Madison
// routetrace Application
/*Program details:
1) It gets the source and destination IP and port from the command line.
2) It sets the TTL to 0
3) Sends a TTL packet to the source with packet fields: "T", TTL, routetrace IP, routetrace Port, Destination IP, Destination Port
4) Waits for a response.
5) Once it gets a response prints out the responders IP and port (that it gets from the response packet). 
6) If the source IP and port fields of the TTL packet that it received equals the destination IP and port that it received from the command line then TERMINATES.
7) Otherwise, TTL = TTL + 1, goto 3.
*/
#include "blastlib.h"
#include <stdio.h>

const int maxPacketSize=60000;

bool argsOK = true;

unsigned long myIP = 0;   //in network byte order
unsigned short myPort = 0; //in network byte order
char myIPAddress[255];

unsigned long sourceIP = 0;   //in network byte order
unsigned short sourcePort = 0;  //in network byte order
char sourceIPAddress[255];

unsigned long destinationIP = 0;   //in network byte order
unsigned short destinationPort = 0;  //in network byte order
char destinationIPAddress[255];

unsigned short responderPort = 0;
char responderIPAddress[255];

char *sourceName = 0;
char *destinationName = 0;
struct hostent *hostInfo;
int traceSocket;




//const int packetSize = sizeof(char)+ sizeof(unsigned long) + 2*(sizeof (unsigned long) + sizeof (unsigned short)); //Type+TTL+IP+port
const int packetSize = 17;
char packet[packetSize];

bool done = false;
int debug = 0;

unsigned long globalTTL = 0;

struct QueueEntry
{
	int hopNumber;
	char IP[255];
	int port;
};	
vector<QueueEntry> queue;

using namespace std;

bool parseArgs(int argc, char* argv[])
{
	const char myPortCommand[] = "-a";
	const char sourceHostCommand[] = "-b";
	const char sourcePortCommand[] = "-c";
	const char destHostCommand[] = "-d";
	const char destPortCommand[] = "-e";
	const char debugCommand[] = "-f";


	for (int i = 1; i < argc; i++)
	{
		if ((strcmp(myPortCommand, argv[i]) == 0) && (i < argc - 1) && isNumeric(argv[i + 1], false))
		{
			myPort = htons(atoi(argv[i + 1]));
		}
		if ((strcmp(sourcePortCommand, argv[i]) == 0) && (i < argc - 1) && isNumeric(argv[i + 1], false))
		{
			sourcePort = htons(atoi(argv[i + 1]));
		}
		if ((strcmp(destPortCommand, argv[i]) == 0) && (i < argc - 1) && isNumeric(argv[i + 1], false))
		{
			destinationPort = htons(atoi(argv[i + 1]));
		}
		if ((strcmp(sourceHostCommand, argv[i]) == 0) && (i < argc - 1))
		{
			sourceName = argv[i + 1];
		}
		if ((strcmp(destHostCommand, argv[i]) == 0) && (i < argc - 1))
		{
			destinationName = argv[i + 1];
		}
		if ((strcmp(debugCommand, argv[i]) == 0) && (i < argc - 1) && isNumeric(argv[i + 1], false))
		{
			debug = atoi(argv[i + 1]);

		}

	}
	if (myPort == 0)
	{
		cout << "ERROR: invalid port or no port specified." << endl;
		argsOK = false;
	}
	if (sourcePort == 0)
	{
		cout << "ERROR: invalid source port or no port specified." << endl;
		argsOK = false;
	} 
	if (destinationPort == 0)
	{
		cout << "ERROR: invalid destination port or no port specified." << endl;
		argsOK = false;
	}
	if (sourceName == 0)
	{
		cout << "ERROR: no source name specified." << endl;
		argsOK = false;
	} else if ((hostInfo = gethostbyname(sourceName)) == NULL)
	{
		cout << "ERROR: invalid source name." << endl;
		argsOK = false;
	}
	if (destinationName == 0)
	{
		cout << "ERROR: no destination name specified." << endl;
		argsOK = false;
	} else if ((hostInfo = gethostbyname(destinationName)) == NULL)
	{
		cout << "ERROR: invalid destination name." << endl;
		argsOK = false;
	}
	if ((debug != 0) && (debug != 1))
	{
		cout << "ERROR: invalid debug option." << endl;
		argsOK = false;
	}

	return argsOK;
}

int parsePacket(char packet[], unsigned long responderIP)
{ 	
	bool packetOK = true;
	char type;
	unsigned long TTL;
	unsigned long packetResponderIP;
	unsigned short packetResponderPort;
	unsigned long packetDestIP;
	unsigned short packetDestPort;

	int A = sizeof(type);
	int B = sizeof(TTL);
	int C = sizeof(packetResponderIP);
	int D = sizeof(packetResponderPort);
	int E = sizeof(packetDestIP);
	int F = sizeof(packetDestPort);

	memcpy(&type, &packet[0], A);
	memcpy(&TTL, &packet[A], B);
	memcpy(&packetResponderIP, &packet[A + B], C);
	memcpy(&packetResponderPort, &packet[A + B + C], D);
	memcpy(&packetDestIP, &packet[A + B + C + D], E);
	memcpy(&packetDestPort, &packet[A + B + C + D + E], F);

	if (type != 'T')
	{
		cout <<"ERROR: packet has invalid type." << endl;
		packetOK = false;

	}

	TTL = ntohl(TTL);

	if(TTL != 0){
		cout <<"ERROR: Got a packet with TTL > 0." << endl;
		packetOK = false;	
	}

	/*if(responderIP != packetResponderIP){
	  cout <<"ERROR: IP encoded in packet does not match the IP I received the packet from." << endl;
	  packetOK = false;
	  }*/
	if(packetResponderPort == destinationPort){
		done = true;
	}

	if(debug){
		char packetDestIPAddress[255];
		inet_ntop(AF_INET, &packetResponderIP, responderIPAddress, sizeof(responderIPAddress));
		inet_ntop(AF_INET, &packetDestIP, packetDestIPAddress, sizeof(packetDestIPAddress));
		cout << "Received: "; 
		cout << type << " | ";
		cout << TTL <<" | ";
		cout << responderIPAddress << " | ";
		cout << ntohs(packetResponderPort) << " | ";
		cout << packetDestIPAddress << " | ";
		cout << ntohs(packetDestPort) << " | ";		
		cout<<endl;
	}
	QueueEntry qEntry;
	qEntry.hopNumber = globalTTL+1;
	memcpy(&qEntry.IP[0], &responderIPAddress[0], sizeof(qEntry.IP));
	qEntry.port = ntohs(packetResponderPort);

	queue.push_back(qEntry);

	return packetOK;
}

int generateProbe(){
	//initialize the packet
	for (int i = 0; i < packetSize; i++){
		packet[i] = '\0';
	}
	char type = 'T';

	int A = sizeof(type); //sizeof priority
	int B = sizeof(globalTTL);
	int C = sizeof(myIP);
	int D = sizeof(myPort);
	int E = sizeof(destinationIP);
	int F = sizeof(destinationPort);

	unsigned long myTTL = htonl(globalTTL);
	memcpy(&packet[0], &type, A);
	memcpy(&packet[A], &myTTL, B);
	memcpy(&packet[A + B], &myIP, C);
	memcpy(&packet[A + B + C], &myPort, D);
	memcpy(&packet[A + B + C + D], &destinationIP, E);
	memcpy(&packet[A + B + C + D + E], &destinationPort, F);
	if(debug){
		cout << "Sent:     ";

		cout << type << " | ";
		cout << globalTTL<<" | ";
		cout << myIPAddress << " | ";
		cout << ntohs(myPort) << " | ";		
		cout << destinationIPAddress << " | ";
		cout << ntohs(destinationPort) << " | ";		

		cout<<endl;
	}

	return true;
}

int listen4responder(){
	if(debug){
		cout<<"Waiting for probe response"<<endl;
	}
	//initialize socket and bind to for receiving
	int probeSocket = socket(AF_INET, SOCK_DGRAM,0);
	if (probeSocket < 0)
	{
		printf ("%s: cannot open socket for receiving probe response \n");
		exit (1);
	}
	sockaddr_in probeAddress;
	probeAddress.sin_family = AF_INET;
	probeAddress.sin_addr.s_addr = htonl(INADDR_ANY);
	probeAddress.sin_port = htons(myPort); 
	if (bind(probeSocket, (struct sockaddr *) &probeAddress,sizeof(probeAddress)) < 0)
	{
		cout << "ERROR: unable to bind socket" << endl;
		return false;
	}

	char buffer[maxPacketSize];
	for(int i=0;i<maxPacketSize;i++){
		buffer[i]='\0';
	}	

	sockaddr_in responderAddress;
	socklen_t responderAddressLength = sizeof(responderAddress);

	if (recvfrom(probeSocket, buffer, sizeof(buffer), 0, (struct sockaddr *) &responderAddress, &responderAddressLength) < 0)
	{
		cout << "ERROR: problem with receiving data" << endl;
		return false;
	}

	inet_ntop(AF_INET, &(responderAddress.sin_addr), responderIPAddress, sizeof(responderIPAddress));


	// parse the response to get the IP	

	int returnValue = parsePacket(buffer, responderAddress.sin_addr.s_addr);
	if(!returnValue){
		return false;
	}


	close(probeSocket);

	return true;
}

// send request to recieve the file
int sendProbe(){
	// probe will be sent to the source name always.
	int bytesSent;
	if ((hostInfo = gethostbyname(sourceName)) == NULL)
	{
		cout << "ERROR: invalid server name." << endl;
		return -1;
	}

	int sendSocket = socket(AF_INET, SOCK_DGRAM, 0);
	sockaddr_in sendAddress;
	sendAddress.sin_family = hostInfo->h_addrtype;
	sendAddress.sin_port = htons(sourcePort); 
	memcpy((char *) &sendAddress.sin_addr.s_addr, hostInfo->h_addr_list[0], hostInfo->h_length);
	bytesSent = sendto(sendSocket, packet, packetSize, 0,(struct sockaddr*) &sendAddress, sizeof(struct sockaddr_in));
	cout << "Bytes sent: " << bytesSent << endl;
	if(bytesSent < 0)
	{
		cout << "ERROR: problem with sending data. sendto function returned error." << endl;
		return -1;
	}

	close(sendSocket);

	return true;
}


int main(int argc, char* argv[])
{
	myIP = GetLocalIP();
	inet_ntop(AF_INET, &myIP, myIPAddress, sizeof(myIPAddress));
	if (!parseArgs(argc, argv))
		return -1;

	if ((hostInfo = gethostbyname(destinationName)) == NULL)
	{
		cout << "ERROR: invalid server name." << endl;
		return -1;
	}
	memcpy((char *) &destinationIP, hostInfo->h_addr_list[0], hostInfo->h_length);
	inet_ntop(AF_INET, &destinationIP, destinationIPAddress, sizeof(destinationIPAddress));
	while (!done){

		generateProbe();
		sendProbe();
		listen4responder();
		globalTTL++;

	}

	cout<<"Hop#"<<"  "<<"IP"<<", "<<"Port"<<endl;
	for(int i = 0; i < queue.size(); i++) {
		cout<< queue.at(i).hopNumber <<"     ";
		cout<< queue.at(i).IP <<", ";
		cout<< queue.at(i).port <<endl;	
	}

	queue.clear();
	return true;
}


