/*++
*******************************************************************************
* Copyright (c) 1990-2000 Digi International Inc., All Rights Reserved			*
* 																										*
* This software contains proprietary and confidential information of Digi		*
* International Inc.  By accepting transfer of this copy, Recipient agrees		*
* to retain this software in confidence, to prevent disclosure to others,		*
* and to make no use of this software other than that for which it was			*
* delivered.  This is an unpublished copyrighted work of Digi International	*
* Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly	*
* prohibited.																						*
* 																										*
* Restricted Rights Legend																		*
*																										*
* Use, duplication, or disclosure by the Government is subject to					*
* restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in			*
* Technical Data and Computer Software clause at DFARS 252.227-7031 or			*
* subparagraphs (c)(1) and (2) of the Commercial Computer Software -				*
* Restricted Rights at 48 CFR 52.227-19, as applicable.								*
*																										*
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343				*
*																										*
*******************************************************************************

Module Name:

   pppcall.c

Abstract:

   Call central.  Includes call control manager code, call state machine, data transmit
   routines.  

	Cm* routines:       State machine "Action" routines, where Cm means Call Mgr
	pppCall* routines: Public routines exposed to the world
	CallMgrThread:      Private thread used to synchronize opens and closes

--*/

#include "digippp.h"
#include "iwan_proto.h"

#define GetState( pCall )					( (pCall)->State )
#define SetState( pCall, NewState )		( (pCall)->State = (NewState) )

#define QueueTapiNewCall( pCall )	\
	(void)PPPQueueTapiIndicate( pCall, pCall->pAdapter->pLines[pCall->hdLine].htLine, LINE_NEWCALL, (ULONG_PTR)pCall, 0, 0 )

#define QueueTapiOffering( pCall )	\
	(void)PPPQueueTapiIndicate( pCall, pCall->pAdapter->pLines[pCall->hdLine].htLine, LINE_CALLSTATE, LINECALLSTATE_OFFERING, 0, LINEMEDIAMODE_DIGITALDATA )

#define QueueTapiIdle( pCall )	\
	(void)PPPQueueTapiIndicate( pCall, pCall->pAdapter->pLines[pCall->hdLine].htLine, LINE_CALLSTATE, LINECALLSTATE_IDLE, 0, 0 )

#define QueueTapiDialing( pCall )	\
	(void)PPPQueueTapiIndicate( pCall, pCall->pAdapter->pLines[pCall->hdLine].htLine, LINE_CALLSTATE, LINECALLSTATE_DIALING, 0, 0 )
	
#define QueueTapiConnected( pCall )	\
	(void)PPPQueueTapiIndicate( pCall, pCall->pAdapter->pLines[pCall->hdLine].htLine, LINE_CALLSTATE, LINECALLSTATE_CONNECTED, 0, 0 )

#define QueueTapiDisconnected( pCall, DisconnectMode )	\
	(void)PPPQueueTapiIndicate( pCall, pCall->pAdapter->pLines[pCall->hdLine].htLine, LINE_CALLSTATE, LINECALLSTATE_DISCONNECTED, DisconnectMode, 0 )

#define QueueWanLineUp( pCall )	\
	(void)PPPQueueWanLineup( pCall, pCall->htCall, pCall->Rate );

#define QueueWanLineDown( pCall )	\
	(void)PPPQueueWanLinedown( pCall )

#define CallAcquireLock( pCall )		DigiAcquireSpinLock( &(pCall)->Lock )
#define CallReleaseLock( pCall )		DigiReleaseSpinLock( &(pCall)->Lock )


//
// State Machine 
// -------------
// 
// All calls have a finite set of states (defined by enum STATE) and a finite set of 
// inputs stimuli (defined by enum INPUT).  

// ppp call objects have following states:
typedef enum STATE
{
	STATE_IDLE,				// Idle.  Not used
	STATE_DIALING,			// Sent XN_CONNECT, waiting for CACTIVE, CONNACK, or DISCONN
	STATE_OFFERING,		// XN_CONNECT received.  Offering to TAPI, waiting for Answer
	STATE_CONNECTED,		// Connected.  Only state in which we can pass data
	STATE_DISCONNECTING,	// Got an XN_DISCONN from remote.  Waiting for XN_CONNEND.
	STATE_DISCONNECTED,	// Got XN_CONNEND from remote.  Wait for Tapi to Drop or Close
	STATE_DROPPING,		// Tapi Dropped.  Waiting for XN_CONNEND
	STATE_DROPPED,			// XN_CONNEND received.  ppp call is completely torn down
	STATE_CLOSING,			// Tapi CloseCall.  Close stream, release resources for call
	STATE_ERROR,			// The error state.  Only way out of this state is Close.
	STATE_COUNT
} STATE;

////////////////////////////////////////////////////////
// WARNING WARNING WARNING WARNING 							//
//																		//
// These strings must be updated to match any changes //
// you make to the STATE enum! They are used only for //
// debugging state changes.									//
////////////////////////////////////////////////////////
#if DBG
const PCSTR StateStrings[] =
{
	"IDLE",
	"DIALING",
	"OFFERING",
	"CONNECTED",
	"DISCONNECTING",
	"DISCONNECTED",
	"DROPPING",
	"DROPPED",
	"CLOSING",
	"ERROR"
};
#endif

