#include "PalmType.h"
#include "Palm1lib.h"
#include "p_tcpip.h"
#include "p_apps.h"
#include "p_iax2.h"
#include "v_task.h"
#include "port.h"

#define _IAX2_GLOBALS
#include "my_iax2.h"

#define DPSTATUS_EXISTS			0x0001
#define DPSTATUS_CAN_EXISTS		0x0002
#define DPSTATUS_NON_EXISTS		0x0004
#define DPSTATUS_RETAIN_DAILTONE	0x4000
#define DPSTATUS_MORE_DIGITS	0x8000

 volatile DLONG Sys_lTicker = 10;

static CCHAR _cCauseBusy[] = "We are busy!";
static CCHAR _cCauseClearing[] = "Normal clearing";
static CCHAR _cCauseCodec[] = "codec mismatch";
static CCHAR _cCausePeerBusy[] = "Peer Busy";
static CCHAR _cCauseWrongNumber[] = "Wrong Number";

static XSHORT _iRegTimer, _iRegTTL;
static BOOLEAN _bAnswer;

static XLONG Ie_lCapability;
static XLONG Ie_lFormat;
static XSHORT Ie_iAuthMethods;
static XCHAR Ie_pCalledNumber[DIALED_MAX_LEN+1];
static XCHAR Ie_pApparentAddrIP[IP_ALEN];
static XSHORT Ie_iApparentAddrPort;
static XSHORT Ie_iRefresh;
static XSHORT Ie_iCallNo;
static XSHORT Ie_iMsgCount;
static XCHAR Ie_pTransferId[IAX_TRANSFERID_LEN];
static XSHORT Ie_iDPStatus;

/*******************************************************************************
 *****
 *******************************************************************************/
void Iax2CallInit(UCHAR iIndex)
{
	DCHAR i;

	i = iIndex;
	if (i == IAX_CALL_ERROR)	return;
	Iax2_iCallSess[i] = IAX_SESSION_ERROR;
	Iax2_iCallState[i] = IAX_CALL_STATE_IDLE;
	Iax2_iIsCaller[i] = FALSE;
	Iax2_iAudioType[i] = CODEC_NULL;
	Iax2_iCallTimer[i] = 0;
}

BOOLEAN Iax2InCall()
{
	if (Iax2_iCallSess[0] == IAX_SESSION_ERROR && Iax2_iCallSess[1] == IAX_SESSION_ERROR)
		return FALSE;
	else
		return TRUE;
}

UCHAR Iax2NewCall()
{
	if (Iax2_iCallSess[0] == IAX_SESSION_ERROR)
	{
		return 0;
	}
	else if (Iax2_iCallSess[1] == IAX_SESSION_ERROR)
	{
		return 1;
	}
	return IAX_CALL_ERROR;
}

UCHAR Iax2CallIndex(UCHAR iSession)
{
	if (iSession == Iax2_iCallSess[0])
		return 0;
	if (iSession == Iax2_iCallSess[1])
		return 1;
	return IAX_CALL_ERROR;
}

void Iax2SwitchCall(BOOLEAN bCaller)
{
	Task_bCallHold = FALSE;
	Task_bConnected = FALSE;
	Task_bDisconnecting = FALSE;
	Task_bConnecting = TRUE;
	Task_bCaller = bCaller;
}

void Iax2StartVoice(UCHAR iIndex)
{
	iax2_voice_init();
	Sys_iAudioType = Iax2_iAudioType[iIndex];
	TaskStartRtp();
}

BOOLEAN iax2_activate_alt_call(UCHAR iCallIndex)
{
	DCHAR iAltCall;

	iAltCall = 1 - iCallIndex;
	if (Iax2_iCallState[iAltCall] == IAX_CALL_STATE_IDLE)
	{
		return FALSE;
	}
	
	if (Iax2_iCallState[iAltCall] == IAX_CALL_STATE_CONNECTING && !Iax2_iIsCaller[iAltCall])
	{
		Iax2_iCurCall = iAltCall;
		Iax2SwitchCall(FALSE);
		Iax2Connect();
		TaskSignalMsg(SIGNAL_STOP_WAITING);
	}
	else if (Iax2_iCallState[iAltCall] == IAX_CALL_STATE_CONNECTED)
	{
		Iax2_iCurCall = iAltCall;	
		iax2_session_send_packet(Iax2_iCallSess[iAltCall], AST_FRAME_IAX, IAX_COMMAND_UNQUELCH, 0);
		Task_bConnected = TRUE;
		Task_bDisconnecting = FALSE;
		Task_bConnecting = FALSE;
		Task_bCaller = FALSE;
		Task_bCallHold = FALSE;
		Iax2StartVoice(Iax2_iCurCall);
	}
	return TRUE;
}

void iax2_call_end(UCHAR iCallIndex)
{
	if (iCallIndex == IAX_CALL_ERROR)	return;

	TaskSignalMsg(SIGNAL_STOP_WAITING);
	Iax2CallInit(iCallIndex);
	if (!Iax2InCall())
	{
		TaskRestart(0);
	}
	else
	{
		if (!Task_bCallTransfer)
		{
			iax2_activate_alt_call(iCallIndex);
		}
	}
}
/*******************************************************************************
 *****
 *******************************************************************************/
void iax2_init_ie()
{
	SdramWriteData(PROTOCOL_SDRAM_POS+IAX2_IE_CHALLENGE_SDRAM_LO, 0);
	Ie_lCapability = 0;
	Ie_lFormat = 0;
	Ie_iAuthMethods = IAX_AUTH_PLAINTEXT;
	p_memset_ip_0(Ie_pApparentAddrIP);
	Ie_iApparentAddrPort = 0;
	Ie_iRefresh = 0;
	Ie_iCallNo = 0;
	Ie_iMsgCount = 0;
	p_memset_x(Ie_pTransferId, 0, IAX_TRANSFERID_LEN);
}

