#include "p_apps.h"
#include "v_task.h"

/******************************************************************
	RFC 3489
******************************************************************/

/*
All STUN messages consist of a 20 byte header:

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |      STUN Message Type        |         Message Length        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                            Transaction ID
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                                                                   |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/

#ifdef CALL_SIP

#include "p_sip.h"
#include "p_rtp.h"

#define MSG_FIELD_TYPE		0
#define MSG_FIELD_LENGTH	2
#define MSG_FIELD_TRANS_ID	4
#define MSG_FIELD_DATA		20

#define MSG_TYPE_BREQ		0x0001  // Binding Request
#define MSG_TYPE_BRESP		0x0101  // Binding Response
#define MSG_TYPE_BERESP		0x0111  // Binding Error Response
#define MSG_TYPE_SSREQ		0x0002  // Shared Secret Request
#define MSG_TYPE_SSRESP		0x0102  // Shared Secret Response
#define MSG_TYPE_SSERESP	0x0112  // Shared Secret Error Response

/*
   After the header are 0 or more attributes.  Each attribute is TLV
   encoded, with a 16 bit type, 16 bit length, and variable value:

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         Type                  |            Length             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                             Value                             ....
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/

#define MSG_ATTR_TYPE		0
#define MSG_ATTR_LENGTH		2
#define MSG_ATTR_VALUE		4

#define ATTR_MAPPED_ADDRESS		0x01
#define ATTR_RESPONSE_ADDRESS	0x02
#define ATTR_CHANGE_REQUEST		0x03
#define ATTR_SOURCE_ADDRESS		0x04
#define ATTR_CHANGED_ADDRESS	0x05
#define ATTR_USERNAME			0x06
#define ATTR_PASSWORD			0x07
#define ATTR_MESSAGE_INTEGRITY  0x08
#define ATTR_ERROR_CODE			0x09
#define ATTR_UNKNOWN_ATTRIBUTES 0x0a
#define ATTR_REFLECTED_FROM		0x0b

/*
                                         Binding  Shared  Shared  Shared
                       Binding  Binding  Error    Secret  Secret  Secret
   Att.                Req.     Resp.    Resp.    Req.    Resp.   Error
                                                                  Resp.
   _____________________________________________________________________
   MAPPED-ADDRESS      N/A      M        N/A      N/A     N/A     N/A
   RESPONSE-ADDRESS    O        N/A      N/A      N/A     N/A     N/A
   CHANGE-REQUEST      O        N/A      N/A      N/A     N/A     N/A
   SOURCE-ADDRESS      N/A      M        N/A      N/A     N/A     N/A
   CHANGED-ADDRESS     N/A      M        N/A      N/A     N/A     N/A
   USERNAME            O        N/A      N/A      N/A     M       N/A
   PASSWORD            N/A      N/A      N/A      N/A     M       N/A
   MESSAGE-INTEGRITY   O        O        N/A      N/A     N/A     N/A
   ERROR-CODE          N/A      N/A      M        N/A     N/A     M
   UNKNOWN-ATTRIBUTES  N/A      N/A      C        N/A     N/A     C
   REFLECTED-FROM      N/A      C        N/A      N/A     N/A     N/A
*/