// ppp call objects can receive the following input stimuli, including requests from 
// TAPI and indications from the line.
typedef enum INPUT
{
	INPUT_TAPI_DIAL,		// Tapi Dial request.  Send out an XN_CONNECT msg.
	INPUT_TAPI_ANSWER,	// Tapi Answer request.  Send out an XN_CONNACK msg.
	INPUT_TAPI_DROP,		// Tapi Drop request.  Disconnect call, do not close stream.
	INPUT_TAPI_CLOSECALL,// Tapi CloseCall request.  Close stream, free call.
	INPUT_TAPI_CLOSE,		// Tapi CloseCall request.  Close stream, free call.
	INPUT_LINE_CDACTIVE,	// CD on line 
	INPUT_LINE_CONNEND,	// Disconnect is complete - CD dropped
	INPUT_COUNT
} INPUT;

////////////////////////////////////////////////////////
// WARNING WARNING WARNING WARNING 							//
//																		//
// These strings must be updated to match any changes //
// you make to the INPUT enum! They are used only for //
// debugging state changes.									//
////////////////////////////////////////////////////////
#if DBG
const PCSTR InputStrings[] =
{
	"TAPI_DIAL",
	"TAPI_ANSWER",
	"TAPI_DROP",
	"TAPI_CLOSECALL",
	"TAPI_CLOSE",
	"LINE_CDACTIVE",
	"LINE_CONNEND"
};
#endif


PDIGI_PPP_CALL 
PPPCallConstruct( 
	IN PDIGI_PPP_ADAPTER pAdapter,
	IN ULONG             iLine )
/*++

Description 

	Alloc and initialize a call object.  Attach it to the specified Tapi line.  If iLine
	is DIGI_PPP_LINE_ANY, grab any unused line which Tapi is listening on.

--*/
{
	PDIGI_PPP_CALL pCall;

	pCall = DigiAllocMem( NonPagedPool, sizeof *pCall );
	if ( pCall )
	{
		NdisZeroMemory( pCall, sizeof *pCall );
		pCall->Type     = DIGI_PPP_TYPE_CALL;
		pCall->pAdapter = pAdapter;

		pCall->RemoteAddress.Buffer = pCall->RemoteAddressBuffer;
		ASSERT( sizeof pCall->RemoteAddressBuffer > DIGI_PPP_NUMBER_SIZE );
		pCall->RemoteAddress.MaximumLength = sizeof pCall->RemoteAddressBuffer - 1;

		pCall->MaxSendFrameSize    = pAdapter->MaxFrameSize;
		pCall->MaxRecvFrameSize    = pAdapter->MaxFrameSize;
		pCall->HeaderPadding       = DIGI_PPP_HEADER_PADDING;
		pCall->TailPadding         = DIGI_PPP_TAIL_PADDING;
		pCall->SendFramingBits     = DIGI_PPP_FRAMING_BITS;
		pCall->RecvFramingBits     = DIGI_PPP_FRAMING_BITS;
		pCall->SendCompressionBits = DIGI_PPP_COMP_BITS;
		pCall->RecvCompressionBits = DIGI_PPP_COMP_BITS;
		pCall->hStream = pAdapter->pLines[iLine].hStream;
		DigiDump( DIGIERROR, ( "CallConstruct: iLine 0x%x, hStream 0x%x\n", iLine, pCall->hStream ) );

		DigiInitializeSpinLock( &pCall->Lock );
		InitializeListHead( &pCall->RxCompleteListEntry );

		AdapterAcquireLock( pAdapter );
		{
			pAdapter->pLines[iLine].pCall = pCall;
			pCall->hdLine = iLine;
			pCall->Rate = pAdapter->pLines[iLine].Speed;
		}
		AdapterReleaseLock( pAdapter );
	}
	return pCall;
}

VOID
PPPCallDestruct( 
	IN PDIGI_PPP_CALL pCall )
/*++

Description 

	Free the specified call object.

--*/
{
	AdapterAcquireLock( pCall->pAdapter );
	ASSERT( pCall->pAdapter->pLines[pCall->hdLine].pCall == pCall );
	pCall->pAdapter->pLines[pCall->hdLine].pCall = NULL;
	AdapterReleaseLock( pCall->pAdapter );
	DigiFreeMem( pCall );
}


///////////////////////////////////////////////////////////////////////////////
//
// Call Manager Thread
// 
// This thread is responsible for initiating and terminating calls.  Specifically,
// it handles the Ndis APIs which must *wait* for some event to occur, typically 
// waiting for a Dxb request to complete.  Tapi requests happen at dispatch_level so
// we perform the wait operation in this thread which runs at passive_level.
// 
// MiniportHalt is the special case handled here.  Since Dxb closes are normally 
// handled by the thread, it also handle closes at MiniportHalt time.
//
typedef enum
{
	CALL_THREAD_UNKNOWN,
	CALL_THREAD_DIAL,
	CALL_THREAD_ANSWER,
	CALL_THREAD_CLOSECALL,
	CALL_THREAD_CLOSE,
} CALL_THREAD_ACTION;

STATIC VOID
MgrNotifyThread( 
	IN PDIGI_PPP_CALL     pCall, 
	IN CALL_THREAD_ACTION Action )
/*++

Description

	Internal routine used by Cm* api's to queue work for the call manager thread.  Call
	Manager queue is protected by the adapter lock.  Insert call on work list, notify 
	thread of work to do.

--*/
{
	AdapterAcquireLock( pCall->pAdapter );
	pCall->Mgr.Action = Action;
	InsertTailList( &pCall->pAdapter->Mgr.Queue, &pCall->Mgr.ListEntry );
	(void)KeSetEvent( &pCall->pAdapter->Mgr.WorkEvent, EVENT_INCREMENT, FALSE );
	AdapterReleaseLock( pCall->pAdapter );
}

