/*
	Date	01/18/2014
	Auther	Satoshi Yasuda	7M3TJZ/AD6GZ
	Mod.
	2014.01.21 V00.00
	2014.01.22 V00.01 Bug fixed for from_rig.c
	2014.01.22 V00.02 Bug fixed for beep.c
	2014.01.23 V00.03 drop "htons"
	2014.01.23 V00.04 Added RnRoute messages routine
	2014.01.28 V00.05 Changed "hole punching" routine & bug fixed
	2014.01.29 V00.06 Added PacketID check
	2014.01.30 V00.07 Added ProductID & Vendor ID set routine
	2014.01.31 V00.08 Bug fixed for login logic. 
		(When callsign or password is wrong, dv_echo was looped.)
	2014.02.03 V00.09 Added CallCheck
	2014.02.04 V00.10 Bug fixed CallCheck
	2014.02.04 V00.11 Added  ReloadCallCheckACL
	2014.02.05 V00.12 Added log file
	2014.02.06 V00.13 Bug fixed
	2014.02.06 V00.14 Added KeepAlive for TCP
	2014.02.07 V00.15 Changed hole punching interval
	2014.02.12 V00.16 Changed Busy logic
	2014.02.13 V00.17 Changed Busy logic
	2014.02.14 V00.18 Changed Hole Punching packet format
	2014.02.16 V00.19 Bug fixed for CallCheck
*/

#include	"dsgwd_pi.h"
#include	"version.h"
#include	"node.h"
#include	"holeP.h"	/* Hole Punching packet format */

void	*node_adapter (void *arg);
void	send_dsvt(void);
void	receive_dsvt(void);

int	dstSocket;
int	udpSocket;

struct	sockaddr_in	udpAddr;
struct	sockaddr_in	sndAddr;

mqd_t	rf_qid;
mqd_t	inet_qid;
char	ConnectWait;
char	FirstHeaderSend;

struct	in_addr trustIP;
struct	in_addr	dstIP;
struct	in_addr	dbIP;

in_port_t	dstPort;

struct	mq_attr	rf_mattr;
struct	mq_attr	inet_mattr;
struct  repMsg  reqConMsg;

pthread_t	usb_ID;

char	SendConnect;
char	RecvConnect;
struct  packet  	send_pkt;
struct	shm_form	send_msg;
int	AckTimeOut;
int	DSTREQcnt;
char	RelaySW;
char	respFlags;
char	Connected;
char	DVwait;
char	AckSW;
unsigned char	SndPacketIdSave[2];
unsigned char	RcvPacketIdSave[2];

char	PTT_On;
char	COS_On;

void CloseSocket()
{
        if (dstSocket != 0){
                close(dstSocket);
                dstSocket=0;
        }
        if (udpSocket != 0){
                close(dstSocket);
                dstSocket=0;
        }
	mq_close (rf_qid);
	mq_close (inet_qid);
	mq_unlink ("/dstar_rf");
	mq_unlink ("/dstar_inet");

//	pthread_join (usb_ID, NULL);

        logfile = fopen (LOGFILE, "a");
	now = time(NULL);
        fprintf (logfile, "%24.24s dsgwd_pi %s End\n", ctime(&now), VERSION);
        fclose(logfile);
        exit(0);
}