void iax2_parse_ie(PXCHAR pData, USHORT iDataLen)
{
	DCHAR iLen, iType;
	DSHORT iLeftLen;
	PXCHAR_D pCur;

	iax2_init_ie();

	pCur = pData;
	iLeftLen = iDataLen;
	while (iLeftLen >= 2)
	{
		iType = pCur[0];
		iLen = pCur[1];
		pCur += 2;
		iLeftLen -= 2;
		if (iLen > iLeftLen)
			break;
		switch (iType)
		{
		case IAX_IE_CALLING_NUMBER:
			if (iLen)
			{
				if (iLen <= PHONE_NUMBER_LEN)
				{
					p_memcpy_x2x_0(Sys_pPeerDisplay, pCur, iLen);
				}
				else
				{
					p_memcpy_x2x_0(Sys_pPeerDisplay, pCur, PHONE_NUMBER_LEN);
				}
			}
			break;

		case IAX_IE_CALLING_NAME:
			if (iLen)
			{
				if (iLen <= PHONE_NUMBER_LEN)
				{
					p_memcpy_x2x_0(Sys_pRemoteNumber, pCur, iLen);
				}
				else
				{
					p_memcpy_x2x_0(Sys_pRemoteNumber, pCur, PHONE_NUMBER_LEN);
				}
			}
			break;

		case IAX_IE_CALLED_NUMBER:
			if (iLen)
			{
				if (iLen <= DIALED_MAX_LEN)
				{
					p_memcpy_x2x_0(Ie_pCalledNumber, pCur, iLen);
				}
				else
				{
					p_memcpy_x2x_0(Ie_pCalledNumber, pCur, DIALED_MAX_LEN);
				}
			}
			break;

		case IAX_IE_CAPABILITY:
			Ie_lCapability = PXCHAR2ULONG(pCur);
			break;

		case IAX_IE_FORMAT:
			Ie_lFormat = PXCHAR2ULONG(pCur);
			break;

		case IAX_IE_AUTHMETHODS:
			Ie_iAuthMethods = PXCHAR2USHORT(pCur);
			break;

		case IAX_IE_CHALLENGE:
			if (iLen)
			{
				p_memcpy_x2x_0((PXCHAR)Sdram_pDataCache, pCur, iLen);
				SdramWriteString(PROTOCOL_SDRAM_POS+IAX2_IE_CHALLENGE_SDRAM_LO);
			}
			break;


		case IAX_IE_APPARENT_ADDR:
			// Apparent address is a socket structure. 2 bytes family, 2 bytes port, 4 bytes address, 8 bytes 0
			p_memcpy_ip(Ie_pApparentAddrIP, (PXCHAR)(pCur + 4));
			Ie_iApparentAddrPort = PXCHAR2USHORT((PXCHAR)(pCur + 2));
			break;

		case IAX_IE_REFRESH:
			Ie_iRefresh = PXCHAR2USHORT(pCur);
			break;

		case IAX_IE_CALLNO:
			Ie_iCallNo = PXCHAR2USHORT(pCur);
			break;

		case IAX_IE_TRANSFERID:
			p_memcpy_x2x(Ie_pTransferId, pCur, IAX_TRANSFERID_LEN);
			break;

		case IAX_IE_MSGCOUNT:
			Ie_iMsgCount = PXCHAR2USHORT(pCur);
			TaskHandleData(VOICE_MSG_HINT, 0, (UCHAR)Ie_iMsgCount);
			break;

		case IAX_IE_DPSTATUS:
			Ie_iDPStatus = PXCHAR2USHORT(pCur);
			break;

		}
		pCur += iLen;
		iLeftLen -= iLen;
	}
}

UCHAR iax2_check_format()
{
	DCHAR i, iAudioType;

	iAudioType = CODEC_NULL;

	if (Ie_lFormat)
	{
		iAudioType = iax2_get_codec(Ie_lFormat);
		iAudioType = iax2_audio_match(iAudioType);
	}

	if (iAudioType == CODEC_NULL)
	{
		for (i = 0; i < SYS_CODEC_NUM; i ++)
		{
			if (Sys_pCodecType[i] != CODEC_NULL)
			{
				if (iax2_get_format(Sys_pCodecType[i]) & Ie_lCapability)
				{
					iAudioType = Sys_pCodecType[i];
					break;
				}
			}
		}
	}

	return iAudioType;
}

void iax2_reg_req()
{
	iax2_ie_start();
	iax2_ie_add_xstr(IAX_IE_USERNAME, Sys_pAccount);
	iax2_ie_add_short(IAX_IE_REFRESH, _iRegTTL);
	iax2_session_send_packet(IAX_REG_SESSION, AST_FRAME_IAX, IAX_COMMAND_REGREQ, iax2_ie_get_len());
}
/*
void iax2_reg_rel()
{
	iax2_ie_start();
	iax2_ie_add_xstr(IAX_IE_USERNAME, Sys_pAccount);
	iax2_session_send_packet(IAX_REG_SESSION, AST_FRAME_IAX, IAX_COMMAND_REGREL, iax2_ie_get_len());
	iax2_session_set_state(IAX_REG_SESSION, SESSION_STATE_FINALACK);
}
*/
void iax2_regauth_req()
{
	iax2_ie_start();
	iax2_ie_add_xstr(IAX_IE_USERNAME, Sys_pAccount);
	iax2_ie_add_short(IAX_IE_REFRESH, _iRegTTL);

	if (Ie_iAuthMethods & IAX_AUTH_MD5)
	{
		SdramReadString(PROTOCOL_SDRAM_POS+IAX2_IE_CHALLENGE_SDRAM_LO);
		p_strcat_x2x((PXCHAR)Sdram_pDataCache, Sys_pPin);
		MD5GenValue((PXCHAR)Sdram_pDataCache2, (PXCHAR)Sdram_pDataCache, p_strlen_x((PXCHAR)Sdram_pDataCache));
		p_char2asc_str((PXCHAR)Sdram_pDataCache, (PXCHAR)Sdram_pDataCache2, MD5_SIGNATURE_SIZE, FALSE);
		iax2_ie_add_xstr(IAX_IE_MD5_RESULT, (PXCHAR)Sdram_pDataCache);
	}
	else
	{
		iax2_ie_add_xstr(IAX_IE_PASSWORD, Sys_pPin);
	}
	iax2_session_send_packet(IAX_REG_SESSION, AST_FRAME_IAX, IAX_COMMAND_REGREQ, iax2_ie_get_len());
}

void iax2_ack(UCHAR iIndex, ULONG lTimeStamp, UCHAR iSeq)
{
	iax2_session_fill(iIndex, AST_FRAME_IAX, IAX_COMMAND_ACK);
	ULONG2PUCHAR(lTimeStamp, (PUCHAR)(PXCHAR)(Adapter_pSendUdpData + IAX_FULLFR_TSTAMP));
	X_VAL(Adapter_pSendUdpData, IAX_FULLFR_OSEQNO) = iSeq;
	iax2_debug_string(AST_FRAME_IAX, IAX_COMMAND_ACK);
	iax2_dbg_send(IAX_FULLFR_DATA, iIndex);
}

void iax2_vnak(UCHAR iIndex)
{
	iax2_session_fill(iIndex, AST_FRAME_IAX, IAX_COMMAND_VNAK);
	ULONG2PUCHAR((ULONG)(Sys_lTicker - Sess_lOffset[iIndex]), (PUCHAR)(PXCHAR)(Adapter_pSendUdpData + IAX_FULLFR_TSTAMP));
	X_VAL(Adapter_pSendUdpData, IAX_FULLFR_OSEQNO) = Sess_iISeqNo[iIndex];
	iax2_debug_string(AST_FRAME_IAX, IAX_COMMAND_VNAK);
	iax2_dbg_send(IAX_FULLFR_DATA, iIndex);
}

void iax2_accept(UCHAR iAudioType, UCHAR iSession)
{
	iax2_ie_start();
	iax2_ie_add_long(IAX_IE_FORMAT, iax2_get_format(iAudioType));
	iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_ACCEPT, iax2_ie_get_len());
}

void iax2_clear_txsession(UCHAR iSession)
{
	DCHAR iTransSession;

	// Clear out transferred call
	iTransSession = Sess_iTransSession[iSession];
	if (iTransSession != IAX_SESSION_ERROR)
	{
		iax2_session_free(iTransSession);
	}
}

void iax2_hangup(UCHAR iSession, PCCHAR cCause)
{
	iax2_ie_start();
	iax2_ie_add_cstr(IAX_IE_CAUSE, cCause);
	iax2_ie_add_byte(IAX_IE_CAUSECODE, 16);
	iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_HANGUP, iax2_ie_get_len());
	iax2_session_set_state(iSession, SESSION_STATE_FINALACK);

	iax2_clear_txsession(iSession);
}

void iax2_reject(UCHAR iSession, PCCHAR cCause)
{
	iax2_ie_start();
	iax2_ie_add_cstr(IAX_IE_CAUSE, cCause);
	iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_REJECT, iax2_ie_get_len());
	iax2_session_set_state(iSession, SESSION_STATE_FINALACK);
}

