/*-------------------------------------------------------------------------
   DNS function copy right information

   Copyright (C) 2006-2008. Tang, Li <tangli@palmmicro.com>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This software is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
-------------------------------------------------------------------------*/

#include "type.h"
#include "ar168.h"
#include "core.h"
#include "apps.h"
#include "tcpip.h"

/* RFC 1035 & RFC 2136 & RFC 2782*/

#define DNS_SERVER_PORT		53
#define DNS_SRC_PORT		1027

/*
The header contains the following fields:

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      ID                       |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    QDCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ANCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    NSCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ARCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*/
#define DNS_HEADER_ID		0
#define DNS_HEADER_FALG0	2
#define DNS_HEADER_FALG1	3
#define DNS_HEADER_QDCOUNT	4	/* the number of entries in the question section */
#define DNS_HEADER_ANCOUNT	6	/* the number of resource records in the answer section */
#define DNS_HEADER_NSCOUNT	8	/* the number of name server resource records in the authority records section */
#define DNS_HEADER_ARCOUNT	10	/* the number of resource records in the additional records section */
#define DNS_HEADER_DATA		12

/*
Question section format:

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                     QNAME                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     QTYPE                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     QCLASS                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*/
#define DNS_QNAME	0	/* domain name , vairiable length */
#define DNS_QTYPE	0	
#define DNS_QCLASS	2
#define DNS_QDATA	4

/*
Resource record format:
                                   1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                                               /
    /                      NAME                     /
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TYPE                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     CLASS                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TTL                      |
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                   RDLENGTH                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
    /                     RDATA                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*/
#define DNS_RR_NAME			0	/* a domain name to which this resource record pertains */
#define DNS_RR_TYPE			0
#define DNS_RR_CLASS		2
#define DNS_RR_TTL			4
#define DNS_RR_RDLENGTH		8
#define DNS_RR_RDATA		10


#define DNS_MASK_QR		0x80
#define DNS_MASK_RCODE	0x0f

#define DNS_OPCODE_QUERY	0	/* a standard query (QUERY) */
#define DNS_OPCODE_IQUERY	1	/* an inverse query (IQUERY) */
#define DNS_OPCODE_STATUS	2	/* a server status request (STATUS) */
#define DNS_OPCODE_UPDATE	5	/* DNS UPDATE message */

#define DNS_RCODE_NOERROR			0	/* No error condition */
#define DNS_RCODE_FORMERR			1	/* Format error */	
#define DNS_RCODE_SERVFAIL			2	/* Server failure */
#define DNS_RCODE_NXDOMAIN			3	/* Name Error */
#define DNS_RCODE_NOTIMP			4	/* Not Implemented */
#define DNS_RCODE_REFUSED			5	/* Refused  */
#define DNS_RCODE_YXDOMAIN			6	/* Some name that ought not to exist does exist. */
#define DNS_RCODE_YXRRSET			7	/* Some RRset that ought not to exist does exist. */
#define DNS_RCODE_NXRRSET			8	/* Some RRset that ought to exist does not exist */
#define DNS_RCODE_NOTAUTH			9	/* The server is not authoritative for the zone named in the Zone Section */ 
#define DNS_RCODE_NOTZONE			10	/* A name used in the Prerequisite or Update Section is not within the zone denoted by the Zone Section. */

#define DNS_CLASS_IN	1	/* the internet */
#define DNS_CLASS_CS	2	/* the CSNET class (Obsolete - used only for examples in some obsolete RFCs) */
#define DNS_CLASS_CH	3	/* the CHAOS class */
#define DNS_CLASS_HS	4	/* Hesiod [Dyer 87] */

#define DNS_RETRY_TIMEOUT	5
#define DNS_MAX_RETRY		4
#define DNS_CB_TSIZE		3	

/* DNS entry status */
#define DS_FREE     0   /* Entry is unused (initial value) */
#define DS_PENDING  1   /* Entry is used but incomplete */
#define DS_RESOLVED 2   /* Entry has been resolved */

#define DNS_ID_LEN		2

// DNS control block
struct dns_cb
{
	UCHAR iState;
	UCHAR iType;
	UCHAR pID[DNS_ID_LEN];
	UCHAR iTimer;
	UCHAR iRetry;
	UCHAR iBankOffset;
	USHORT sCallBack;
	UCHAR pIPAddrPort[IP_ALEN+2];
	PCHAR pName;
	BOOLEAN bDns1Failed;
	BOOLEAN bDns2Failed;
};