void	send_dsvt(void)
{
	int	length;
	int	n;
	int	ret;
        struct  repMsg  ConMsg;

	if (ConnectWait) return;
	if (FirstHeaderSend)
	{
		if (RelaySW)
		{
			memcpy ((char *)&RelayPacket.dv, (char *)&send_pkt, 56);
			RelayPacket.ip = dstIP;
			RelayPacket.port = dstPort;
        		sndAddr.sin_family = PF_INET;
        		sndAddr.sin_port = trust_db_port;
        		sndAddr.sin_addr = dbIP;
        		sendto (udpSocket, (char *)&RelayPacket, 
				sizeof(RelayPacket), 0,
                		(struct sockaddr *)&sndAddr, sizeof(sndAddr));
		}
		else
		{
                	sndAddr.sin_addr = dstIP;
                	sndAddr.sin_port = dstPort;
                	sndAddr.sin_family = PF_INET;
                	sendto (udpSocket, &send_pkt, 56, 0,
                        	(struct sockaddr *)&sndAddr, sizeof(sndAddr));
		}
		FirstHeaderSend = FALSE;
	}
	length = mq_receive (rf_qid, (char *)&send_pkt, sizeof(send_pkt), NULL); 
	if (length > 0)
	{
		if (memcmp (send_pkt.ID, STREAM_ID, 4) == 0)
		{
			if (send_pkt.B_header.seq == 0x80)
			{
        			SndPacketIdSave[0] = send_pkt.B_header.PacketID[0];
        			SndPacketIdSave[1] = send_pkt.B_header.PacketID[1];
				ConSend();
				ConnectWait = TRUE;
				AckTimeOut = 0;
				DSTREQcnt = 0;
				Connected = FALSE;
				RelaySW = FALSE;
				return;
			}
			if (RelaySW)
			{
                        	memcpy ((char *)&RelayPacket.dv, 
					(char *)&send_pkt, 27);
                        	RelayPacket.ip = dstIP;
                        	RelayPacket.port = dstPort;
                        	sndAddr.sin_family = PF_INET;
                        	sndAddr.sin_port = trust_db_port;
                        	sndAddr.sin_addr = dbIP;
                        	sendto (udpSocket, (char *)&RelayPacket, 33, 0,
                                	(struct sockaddr *)&sndAddr, 
					sizeof(sndAddr));
			}
			else
			{
                		sndAddr.sin_addr = dstIP;
                		sndAddr.sin_port = dstPort;
                		sndAddr.sin_family = PF_INET;
        			sendto (udpSocket, &send_pkt, 27, 0,
               				(struct sockaddr *)&sndAddr, 
					sizeof(sndAddr));
			}
			if (send_pkt.B_header.seq & 0x40)
			{
				ConnectWait = FALSE;
				FirstHeaderSend = FALSE;
				Connected = FALSE;
				AckSW = FALSE;
			}
		}
	}
}

void	receive_dsvt(void)
{
	socklen_t addrlen;
	struct	packet	inet_recv;

	addrlen = sizeof(struct sockaddr_in);
	recvfrom (udpSocket, (char *)&inet_recv, sizeof(inet_recv), 0, 
		(struct sockaddr *)&sndAddr, &addrlen);
        if (memcmp ((char *)&inet_recv, SRCREQ, 5) == 0)
        {
		sendto (udpSocket, SRCACK, 5, 0,
               		(struct sockaddr *)&sndAddr, sizeof(sndAddr));
        }
        if (memcmp ((char *)&inet_recv, DSTREQ, 5) == 0)
        {
		if (!Connected && AckSW)
		{	
                	ConnectWait = FALSE;
                	FirstHeaderSend = TRUE;
			dstPort = sndAddr.sin_port;
			dstIP = sndAddr.sin_addr;
			Connected = TRUE;
		}
        }
        if (memcmp ((char *)&inet_recv, SRCACK, 5) == 0)
        {
		if (!Connected && AckSW)
		{
			dstPort = sndAddr.sin_port;
			dstIP = sndAddr.sin_addr;
			ConnectWait = FALSE;
			FirstHeaderSend = TRUE;
			Connected = TRUE;
		}
        }
	if (!memcmp ((char *)&inet_recv.ID, STREAM_ID, 4) 
		&& !memcmp(&RcvPacketIdSave, &inet_recv.B_header.PacketID, 2))
	{
		mq_send (inet_qid, (char *)&inet_recv, sizeof(inet_recv), 1);
		DVwait = FALSE;
	}
}

void	AckSendTrust(char srcRptCall[], char PacketID[] )
{
        struct  shm_form        ack_msg;

	ack_msg.type = Ack;
        memcpy (ack_msg.Rep1Call, srcRptCall, 8);
        memcpy (ack_msg.dstCall, client_call, 8);
        sndAddr.sin_family = PF_INET;
        sndAddr.sin_port = trust_db_port;
	sndAddr.sin_addr = dbIP;
        ack_msg.PacketID[0] = PacketID[0];
        ack_msg.PacketID[1] = PacketID[1];
        sendto (udpSocket, (char *)&ack_msg, sizeof(ack_msg), 0,
                 (struct sockaddr *)&sndAddr, sizeof(sndAddr));
}

void    NakSendTrust(char srcRptCall[], char PacketID[])
{
        struct  shm_form        ack_msg;

        ack_msg.type = Nak;
        memcpy (ack_msg.Rep1Call, srcRptCall, 8);
        memcpy (ack_msg.dstCall, client_call, 8);
	ack_msg.PacketID[0] = PacketID[0];
	ack_msg.PacketID[1] = PacketID[1];
        sndAddr.sin_family = PF_INET;
        sndAddr.sin_port = trust_db_port;
        sndAddr.sin_addr = dbIP;
	
        sendto (udpSocket, (char *)&ack_msg, sizeof(ack_msg), 0,
                 (struct sockaddr *)&sndAddr, sizeof(sndAddr));
}