void iax2_auth_reply(UCHAR iSession)
{
	iax2_ie_start();

	if ((Ie_iAuthMethods & IAX_AUTH_MD5) && SdramReadString(PROTOCOL_SDRAM_POS+IAX2_IE_CHALLENGE_SDRAM_LO))
	{
		p_strcat_x2x((PXCHAR)Sdram_pDataCache, Sys_pPin);
		MD5GenValue((PXCHAR)Sdram_pDataCache2, (PXCHAR)Sdram_pDataCache, p_strlen_x((PXCHAR)Sdram_pDataCache));
		p_char2asc_str((PXCHAR)Sdram_pDataCache, (PXCHAR)Sdram_pDataCache2, MD5_SIGNATURE_SIZE, FALSE);
		iax2_ie_add_xstr(IAX_IE_MD5_RESULT, (PXCHAR)Sdram_pDataCache);
	}
	else
	{
		iax2_ie_add_xstr(IAX_IE_PASSWORD, Sys_pPin);
	}
	iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_AUTHREP, iax2_ie_get_len());
}

void iax2_pong(UCHAR iSession)
{
	iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_PONG, 0);
}

void iax2_ctrl_ringing(UCHAR iSession)
{
	iax2_session_send_packet(iSession, AST_FRAME_CONTROL, AST_CONTROL_RINGING, 0);
}

void iax2_ctrl_answer(UCHAR iSession)
{
	iax2_session_send_packet(iSession, AST_FRAME_CONTROL, AST_CONTROL_ANSWER, 0);
}

void iax2_ctrl_busy(UCHAR iSession)	// Thomas NOEL <thomas.noel@auf.org>
{
        iax2_session_send_packet(iSession, AST_FRAME_CONTROL, AST_CONTROL_BUSY, 0);
}

void iax2_lagrp(UCHAR iSession, ULONG lTimeStamp)
{
	iax2_session_fill(iSession, AST_FRAME_IAX, IAX_COMMAND_LAGRP);
	ULONG2PUCHAR(lTimeStamp, (PUCHAR)(PXCHAR)(Adapter_pSendUdpData + IAX_FULLFR_TSTAMP));
	X_VAL(Adapter_pSendUdpData, IAX_FULLFR_OSEQNO) = Sess_iOSeqNo[iSession];
	Sess_iOSeqNo[iSession] ++;
	iax2_frame_save(iSession, IAX_FULLFR_DATA, X_VAL(Adapter_pSendUdpData, IAX_FULLFR_OSEQNO));
	iax2_debug_string(AST_FRAME_IAX, IAX_COMMAND_LAGRP);
	iax2_dbg_send(IAX_FULLFR_DATA, iSession);
}

void iax2_incoming_key(UCHAR iKey)
{
	XCHAR pKey[2];

	pKey[0] = iKey;
	pKey[1] = 0;
	if (Iax2_iCallIndex == Iax2_iCurCall)
	{
		TaskIncomingKeys(1, pKey);
	}
}


// Transfer-related routines
void iax2_transfer(UCHAR iSession, PXCHAR pDstNumber)
{
	iax2_ie_start();
	iax2_ie_add_xstr(IAX_IE_CALLED_NUMBER, pDstNumber);
	iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_TRANSFER, iax2_ie_get_len());
}

void iax2_send_txrej(UCHAR iSession)
{
	iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_TXREJ, 0);
	Sess_iTransState[iSession] = IAX_TRANS_STATE_NONE;
}

void iax2_send_txrel(UCHAR iSession)
{
	iax2_ie_start();
	iax2_ie_add_short(IAX_IE_CALLNO, Sess_iTransCallNo[iSession]);
	iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_TXREL, iax2_ie_get_len());
}

void iax2_send_txreq(UCHAR iSession, USHORT iDstPort, PXCHAR pDstIP)
{
	PXCHAR_D pCur;

	iax2_ie_start();

	pCur = (PXCHAR)Sdram_pDataCache;
	p_memset_x(pCur, 0, 16);
	pCur[0] = 2;
	USHORT2PXCHAR(iDstPort, (PXCHAR)(pCur + 2));
	p_memcpy_ip((PXCHAR)(pCur + 4), pDstIP);
	iax2_ie_add_xraw(IAX_IE_APPARENT_ADDR, pCur, 16);
	iax2_ie_add_short(IAX_IE_CALLNO, Sess_iTransCallNo[iSession]);
	iax2_ie_add_xraw(IAX_IE_TRANSFERID, Sess_pTransferID[iSession], IAX_TRANSFERID_LEN);

	iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_TXREQ, iax2_ie_get_len());
}

void iax2_recv_txready(UCHAR iSession)
{
	DCHAR iTransSession;

	DebugCString((PCCHAR)"Got txready");

	iTransSession = Sess_iTransSession[iSession];
	if (iTransSession != IAX_SESSION_ERROR)
	{
		if (Sess_iTransState[iTransSession] == IAX_TRANS_STATE_READY)
		{
			iax2_send_txrel(iSession);
			iax2_session_set_state(iSession, SESSION_STATE_FINALACK);
			Sess_iTransState[iSession] = IAX_TRANS_STATE_RELEASED;

			iax2_send_txrel(iTransSession);
			iax2_session_set_state(iTransSession, SESSION_STATE_FINALACK);
			Sess_iTransState[iTransSession] = IAX_TRANS_STATE_RELEASED;
			
			iax2_call_end(0);
			iax2_call_end(1);
		}
		else
		{
			Sess_iTransState[iSession] = IAX_TRANS_STATE_READY;
		}
	}
	else
	{
		iax2_send_txrel(iSession);
		iax2_session_set_state(iSession, SESSION_STATE_FINALACK);
		Sess_iTransState[iSession] = IAX_TRANS_STATE_RELEASED;
	}
}