#define DNS_CB_LENGTH		(sizeof(struct dns_cb))

void DnsRun(UDP_SOCKET pUcb);

UDP_SOCKET _pDnsSocket;
struct dns_cb _pDcbData[DNS_CB_TSIZE];

void dns_dealloc(DNS_HANDLE pDcb)
{
	PCHAR pName;

	pName = pDcb->pName;
	if (pName)
	{
		free((void *)pName);
	}
	memset((PCHAR)pDcb, 0, DNS_CB_LENGTH);
}

DNS_HANDLE dns_alloc()
{
	UCHAR i;
	DNS_HANDLE pDcb;

	pDcb = _pDcbData;
	for (i = 0; i < DNS_CB_TSIZE; i ++)
	{
		if (pDcb->iState == DS_FREE)
		{
			return pDcb;
		}
		pDcb ++;
	}

	pDcb = _pDcbData;
	for (i = 0; i < DNS_CB_TSIZE; i ++)
	{
		if (pDcb->iState == DS_RESOLVED)
		{
			dns_dealloc(pDcb);
			return pDcb;
		}
		pDcb ++;
	}

	return NULL;
}

void dns_send(DNS_HANDLE pDcb)
{
	UCHAR i;
	USHORT sSendLen;
	PCHAR pLabel;
	PCHAR pDst;
	PCHAR pCur;

	pDst = Adapter_pUdpBuf;
	memset(pDst, 0, DNS_HEADER_DATA);
	memcpy(pDst, pDcb->pID, DNS_ID_LEN);
	pDst[DNS_HEADER_FALG0] = 1;
	pDst[DNS_HEADER_QDCOUNT+1] = 1;

	sSendLen = DNS_HEADER_DATA;
	i = 0;
	pDst += DNS_HEADER_DATA;
	pLabel = pDst;
	pDst ++;
	pCur = pDcb->pName;
	if (!pCur)	return;

	while (sSendLen < 255)
	{
		if (*pCur == 0x2e || *pCur == 0)
		{
			sSendLen += i;
			sSendLen ++;
			*pLabel = i;
			if (*pCur)
			{
				i = 0;
				pLabel = pDst;
			}
			else
			{
				sSendLen ++;
				*pDst = 0;
				pDst ++;
				break;
			}
		}
		else
		{
			*pDst = *pCur;
			i ++;
		}
		pDst ++;
		pCur ++;
	}

	pDst[DNS_QTYPE] = 0;
	pDst[DNS_QTYPE + 1] = pDcb->iType;
	pDst[DNS_QCLASS] = 0;
	pDst[DNS_QCLASS+1] = DNS_CLASS_IN;
	sSendLen += DNS_QDATA;

	if (!pDcb->bDns1Failed)
	{
		UdpSendTo(_pDnsSocket, sSendLen, Sys_pDnsIp, DNS_SERVER_PORT);
	}
	if (!pDcb->bDns2Failed)
	{
		UdpSendTo(_pDnsSocket, sSendLen, Sys_pDnsIp2, DNS_SERVER_PORT);
	}
}

void dns_failed(USHORT sAddr, UCHAR iBank)
{
	UdpDebugString("Dns failed");
	if (sAddr)
	{
		TaskDnsDone(NULL, sAddr, iBank);
	}
}

void dns_save_send(DNS_HANDLE pDcb, PCHAR pDomainName, USHORT sAddr, UCHAR iBankOffset, UCHAR iType)
{
	PCHAR pBuf;
	BOOLEAN bDns1Failed, bDns2Failed;

	if (IsValidIP(Sys_pDnsIp))
	{
		 bDns1Failed = FALSE;
	}
	else
	{
		bDns1Failed = TRUE;
	}
	if (IsValidIP(Sys_pDnsIp2))
	{
		bDns2Failed = FALSE;
	}
	else
	{
		bDns2Failed = TRUE;
	}
	if (bDns1Failed && bDns2Failed)
	{
		dns_failed(sAddr, iBankOffset);
		return;
	}

	pBuf = heap_save_str(pDomainName);
	if (pBuf)
	{
		pDcb->iBankOffset = iBankOffset;
		pDcb->sCallBack = sAddr;
		pDcb->iState = DS_PENDING;
		rand_array(pDcb->pID, DNS_ID_LEN);
		pDcb->iType = iType;
		pDcb->pName = pBuf;
		pDcb->bDns1Failed = bDns1Failed;
		pDcb->bDns2Failed = bDns2Failed;
		dns_send(pDcb);
	}
	else
	{
		// dns module resource limited, try later
		dns_failed(sAddr, iBankOffset);
	}
}