void	ReqSendSender()
{
	sndAddr.sin_family = PF_INET;
	sndAddr.sin_port = dstPort;
	sndAddr.sin_addr = dstIP;
	sendto (udpSocket, DSTREQ, 5, 0,
		(struct sockaddr *)&sndAddr, sizeof(sndAddr));
}

void	ConSend(void)
{
        memcpy (&send_msg.Rep2Call, &send_pkt.header.Rpt2Call, 32);
	memcpy (&send_msg.dstRep, &send_pkt.header.YourCall, 8);
	send_msg.type = ReqConnect;
	send_msg.protocol = G3;
	send_msg.PacketID[0] = SndPacketIdSave[0];
	send_msg.PacketID[1] = SndPacketIdSave[1];
	sndAddr.sin_family = PF_INET;
	sndAddr.sin_port = trust_db_port;
	sndAddr.sin_addr = dbIP;
	sendto (udpSocket, (char *)&send_msg, sizeof (struct shm_form), 0,
		(struct sockaddr *)&sndAddr, sizeof(sndAddr));
}

int main(int argc, const char **argv)
{
        int     ret;
        int     width;
        int     n;
	int	k;
        fd_set  readOk, Mask;
        struct  timeval timeout;

	// IP アドレス，ソケット，sockaddr_in 構造体
	struct sockaddr_in dstAddr;
 	struct	hostent *hp;

  	char	buf[128];
	int	numrcv;
	int	status;

	FILE	*PidFile;
	int	cmdin;
	int	KeepAliveCnt;

        signal(SIGINT,CloseSocket);
        signal(SIGTERM,CloseSocket);


	logfile = fopen (LOGFILE, "a");
	now = time(NULL);
	fprintf (logfile, "%24.24s dsgwd_pi %s Start\n", ctime(&now), VERSION);
	fclose(logfile);

	srand ((unsigned) time (NULL));

	/* PID の書き出し　/var/run/dsgwd_pi.pid  */
	PidFile = fopen ("/var/run/dsgwd_pi.pid","w");
	if (PidFile == NULL)
	{
        	logfile = fopen (LOGFILE, "a");
        	now = time(NULL);
        	fprintf (logfile, "%24.24s Must run on supper user\n", ctime(&now));
        	fclose(logfile);
		exit(-1);
	}

	fprintf (PidFile, "%d\n",(int)getpid());
	fclose (PidFile);

	cmdmode = 0;
	ConnectWait = FALSE;
	FirstHeaderSend = FALSE;
	SendConnect = FALSE;
	RecvConnect = FALSE;
	AckTimeOut = 0;
	RelaySW = FALSE;
	respFlags = RespNULL;
	repeater_mode = 0;
	COS_On = FALSE;
	Connected = FALSE;
	DSTREQcnt = 0;
	DVwait = FALSE;
	AckSW = FALSE;
	KeepAliveCnt = 0;
	NodeVendorID =  VendorID;
	NodeProductID = ProductID;
	CallCheck = FALSE;

	config (argc, (char **)argv);

	if (cmdmode == 0)
	{
		cmdin = 0;
	}
	else
	{
		/* コマンド入力の名前付きのパイプ(FIFO)のオープン */

		mkfifo ("/tmp/dsgwd_pi-cmdin", 0600);

		cmdin = open ("/tmp/dsgwd_pi-cmdin", O_RDONLY | O_NONBLOCK);
		if (cmdin < 0)
		{
        		logfile = fopen (LOGFILE, "a");
        		now = time(NULL);
			fprintf (logfile, "%24.24s ERROR FIFO file not open /tmp/dsgwd_pi-cmdin\n", ctime(&now));
			fclose (logfile);
			cmdin = 0;
		}
	}

        hp = gethostbyname(TrustDomainName);
        memcpy (&trustIP, hp->h_addr_list[0], hp->h_length);

        hp = gethostbyname(TrustDbDomainName);
        memcpy (&dbIP, hp->h_addr_list[0], hp->h_length);

	/* define mq attribute */
	rf_mattr.mq_flags = O_NONBLOCK;
	rf_mattr.mq_maxmsg = 100;
	rf_mattr.mq_msgsize = sizeof(struct packet);
	rf_mattr.mq_curmsgs = 0;

	rf_qid = mq_open ("/dstar_rf", O_RDWR | O_CREAT, 
		S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, &rf_mattr);

        /* define mq attribute */
        inet_mattr.mq_flags = O_NONBLOCK;
        inet_mattr.mq_maxmsg = 100;
        inet_mattr.mq_msgsize = sizeof(struct packet);
        inet_mattr.mq_curmsgs = 0;

        inet_qid = mq_open ("/dstar_inet", O_RDWR | O_CREAT,
                S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, &inet_mattr);

	status = pthread_create (&usb_ID, NULL, node_adapter, (void *)NULL);
	if (status)
	{
        	logfile = fopen (LOGFILE, "a");
        	now = time(NULL);
		fprintf (logfile, "%24.24s pthread error %s\n", 
			ctime(&now), strerror(status));
		fclose (logfile);
		exit(-1);
	}

	//sockaddr_in 構造体のセット
	bzero((char *)&dstAddr, sizeof(dstAddr));
	dstAddr.sin_family = PF_INET;
	dstAddr.sin_port = trust_port;
	dstAddr.sin_addr = trustIP;

	//ソケットの生成
	dstSocket = socket(PF_INET, SOCK_STREAM, 0);
	udpSocket = socket(PF_INET, SOCK_DGRAM, 0);

	//接続
	if (connect(dstSocket, (struct sockaddr *)&dstAddr, 
			sizeof(dstAddr)) < 0){
        	logfile = fopen (LOGFILE, "a");
        	now = time(NULL);
		fprintf(logfile, "%24.24s Does not connect to '%s'.\n",
			ctime(&now),TrustDomainName);
		fclose (logfile);
		CloseSocket();	
	}

        logfile = fopen (LOGFILE, "a");
        now = time(NULL);
	fprintf(logfile, "%24.24s Connect to '%s'.\n", 
		ctime(&now), TrustDomainName);
	fclose (logfile);

	//コールサイン入力のプロンプト
	memset (buf, 0x20, 14); 
	while (memcmp (buf,"Repeater Call:", 14) != 0)
	{
		numrcv = recv(dstSocket, buf, 64, 0);
	}

	//コールサインの送信
	for (n = 0 ; n < 8 ; n++)
	{
		client_call[n] = toupper (client_call[n]);
	}
	send (dstSocket, client_call, 8, 0);
        //パスワード入力のプロンプト
	memset (buf, 0x20, 9);
	while (memcmp (buf, "Password:", 9) != 0)
	{
        	numrcv = recv(dstSocket, buf,  128, 0);
	}

        //パスワードの送信
        send (dstSocket, client_password, strlen(client_password), 0);
        //ポート番号入力のプロンプト
	memset (buf, 0x20, 12);
	numrcv = recv (dstSocket, buf, 128, 0);
        if (!memcmp (buf, "Port Number:", 12) == 0)
	{
        	logfile = fopen (LOGFILE, "a");
        	now = time(NULL);
		fprintf (logfile, "%24.24s Invalid CallSign or Password\n", 
			ctime(&now));
		fclose (logfile);
		CloseSocket();
	}
		
	sprintf (buf, "%0d\0", client_port);
	//ポート番号の送信
        send (dstSocket, buf, strlen(buf), 0);

        udpAddr.sin_family = PF_INET;
        udpAddr.sin_port = htons(client_port);
        udpAddr.sin_addr.s_addr = htonl(INADDR_ANY);
        bind(udpSocket, (struct sockaddr *)&udpAddr, sizeof(udpAddr));

	if (CallCheck) loadCallCheckACL();

        FD_ZERO(&Mask);
        FD_SET(dstSocket, &Mask);
	FD_SET(udpSocket, &Mask);
	FD_SET(cmdin, &Mask);
	FD_SET(rf_qid, &Mask);
        width = dstSocket;
	if (width < rf_qid) width = rf_qid;
	if (width < udpSocket) width = udpSocket;
	if (width < cmdin) width = cmdin;
	width++;

	while (1){
		readOk = Mask;
		if (ConnectWait) FD_CLR(rf_qid, &readOk);
		timeout.tv_sec = 0;
		timeout.tv_usec = 5000;		/* wait 5 m sec. */

		switch (select (width, (fd_set *)&readOk, NULL, NULL, &timeout))
		{
			case -1:
				break;
			case 0:
				if (ConnectWait)
				{
					AckTimeOut++;
					if (AckTimeOut > 100)	/* 500 m sec. */
					{
						RelaySW = TRUE;
						ConnectWait = FALSE;
						FirstHeaderSend = TRUE;
						AckTimeOut = 0;
						Connected = TRUE;
        					logfile = fopen (LOGFILE, "a");
        					now = time(NULL);
						fprintf (logfile, "%24.24s Change to Relay Mode\n", ctime(&now));
						fclose (logfile);
					}
                                        else if ((AckTimeOut % 20) == 0)     /* 100 m sec, interval */
                                        {
                                                ConSend();
                                        }

				}
				if (DVwait)
				{
					DSTREQcnt++;
					if ((DSTREQcnt % 4) == 0)	/* 20 m sec. interval */
					{
						ReqSendSender();
					}
				}
				KeepAliveCnt++;
				if (KeepAliveCnt > 2000)
				{
                                        reqConMsg.reqType = KeepAlive;
					memset (&reqConMsg.Keep_Alive, 0x00, 3);
                                        send (dstSocket, (char *)&reqConMsg, 4, 0);
					KeepAliveCnt = 0;
				}
				break;
			default:
                                if (FD_ISSET(udpSocket, &readOk))
                                {
                                        receive_dsvt();
                                }
				if(FD_ISSET(dstSocket, &readOk))
				{
                        		//パケットの受信
                        		numrcv = recv(dstSocket, (char *)&reqConMsg, sizeof(reqConMsg), 0);
					if (numrcv <= 0)
                                        {
                                                reqConMsg.reqType = ReqEnd;
                                                send (dstSocket, (char *)&reqConMsg, sizeof(reqConMsg), 0);
                    				close(dstSocket);
                                                return(0);
                                        }
                        		if (reqConMsg.reqType == Ack)
                        		{
						if (!Connected && !memcmp(&SndPacketIdSave, &reqConMsg.ackConnect.PacketID, 2))
						{
							logfile = fopen	(LOGFILE, "a");
							now = time(NULL);
							fprintf (logfile, "%24.24s Receive Ack\n", ctime(&now));
							fclose (logfile);
							dstPort = reqConMsg.ackConnect.port;
							dstIP = reqConMsg.ackConnect.ip;
                        				sndAddr.sin_family = PF_INET;
                        				sndAddr.sin_port = dstPort;
                        				sndAddr.sin_addr = dstIP;
							respFlags = RespNULL;
							AckSW = TRUE;
                        				sendto (udpSocket, SRCREQ, 5, 0,
                                				(struct sockaddr *)&sndAddr, sizeof(sndAddr));
						}
                        		}
                                        else if (reqConMsg.reqType == Nak)
                                        {
        					logfile = fopen (LOGFILE, "a");
        					now = time(NULL);
                                                fprintf (logfile, "%24.24s Nak %2.2x %8.8s %s %d\n", ctime(&now),
                                                        reqConMsg.reqType, reqConMsg.ackConnect.YourCall,
                                                        inet_ntoa (reqConMsg.ackConnect.ip),
                                                        ntohs(reqConMsg.ackConnect.port));
						fclose (logfile);
						respFlags = reqConMsg.ackConnect.RespFlags;
                                        }
                        		else if (reqConMsg.reqType == ReqConnect)
                        		{
        					logfile = fopen (LOGFILE, "a");
        					now = time(NULL);
                                		fprintf (logfile, "%24.24s Req %2.2x %2.2x %8.8s %s %d\n", 
							ctime(&now),
							reqConMsg.reqType,
							reqConMsg.reqConnect.protocol, 
							reqConMsg.reqConnect.YourCall, 
							inet_ntoa(reqConMsg.reqConnect.ip),
							ntohs(reqConMsg.reqConnect.port));
						fclose (logfile);
                                                RcvPacketIdSave[0] = reqConMsg.reqConnect.PacketID[0];
                                                RcvPacketIdSave[1] = reqConMsg.reqConnect.PacketID[1];						
						if (PTT_On || COS_On)
						{
							NakSendTrust(reqConMsg.reqConnect.Rpt1Call, RcvPacketIdSave);
						}
						else
						{
							ReqSendSender();
							DVwait = TRUE;
							DSTREQcnt = 0;
							AckSendTrust (reqConMsg.reqConnect.Rpt1Call, RcvPacketIdSave);
						}
                        		}
				}
				if (FD_ISSET(rf_qid, &readOk))
				{
					send_dsvt();
				}
				if (FD_ISSET(cmdin, &readOk))
				{
                			read (cmdin, buf, 64);
                			if (!memcmp (buf, "end", 3))
					{
						reqConMsg.reqType = ReqEnd;
        					send (dstSocket, (char *)&reqConMsg, sizeof(reqConMsg), 0);
        					CloseSocket();
					}
					else if (!memcmp (buf, "reload_callcheck", 16))
					{
						ReloadCallCheckACL();
					}
				}
				break;
		}
  	}
}