void iax2_recv_txacc(UCHAR iSession)
{
	DCHAR iOriginalSession;
	iOriginalSession = Sess_iTransOriginalSession[iSession];
	DebugCString((PCCHAR)"Got txacc");

	// Ensure an original session exists
	if (iOriginalSession == IAX_SESSION_ERROR)
	{
		DebugCString((PCCHAR)"Ignoring txacc: No original session.");
		return;
	}

	// Ensure original session is in transfer state
	if (Sess_iTransState[iOriginalSession] == IAX_TRANS_STATE_NONE)
	{
		DebugCString((PCCHAR)"Ignoring txacc: Original session not in transfer.");
		return;
	}

	// Ensure original session has not been released
	if (Sess_iTransState[iOriginalSession] == IAX_TRANS_STATE_RELEASED)
	{
		DebugCString((PCCHAR)"Ignoring txacc: Original session already transfered.");
		return;
	}

	iax2_frame_txacked(iSession);
	// Things are o.k., so send a TXREADY to the original and set our status as ready
	iax2_ie_start();
	iax2_ie_add_xraw(IAX_IE_TRANSFERID, Sess_pTransferID[iOriginalSession], IAX_TRANSFERID_LEN);
	iax2_session_send_packet(iOriginalSession, AST_FRAME_IAX, IAX_COMMAND_TXREADY, iax2_ie_get_len());
	Sess_iTransState[iOriginalSession] = IAX_TRANS_STATE_READY;
}

	
void iax2_recv_txcnt(UCHAR iSession)
{
	USHORT sDstPort;

	// We base our ready status off of receiving a TXACC, so no transfer state change.
	DCHAR iOriginalSession;
	iOriginalSession = Sess_iTransOriginalSession[iSession];
	DebugCString((PCCHAR)"Got a txcnt");
	
	// Ensure an original session exists
	if (iOriginalSession == IAX_SESSION_ERROR)
	{
		DebugCString((PCCHAR)"Ignoring txcnt: No original session.");
		return;
	}

	// Ensure original session is in transfer state
	if (Sess_iTransState[iOriginalSession] == IAX_TRANS_STATE_NONE)
	{
		DebugCString((PCCHAR)"Ignoring txcnt: Original session not in transfer.");
		return;
	}

	// If there is a transfer in progress, just reply with a TXACC
	DebugCString((PCCHAR)"Sending TXACC.");
	sDstPort = PXCHAR2USHORT((PXCHAR)Adapter_pReceivePacket);
	iax2_open_socket(iSession, Adapter_pReceiveIPAddress, sDstPort);

	iax2_ie_start();
	iax2_ie_add_xraw(IAX_IE_TRANSFERID, Sess_pTransferID[iSession], IAX_TRANSFERID_LEN);
	iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_TXACC, iax2_ie_get_len());
}

	
void iax2_recv_txreq(UCHAR iSession)
{
	// Sets up a transaction
	DCHAR iTransSession;	

	// Open socket to transfer peer and connect
	iTransSession = iax2_session_new(); 
	if (iTransSession == IAX_SESSION_ERROR)
	{
		DebugCString((PCCHAR)"Couldn't get a transfer session!");
		iax2_send_txrej(iSession);
		return;
	}
	DebugCString((PCCHAR)"Got a TXREQ, sent a txcnt to:");
	OutputIPPort(Ie_pApparentAddrIP, Ie_iApparentAddrPort, 0x00);
	iax2_open_socket(iTransSession, Ie_pApparentAddrIP, Ie_iApparentAddrPort);

	// Send TxCnt
	// Create the relationship between transferer/transferee
	Sess_iTransSession[iSession] = iTransSession;	// The current session needs to know who it's transferring to
	Sess_iTransOriginalSession[iTransSession] = iSession; // And the transfer session needs to know it's daddy
	p_memcpy_x2x(Sess_pTransferID[iSession], Ie_pTransferId, IAX_TRANSFERID_LEN);
	
	// The transfer session's call numbers depend on the current session + the txreq specified callno
	Sess_iPeerCallNo[iTransSession] = Ie_iCallNo;	
	Sess_iCallNo[iTransSession] = Sess_iCallNo[iSession];
	p_memcpy_x2x(Sess_pTransferID[iTransSession], Ie_pTransferId, IAX_TRANSFERID_LEN);

	// Send a TXCNT with the right transfer id
	iax2_ie_start();
	iax2_ie_add_xraw(IAX_IE_TRANSFERID, Ie_pTransferId, IAX_TRANSFERID_LEN);
	iax2_session_send_packet(iTransSession, AST_FRAME_IAX, IAX_COMMAND_TXCNT, iax2_ie_get_len());
	
	// Save our new status, now that we've sent the connect packet. 
	if (Sess_iTransState[iSession] < IAX_TRANS_STATE_CONNECTING)
	{
		Sess_iTransState[iSession] = IAX_TRANS_STATE_CONNECTING;
	}

	// Now we just need to wait for TXACC to come in to signify we're ready! (tx_recv_txacc)
	DebugCString((PCCHAR)"TransSession has CallNo and PeerNo:");
	OutputVal(Sess_iCallNo[iTransSession], 0x00);
	OutputVal(Sess_iPeerCallNo[iTransSession], 0x01);
}

	
void iax2_recv_txrel(UCHAR iSession)
{
	DCHAR iTransSession;
	DebugCString((PCCHAR)"Got txrel");
	// The TXREL comes in on the original session
	// Ensure a transfer is ready
	if (Sess_iTransState[iSession] == IAX_TRANS_STATE_NONE)
	{
		DebugCString((PCCHAR)"Ignoring txrel: No transfer state.");
		return;
	}

	// Ensure there's a transfer session
	iTransSession = Sess_iTransSession[iSession];
	if (iTransSession == IAX_SESSION_ERROR)
	{
		DebugCString((PCCHAR)"Ignoring txrel: No transfer session.");
		return;
	}

	// Ensure the release has not been processed
	if (Sess_iTransState[iSession] == IAX_TRANS_STATE_RELEASED)
	{
		DebugCString((PCCHAR)"Ignoring txrel: Session already released.");
		return;
	}

	// Switch current session to transfer session
	DebugCString((PCCHAR)"Switching current session from - to -:");
	OutputVal(Iax2_iCallSess[Iax2_iCallIndex], 0x00);
	OutputVal(iTransSession, 0x01);
	Iax2_iCallSess[Iax2_iCallIndex] = iTransSession;

	// Finalize the original session
	Sess_iTransState[iSession] = IAX_TRANS_STATE_RELEASED;
	// should wait for session to time out
//	iax2_session_free(iSession);
	iax2_session_set_state(iSession, SESSION_STATE_TIMEWAIT);
}

void iax2_recv_txrej(UCHAR iSession)
{
	DCHAR iTransSession;

	DebugCString((PCCHAR)"Got txrej");

	iTransSession = Sess_iTransSession[iSession];
	if (iTransSession != IAX_SESSION_ERROR)
	{
		iax2_session_free(iTransSession);
		Sess_iTransSession[iSession] = IAX_SESSION_ERROR;
	}
	Sess_iTransState[iSession] = IAX_TRANS_STATE_NONE;
}

void iax2_recv_xfer(UCHAR iSession)
{
	DebugCString((PCCHAR)"Got transfer");
	iax2_hangup(iSession, _cCauseClearing);
	iax2_call_end(Iax2_iCallIndex);

	TaskHandleData(IAX2_TRANSFER_HINT, Ie_pCalledNumber, p_strlen_x(Ie_pCalledNumber));
}

void iax2_recv_dprep(UCHAR iSession)
{
	DebugCString((PCCHAR)"Got DPRep");
	if (Ie_iDPStatus & DPSTATUS_EXISTS)
	{
		iax2_ie_start();
		iax2_ie_add_xstr(IAX_IE_CALLED_NUMBER, Ie_pCalledNumber);
		iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_DIAL, iax2_ie_get_len());
		if (Iax2_iCallIndex == Iax2_iCurCall)
		{
			Iax2StartVoice(Iax2_iCallIndex);
		}
		TaskSignalMsg(IAX2_START_CALL);
	}
	else if (Ie_iDPStatus & DPSTATUS_NON_EXISTS)
	{
		iax2_hangup(iSession, _cCauseWrongNumber);
		iax2_call_end(Iax2_iCallIndex);
	}
}