STATIC BOOLEAN
MgrGetCall( 
	IN PDIGI_PPP_ADAPTER   pAdapter, 
	OUT PDIGI_PPP_CALL     *ppCall, 
	OUT CALL_THREAD_ACTION *pAction )
{
	BOOLEAN fFound;
	AdapterAcquireLock( pAdapter );
	if ( IsListEmpty( &pAdapter->Mgr.Queue ) )
		fFound = FALSE;
	else
	{
		PDIGI_PPP_CALL pCall;
		PLIST_ENTRY pEntry = RemoveHeadList( &pAdapter->Mgr.Queue );
		pCall = CONTAINING_RECORD( pEntry, DIGI_PPP_CALL, Mgr.ListEntry );
		*ppCall = pCall;
		*pAction = pCall->Mgr.Action;
		fFound = TRUE;
	}
	AdapterReleaseLock( pAdapter );
	return fFound;
}

typedef enum
{
	EVENT_WORK = 0,
	EVENT_HALT,
	EVENT_COUNT
} MGR_EVENT;

// Used by callmgrthread.
VOID PPPCallOnCDActive( IN PVOID pOwner );

STATIC VOID
CallMgrThread( 
	IN PVOID Context )
/*++

Description

	This thread	is used by the state machine routines to handle opening and closing 
	streams.  

--*/
{
	PDIGI_PPP_ADAPTER CONST pAdapter = Context;
	KEVENT *Events[EVENT_COUNT];

	Events[EVENT_WORK] = &pAdapter->Mgr.WorkEvent;
	Events[EVENT_HALT] = &pAdapter->Mgr.HaltEvent;

	while ( KeWaitForMultipleObjects( EVENT_COUNT, (PVOID)&Events, WaitAny, Executive,
	        KernelMode, FALSE, NULL, NULL ) != EVENT_HALT )
	{
		PDIGI_PPP_CALL pCall;
		CALL_THREAD_ACTION Action;

		while ( MgrGetCall( pAdapter, &pCall, &Action ) )
		{
			// Acquire lock to force a wait until StateMachine is done updating the 
			// call state before we start a requested action.
			CallAcquireLock( pCall );
			CallReleaseLock( pCall );

			switch ( Action )
			{
				case CALL_THREAD_DIAL:
				{
					NTSTATUS Status = STATUS_SUCCESS;
					PPPQueueQueryComplete( pAdapter, Status );
					QueueTapiDialing( pCall );
					ASSERT( pCall->MaxSendFrameSize == pCall->MaxRecvFrameSize );
					PPPCallOnCDActive( pCall );
					break;
				}

				case CALL_THREAD_ANSWER:
				{
					PPPQueueSetComplete( pAdapter, STATUS_SUCCESS );
					break;
				}

				case CALL_THREAD_CLOSECALL:
				case CALL_THREAD_CLOSE:

					// MdmDropDtrRts(pCall);
					SetState( pCall, STATE_IDLE );
					PPPQueueSetComplete( pAdapter, STATUS_SUCCESS );
					break;

				default:
					
					DigiDump( DIGIERROR, ( "PPP CallMgrThread, unsupported Action 0x%x!\n" ) );
					break;
			}
		}
	}

	// Halt received!  Close all remaining calls
	{
		ULONG i;
		for ( i = 0; i < pAdapter->nLines; i++ )
		{
			if ( pAdapter->pLines[i].pCall )
//				PPPCallDestruct( pAdapter->pLines[i].pCall );
				;
		}
	}

	(void)PsTerminateSystemThread( STATUS_SUCCESS );
}


NTSTATUS
PPPCallMgrThreadStart( 
	IN PDIGI_PPP_ADAPTER pAdapter )
/*++

Description:

	Public routine to start the thread.  This starts the adapter thread which processes 
	all call opens and closes and should be started from MiniportInitialize.  Only one
	thread should be start for any given adapter.

--*/
{

	HANDLE hThread;
	PKTHREAD pThread;
	NTSTATUS Status;
	
	ASSERT( KeGetCurrentIrql() == PASSIVE_LEVEL );	// can only run at PASSIVE
	ASSERT( pAdapter->Mgr.pThread == NULL );	// pThread shouldn't already be assigned

	pAdapter->Mgr.pThread = NULL;
	InitializeListHead( &pAdapter->Mgr.Queue );
	KeInitializeEvent( &pAdapter->Mgr.WorkEvent, SynchronizationEvent, FALSE );
	KeInitializeEvent( &pAdapter->Mgr.HaltEvent, SynchronizationEvent, FALSE );

	Status = PsCreateSystemThread( &hThread, THREAD_ALL_ACCESS, NULL, 0, NULL, CallMgrThread, pAdapter );

	if ( NT_SUCCESS( Status ) )
	{
		Status = ObReferenceObjectByHandle( hThread, THREAD_ALL_ACCESS, NULL, KernelMode, &pThread, NULL );
		ZwClose( hThread );

		if ( NT_SUCCESS( Status ) )
			pAdapter->Mgr.pThread = pThread;
		else
			// Tell thread to stop.  
			KeSetEvent( &pAdapter->Mgr.HaltEvent, EVENT_INCREMENT, FALSE );
	}

	return Status;	
}


VOID
PPPCallMgrThreadStop( 
	IN PDIGI_PPP_ADAPTER pAdapter )