/*
	address attributes:
    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |x x x x x x x x|    Family     |           Port                |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                             Address                           |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/

/*
	change-request attribute:
   0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 A B 0|
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/

#define CHANGE_IP_MASK		0x04
#define CHANGE_PORT_MASK	0x02

#define NAT_TYPE_UNKNOWN			0
#define NAT_TYPE_BLOCKED			1
#define NAT_TYPE_OPEN_INTERNET		2
#define NAT_TYPE_SYM_FIREWALL		3
#define NAT_TYPE_FULL_CONE			4
#define NAT_TYPE_RESTRICTED			5
#define NAT_TYPE_PORT_RESTRICTED	6	
#define NAT_TYPE_SYMMETRIC			7

#define STUN_TRANS_ID_LEN	16
#define STUN_SEND_LEN		28

#define STUN_STATE_IDLE			0
#define STUN_STATE_FIRST_TEST1	1
#define STUN_STATE_TEST2		2
#define STUN_STATE_SECOND_TEST1	3
#define STUN_STATE_TEST3		4
#define STUN_STATE_RESOLVED		5

#define STUN_CHECK_NAT_TTL		3600

static BOOLEAN _bMsgAcked;
static XCHAR _iNatType;
static XCHAR _iRetries;
static XCHAR _iTimer;
static XCHAR _iTask;
static XSHORT _iLocalPort;
static XCHAR _iState;
static XSHORT _iMappedPort;
static XCHAR _pMappedAddr[IP_ALEN];
static XCHAR _pChangedAddr[IP_ALEN];
static XSHORT _iChangedPort;
static XSHORT _iCheckNatTimer;

void _SendBindingReq()
{
	PXCHAR_D pCur;

	pCur = (PXCHAR)Adapter_pSendUdpData;
	// Header
	USHORT2PXCHAR(MSG_TYPE_BREQ, pCur);
	pCur[2] = 0;
	pCur[3] = 8;
	pCur += 4;
	p_rand_array((PXCHAR)Sdram_pDataCache, STUN_TRANS_ID_LEN);
	SdramWriteData(OPTIONS_SDRAM_POS+STUN_TRANSID_SDRAM_LO, STUN_TRANS_ID_LEN);
	p_memcpy_x2x(pCur, (PXCHAR)Sdram_pDataCache, STUN_TRANS_ID_LEN);
	pCur += STUN_TRANS_ID_LEN;
	// Change-Request Attribute
	p_memset_x(pCur, 0, 8);
	pCur[1] = ATTR_CHANGE_REQUEST;
	pCur[3] = 4;

	if (_iState == STUN_STATE_TEST2)
	{
		// change IP and port
		pCur[7] |= CHANGE_IP_MASK;
		pCur[7] |= CHANGE_PORT_MASK;
	}
	else if (_iState == STUN_STATE_TEST3)
	{
		// change port only
		pCur[7] |= CHANGE_PORT_MASK;
	}

	// save for retry
	SdramSaveEthernetFrame((USHORT)(OPTIONS_SDRAM_POS+STUN_SDRAM_LO+1), (PXCHAR)Adapter_pSendUdpData, STUN_SEND_LEN);

	UdpSend(STUN_SEND_LEN, STUN_SDRAM_LO);
	_iRetries = 0;
	_iTimer = 0;
	_bMsgAcked = FALSE;
}

void StunInitialize()
{
	_iCheckNatTimer = 0;
	_iRetries = 0;
	_iTimer = 0;
	_iLocalPort = 0;
	_bMsgAcked = TRUE;
	_iTask = STUN_TASK_NONE;
	_iState = STUN_STATE_IDLE;
	_iNatType = NAT_TYPE_UNKNOWN;
}

void _StartTask()
{
	switch (_iTask)
	{
	case STUN_TASK_NONE:
	case STUN_TASK_CHECKNAT:
		break;

	case STUN_TASK_LOGON:
		ProtocolLogon();
		break;

	case STUN_TASK_CALL:
		if (!SipCall(Key_pBuf))
		{
			TaskRestart(0);
		}
		else
		{
#ifdef GUI_RTP
			RtpStartReceive(TRUE);
#endif
			Task_bCaller = TRUE;
			Task_bConnecting = TRUE;
			DisplayString(g_cCalling, 0);
			KeypadClear();
		}
		break;

	case STUN_TASK_ANSWER:
		SipConnect();
		break;
	}
	_iTask = STUN_TASK_NONE;
}

void _StunEnd()
{
	PXCHAR pNatAddr;
	DSHORT iNatPort;

	switch (_iNatType)
	{
	case NAT_TYPE_UNKNOWN:
		OutputString(IDS_NAT_TYPE_UNKNOWN);
		pNatAddr = Local_pIPAddress;
		iNatPort = _iLocalPort;
		break;

	case NAT_TYPE_FULL_CONE:
		OutputString(IDS_NAT_TYPE_FULL_CONE);
		pNatAddr = _pMappedAddr;
		iNatPort = _iMappedPort;
		break;

	case NAT_TYPE_RESTRICTED:
		OutputString(IDS_NAT_TYPE_RESTRICTED);
		pNatAddr = _pMappedAddr;
		iNatPort = _iMappedPort;
		break;

	case NAT_TYPE_PORT_RESTRICTED:
		OutputString(IDS_NAT_TYPE_PORT_RESTRICTED);
		pNatAddr = _pMappedAddr;
		iNatPort = _iMappedPort;
		break;

	case NAT_TYPE_SYMMETRIC:
		OutputString(IDS_NAT_TYPE_SYMMETRIC);
		pNatAddr = _pMappedAddr;
		iNatPort = _iLocalPort;
		break;
	}

	p_memcpy_ip(Sys_pProxyIP, pNatAddr);

	switch (_iTask)
	{
	case STUN_TASK_LOGON:
	case STUN_TASK_CHECKNAT:
		Sip_iRegisterPort = iNatPort;
		break;

	case STUN_TASK_CALL:
	case STUN_TASK_ANSWER:
		Sip_iRtpPort = iNatPort;
		break;
	}
	_iState = STUN_STATE_IDLE;
	_StartTask();
}

BOOLEAN StunRun()
{
	PXCHAR_D pCur;
	DSHORT iLen, iMsgLen, iMsgType, iMappedPort2;
	PXCHAR pMappedAddr2;

	if (_iLocalPort != Adapter_iReceiveUdpPort)		return FALSE;
	if (_iState == STUN_STATE_IDLE)					return FALSE;

	SdramReadData(OPTIONS_SDRAM_POS+STUN_TRANSID_SDRAM_LO);
	if (p_memcmp_x2x((PXCHAR)(Adapter_pReceiveUdpData + MSG_FIELD_TRANS_ID), (PXCHAR)Sdram_pDataCache, STUN_TRANS_ID_LEN))
		return FALSE;

	if (_bMsgAcked)		return TRUE;

	iMsgType = PXCHAR2USHORT((PXCHAR)(Adapter_pReceiveUdpData + MSG_FIELD_TYPE));
	iMsgLen = PXCHAR2USHORT((PXCHAR)(Adapter_pReceiveUdpData + MSG_FIELD_LENGTH));
	iMsgLen += MSG_FIELD_DATA;
	_bMsgAcked = TRUE;
	iLen = MSG_FIELD_DATA;
	switch (iMsgType)
	{
	case MSG_TYPE_BRESP:
		if (_iState == STUN_STATE_FIRST_TEST1)
		{
			while (iLen < iMsgLen)
			{
				pCur = (PXCHAR)(Adapter_pReceiveUdpData + iLen);
				iLen += MSG_ATTR_VALUE;
				iLen += PXCHAR2USHORT((PXCHAR)(pCur + MSG_ATTR_LENGTH));
				if (pCur[0] == 0)
				{
					if (pCur[1] == ATTR_MAPPED_ADDRESS)
					{
						pCur += MSG_ATTR_VALUE;
						p_memcpy_ip(_pMappedAddr, (PXCHAR)(pCur + 4));
						_iMappedPort = PXCHAR2USHORT((PXCHAR)(pCur + 2));
						OutputIPPort(_pMappedAddr, _iMappedPort, IDS_STUN_FIRST_MAPPED_ADDR);
						if (_iTask != STUN_TASK_LOGON && _iTask != STUN_TASK_CHECKNAT)
						{
							_StunEnd();
							return TRUE;
						}
					}
					else if (pCur[1] == ATTR_CHANGED_ADDRESS)
					{
						pCur += MSG_ATTR_VALUE;
						p_memcpy_ip(_pChangedAddr, (PXCHAR)(pCur + 4));
						_iChangedPort = PXCHAR2USHORT((PXCHAR)(pCur + 2));
					}
				}
			}
			_iState = STUN_STATE_TEST2;
			_SendBindingReq();
		}
		else if (_iState == STUN_STATE_TEST2)
		{
			_iNatType = NAT_TYPE_FULL_CONE;
			_StunEnd();
		}
		else if (_iState == STUN_STATE_SECOND_TEST1)
		{
			while (iLen < iMsgLen)
			{
				pCur = (PXCHAR)(Adapter_pReceiveUdpData + iLen);
				iLen += MSG_ATTR_VALUE;
				iLen += PXCHAR2USHORT((PXCHAR)(pCur + MSG_ATTR_LENGTH));
				if (pCur[0] == 0)
				{
					if (pCur[1] == ATTR_MAPPED_ADDRESS)
					{
						pCur += MSG_ATTR_VALUE;
						pMappedAddr2 = (PXCHAR)(pCur + 4);
						iMappedPort2 = PXCHAR2USHORT((PXCHAR)(pCur + 2));
						OutputIPPort(pMappedAddr2, iMappedPort2, IDS_STUN_SECOND_MAPPED_ADDR);
						if (p_memcmp_ip(_pMappedAddr, pMappedAddr2) || _iMappedPort != iMappedPort2)
						{
							_iNatType = NAT_TYPE_SYMMETRIC;
							_StunEnd();
						}
						else
						{
							_iState = STUN_STATE_TEST3;
							UdpOpen(Sys_pProxyServerIP, Sys_iNatPort, _iLocalPort, STUN_SDRAM_LO);
							_SendBindingReq();
						}
					}
				}
			}
		}
		else if (_iState == STUN_STATE_TEST3)
		{
			_iNatType = NAT_TYPE_RESTRICTED;
			_StunEnd();
		}

		break;

	case MSG_TYPE_BERESP:
		OutputString(IDS_STUN_BINDING_ERROR);
		break;
	}
	return TRUE;
}

void StunHandleTimer()
{
	DCHAR iOldType;

	if (_iTask == STUN_TASK_NONE)
	{
		_iCheckNatTimer ++;
		if (_iCheckNatTimer > STUN_CHECK_NAT_TTL)
		{
			StunCheckIPPort(STUN_TASK_CHECKNAT, Sys_iRegisterPort);
		}
	}
	else
	{
		if (_bMsgAcked)		return;
		_iTimer ++;
		if (_iTimer > 2)
		{
			_iTimer = 0;
			_iRetries ++;
			if (_iRetries > 2)
			{
				switch (_iState)
				{
				case STUN_STATE_FIRST_TEST1:
				case STUN_STATE_SECOND_TEST1:
					OutputString(IDS_STUN_NO_RESPONSE);
					DebugCString((PCCHAR)"Please try another STUN server!");
					iOldType = _iNatType;
					_iNatType = NAT_TYPE_UNKNOWN;
					_StunEnd();
					if (_iTask != STUN_TASK_LOGON && _iTask != STUN_TASK_CHECKNAT)
					{
						_iNatType = iOldType;
					}
					break;

				case STUN_STATE_TEST2:
					_iState = STUN_STATE_SECOND_TEST1;
					UdpOpen(_pChangedAddr, _iChangedPort, _iLocalPort, STUN_SDRAM_LO);
					_SendBindingReq();
					break;

				case STUN_STATE_TEST3:
					_iNatType = NAT_TYPE_PORT_RESTRICTED;
					_StunEnd();
					break;
				}
			}
			else
			{
				OutputString(IDS_STUN_RETRY);
				// retry, load from sdram
				SdramCopyEthernetFrame((USHORT)(OPTIONS_SDRAM_POS+STUN_SDRAM_LO+1), (PXCHAR)Adapter_pSendUdpData, STUN_SEND_LEN);
				UdpSend(STUN_SEND_LEN, STUN_SDRAM_LO);
			}
		}
	}
}

void StunCheckIPPort(UCHAR iTask, USHORT iSrcPort)
{
	_iTask = iTask;
	_iLocalPort = iSrcPort;
	if (_iTask == STUN_TASK_LOGON || _iTask == STUN_TASK_CHECKNAT)
	{
		_iCheckNatTimer = 0;
	}
	else
	{
		if (_iNatType == NAT_TYPE_UNKNOWN)
		{
			_StunEnd();
			return;
		}
	}
	_iState = STUN_STATE_FIRST_TEST1;
	UdpOpen(Sys_pProxyServerIP, Sys_iNatPort, _iLocalPort, STUN_SDRAM_LO);
	_SendBindingReq();
}

#endif