void iax2_recv_frame(UCHAR iSession)
{
	DCHAR iTemp, iOSeqNo, iISeqNo, iType, iSubClass;
	DSHORT iDataLen;
	PXCHAR_D pCur;
	XLONG lTimeStamp;
	BOOLEAN bAck;
	BOOLEAN bXfer;

	pCur = (PXCHAR)Adapter_pReceiveUdpData;
	iOSeqNo = pCur[IAX_FULLFR_OSEQNO];
	iISeqNo = pCur[IAX_FULLFR_ISEQNO];
	iType = pCur[IAX_FULLFR_TYPE];
	iSubClass = pCur[IAX_FULLFR_CSUB];
	lTimeStamp = PXCHAR2ULONG((PXCHAR)(pCur + IAX_FULLFR_TSTAMP));

	bAck = (iType == AST_FRAME_IAX) && (iSubClass == IAX_COMMAND_ACK || iSubClass == IAX_COMMAND_INVAL || iSubClass == IAX_COMMAND_VNAK);
	bXfer = (iType == AST_FRAME_IAX) && (iSubClass == IAX_COMMAND_TXCNT || iSubClass == IAX_COMMAND_TXACC);

	/* Handle implicit ACKing unless this is an INVAL, and only if this is from the real peer, not the transfer peer */
	if (!(iType == AST_FRAME_IAX && iSubClass == IAX_COMMAND_INVAL) && !bXfer) 
	{
		/* XXX This code is not very efficient.  Surely there is a better way which still properly handles boundary conditions? XXX */
		/* First we have to qualify that the ACKed value is within our window */
		for (iTemp = Sess_iRSeqNo[iSession]; iTemp != Sess_iOSeqNo[iSession]; iTemp ++)
		{
			if (iTemp == iISeqNo)	break;
		}
		if (iTemp != Sess_iOSeqNo[iSession] || Sess_iOSeqNo[iSession] == iISeqNo) 
		{
			/* The acknowledgement is within our window.  Time to acknowledge everything that it says to */
			for (iTemp = Sess_iRSeqNo[iSession]; iTemp != iISeqNo; iTemp ++) 
			{
				/* Ack the packet with the given timestamp */
				iax2_frame_cancel(iSession, iTemp);
			}
			/* Note how much we've received acknowledgement for */
			Sess_iRSeqNo[iSession] = iISeqNo;
		} 
		else
		{
			/* Received iseqno not within window */
		}
	}

	if (!bXfer)
	{
		if (Sess_iISeqNo[iSession] != iOSeqNo) 
		{
			if (!bAck) 
			{
				/* If it's not an ACK packet, it's out of order. */
				if (Sess_iISeqNo[iSession] > iOSeqNo) 
				{
					/* If we've already seen it, ack it XXX There's a border condition here XXX */
					/* XXX Maybe we should handle its ack to us, but then again, it's probably outdated anyway, and if
					   we have anything to send, we'll retransmit and get an ACK back anyway XXX */
					iax2_ack(iSession, lTimeStamp, iISeqNo);
				} 
				else 
				{
					/* Send a VNAK requesting retransmission */
					iax2_vnak(iSession);
				}
			}
			return;
		} 
		else 
		{
			/* Increment unless it's an ACK or VNAK */
			if (!bAck)	Sess_iISeqNo[iSession] ++;
		}
	}

	Iax2_iCallIndex = Iax2CallIndex(iSession);
	pCur += IAX_FULLFR_DATA;
	iDataLen = Adapter_iReceiveLen - IAX_FULLFR_DATA;

	iax2_parse_ie(pCur, iDataLen);
	iax2_debug_string(iType, iSubClass);

	switch(iType) 
	{
	case AST_FRAME_DTMF:
		iax2_incoming_key(iSubClass);
		iax2_ack(iSession, lTimeStamp, iISeqNo);
		break;

	case AST_FRAME_VOICE:
		if (Iax2_iCallIndex == Iax2_iCurCall)
		{
			iSubClass = iax2_get_codec(iax2_uncompress_subclass(iSubClass));
			if (iSubClass == Iax2_iAudioType[Iax2_iCallIndex])
			{
				iax2_recv_voice(pCur, iDataLen);
			}
		}

		iax2_ack(iSession, lTimeStamp, iISeqNo);
		break;

	case AST_FRAME_IAX:
		switch(iSubClass) 
		{
		case IAX_COMMAND_AUTHREQ:
			/* This is a request for a call */
			iax2_auth_reply(iSession);
			break;

		case IAX_COMMAND_HANGUP:
		case IAX_COMMAND_INVAL:
		case IAX_COMMAND_REJECT:
			iax2_ack(iSession, lTimeStamp, iISeqNo);
			iax2_session_set_state(iSession, SESSION_STATE_TIMEWAIT);
			iax2_clear_txsession(iSession);
			iax2_call_end(Iax2_iCallIndex);
			break;

		case IAX_COMMAND_ACK:
			iax2_session_finalack(iSession);
			break;

		case IAX_COMMAND_VNAK:
			iax2_frame_rexmit(iSession, iISeqNo);
			break;

		case IAX_COMMAND_LAGRQ:
			iax2_lagrp(iSession, lTimeStamp);
			break;

		case IAX_COMMAND_PING:
			/* PINGS and PONGS don't get scheduled; */
			iax2_pong(iSession);
			break;

		case IAX_COMMAND_ACCEPT:
			iax2_ack(iSession, lTimeStamp, iISeqNo);
			iTemp = iax2_get_codec(Ie_lFormat);
			if (iTemp == CODEC_NULL)
			{
				iax2_hangup(iSession, _cCauseCodec);
				iax2_call_end(Iax2_iCallIndex);
			}
			else
			{
				Iax2_iAudioType[Iax2_iCallIndex] = iTemp;
				if (Iax2_iCallIndex == Iax2_iCurCall)
				{
					Iax2StartVoice(Iax2_iCallIndex);
				}
			}
			break;

		case IAX_COMMAND_REGACK:
			if (iSession == IAX_REG_SESSION)
			{
				if (Ie_iRefresh)	
				{
					_iRegTTL = Ie_iRefresh;
				}
				iax2_ack(iSession, lTimeStamp, iISeqNo);
				iax2_session_set_state(iSession, SESSION_STATE_TIMEWAIT);
				if (!Task_bLogoned)
				{
					TaskLogonCompleted();
				}
				else
				{
					DebugCString((PCCHAR)"Reg refresh OK");
				}
			}
			break;

		case IAX_COMMAND_REGAUTH:
			if (iSession == IAX_REG_SESSION)
			{
				iax2_regauth_req();
			}
			break;

		case IAX_COMMAND_REGREJ:
			if (iSession == IAX_REG_SESSION)
			{
				iax2_ack(iSession, lTimeStamp, iISeqNo);
				iax2_session_set_state(iSession, SESSION_STATE_TIMEWAIT);
				TaskLogonFailed(0);
			}
			break;

		case IAX_COMMAND_QUELCH:
			iax2_ack(iSession, lTimeStamp, iISeqNo);
			Sess_iQuelch[iSession] = 1;
			break;

		case IAX_COMMAND_UNQUELCH:
			iax2_ack(iSession, lTimeStamp, iISeqNo);
			Sess_iQuelch[iSession] = 0;
			break;

		case IAX_COMMAND_TXREQ:
			iax2_ack(iSession, lTimeStamp, iISeqNo);
			iax2_recv_txreq(iSession);
			break;

		case IAX_COMMAND_TXCNT:
			// TXCNT gets ack'd with a TXACC
			iax2_recv_txcnt(iSession);
			break;

		case IAX_COMMAND_TXACC:
			iax2_ack(iSession, lTimeStamp, iISeqNo);
			iax2_recv_txacc(iSession);
			break;

		case IAX_COMMAND_TXREL:
			iax2_ack(iSession, lTimeStamp, iISeqNo);
			iax2_recv_txrel(iSession);
			break;

		case IAX_COMMAND_TXREJ:
			iax2_ack(iSession, lTimeStamp, iISeqNo);
			iax2_recv_txrej(iSession);
			break;

		case IAX_COMMAND_TXREADY:
			iax2_ack(iSession, lTimeStamp, iISeqNo);
			iax2_recv_txready(iSession);
			break;

		case IAX_COMMAND_MWI:
			TaskHandleData(VOICE_MSG_HINT, 0, (UCHAR)Ie_iMsgCount);
			break;

		case IAX_COMMAND_TRANSFER:
			iax2_ack(iSession, lTimeStamp, iISeqNo);
			iax2_recv_xfer(iSession);
			break;

		case IAX_COMMAND_DPREP:
			iax2_ack(iSession, lTimeStamp, iISeqNo);
			iax2_recv_dprep(iSession);
			break;

		default:
			iax2_ack(iSession, lTimeStamp, iISeqNo);
			break;
		}
		break;

	case AST_FRAME_CONTROL:
		iax2_ack(iSession, lTimeStamp, iISeqNo);
		switch(iSubClass) 
		{
		case AST_CONTROL_ANSWER:
			Iax2_iCallState[Iax2_iCallIndex] = IAX_CALL_STATE_CONNECTED;
//			Iax2StartVoice(Iax2_iCallIndex);
			TaskConnected();
			break;

		case AST_CONTROL_RINGING:
			TaskSignalMsg(SIGNAL_RING_BACK);
			break;

		case AST_CONTROL_BUSY:
		case AST_CONTROL_CONGESTION:
			iax2_hangup(iSession, _cCausePeerBusy);
			iax2_call_end(Iax2_iCallIndex);
			break;
		}
		break;

	default:
		iax2_ack(iSession, lTimeStamp, iISeqNo);
		break;
	}
}


