/*
 ActiveGS, Copyright 2004-2016 Olivier Goguel, https://github.com/ogoguel/ActiveGS
 Based on Kegs, Copyright 2004 Kent Dickey, https://kegs.sourceforge.net
 This code is covered by the GNU GPL licence
*/

#include "defc.h"
#include "sim65816.h"
#include "marinetti.h"

#ifndef NO_MARINETTI_SUPPORT

const MAC_STRUCT broadcast_mac_addr={ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF };


void print_ip(IP_STRUCT _p)
{
#ifdef _DEBUG
	const byte*p = (const byte*)&_p;
	printf("%3d.%3d.%3d.%3d",p[0],p[1],p[2],p[3]);
#endif
}

void print_mac(MAC_STRUCT _p)
{
#ifdef _DEBUG
	const byte*mac = (const byte*)&_p;
    printf("%0X.%0X.%0X.%0X.%0X.%0X",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
#endif
}

int dns_readname(const  byte* _reader,const  byte* _buffer,char _outname[] )
{
	unsigned char *name = (unsigned char*)_outname;
	const unsigned char *reader = (const unsigned char*)_reader;
	const unsigned char *buffer = (const unsigned char*)_buffer;

	unsigned int p=0,jumped=0,offset;
	int i , j;

	int count = 1;

	name[0]='\0';

	//read the names in 3www6google3com format
	while(*reader!=0)
	{
		if(*reader>=192)
		{
			offset = (*reader)*256 + *(reader+1) - 49152; //49152 = 11000000 00000000 ;)
			reader = buffer + offset - 1;
			jumped = 1; //we have jumped to another location so counting wont go up!
		}
		else
		{
			name[p++]=*reader;
		}

		reader=reader+1;

		if(jumped==0) count = count + 1; //if we havent jumped to another location then we can count up
	}

	name[p]='\0'; //string complete
	if(jumped==1)
	{
		count = count + 1; //number of steps we actually moved forward in the packet
	}

	//now convert 3www6google3com0 to www.google.com
	for(i=0;i<(int)strlen((const char*)name);i++)
	{
		p=name[i];
		for(j=0;j<(int)p;j++)
		{
			name[i]=name[i+1];
			i=i+1;
		}
		name[i]='.';
	}

	name[i-1]='\0'; //remove the last dot
	return count;
}


u16 ip_sum_calc(u16 len_ip_header,const byte* _buf)
{
u16 word16;
u32 sum=0;
u16 i;

	const byte* buff = _buf;
	for (i=0;i<len_ip_header;i=i+2)
	{
		word16 =((buff[i]<<8)&0xFF00)+(buff[i+1]&0xFF);
		sum = sum + (u32) word16;
	}
	while (sum>>16)
		sum = (sum & 0xFFFF)+(sum >> 16);		

	sum = ~sum;
	return ((u16) sum);
}

u16 udp_sum_calc(u16 _proto,u16 len_udp, const byte* src_addr,const byte* dest_addr, const byte* buff)
{
u16 word16;
u32 sum=0;	

	int i;
	for (i=0;i<len_udp;i=i+2)
	{
		word16 =((buff[i]<<8)&0xFF00)+(buff[i+1]&0xFF);
		sum = sum + (u32)word16;
	}

    while (sum>>16)
		sum = (sum & 0xFFFF)+(sum >> 16);

	for (i=0;i<4;i=i+2)
	{
		word16 =((src_addr[i]<<8)&0xFF00)+(src_addr[i+1]&0xFF);
		sum=sum+ (u32)word16;	
		word16 =((dest_addr[i]<<8)&0xFF00)+(dest_addr[i+1]&0xFF);
		sum=sum+(u32)word16; 	
	}

	sum = sum + _proto + len_udp;
    while (sum>>16)
		sum = (sum & 0xFFFF)+(sum >> 16);
	sum = ~sum;

	

	return ((u16) sum);
}
 

void print_dns(const byte* _ptr)
{
	DNS_HEADER* dns = (DNS_HEADER*)_ptr;

	char namestr [256];
	const byte* name = (const byte*)(_ptr+sizeof(DNS_HEADER));
	dns_readname(name,_ptr, namestr);
	
#ifdef _DEBUG
	QUESTION* q = (QUESTION*)(_ptr+strlen((const char*)name)+1+sizeof(DNS_HEADER));

	printf("nb_question:%d nb_answer;%d\n", ntohs(dns->q_count), ntohs(dns->ans_count));
	printf("dns query : %s\n",namestr);
	printf("question type:%d class: %d\n", ntohs(q->qtype), ntohs(q->qclass));
#endif
	RES_RECORD answers[20];//s,auth[20],addit[20]; //the replies from the DNS server

	//reading answers
		int stop=0;
		int i;
		byte* reader = (byte*)(_ptr+strlen((const char*)name)+1+sizeof(DNS_HEADER)+sizeof(QUESTION));
		byte* buf = (byte*)_ptr;

		for(i=0;i<ntohs(dns->ans_count);i++)
		{
			stop = dns_readname(reader,buf,answers[i].name);
			reader+=stop;
			answers[i].resource=(R_DATA*)(reader);
			reader+=sizeof(R_DATA);
			if(ntohs(answers[i].resource->type)==1)
			{
				for(int j=0;j<ntohs(answers[i].resource->data_len);j++)
					answers[i].rdata[j]=reader[j];
				answers[i].rdata[ntohs(answers[i].resource->data_len)]=0;
				reader+=ntohs(answers[i].resource->data_len);
			}
			else
			{
				stop = dns_readname(reader,buf,answers[i].rdata);
				reader+=stop;
			}
		}

	for(i=0;i<ntohs(dns->ans_count);i++)
	{
		printf(" ### [DNS] Reply -- Name : %s ",answers[i].name);

		if(ntohs(answers[i].resource->type)==1) //IPv4 address
		{
			long *p;
			p=(long*)answers[i].rdata;
			sockaddr_in a;
			a.sin_addr.s_addr=(*p); //working without ntohl
			printf("has IPv4 address : %s",inet_ntoa(a.sin_addr));
		}
		if(ntohs(answers[i].resource->type)==5) //Canonical name for an alias
		{
			printf("has alias name : %s",answers[i].rdata);
		}

		printf("\n");
	}

	if (i==0)
		printf(" ### [DNS] Query -- %s",namestr);

}

void print_udp(const UDP_HEADER *p)
{
	printf(" ### [UDP] ");
	printf("len:%d, src: %d, dest %d ",ntohs(p->udp_len),ntohs(p->udp_src),ntohs(p->udp_dst));
}


void print_arp(const ARP_HEADER * p)
{
#ifdef DEBUG_ARP
	int arp_type = ntohs(p->opcode);
	printf("[ARP] -- type %d, ",arp_type);
	printf(", src:");
	print_ip(p->src_protoaddr);
	printf("/");
	print_mac(p->src_hwaddr);
	printf(", dest:");
	print_ip(p->dst_protoaddr);
	printf("/");
	print_mac(p->dst_hwaddr);
#endif
}

void print_tcp(TCP_HEADER* p)
{

#ifdef _DEBUG
	printf(" ### [TCP]");
    printf(" src_port:%d dst_port:%d",ntohs(p->src_port),ntohs(p->dest_port));
	printf(" s:%x",ntohl(p->seq_number));

	if (p->flags & TCP_SYN)
		printf(" SYN");
	if (p->flags & TCP_ACK)
	{
		printf(" ACK");
		printf("%x ",ntohl(p->ack_number));
	}
	if (p->flags & TCP_RST)
		printf(" RST");
	if (p->flags & TCP_FIN)
		printf(" FIN");
	if (p->flags & TCP_PSH)
		printf(" PSH");
	if (p->flags & TCP_URG)
		printf(" URG");
#endif
}


int tcp_queue::check_tcp()
{
	TCP_HEADER* p = &this->tcp;

	int len = this->iplen; 

	u16 chk = udp_sum_calc(6,len,(const byte*)&GET_INT_FROM_IP(this->ip.ucDestination),(const byte*)&GET_INT_FROM_IP(this->ip.ucSource),(const byte*)p);
	if (chk!=0 && p->checksum)
	{
		printf("tcp checksum errror!");
		return 0;
	}

	return 1;
}


void print_ip_packet(const IP_HEADER* p)
{
#ifdef DEBUG_IP
	int l =ntohs(p->length);
	printf("[IP] -- len:%3d", l);
    printf(" src_ip:");
	print_ip(p->ucSource);
	printf(" dest_ip:");
	print_ip(p->ucDestination);
	
	short unsigned int flag_offset = ntohs(p->flag_offset);
	if (flag_offset & FLAG_MG)
		printf(" MF");
	printf("\n");
#endif
}


PConnection::PConnection()
{
	memset(this,0,sizeof(PConnection));
	tcpsocket=-1;
}

void PConnection::Reset()
{
	// regarde si il y a encore des données à envoyer pour cette connection
	
	for(int q=0;q<MAX_TCP_QUEUE;q++)
	{
		if (tcpqueues[q].state != QS_IDLE && tcpqueues[q].connection == this )
		{
			printf("*** reset queue : %x  ***\n",tcpqueues[q].queueseq);
			tcpqueues[q].state = QS_IDLE;
		}
	}
	
	SockClose(tcpsocket);
	state = PCS_CLOSED;
}

PConnection::~PConnection()
{
	Reset();
	
}

PConnection* init_connection(enum PConnectionType _socktype,int _type, int _srcport,IP_STRUCT _destip, int _destport, MAC_STRUCT _destmac, int _window)
{
	if (_type != RT_TCP && _type != RT_UDP)
	{
		printf("type mismatch\n");
		return NULL;
	}

	PConnection* p = find_connection(_socktype,_type,_srcport,_destip,_destport);
	if (p)
	{
		if (p->tcpsocket>0)
            return p;
	}
	else
	for(int tcpindex=0;tcpindex<MAX_TCP_CONNECTION;tcpindex++)
	{
		if (PConnections[tcpindex].tcpsocket ==-1)
		{
			p = &PConnections[tcpindex];
			p->source_port = _srcport;
			p->dest_port = _destport;
			p->dest_ip = _destip;
			p->dest_mac = _destmac;
			p->window = _window;
			p->already_sent = 0;
			p->type = _type;
            p->socktype = _socktype;
			p->state=PCS_CLOSED;
			
			static int la = 0x300;

			p->last_ack = la;
			p->send_seq = p-> last_ack;
			la += 0x010000;

			printf("\nconnection created ");
			print_ip(_destip);
			printf("\n");
			break;
		}
	}
	if (p==NULL)
	{
		printf("\n *** too many open connections ****\n");
		return NULL;
	}

    
    if (_socktype == PCT_CLIENT)
    {
        p->tcpsocket = _window;
        p->window = 16384;
        p->state=PCS_SYNSENT;
		p->state_seq = p->send_seq;
        p->sendtcppacket(TCP_SYN,0,NULL);
        
        return p;
		
    }
    
	if (_type==RT_TCP) // TCP
	{
		p->tcpsocket =  socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (p->tcpsocket < 0)  
		{
			printf("cannot create socket\n");
			return NULL;
		}
		// envoie la commande
		struct sockaddr_in server;
		server.sin_family = AF_INET;
        if (GET_INT_FROM_IP(_destip)==GET_INT_FROM_IP(host_ip_addr))
            server.sin_addr = local_ip_addr; // host loop back
        else
            server.sin_addr = _destip;
		server.sin_port = htons( _destport );
		 memset(&(server.sin_zero),0,8); 

		int err = connect(p->tcpsocket,(struct sockaddr *) &server,sizeof(server) );
		if (err < 0)
		{
			printf("connect send failed with err:%d\n",SockLastError());
			p->Reset();
			return NULL;
		}
		p->state=PCS_INITIALIZED;

	}
	else
	{
		// udp
		p->tcpsocket =  socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
		if (p->tcpsocket < 0)
		{
			printf("cannot open udp socket\n");
			p->Reset();
			return NULL;
		}
		
		p->state=PCS_INITIALIZED;
	}
	return p;
		
};

int PConnection::sendudppacket(int _len, const byte* _data)
{
	tcp_queue* packet = findTCPQueue();
	if (!packet) return 0;

	// UDP
	int udp_len = sizeof(UDP_HEADER)+_len;
	packet->udp.udp_len = htons(udp_len);
	packet->udp.udp_dst = htons(source_port);	// inverse
	packet->udp.udp_src = htons(dest_port);
	memcpy((byte*)&packet->udp+sizeof(UDP_HEADER),_data,_len);
	u16 chk = udp_sum_calc(17,udp_len,(const byte*)&dest_ip,(const byte*)&client_ip_addr,(const byte*)&packet->udp);
	packet->udp.udp_chk = htons(chk);


	// IP
	packet->ip.ucSource = dest_ip;
	packet->ip.ucDestination = client_ip_addr;
	packet->ip.protocol = 17; // udp
	packet->ip.ver = 4;
    packet->ip.ihl = 5;
	packet->ip.ttl = 64;


	int iplen = sizeof(IP_HEADER) + udp_len ;
	packet->ip.length = htons(iplen);
	u16 chkip=	ip_sum_calc(sizeof(IP_HEADER),(const byte*)&packet->ip);
	packet->ip.checksum = htons(chkip);

	// ETH
	packet->eth.dest_mac = client_mac_addr;
	packet->eth.source_mac = dest_mac;
	packet->eth.type = htons(0x0800); //IP
	
	int eth_len = iplen+sizeof(ETH_HEADER);
	packet->queuelen = eth_len ;
	packet->iplen = udp_len;
	packet->state = QS_TOSEND;
	return eth_len;

}

tcp_queue*	PConnection::sendtcppacket(int _flags,int _len, byte* _data)
{
	
	tcp_queue* packet = findTCPQueue();
	if (!packet) return 0;

	// TCP
	const int offset = 5;
	int tcp_header_len = offset*4;
	int tcp_len = tcp_header_len + _len;

	packet->tcp.flags = _flags ; 
	packet->tcp.ack_number = htonl(last_ack);

	packet->tcp.seq_number = htonl (send_seq );
	packet->tcp.window = htons(16384);
	packet->tcp.data_offset = offset;
	packet->tcp.dest_port = htons(source_port);	// inverse
	packet->tcp.src_port = htons(dest_port);
	memcpy((byte*)&packet->tcp+tcp_header_len,_data,_len);

	u16 chk = udp_sum_calc(6,tcp_len,(const byte*)&dest_ip,(const byte*)&client_ip_addr,(const byte*)&packet->tcp);
	packet->tcp.checksum = htons(chk);

	// IP
	packet->ip.ucSource = dest_ip;
	packet->ip.ucDestination = client_ip_addr;
	packet->ip.protocol = 6;
	packet->ip.ver = 4;
    packet->ip.ihl = 5;
	packet->ip.ttl = 64;


	int iplen = sizeof(IP_HEADER) + tcp_len ;
	packet->ip.length = htons(iplen);
	u16 chkip=	ip_sum_calc(sizeof(IP_HEADER),(const byte*)&packet->ip);
	packet->ip.checksum = htons(chkip);

	// ETH
	packet->eth.dest_mac = client_mac_addr;
	packet->eth.source_mac = dest_mac;
	packet->eth.type = htons(0x0800);

	int eth_len = iplen+sizeof(ETH_HEADER);
	packet->queuelen = eth_len ;
	packet->queueseq = send_seq+_len;
	packet->state = QS_TOSENDANDRETRY;
	packet->connection = this;
	
	packet->tcplen = _len;
	packet->iplen = tcp_len;
	send_seq+=_len;
	already_sent += _len;

	if (already_sent > window)
	{
		printf("****window error****\n");
	}
	return packet;	// nb d'octect envoyés
}

PConnection*  find_connection(enum PConnectionType _socktype,int _type,int _srcport,IP_STRUCT _destip, int _destport)
{
	for(int i=0;i<MAX_TCP_CONNECTION;i++)
	{
		PConnection* p  = &PConnections[i];
		if (
			p
			&&
			(p->type == _type)
			&&			
            (p->source_port == _srcport)
			&&
			(p->dest_port == _destport)
			&&
			(GET_INT_FROM_IP(p->dest_ip) == GET_INT_FROM_IP(_destip))
			&& 
			(p->state != PCS_CLOSED)
			)
			return p;
	}
	return NULL ;

}

void reset_connection(enum PConnectionType _socktype, int _type,int _srcport,IP_STRUCT _destip, int _destport)
{
	PConnection* p = find_connection(_socktype,_type,_srcport,_destip,_destport);
	if (p)
		p->Reset();
	else
		printf("ignoring reset on unknown connection\n");
}

void eth_queue::print()
{
	
#ifdef DEBUG_ETH
	if (this->eth.type==htons(0x0806))
		print_arp(&this->arp);
	else
	{
		print_ip_packet(&this->ip);
		int frag_offset = ntohs(this->ip.flag_offset);
		if (frag_offset & FLAG_MF)
				printf(" F");

		printf("id:%x offset:%d ",this->ip.identification,GET_OFFSET(frag_offset));
		switch(this->ip.protocol)
		{
			case 17: // udp
				printf("/UDP");
				break;
			case 6: // tcp
				printf("/TCP");
				break;
			default:
				break;
		}
	}
	printf("\n");
#endif
}


void tcp_queue::print()
{

	if (this->ip.protocol == 17 ) // UDP
	{
		print_udp(&this->udp);
			if (isDNS())				
				print_dns((const byte*)&this->udp+sizeof(UDP_HEADER));
	}
	else
	if (this->ip.protocol == 6 ) // TCP
	{


		print_tcp(&this->tcp);
		
		int offset =this->tcp.data_offset*4;
		int l = ntohs(this->ip.length);
		int tcplen = l - sizeof(IP_HEADER) - offset;
		if (tcplen)
			printf(" [tcplen:%d]",tcplen);
	}
	else
		printf("unknown protocol : %d",this->ip.protocol);
			printf("\n");
}



// SERVER

int server_sock;

int init_server(void)
{
  //  nb_client = 0;
    
    server_sock = socket(AF_INET, SOCK_STREAM, 0);
    
    if(server_sock == SOCKET_ERROR)
    {
        printf("server init failed\n");
        return 0;
    }
    
    struct sockaddr_in sin;
    
    sin.sin_addr.s_addr = htonl(INADDR_ANY);
    sin.sin_port = htons(8080);
    sin.sin_family = AF_INET;
    
    if(bind(server_sock,(struct sockaddr *)&sin, sizeof sin) == SOCKET_ERROR)
    {
        printf("cannot bind\n");
        return 0;
    }
    
    if(listen(server_sock, 0) == SOCKET_ERROR)
    {
        printf("cannot listent\n");
        return 0;    
    }
    
    
    
    return 1;
}

void server_loop(void)
{
 	for(int i=0;i<MAX_TCP_CONNECTION;i++)
		{
			PConnection* p  = &PConnections[i];
			if (p->state != PCS_CLOSED) // *** ON ATTEND LA FIN DE LA CONNECTION => BCP PLUS RAPIDE??
			{
				// server not ready to accept new connection		
				return ;
			}
		}

    int max = server_sock;
    
    fd_set rdfs;
    
 
    FD_ZERO(&rdfs);
        
     // add the connection socket 
    FD_SET(server_sock, &rdfs);
      
    timeval timeout;
    timeout.tv_usec = 1;
    timeout.tv_sec = 0;
    
    if(select(max + 1, &rdfs, NULL, NULL, &timeout) == -1)
    {
 //       printf("failed select\n");
        return ;
    }
        
    if(FD_ISSET(server_sock, &rdfs))
    {
        // new client
        struct sockaddr csin = { 0 };
        socklen_t sinsize = sizeof(struct sockaddr);
        int csock = accept(server_sock, &csin, &sinsize);
        if(csock == SOCKET_ERROR)
        {
            printf("failed accept\n");;
            return;
        }
            
            
        max = csock > max ? csock : max;
            
        FD_SET(csock, &rdfs);
        IP_STRUCT *ip_client = (IP_STRUCT*)&csin.sa_data[0];
        MAC_STRUCT mac_client;
        mac_client.SetFromIP(*ip_client);
		

		for(int i=0;i<MAX_TCP_CONNECTION;i++)
		{
			PConnection* p  = &PConnections[i];
			if (p->state == PCS_SYNSENT)
			{
				printf("\n*** INGORING %d from ",csock);
				print_ip(*ip_client);
				printf("\n");
				return ;
			}
		}
	
		printf("\n*** connection %d from ",csock);
		print_ip(*ip_client);
		printf("\n");

		// génère un port random pour ne pallier le IPOPENUSER qui ne s base que sur les ports!
		static int free_port=100;
		init_connection(PCT_CLIENT,RT_TCP, 80, *ip_client, free_port++, mac_client,csock);
		
	}
  
}

#endif