void DnsInit()
{
	memset((PCHAR)_pDcbData, 0, DNS_CB_LENGTH*DNS_CB_TSIZE);
	_pDnsSocket = UdpListen(DNS_SRC_PORT, NET_BANK_OFFSET, (USHORT)DnsRun); 
}

void DnsQuery(PCHAR pDomain, UCHAR iType, UCHAR iBankOffset, USHORT sCallBackAddr)
{
	UCHAR i;
	DNS_HANDLE pDcb;

	pDcb = _pDcbData;
	for (i = 0; i < DNS_CB_TSIZE; i ++)
	{
		if(pDcb->iState == DS_RESOLVED)	
		{
			// this should be a case insensitive string compare
			if (!strcmp(pDomain, pDcb->pName))
			{
				// domain name alreay resolved
				TaskDnsDone(pDcb->pIPAddrPort, sCallBackAddr, iBankOffset);
				return;
			}
		}
		pDcb ++;
	}

	pDcb = dns_alloc();
	if (pDcb)
	{
		dns_save_send(pDcb, pDomain, sCallBackAddr, iBankOffset, iType);
	}
	else
	{
		dns_failed(sCallBackAddr, iBankOffset);
	}
}

UCHAR _GetNameLength(PCHAR pData)
{
	UCHAR iLen, iTemp;
	PCHAR pCur;

	iLen = 0;
	pCur = pData;
	while (iLen < 255)
	{
		if ((pCur[0] & 0xc0) == 0xc0)
		{
			// This is a pointer
			iLen += 2;
			break;
		}
		else if (!pCur[0])
		{
			iLen ++;
			break;
		}
		else
		{
			iTemp = pCur[0];
			iTemp ++;
			iLen += iTemp;
			pCur += iTemp;
		}
	}
	return iLen;
}

void _GetDomainName(PCHAR pDstName, PCHAR pSrcName, PCHAR pBuf)
{
	UCHAR iLen;
	USHORT sOffset;
	PCHAR pDst;
	PCHAR pSrc;

	pSrc = pSrcName;
	pDst = pDstName;
	while ((USHORT)(pDst - pDstName) < MAX_DNS_LEN)
	{
		if ((*pSrc & 0xc0) == 0xc0)
		{
			// This is a pointer
			sOffset = PCHAR2USHORT(pSrc) & 0x3fff;
			pSrc = (PCHAR)(pBuf + sOffset);
			continue;
		}
		else
		{
			iLen = *pSrc;
			pSrc ++;
			memcpy(pDst, pSrc, iLen);
			pDst += iLen;
			pSrc += iLen;
			if (*pSrc)
			{
				pDst[0] = 0x2e;
				pDst ++;
			}
			else
			{
				break;
			}
		}
	}
	*pDst = 0;
}