void iax2_accept_call(UCHAR iCallIndex, UCHAR iSession)
{
	DCHAR iIndex;

	iIndex = iCallIndex;
	Iax2_iCallSess[iIndex] = iSession;
	Iax2_iIsCaller[iIndex] = FALSE;
	Iax2_iCallState[iIndex] = IAX_CALL_STATE_CONNECTING;
	iax2_accept(Iax2_iAudioType[iCallIndex], iSession);
	iax2_ctrl_ringing(iSession);
}

void iax2_handle_poke(USHORT iSrcCallNo)
{
	DCHAR iSession;

	iSession = iax2_session_new();
	if (iSession == IAX_SESSION_ERROR)
	{
		DebugCString((PCCHAR)"No new session");
		return;
	}

	iax2_open_socket(iSession, Adapter_pReceiveIPAddress, PXCHAR2USHORT((PXCHAR)Adapter_pReceivePacket));
	Sess_iPeerCallNo[iSession] = iSrcCallNo;
	Sess_iISeqNo[iSession] = X_VAL(Adapter_pReceiveUdpData, IAX_FULLFR_OSEQNO) + 1;
	
	iax2_pong(iSession);
	iax2_session_set_state(iSession, SESSION_STATE_FINALACK);
}

void iax2_accept_busy(UCHAR iSession)	// Thomas NOEL <thomas.noel@auf.org>
{
	// accept a call, but reply a "busy" status
        DCHAR iAudioType;
	iax2_ie_start();
	iAudioType = iax2_check_format();
	iax2_ie_add_long(IAX_IE_FORMAT, iax2_get_format(iAudioType));
	iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_ACCEPT, iax2_ie_get_len());
	iax2_ctrl_busy(iSession);
	iax2_session_set_state(iSession, SESSION_STATE_FINALACK);
}

void iax2_handle_new(USHORT iSrcCallNo)
{
	DCHAR iSession, iNewCall, iISeqNo, iAudioType;
	XLONG lTimeStamp;

	iSession = iax2_session_find_new(iSrcCallNo);
	if (iSession != IAX_SESSION_ERROR)
	{
		iISeqNo = X_VAL(Adapter_pReceiveUdpData, IAX_FULLFR_ISEQNO);
		lTimeStamp = PXCHAR2ULONG((PXCHAR)(Adapter_pReceiveUdpData + IAX_FULLFR_TSTAMP));
		iax2_ack(iSession, lTimeStamp, iISeqNo);
		return;
	}

	iSession = iax2_session_new();
	if (iSession == IAX_SESSION_ERROR)
	{
		DebugCString((PCCHAR)"No new session");
		return;
	}

	iax2_open_socket(iSession, Adapter_pReceiveIPAddress, PXCHAR2USHORT((PXCHAR)Adapter_pReceivePacket));
	Sess_iPeerCallNo[iSession] = iSrcCallNo;
	Sess_iISeqNo[iSession] = X_VAL(Adapter_pReceiveUdpData, IAX_FULLFR_OSEQNO) + 1;

	iax2_parse_ie((PXCHAR)(Adapter_pReceiveUdpData + IAX_FULLFR_DATA), (USHORT)(Adapter_iReceiveLen - IAX_FULLFR_DATA));
	iAudioType = iax2_check_format();
	if (iAudioType == CODEC_NULL)
	{
		iax2_reject(iSession, _cCauseCodec);
		return;
	}

	if (Sys_iForwardType & FORWARD_ALWAYS_OR)
	{
		goto Forward;
	}

	iNewCall = Iax2NewCall();
	if (iNewCall != IAX_CALL_ERROR)
	{
		Iax2_iAudioType[iNewCall] = iAudioType;
		if (Iax2InCall())
		{
			if (Sys_iForwardType & CALL_WAITING_OR)
			{
				TaskSignalMsg(SIGNAL_CALL_WAITING);
				iax2_accept_call(iNewCall, iSession);
				return;
			}
		}
		else
		{
			TaskSignalMsg(SIGNAL_INCOMING_CALL);
			if (_bAnswer)
			{
				Iax2_iCurCall = iNewCall;
				iax2_accept_call(iNewCall, iSession);
				return;
			}
		}
	}
	if (!(Sys_iForwardType & FORWARD_BUSY_OR))
	{
		iax2_reject(iSession, _cCauseBusy);
//		iax2_accept_busy(iSession);	// Thomas NOEL <thomas.noel@auf.org>
		return;
	}

Forward:
	iax2_accept(iAudioType, iSession);
	iax2_ctrl_ringing(iSession);
	iax2_ctrl_answer(iSession);
	iax2_transfer(iSession, Sys_pForwardNumber);
	iax2_session_set_state(iSession, SESSION_STATE_TIMEWAIT);
}

void iax2_recv_new(USHORT iSrcCallNo)
{
	DCHAR iFrameType, iCommand;

	iFrameType = X_VAL(Adapter_pReceiveUdpData, IAX_FULLFR_TYPE);
	iCommand = X_VAL(Adapter_pReceiveUdpData, IAX_FULLFR_CSUB);
	
	iax2_debug_string(iFrameType, iCommand);
	if (iFrameType == AST_FRAME_IAX)
	{
		if (iCommand == IAX_COMMAND_POKE)
		{
			iax2_handle_poke(iSrcCallNo);
		}
		else if (iCommand == IAX_COMMAND_NEW)
		{
			iax2_handle_new(iSrcCallNo);
		}
		else
		{
			DebugCString((PCCHAR)"Unsupported command");
		}
	}
	else
	{
		DebugCString((PCCHAR)"Unsupported frame type");
	}
}

void iax2_run_data()
{
	DCHAR iSession;
	DSHORT iSrcCallNo, iDstCallNo;
	BOOLEAN bFullFr;

	iSrcCallNo = PXCHAR2USHORT((PXCHAR)(Adapter_pReceiveUdpData + IAX_FULLFR_SCALLNO));
	bFullFr = (iSrcCallNo & IAX_FLAG_FULL) ? TRUE : FALSE;

	if (bFullFr)
	{
		OutputVals((PXCHAR)Adapter_pReceiveUdpData, Adapter_iReceiveLen);

		iSrcCallNo &= ~IAX_FLAG_FULL;
		iDstCallNo = PXCHAR2USHORT((PXCHAR)(Adapter_pReceiveUdpData + IAX_FULLFR_DCALLNO)) & ~IAX_FLAG_RETRANS;
		if (iDstCallNo)
		{
			iSession = iax2_session_find_full(iSrcCallNo, iDstCallNo);
			if (iSession != IAX_SESSION_ERROR)
			{
				iax2_recv_frame(iSession);
			}
			else
			{
				DebugCString((PCCHAR)"session don't exist");
			}
		}
		else
		{
			iax2_recv_new(iSrcCallNo);
		}
	}
	else
	{
		// Only process voice if the source call is the active voice session
		// This is for all transfers, since the session that has voice 
		iSession = iax2_session_find_mini(iSrcCallNo);
		if (iSession == Iax2_iCallSess[Iax2_iCurCall])
		{
			iax2_recv_voice((PXCHAR)(Adapter_pReceiveUdpData + IAX_MINIFR_DATA), (USHORT)(Adapter_iReceiveLen - IAX_MINIFR_DATA));
		}
	}
}