/*++

Description

	Public routine to stop the thread.  Set Stop event and wait for thread to exit.

--*/
{
	ASSERT( KeGetCurrentIrql() == PASSIVE_LEVEL );

	if ( pAdapter->Mgr.pThread )
	{
		(void)KeSetEvent( &pAdapter->Mgr.HaltEvent, EVENT_INCREMENT, FALSE );
		(void)KeWaitForSingleObject( pAdapter->Mgr.pThread, Executive, KernelMode, FALSE, NULL );
		ObDereferenceObject( pAdapter->Mgr.pThread );
		pAdapter->Mgr.pThread = NULL;
	}
}


///////////////////////////////////////////////////////////////////////////////
//
// State machine Proc and helper routines
//
// State machine proc simply takes a call and an input stimuli, performs the
// appropriate action (calls the Cm* handler routine), and moves the call to 
// a new state.
// 

STATIC VOID 
CmOffer( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	An incoming call has arrived.  Indicate (offer) it up to Tapi.

--*/
{
	QueueTapiNewCall( pCall );
	QueueTapiOffering( pCall );
}

STATIC VOID
CmDial( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	Received a MakeCall request from Tapi.  The callMgrThread handles all dial requests.
	Notify the thread that there's work to do.

--*/
{
	MgrNotifyThread( pCall, CALL_THREAD_DIAL );
}

STATIC VOID
CmRedial( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	Tried to dial but couldn't connect at the rate requested.  Step down a rate and try
	again.

--*/
{
	XnConnect( pCall->pAdapter, pCall, pCall->hStream, pCall->pAdapter->Snid, pCall->MaxSendFrameSize, pCall->ConnType, &pCall->RemoteAddress );
}

STATIC VOID
CmConnected( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	Received notification from the card that the call is now completely connected.  
	Notify Tapi and NdisWan so we can start sending data.

--*/
{
	QueueWanLineUp( pCall );
	QueueTapiConnected( pCall );
}

STATIC VOID
CmAnswer( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	Received an Answer request from Tapi.  The callMgrThread handles all dial requests.
	Notify the thread that there's work to do.

--*/
{
	MgrNotifyThread( pCall, CALL_THREAD_ANSWER );
	CmConnected(pCall);
}

STATIC VOID
CmReject(
	PDIGI_PPP_CALL pCall )
/*++

Description:

	Received a Drop request from Tapi for the previously offered incoming call.

--*/
{
//	XnConnend( pCall->pAdapter, pCall->pAdapter, pCall->pAdapter->Listen.hStream, pCall->Incoming.ChanIndex, pCall->Incoming.Channel );
//	MdmDropDtrRts(pCall);
}

STATIC VOID
CmEarlyDrop( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	Received a drop request from Tapi, but the call has not completely connected.
	Send a disconnect to the card to stop the call progress.

--*/
{
//	XnDisconn( pCall->pAdapter, pCall, pCall->hStream, pCall->Incoming.ChanIndex, pCall->Incoming.Channel );
//	MdmDropDtrRts(pCall);
}

STATIC VOID
CmDrop( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	Received a drop request from Tapi while the call is connected.  Send a disconnect to
	the card and notify NdisWan that we're dropping the call.

--*/
{
	// Drop DTR Here
//	XnDisconn( pCall->pAdapter, pCall, pCall->hStream, pCall->Incoming.ChanIndex, pCall->Incoming.Channel );

//	MdmDropDtrRts(pCall);
	QueueTapiIdle( pCall );
	QueueWanLineDown( pCall );
}

STATIC VOID
CmDisc( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	Received a disconnect (XN_DISCONN) from the card indicating that the call is being
	disconnected.  Sometime later an XN_CONNEND will tell us the disconnect is complete.

--*/
{
	QueueTapiDisconnected( pCall, LINEDISCONNECTMODE_NORMAL );
	QueueWanLineDown( pCall );
}

STATIC VOID
CmEarlyDisc( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	Received an XN_DISCONN before a call in progress has connected.  Notify Tapi that
	the call has disconnected.  Don't notify NdisWan since we never connected.

--*/
{
	QueueTapiDisconnected( pCall, LINEDISCONNECTMODE_REJECT );
}

STATIC VOID
CmEarlyConnend( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	We're in the process of an outbound (dialed) call, and we're waiting for a 
	connect acknowledge.  Instead, we get an XN_CONNEND meaning the call has been 
	dropped either by the card or the network.  Notify Tapi that the call is over!

--*/
{
	QueueTapiDisconnected( pCall, LINEDISCONNECTMODE_NORMAL );
	QueueTapiIdle( pCall );
}

STATIC VOID
CmConnend( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	Received an XN_CONNEND from the card indicating the disconnection is complete.  
	Notify Tapi.

--*/
{
	QueueTapiIdle( pCall );
}


STATIC VOID
CmConnectedConnend( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	Received an XN_CONNEND while in a CONNECTED state, signalling an abrupt end of the
	call, i.e. the line was cut or disconnected.
	Notify Tapi.

--*/
{
	QueueTapiDisconnected( pCall, LINEDISCONNECTMODE_UNKNOWN );
	QueueWanLineDown( pCall );
	QueueTapiIdle( pCall );
}

STATIC VOID
CmIgnore( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	Some input stimuli has occured that needs no action.  Do nothing.

--*/
{
	UNREFERENCED_PARAMETER( pCall );
}

STATIC VOID
CmErrorConn( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	An error occurred while the call was in the connected state.  Send a disconnect to
	the card and notify both Tapi and NdisWan that we're disconnecting the call.  The
	call will then proceed to the error state.

--*/
{
	DigiDump( DIGIERROR, 
	          ( "CmErrorConn: Illegal input stimuli while call 0x%08x was connected!\n",
	          pCall ) );
	QueueTapiDisconnected( pCall, LINEDISCONNECTMODE_UNKNOWN );
	QueueWanLineDown( pCall );
	QueueTapiIdle( pCall );
}

STATIC VOID
CmErrorDisc( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	An error occurred while disconnecting the call.  Notify Tapi that disconnect is 
	complete and move to the error state.

--*/
{
	DigiDump( DIGIERROR, 
	          ( "CmErrorDisc: Illegal input while call 0x%08x was disconnecting!\n", 
	          pCall ) );
	QueueTapiIdle( pCall );
}

STATIC VOID
CmErrorIdle( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	An error occurred, but no action is necessary.  Move to the error state.

--*/
{
	DigiDump( DIGIERROR, ( "CmErrorIdle: Illegal input on call 0x%08x\n", pCall ) );
}

STATIC VOID
CmErrorInProgress( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	An error occurred before a new call (either incoming or outgoing) has connected.
	Notify Tapi that we're giving up and move to the error state.

--*/
{
	DigiDump( DIGIERROR, 
	          ( "CmErrorInProgress: Illegal input while call 0x%08x before connected\n",
	          pCall ) );
	QueueTapiDisconnected( pCall, LINEDISCONNECTMODE_UNKNOWN );
	QueueTapiIdle( pCall );
}

STATIC VOID
CmCloseCall( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	Received a CloseCall request from Tapi.  Defer actual work to CallMgrThread.

--*/
{
	MgrNotifyThread( pCall, CALL_THREAD_CLOSECALL );
}


STATIC VOID
CmClose( 
	PDIGI_PPP_CALL pCall )
/*++

Description:

	Received a Close request from Tapi.  Defer actual work to CallMgrThread.

--*/
{
	MgrNotifyThread( pCall, CALL_THREAD_CLOSE );
}


// For each state, assign a handler function for every possible stimuli.  The routine
// performs necessary actions, and returns the new state of the call.
typedef VOID (*HANDLER)( pCall );

// For each state and input pair, define an Action to be performed and an output state.
typedef struct STATE_TRANSITION
{
	CONST HANDLER Action;
	CONST STATE   NewState;
} STATE_TRANSITION;

STATIC CONST STATE_TRANSITION StateTable[STATE_COUNT][INPUT_COUNT] = 
{
	// STATE_IDLE        
	{	{CmDial, 		STATE_DIALING },					// INPUT_TAPI_DIAL	
		{CmErrorIdle,	STATE_ERROR },						// INPUT_TAPI_ANSWER	
		{CmErrorIdle, 	STATE_DROPPED },					// INPUT_TAPI_DROP	
		{CmCloseCall,	STATE_CLOSING },					// INPUT_TAPI_CLOSECALL	
		{CmClose, 		STATE_CLOSING },					// INPUT_TAPI_CLOSE	
		{CmOffer, 		STATE_OFFERING },					// INPUT_LINE_CDACTIVE
		{CmErrorIdle, 	STATE_ERROR }						// INPUT_LINE_CONNEND

	},
	//	STATE_DIALING     
	{	{CmErrorInProgress,	STATE_ERROR },				// INPUT_TAPI_DIAL	
		{CmErrorInProgress,	STATE_ERROR },				// INPUT_TAPI_ANSWER	
		{CmEarlyDrop,			STATE_DROPPING },			// INPUT_TAPI_DROP	
		{CmCloseCall,			STATE_CLOSING },			// INPUT_TAPI_CLOSECALL	
		{CmClose,				STATE_CLOSING },			// INPUT_TAPI_CLOSE	
		{CmConnected,			STATE_CONNECTED },		// INPUT_LINE_CDACTIVE
		{CmEarlyConnend,		STATE_DISCONNECTED }		// INPUT_LINE_CONNEND

	},
	//	STATE_OFFERING    
	{	{CmErrorInProgress,	STATE_ERROR },				// INPUT_TAPI_DIAL	
		{CmAnswer,				STATE_CONNECTED },		// INPUT_TAPI_ANSWER	
		{CmReject,				STATE_DROPPED },			// INPUT_TAPI_DROP	
		{CmCloseCall,			STATE_CLOSING },			// INPUT_TAPI_CLOSECALL	
		{CmClose,				STATE_CLOSING },			// INPUT_TAPI_CLOSE	
		{CmErrorInProgress,	STATE_ERROR },				// INPUT_LINE_CDACTIVE
		{CmEarlyDisc,			STATE_DISCONNECTED }		// INPUT_LINE_CONNEND

	},
	//	STATE_CONNECTED   
	{	{CmErrorConn,			STATE_ERROR },				// INPUT_TAPI_DIAL	
		{CmErrorConn,			STATE_ERROR },				// INPUT_TAPI_ANSWER	
		{CmDrop,					STATE_DROPPING },			// INPUT_TAPI_DROP	
		{CmCloseCall,			STATE_CLOSING },			// INPUT_TAPI_CLOSECALL	
		{CmClose,				STATE_CLOSING },			// INPUT_TAPI_CLOSE	
		{CmErrorConn,			STATE_ERROR },				// INPUT_LINE_CDACTIVE
		{CmConnectedConnend,	STATE_DISCONNECTED }		// INPUT_LINE_CONNEND

	},
	//	STATE_DISCONNECTING     
	{	{CmErrorDisc,	STATE_ERROR },						// INPUT_TAPI_DIAL	
		{CmErrorDisc,	STATE_ERROR },						// INPUT_TAPI_ANSWER	
		{CmIgnore,		STATE_DROPPING },					// INPUT_TAPI_DROP	
		{CmCloseCall,	STATE_CLOSING },					// INPUT_TAPI_CLOSECALL	
		{CmClose,		STATE_CLOSING },					// INPUT_TAPI_CLOSE	
		{CmErrorDisc,	STATE_ERROR },						// INPUT_LINE_CDACTIVE
		{CmConnend,		STATE_DISCONNECTED }				// INPUT_LINE_CONNEND

	},
	//	STATE_DISCONNECTED
	{	{CmErrorIdle,	STATE_ERROR },						// INPUT_TAPI_DIAL	
		{CmErrorIdle,	STATE_ERROR },						// INPUT_TAPI_ANSWER	
		{CmDrop,		STATE_DROPPED },						// INPUT_TAPI_DROP	
		{CmCloseCall,	STATE_CLOSING },					// INPUT_TAPI_CLOSECALL	
		{CmClose,		STATE_CLOSING },					// INPUT_TAPI_CLOSE	
		{CmErrorIdle,	STATE_ERROR },						// INPUT_LINE_CDACTIVE
		{CmErrorIdle,	STATE_ERROR }						// INPUT_LINE_CONNEND
		
	},
	//	STATE_DROPPING    
	{	{CmErrorDisc,		STATE_ERROR },					// INPUT_TAPI_DIAL	
		{CmErrorDisc,		STATE_ERROR },					// INPUT_TAPI_ANSWER	
		{CmErrorDisc,		STATE_ERROR },					// INPUT_TAPI_DROP	
		{CmCloseCall,		STATE_CLOSING },				// INPUT_TAPI_CLOSECALL	
		{CmClose,			STATE_CLOSING },				// INPUT_TAPI_CLOSE	
		{CmErrorDisc,		STATE_ERROR },					// INPUT_LINE_CDACTIVE
		{CmConnend,			STATE_DROPPED }				// INPUT_LINE_CONNEND

	},
	//	STATE_DROPPED     
	{	{CmErrorIdle,		STATE_ERROR },					// INPUT_TAPI_DIAL	
		{CmErrorIdle,		STATE_ERROR },					// INPUT_TAPI_ANSWER	
		{CmErrorIdle,		STATE_ERROR },					// INPUT_TAPI_DROP	
		{CmCloseCall,		STATE_CLOSING },				// INPUT_TAPI_CLOSECALL	
		{CmClose,			STATE_CLOSING },				// INPUT_TAPI_CLOSE	
 		{CmErrorIdle,		STATE_ERROR },					// INPUT_LINE_CDACTIVE
		{CmErrorIdle,		STATE_ERROR }					// INPUT_LINE_CONNEND

	},
	// STATE_CLOSING      
	{	{CmErrorIdle,	STATE_CLOSING },					// INPUT_TAPI_DIAL	
		{CmErrorIdle,	STATE_CLOSING },					// INPUT_TAPI_ANSWER	
		{CmErrorIdle,	STATE_CLOSING },					// INPUT_TAPI_DROP	
		{CmCloseCall,	STATE_CLOSING },					// INPUT_TAPI_CLOSECALL	
		{CmClose,		STATE_CLOSING },					// INPUT_TAPI_CLOSE	
		{CmIgnore,		STATE_CLOSING },					// INPUT_LINE_CDACTIVE
		{CmIgnore,		STATE_CLOSING }					// INPUT_LINE_CONNEND

	},
	// STATE_ERROR      
	{	{CmIgnore,		STATE_ERROR },						// INPUT_TAPI_DIAL	
		{CmIgnore,		STATE_ERROR },						// INPUT_TAPI_ANSWER	
		{CmIgnore,		STATE_ERROR },						// INPUT_TAPI_DROP	
		{CmCloseCall,	STATE_CLOSING },					// INPUT_TAPI_CLOSECALL	
		{CmClose,		STATE_CLOSING },					// INPUT_TAPI_CLOSE	
		{CmIgnore,		STATE_ERROR },						// INPUT_LINE_CDACTIVE
		{CmIgnore,		STATE_ERROR }						// INPUT_LINE_CONNEND

	}
};


STATIC VOID 
PPPStateMachine( 
	PDIGI_PPP_CALL pCall, 
	INPUT      Input )
/*++

Description:

	Perform action and move to next state, based on current state and input stimuli.

Parameters:

	pCall
		Call affected

	Input
		Input stimuli

Return Value:

	None

--*/
{
	STATE State;
	// Lock call so that external activities (tx, rx and stats) won't happen during a 
	// state transition.
	CallAcquireLock( pCall );
	State = GetState( pCall );
	DigiDump( DIGISTATE, ( "PPPStateMachine: pCall 0x%x, State %s, Input %s, NewState %s\n", pCall, StateStrings[State], InputStrings[Input], StateStrings[StateTable[State][Input].NewState] ) );

	if ( StateTable[State][Input].NewState == STATE_ERROR && State != STATE_ERROR )
	{
		// Only print message if we're just entering the error state
		DigiDump( DIGIERROR, ("PPPStateMachine: pCall 0x%x, Bad Input %s, CurrentState %s!!!\n", pCall, InputStrings[Input], StateStrings[State] ) );
	}

	StateTable[GetState(pCall)][Input].Action( pCall );
	SetState( pCall, StateTable[GetState(pCall)][Input].NewState );
	CallReleaseLock( pCall );
}


///////////////////////////////////////////////////////////////////////////////
//
// External Api
// 
// The state machine table, proc, and helper routines are all internal to the 
// pppcall module.  They are accessed only by the following routines which
// define the api exposed outside this module.  This keeps all state handling
// localized to this file.
//

#define IsDigit( ch )	( (ch) >= '0' && (ch) <= '9' )

NTSTATUS 
PPPCallDial( 
	IN PDIGI_PPP_ADAPTER pAdapter,
	IN HDRV_LINE         hdLine,
	IN HTAPI_CALL        htCall,
	IN ULONG         		MediaMode,
	IN ULONG         		BearerMode,
	IN PCHAR             pDestAddr,
	IN ULONG             cbDestAddr,
	OUT PDIGI_PPP_CALL   *ppCall )
/*++

Description

	Called by Ndis via our TapiMakeCall routine.  Alloc a call object, initiate the 
	call.

--*/
{
	NTSTATUS Status;
	PDIGI_PPP_CALL pCall;
	ASSERT( pDestAddr != NULL );
	ASSERT( cbDestAddr != 0 );
	ASSERT( cbDestAddr < MAXUSHORT );
	ASSERT( ppCall != NULL );

	pCall = pAdapter->pLines[hdLine].pCall;
	if ( pCall == NULL )
	{
		Status = NDIS_STATUS_RESOURCES;
		goto Exit;
	}

	pCall->htCall = htCall;
	pCall->MediaMode  = MediaMode;
	pCall->BearerMode = BearerMode;

	{
		STRING Address;	// Destination addr, as passed in from tapi.
		RtlInitString( &Address, pDestAddr );

		// Tapi likes to prepend 'T' or 'P' prefixes.  Strip them off
		while ( Address.Length && !IsDigit( Address.Buffer[0] ) )
		{
			Address.Buffer++;
			Address.Length--;
		}

		RtlCopyString( &pCall->RemoteAddress, &Address );
	}
   
	PPPStateMachine( pCall, INPUT_TAPI_DIAL );

	*ppCall = pCall;
	Status = STATUS_PENDING;	// Request completed by CallMgrThread

Exit:
	return Status;
}


NTSTATUS
PPPCallOffer( 
	PDIGI_PPP_CALL pCall )
/*++

Description

	Called by listen stream handler when an incoming call arrives (via XN_CONNECT 
	message).  Indicate (offer) the call up to Tapi.
--*/
{
	PPPStateMachine( pCall, INPUT_LINE_CDACTIVE );
	return STATUS_SUCCESS;
}


NTSTATUS
PPPCallAnswer( 
	PDIGI_PPP_CALL pCall )
/*++

Description

	Called by Tapi to answer an incoming call we previously offered.

--*/
{
	PPPStateMachine( pCall, INPUT_TAPI_ANSWER );
	return STATUS_PENDING;	// Completed by CallMgrThread 
}

NTSTATUS
PPPCallDrop( 
	PDIGI_PPP_CALL pCall )
/*++

Description

	Called by Tapi to Drop a call currently in progress.  Hangup the call by sending out
	an XN_DISCONN request, as necessary.  

--*/
{
	// Called by Ndis via our TapiDrop routine to drop a call.
	PPPStateMachine( pCall, INPUT_TAPI_DROP );
	return STATUS_SUCCESS;
}

NTSTATUS 
PPPCallCloseCall( 
	PDIGI_PPP_CALL pCall )
/*++

Description

	Called by Tapi to Close a call.  Release all resources acquired for the call.

--*/
{
	// Called by Ndis via our TapiCloseCall routine to close down a call and
	// free all resources used by that call instance.
	PPPStateMachine( pCall, INPUT_TAPI_CLOSECALL );
	return STATUS_PENDING;	// Completed by CallMgrThread 
}


NTSTATUS 
PPPCallClose( 
	PDIGI_PPP_CALL pCall )
/*++

Description

	Called by Tapi to Close a *line* on which a call still exists.

--*/
{
	// Called by Ndis via our TapiClose routine to close down a call and
	// free all resources used by that call instance.
	PPPStateMachine( pCall, INPUT_TAPI_CLOSE );
	return STATUS_PENDING;	// Completed by CallMgrThread 
}


// TODO - Temporary Hack
// Clean this mess up - Parag

typedef struct  SEND_PACK
{
PNDIS_WAN_PACKET	pPacket;
PDIGI_PPP_CALL		pCall;
} SEND_PACK, *PSEND_PACK;



VOID
PPPSendComplete( 
	IN HANDLE   hOwner,
	IN HANDLE   Context,
	IN PVOID    pControl,
	IN ULONG    cbControl,
	IN PVOID    pData,
	IN ULONG    cbData,
	IN NTSTATUS IoStatus )
/*++

Description:

	Called when a previous DsPutMsgEx packet has been sent down to the card.  Free 
	resources associated with the request, notify Ndis the packet has been sent.

--*/
{
	PSEND_PACK pSendPack = Context;
	PDIGI_PPP_CALL CONST pCall = pSendPack->pCall;
	PNDIS_WAN_PACKET CONST pPacket = pSendPack->pPacket;

	DigiFreeMem(pSendPack);


	ASSERT( pPacket != NULL );

	// Sanity check
	ASSERT( pData == pPacket->CurrentBuffer );
	ASSERT( cbData == pPacket->CurrentLength );

	DigiDump( DIGITX, ( "PPPCallSendComplete: pCall 0x%x, pPacket 0x%x, Status 0x%x\n",
	          pCall, pPacket, IoStatus ) );

	PPPQueueSendComplete( pCall, pPacket, IoStatus );
}


NTSTATUS 
PPPCallSend( 
	PDIGI_PPP_CALL   pCall, 
	PNDIS_WAN_PACKET pPacket )
/*++

Description

	Called by our miniport send dispatch routine.  Verify call state and send data if 
	appropriate.

--*/
{
	NTSTATUS Status;
	PSEND_PACK pSendPack = DigiAllocMem(NonPagedPool, sizeof *pSendPack);

	if (pSendPack == NULL)
	{
		Status = NDIS_STATUS_FAILURE;
		goto Exit;
	}
		
	CallAcquireLock( pCall );
	if ( GetState( pCall ) == STATE_CONNECTED )
	{
		struct wan_msg Msg = {0};
		Msg.wan_type = WAN_DAT;

		DigiDump( DIGITXDATA|DIGITX, ( "PPPCallSend: pCall 0x%08x, pPacket 0x%08x, Length 0x%08x\n", 
		          pCall, pPacket, pPacket->CurrentLength ) );
		DigiDumpData( DIGITXDATA, pPacket->CurrentBuffer, pPacket->CurrentLength );
		
		pSendPack->pCall = pCall;
		pSendPack->pPacket = pPacket;

		Status = DsPutMsgEx( pCall->hStream, &Msg, sizeof Msg, pPacket->CurrentBuffer, pPacket->CurrentLength, 
		                     pSendPack, DS_PRIORITY_LOW, PPPSendComplete );

		DigiDump( DIGITX, ( "PPPCallSend: hStream 0x%x, Status 0x%x\n", pCall->hStream, Status ) );
		if ( Status == STATUS_SUCCESS )
			Status = STATUS_PENDING;
	}
	else
		Status = NDIS_STATUS_FAILURE;

	CallReleaseLock( pCall );

Exit:
	return Status;
}


///////////////////////////////////////////////////////////////////////////////
//
// Incoming Message Handlers
//

VOID
PPPCallOnCDActive( 
	IN PVOID pOwner )
/*++

Description

	All calls.  Called when a carrier is detected .
--*/
{
	PDIGI_PPP_CALL pCall = pOwner;
	ASSERT( MmIsAddressValid( pCall ) );
	ASSERT( pCall->Type == DIGI_PPP_TYPE_CALL );
	DigiDump( DIGICALL, ( "PPP!CallOnCDActive: pCall 0x%x\n", pCall ) );
	PPPStateMachine( pCall, INPUT_LINE_CDACTIVE );
}


VOID
PPPCallOnConnend( 
	IN PVOID pOwner)

/*++

Description

	Called when an XN_CONNEND message arrives from the board, indicating a call has been
	completely disconnected.  Received regardless of the originator of the disconnect 
	(i.e. whether we or the remote side initiated the disconnect).

--*/
{
	PDIGI_PPP_CALL pCall = pOwner;
	INPUT Input;
	ASSERT( MmIsAddressValid( pCall ) );
	ASSERT( pCall->Type == DIGI_PPP_TYPE_CALL );
	DigiDump( DIGICALL, ( "PPP!CallOnConnend: pCall 0x%x\n", pCall ) );

	PPPStateMachine( pCall, INPUT_LINE_CONNEND );
}


VOID
PPPCallOnData(
	IN PVOID pOwner,
	IN PVOID pData, 
	IN ULONG cbData )
/*++

Description:

	Called to process an incoming data packet.

--*/
{
	STATE State;
	PDIGI_PPP_CALL pCall = pOwner;
	ASSERT( MmIsAddressValid( pCall ) );
	ASSERT( pCall->Type == DIGI_PPP_TYPE_CALL );

	DigiDump( DIGIRXDATA|DIGIRX, ( "PPPCallReceive: pCall 0x%x, pData 0x%x, cbData 0x%x\n", pCall, pData, cbData ) );
	DigiDumpData( DIGIRXDATA, pData, cbData );

	State = GetState( pCall );
	switch ( State )
	{
		case STATE_CONNECTED:
			PPPQueueIndicateReceive( pCall, pData, cbData );
			break;

		case STATE_IDLE:
			// If we're in an idle state and receive an incoming packet - this might be
			// a new incoming call.  Check the data for an LCP Config Request packet.  If
			// we get a match - this really is a new call, Offer it up to Tapi.

			// Is TAPI listening for incoming calls?  If not, don't indicate it.
			if ( pCall->pAdapter->pLines[pCall->hdLine].Listen )
			{
				// Is this an LCP Config Request Packet?
				UCHAR LcpConfigRequestPacket[] = {0xFF, 0x03, 0xC0, 0x21, 0x01};
				UCHAR LcpConfigRequestPacketNDtest[] = {0xFF, 0x03, 0x12, 0x34, 0x56};
				ULONG cbLcpPacket = sizeof LcpConfigRequestPacket;
				DigiDump( DIGICALL, ( "PPP!CallOnData: pData 0x%x\n", pData ) );

				if ( cbData > cbLcpPacket && 
				     ( ( RtlCompareMemory( &LcpConfigRequestPacket, pData, cbLcpPacket ) == cbLcpPacket )
						||	( RtlCompareMemory( &LcpConfigRequestPacketNDtest, pData, cbLcpPacket ) == cbLcpPacket )))
					PPPCallOffer( pCall );
				RxEngineFreeData( pData );
			}
			break;
			
		default:
			RxEngineFreeData( pData );
			break;
	}
}