void DnsRun(UDP_SOCKET pUcb)
{
	UCHAR i, iBankOffset;
	USHORT sCount, sLength, sAnswerType, sAddr;
	UCHAR pSrvDomain[MAX_DNS_LEN];
	DNS_HANDLE pDcb;
	DNS_HANDLE pTempDcb;
	PCHAR pDnsIP;
	PCHAR pBuf;

	if (pUcb != _pDnsSocket)	return;

	pBuf = Adapter_pUdpBuf;

	pDcb = NULL;
	pTempDcb = _pDcbData;
	for (i = 0; i < DNS_CB_TSIZE; i ++)
	{
		if (!memcmp(pTempDcb->pID, pBuf, DNS_ID_LEN))
		{
			pDcb = pTempDcb;
			break;
		}
		pTempDcb ++;
	}
	if (pDcb == NULL)
	{
		// transaction id mismatch, this response is not what we need
		return;
	}
	if (pDcb->iState == DS_RESOLVED)
	{
		// this is a retransmission which has already been handled, ignore it
		return;
	}

	iBankOffset = pDcb->iBankOffset;
	sAddr = pDcb->sCallBack;
	if (!sAddr)		return;

	if ((pBuf[DNS_HEADER_FALG1] & DNS_MASK_RCODE) != DNS_RCODE_NOERROR)
	{
		// dns failed, do something here
		UdpDebugString("dns error!");
		pDnsIP = UdpGetDstIP(pUcb);
		if (!memcmp(pDnsIP, Sys_pDnsIp, IP_ALEN))
		{
			pDcb->bDns1Failed = TRUE;
		}
		if (!memcmp(pDnsIP, Sys_pDnsIp2, IP_ALEN))
		{
			pDcb->bDns2Failed = TRUE;
		}
		if (pDcb->bDns1Failed && pDcb->bDns2Failed)
		{
			dns_failed(sAddr, iBankOffset);
			dns_dealloc(pDcb);
		}		
		return;
	}

	/* pass the question section */
	sLength = DNS_HEADER_DATA;
	sCount = PCHAR2USHORT((PCHAR)(pBuf + DNS_HEADER_QDCOUNT));
	for (i = 0; i < sCount; i ++)
	{
		if (sLength > pUcb->sLen)	return;
		sLength += _GetNameLength((PCHAR)(pBuf + sLength));
		sLength += 4;
	}

	/* handle the answer section */
	sCount = PCHAR2USHORT((PCHAR)(pBuf + DNS_HEADER_ANCOUNT));
	for (i = 0; i < sCount; i ++)
	{
		if (sLength > pUcb->sLen)	
			return;

		sLength += _GetNameLength((PCHAR)(pBuf + sLength));
		sAnswerType = PCHAR2USHORT((PCHAR)(pBuf + sLength + DNS_RR_TYPE));
		if (sAnswerType == pDcb->iType)
		{
			if (sAnswerType == DNS_TYPE_A)
			{
				pDcb->iState = DS_RESOLVED;
				// set the cache time out
				if (PCHAR2USHORT((PCHAR)(pBuf+sLength+DNS_RR_TTL)))
				{
					pDcb->iTimer = 255;
					pDcb->iRetry = 255;
				}
				else
				{
					pDcb->iTimer = pBuf[sLength+DNS_RR_TTL+3];
					pDcb->iRetry = pBuf[sLength+DNS_RR_TTL+2];
				}
				memcpy4(pDcb->pIPAddrPort, (PCHAR)(pBuf + sLength + DNS_RR_RDATA));
				// dns successful, inform application
				TaskDnsDone(pDcb->pIPAddrPort, sAddr, iBankOffset);
				return;
			}
			else if (sAnswerType == DNS_TYPE_SRV)
			{
				_GetDomainName(pSrvDomain, (PCHAR)(pBuf + sLength + DNS_RR_RDATA + 6), pBuf);
				dns_dealloc(pDcb);
				memcpy((PCHAR)(pDcb->pIPAddrPort + IP_ALEN), (PCHAR)(pBuf + sLength + DNS_RR_RDATA + 4), 2);
				dns_save_send(pDcb, pSrvDomain, sAddr, iBankOffset, DNS_TYPE_A);
				return;
			}
		}
		sLength += DNS_RR_RDATA + PCHAR2USHORT((PCHAR)(pBuf + sLength + DNS_RR_RDLENGTH));
	}
}

void DnsTimer()
{
	UCHAR i;
	DNS_HANDLE pDcb;

	pDcb = _pDcbData;
	for (i = 0; i < DNS_CB_TSIZE; i ++)
	{
		if (pDcb->iState == DS_PENDING)
		{
			pDcb->iTimer ++;
			if (pDcb->iTimer >= DNS_RETRY_TIMEOUT)
			{
				pDcb->iTimer = 0;
				pDcb->iRetry ++;
				if (pDcb->iRetry > DNS_MAX_RETRY)
				{
					// dns failed, do something here
					pDcb->iRetry = 0;
					dns_failed(pDcb->sCallBack, pDcb->iBankOffset);
					dns_dealloc(pDcb);
				}
				else
				{
					dns_send(pDcb);
				}
			}
		}
		else if (pDcb->iState == DS_RESOLVED)
		{
			if (!pDcb->iTimer)
			{
				if (!pDcb->iRetry)
				{
					// cache time out, remove this entry
					dns_dealloc(pDcb);
				}
				else
				{
					pDcb->iRetry --;
					pDcb->iTimer = 255;
				}
			}
			else
			{
				pDcb->iTimer --;
			}
		}
		pDcb ++;
	}
}