/********************************************************************************************
			API functions
********************************************************************************************/
void Iax2Startup()
{
	Sys_iProtocol = PROTOCOL_IAX2;
	iax2_session_startup();
	iax2_frame_startup();
	Iax2_iCallNo = p_rand() % 32767 + 1;
}

void Iax2Initialize()
{
	Iax2CallInit(0);
	Iax2CallInit(1);
	Iax2_iCurCall = 0;
	iax2_voice_init();
}

void Iax2Logon()
{
	if (Task_bLogoned)	return;

	Iax2_iCallNo = p_rand() % 32767 + 1;
	_iRegTimer = 0;
	_iRegTTL = Sys_iRegisterTTL;
	iax2_session_init(IAX_REG_SESSION);
	iax2_open_socket(IAX_REG_SESSION, Sys_pServiceIP, Sys_iServicePort);
	iax2_reg_req();
}

void Iax2Logoff()
{
/*	if (Task_bLogoned && Sys_bUseService)
	{
		iax2_session_init(IAX_REG_SESSION);
		iax2_reg_rel();
	}
*/
}

BOOLEAN Iax2Call(PXCHAR pKeyBuf)
{
	DCHAR iSession, iNewCall;
	BOOLEAN bUseGW;
	XCHAR pDstIP[IP_ALEN];

	if (!Task_bLogoned)		return FALSE;

	iNewCall = Iax2NewCall();
	if (iNewCall == IAX_CALL_ERROR)	
	{
//		DebugCString((PCCHAR)"Could not new call.");
		return FALSE;
	}
	iSession = iax2_session_new();
	if (iSession == IAX_SESSION_ERROR)
	{
//		DebugCString((PCCHAR)"Could not new session.");
		return FALSE;
	}

	if (IsValidIP(Sys_pGWIP))
	{
		bUseGW = TRUE;
	}
	else
	{
		bUseGW = FALSE;
	}

	if (Sys_bUseService)
	{
		if (!p_strcmp_x2x(Sys_pRemoteNumber, Sys_pAccount))		return FALSE;
	}
	else
	{
		if (!p_String2IP(pKeyBuf, pDstIP))	return FALSE;
		if (!IsValidIP(pDstIP))		return FALSE;
		if (!p_memcmp_ip(pDstIP, Local_pIPAddress))	return FALSE;
	}
	
	Iax2_iCurCall = iNewCall;
	Iax2_iCallSess[iNewCall] = iSession;
	Iax2_iCallState[iNewCall] = IAX_CALL_STATE_CONNECTING;
	Iax2_iIsCaller[iNewCall] = TRUE;
	if (Sys_bUseService)
	{
		iax2_open_socket(iSession, Sys_pServiceIP, Sys_iServicePort);
	}
	else
	{
		if (bUseGW)
		{
			iax2_open_socket(iSession, Sys_pGWIP, Sys_iServicePort);
		}
		else
		{
			iax2_open_socket(iSession, pDstIP, Sys_iServicePort);
		}
	}
	Iax2SwitchCall(TRUE);

	iax2_ie_start();
	iax2_ie_add_short(IAX_IE_VERSION, IAX_PROTO_VERSION);
	if (Sys_bUseService || bUseGW)
	{
		iax2_ie_add_xstr(IAX_IE_CALLED_NUMBER, Sys_pRemoteNumber);
		if (p_strlen_x(Sys_pLocalNumber))
		{
			iax2_ie_add_xstr(IAX_IE_CALLING_NUMBER, Sys_pLocalNumber);
			iax2_ie_add_xstr(IAX_IE_CALLING_NAME, Sys_pLocalNumber);
		}
		if (p_strlen_x(Sys_pAccount))
		{
			iax2_ie_add_xstr(IAX_IE_USERNAME, Sys_pAccount);
		}
	}
	else
	{
		iax2_ie_add_cstr(IAX_IE_CALLED_NUMBER, (PCCHAR)"s");
		p_IP2String(Local_pIPAddress, (PXCHAR)Sdram_pDataCache);
		iax2_ie_add_xstr(IAX_IE_CALLING_NUMBER, (PXCHAR)Sdram_pDataCache);
		iax2_ie_add_xstr(IAX_IE_CALLING_NAME, (PXCHAR)Sdram_pDataCache);
	}
	iax2_ie_add_long(IAX_IE_FORMAT, iax2_get_format(Sys_pCodecType[0]));
	iax2_ie_add_long(IAX_IE_CAPABILITY, iax2_get_capability());
	iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_NEW, iax2_ie_get_len());
	return TRUE;
}

void Iax2AnswerCall(BOOLEAN bAnswer)
{
	_bAnswer = bAnswer;
}

void Iax2Hangup()
{
	if (Iax2_iCallSess[0] != IAX_SESSION_ERROR)
	{
		iax2_hangup(Iax2_iCallSess[0], _cCauseClearing);
		iax2_call_end(0);
	}
	if (Iax2_iCallSess[1] != IAX_SESSION_ERROR)
	{
		iax2_hangup(Iax2_iCallSess[1], _cCauseClearing);
		iax2_call_end(1);
	}
}

void Iax2Flash()	// Thomas NOEL <thomas.noel@auf.org>
{
	// If there is a call waiting : refuse it (with busy)
	// If we are connected : hangup + call_end
        DCHAR iSession;
        DCHAR iAltCall, iCloseCall;

        iAltCall = 1 - Iax2_iCurCall;
        if (Iax2_iCallState[iAltCall] == IAX_CALL_STATE_CONNECTING && !Iax2_iIsCaller[iAltCall])
        {
                iCloseCall = iAltCall;
        }
        else
        {
                iCloseCall = Iax2_iCurCall;
        }

        iSession = Iax2_iCallSess[iCloseCall];
        if (iSession == IAX_SESSION_ERROR)      return;

        if (Iax2_iCallState[iCloseCall] == IAX_CALL_STATE_CONNECTING)
        {
                iax2_ctrl_busy(iSession);
        }
        else
        {
                iax2_hangup(iSession, _cCauseClearing);
                iax2_call_end(iCloseCall);
        }
}

void Iax2Connect()
{
	DCHAR iSession;

	iSession = Iax2_iCallSess[Iax2_iCurCall];
	if (iSession == IAX_SESSION_ERROR)	return;

	Iax2_iCallState[Iax2_iCurCall] = IAX_CALL_STATE_CONNECTED;
	iax2_ctrl_answer(iSession);
	Iax2StartVoice(Iax2_iCurCall);
	TaskConnected();
}

void Iax2CallHold()
{
	DCHAR iSession, iState;

	iSession = Iax2_iCallSess[Iax2_iCurCall];
	if (iSession == IAX_SESSION_ERROR)	return;

	iState = Iax2_iCallState[Iax2_iCurCall];
	if (Task_bCallHold)
	{
		iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_UNQUELCH, 0);
		Task_bCallHold = FALSE;
		Iax2StartVoice(Iax2_iCurCall);
	}
	else
	{
		if (iState == IAX_CALL_STATE_CONNECTING)
		{
			iax2_hangup(iSession, _cCauseClearing);
			iax2_call_end(Iax2_iCurCall);
		}
		else if (iState == IAX_CALL_STATE_CONNECTED)
		{
//			iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_QUELCH, 0);
			iax2_ie_start();
			iax2_ie_add_xraw(IAX_IE_MUSICONHOLD, (PXCHAR)"", 0);
			iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_QUELCH, iax2_ie_get_len());
			if (!iax2_activate_alt_call(Iax2_iCurCall))
			{
				Task_bCallHold = TRUE;
			}
		}
	}
}

void Iax2UnattendedTransfer(PXCHAR pDstNumber)
{
	DCHAR iSession;

	iSession = Iax2_iCallSess[Iax2_iCurCall];
	iax2_transfer(iSession, pDstNumber);
//	iax2_call_end(Iax2_iCurCall);
}

void Iax2AttendedTransfer()
{
	DCHAR iAltCall, iAltSession;
	DCHAR iSession;

	iAltCall = 1 - Iax2_iCurCall;
	if (Iax2_iCallState[Iax2_iCurCall] != IAX_CALL_STATE_CONNECTED || Iax2_iCallState[iAltCall] != IAX_CALL_STATE_CONNECTED)
	{
		DebugCString((PCCHAR)"No two established calls");
		return;
	}
	iSession = Iax2_iCallSess[Iax2_iCurCall];
	iAltSession = Iax2_iCallSess[iAltCall];

	Sess_iTransSession[iSession] = iAltSession;
	Sess_iTransState[iSession] = IAX_TRANS_STATE_CONNECTING;
	p_rand_array(Sess_pTransferID[iSession], IAX_TRANSFERID_LEN);
	Sess_iTransCallNo[iSession] = Sess_iPeerCallNo[iAltSession];
	Iax2_iCallState[Iax2_iCurCall] = IAX_CALL_STATE_TRANSFERRING;
	iax2_send_txreq(iSession, Sess_iPeerPort[iAltSession], Sess_pPeerIP[iAltSession]);

	Sess_iTransSession[iAltSession] = iSession;
	Sess_iTransState[iAltSession] = IAX_TRANS_STATE_CONNECTING;
	p_memcpy_x2x(Sess_pTransferID[iAltSession], Sess_pTransferID[iSession], IAX_TRANSFERID_LEN);
	Sess_iTransCallNo[iAltSession] = Sess_iPeerCallNo[iSession];
	Iax2_iCallState[iAltCall] = IAX_CALL_STATE_TRANSFERRING;
	iax2_send_txreq(iAltSession, Sess_iPeerPort[iSession], Sess_pPeerIP[iSession]);

	Task_bCallTransfer = TRUE;
}

void Iax2HandleTimer()
{
	DCHAR i, iSession;

	iax2_session_handle_timer();
	iax2_frame_handle_timer();

	if (Sys_bUseService)
	{
		_iRegTimer ++;
		if (Task_bLogoned)
		{
			if (_iRegTimer > (USHORT)((_iRegTTL >> 2) * 3))
			{
				_iRegTimer = 0;
				_iRegTTL = Sys_iRegisterTTL;
				iax2_session_init(IAX_REG_SESSION);
				iax2_reg_req();
			}
		}
		else
		{
			if (_iRegTimer > IAX_DEFAULT_REG_EXPIRE)
			{
				_iRegTimer = 0;
				TaskLogon();
			}
		}
	}

	if (Sys_iForwardType & FORWARD_NOANSWER_OR)
	{
		for (i = 0; i < IAX_MAX_CALL; i ++)
		{
			if (Iax2_iCallState[i] == IAX_CALL_STATE_CONNECTING && !Iax2_iIsCaller[i])
			{
				
				Iax2_iCallTimer[i] ++;
				if (Iax2_iCallTimer[i] > Task_iRingNumber)
				{
					Iax2_iCallTimer[i] = 0;
					iSession = Iax2_iCallSess[i];
					iax2_ctrl_answer(iSession);
					iax2_transfer(iSession, Sys_pForwardNumber);
					iax2_session_set_state(iSession, SESSION_STATE_TIMEWAIT);
					iax2_call_end(i);
				}
			}
		}
	}
}

void Iax2SendDtmfKeys(PXCHAR pKeyBuf, UCHAR iLen)
{
	DCHAR iSession;
	
	iSession = Iax2_iCallSess[Iax2_iCurCall];
	if (iSession != IAX_SESSION_ERROR)
	{
		iax2_session_send_packet(iSession, AST_FRAME_DTMF, pKeyBuf[0], 0);
	}
	pKeyBuf[iLen] = 0;
}

BOOLEAN Iax2RunUdp()
{
	if (Adapter_iReceiveUdpPort != Sys_iRegisterPort)
	{
		return FALSE;
	}

	// send to Sys_iServicePort is the right way
	iax2_run_data();
	return TRUE;
}

BOOLEAN Iax2DPCall()
{
	DCHAR iSession, iNewCall;
	BOOLEAN bUseGW;

	if (!Task_bLogoned)		return FALSE;
	if (IsValidIP(Sys_pGWIP))
	{
		bUseGW = TRUE;
	}
	else
	{
		bUseGW = FALSE;
	}
	if (!Sys_bUseService && !bUseGW)	return FALSE;

	iNewCall = Iax2NewCall();
	if (iNewCall == IAX_CALL_ERROR)	
	{
		return FALSE;
	}
	iSession = iax2_session_new();
	if (iSession == IAX_SESSION_ERROR)
	{
		return FALSE;
	}

	Iax2_iCurCall = iNewCall;
	Iax2_iCallSess[iNewCall] = iSession;
	Iax2_iCallState[iNewCall] = IAX_CALL_STATE_CONNECTING;
	Iax2_iIsCaller[iNewCall] = TRUE;
	if (Sys_bUseService)
	{
		iax2_open_socket(iSession, Sys_pServiceIP, Sys_iServicePort);
	}
	else
	{
		iax2_open_socket(iSession, Sys_pGWIP, Sys_iServicePort);
	}
	Iax2SwitchCall(TRUE);
	Task_bConnecting = FALSE;

	iax2_ie_start();
	iax2_ie_add_short(IAX_IE_VERSION, IAX_PROTO_VERSION);
	iax2_ie_add_cstr(IAX_IE_CALLED_NUMBER, (PCCHAR)"TBD");
	if (p_strlen_x(Sys_pLocalNumber))
	{
		iax2_ie_add_xstr(IAX_IE_CALLING_NUMBER, Sys_pLocalNumber);
		iax2_ie_add_xstr(IAX_IE_CALLING_NAME, Sys_pLocalNumber);
	}
	if (p_strlen_x(Sys_pAccount))
	{
		iax2_ie_add_xstr(IAX_IE_USERNAME, Sys_pAccount);
	}
	iax2_ie_add_long(IAX_IE_FORMAT, iax2_get_format(Sys_pCodecType[0]));
	iax2_ie_add_long(IAX_IE_CAPABILITY, iax2_get_capability());
	iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_NEW, iax2_ie_get_len());
	return TRUE;
}

void Iax2DPReq(PXCHAR pKeyBuf)
{
	DCHAR iSession;
	
	DebugCString((PCCHAR)"Send DPReq");
	iSession = Iax2_iCallSess[Iax2_iCurCall];
	if (iSession != IAX_SESSION_ERROR)
	{
		iax2_ie_start();
		iax2_ie_add_xstr(IAX_IE_CALLED_NUMBER, pKeyBuf);
		iax2_session_send_packet(iSession, AST_FRAME_IAX, IAX_COMMAND_DPREQ, iax2_ie_get_len());
	}
}


