/*++
*******************************************************************************
* 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:

   isdncall.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
	IsdnCall* routines: Public routines exposed to the world
	CallMgrThread:      Private thread used to synchronize opens and closes

Author:

   Michael Zarns

--*/

#include "digiisdn.h"

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

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

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

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

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

#define QueueWanLineUp( pCall )	\
	(void)IsdnQueueWanLineup( pCall, pCall->htCall, pCall->ConnType < ISDN_CONN_D64 ? 56000 : 64000 )

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

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


STATIC ISDN_EVENT_HANDLERS CallMsgHandlers;


STATIC INLINE NTSTATUS
FindListeningLine( 
	PISDN_ADAPTER pAdapter,
	PULONG        pLine )
/*++

Description

	Search the pLines array of the adapter looking for an available, listening line.
	Assumes adapter lock is held!

--*/
{
	NTSTATUS Status = NDIS_STATUS_FAILURE;
	ULONG i;
	ULONG Line = pAdapter->nLines;
	for ( i = 0; i < pAdapter->nLines; i++ )
	{
		if ( pAdapter->pLines[i].Listen && ( pAdapter->pLines[i].pCall == NULL ) )
		{
			Line = i;
			break;
		}
	}
	if ( Line < pAdapter->nLines )
	{
		Status = STATUS_SUCCESS;
		*pLine = Line;
	}
	return Status;
}


PISDN_CALL 
IsdnCallConstruct( 
	IN PISDN_ADAPTER pAdapter,
	IN ULONG_PTR     iLine )
/*++

Description 

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

--*/
{
	PISDN_CALL pCall;

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

		pCall->CalledID.Buffer = pCall->CalledIDBuffer;
		ASSERT( sizeof pCall->CalledIDBuffer > ISDN_NUMBER_SIZE );
		pCall->CalledID.MaximumLength = sizeof pCall->CalledIDBuffer - 1;

		pCall->MaxSendFrameSize    = ISDN_MAX_FRAME_SIZE;
		pCall->MaxRecvFrameSize    = ISDN_MAX_FRAME_SIZE;
		pCall->HeaderPadding       = ISDN_HEADER_PADDING;
		pCall->TailPadding         = ISDN_TAIL_PADDING;
		pCall->SendFramingBits     = ISDN_FRAMING_BITS;
		pCall->RecvFramingBits     = ISDN_FRAMING_BITS;
		pCall->SendCompressionBits = ISDN_COMP_BITS;
		pCall->RecvCompressionBits = ISDN_COMP_BITS;

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

		AdapterAcquireLock( pAdapter );
		{
			ULONG Line = (ULONG)iLine;
			NTSTATUS Status = STATUS_SUCCESS;
			if ( Line == ISDN_LINE_ANY )
				Status = FindListeningLine( pAdapter, &Line );

			if ( NT_ERROR( Status ) || 
			     Line >= pAdapter->nLines || 
			     pAdapter->pLines[Line].pCall != NULL )
			{
				DigiFreeMem( pCall );
				pCall = NULL;
			}
			else
			{
				pAdapter->pLines[Line].pCall = pCall;
				pCall->hdLine = Line;
			}
		}
		AdapterReleaseLock( pAdapter );
	}
	return pCall;
}

VOID
IsdnCallDestruct( 
	IN PISDN_CALL pCall )
/*++

Description 

	Free the specified call object.

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

	// Upper edge api's assert that the type field is valid.  Stash the type field, 
	// and indicate we've freed it.
	pCall->TypeBeforeDestruct = pCall->Type;
	pCall->Type = ISDN_TYPE_FREED;  

	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 PISDN_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 PISDN_ADAPTER       pAdapter, 
	OUT PISDN_CALL         *ppCall, 
	OUT CALL_THREAD_ACTION *pAction )
{
	BOOLEAN fFound;
	AdapterAcquireLock( pAdapter );
	if ( IsListEmpty( &pAdapter->Mgr.Queue ) )
		fFound = FALSE;
	else
	{
		PISDN_CALL pCall;
		PLIST_ENTRY pEntry = RemoveHeadList( &pAdapter->Mgr.Queue );
		pCall = CONTAINING_RECORD( pEntry, ISDN_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;

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

Description

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

--*/
{
	PISDN_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 )
	{
		PISDN_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:
				{
					ULONG Error;
					NTSTATUS Status = DsOpen( pAdapter->hDevice, XnDeviceName(pAdapter), 
					                          pCall, NULL, 0, &pCall->hStream, &Error );
					
					if ( NT_ERROR( Status ) )
					{
						DigiDump( DIGIERROR, 
						          ("ISDN: pCall 0x%x Open failed, Status 0x%x, Error 0x%x\n",
						          pCall, Status, Error ) );
						IsdnQueueQueryComplete( pAdapter, Status );
					}
					else
					{
						IsdnQueueQueryComplete( pAdapter, Status );
						QueueTapiDialing( pCall );
						ASSERT( pCall->MaxSendFrameSize == pCall->MaxRecvFrameSize );
						XnConnect( pCall->pAdapter, pCall, pCall->hStream, pCall->pAdapter->Snid, pCall->MaxSendFrameSize, pCall->ConnType, &pCall->CalledID );
						RxEngineStart( &pCall->RxEngine, pCall->pAdapter->hMiniport, pCall->hStream, pCall, &CallMsgHandlers, pCall->pAdapter->XnDispatch.XnInterpreter, XnCbControl(pAdapter), pCall->MaxRecvFrameSize );
					}
					break;
				}

				case CALL_THREAD_ANSWER:
				{
					ULONG Error;
					NTSTATUS Status = DsOpen( pAdapter->hDevice, XnDeviceName(pAdapter), 
					                          pCall, NULL, 0, &pCall->hStream, &Error );
					
					if ( NT_ERROR( Status ) )
					{
						DigiDump( DIGIERROR, 
						          ("ISDN: pCall 0x%x Open failed, Status 0x%x, Error 0x%x\n",
						          pCall, Status, Error ) );
						IsdnQueueSetComplete( pAdapter, Status );
					}
					else
					{
						IsdnQueueSetComplete( pAdapter, Status );
						XnConnack( pCall->pAdapter, pCall, pCall->hStream, pCall->Incoming.ChanIndex, pCall->Incoming.Channel );
						RxEngineStart( &pCall->RxEngine, pCall->pAdapter->hMiniport, pCall->hStream, pCall, &CallMsgHandlers, pCall->pAdapter->XnDispatch.XnInterpreter, XnCbControl(pAdapter), pCall->MaxRecvFrameSize );
					}
					break;
				}

				case CALL_THREAD_CLOSECALL:
				case CALL_THREAD_CLOSE:

					RxEngineStop( &pCall->RxEngine );
					if ( pCall->hStream )
					{
						(void)DsPurge( pCall->hStream, DS_PURGE_ALL );
						(void)DsClose( pCall->hStream );
						pCall->hStream = NULL;
					}
					else
					{
						DigiDump( DIGIWARN, ("Isdn!CloseCall: Expected a stream on %s, pCall 0x%x\n", Action==CALL_THREAD_CLOSECALL?"CloseCall":"Close", pCall) );
					}

					// TODO: Remove from the offering list on answer and reject too!
					// Doing it here is a catchall, but leaves entries on the list even
					// after they've been accepted and are in use.  This only slows down
					// the list search (isdnlisten.c,FindOfferedCall), no other errors.
					AdapterAcquireLock( pAdapter );
					RemoveEntryList( &pCall->OfferingListEntry );
					AdapterReleaseLock( pAdapter );

					IsdnCallDestruct( pCall );
					IsdnQueueSetComplete( pAdapter, STATUS_SUCCESS );
					break;

				default:
					
					DigiDump( DIGIERROR, ( "ISDN 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 )
			{
				RxEngineStop( &pAdapter->pLines[i].pCall->RxEngine );
				(void)DsClose( pAdapter->pLines[i].pCall->hStream );
				pAdapter->pLines[i].pCall->hStream = NULL;
				IsdnCallDestruct( pAdapter->pLines[i].pCall );
			}
		}
	}

	(void)PsTerminateSystemThread( STATUS_SUCCESS );
}


NTSTATUS
IsdnCallMgrThreadStart( 
	IN PISDN_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
IsdnCallMgrThreadStop( 
	IN PISDN_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( 
	PISDN_CALL pCall )
/*++

Description:

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

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

STATIC VOID
CmDial( 
	PISDN_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( 
	PISDN_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->CalledID );
}


STATIC VOID
CmAnswer( 
	PISDN_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 );
}

STATIC VOID
CmReject(
	PISDN_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 );
}

STATIC VOID
CmConnected( 
	PISDN_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.

--*/
{
	QueueTapiConnected( pCall );
}

STATIC VOID
CmEarlyDrop( 
	PISDN_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 );
}

STATIC VOID
CmLinedown( 
	PISDN_CALL pCall )
/*++

Description:

	Notify NdisWan that we're dropping the call.

--*/
{
	QueueWanLineDown( pCall );
}

STATIC VOID
CmDrop( 
	PISDN_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.

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

STATIC VOID
CmDisc( 
	PISDN_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 );
}

STATIC VOID
CmEarlyConnend( 
	PISDN_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( 
	PISDN_CALL pCall )
/*++

Description:

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

--*/
{
	QueueTapiIdle( pCall );
}


STATIC VOID
CmConnectedConnend( 
	PISDN_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( 
	PISDN_CALL pCall )
/*++

Description:

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

--*/
{
	UNREFERENCED_PARAMETER( pCall );
}

STATIC VOID
CmErrorConn( 
	PISDN_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 );
	QueueTapiIdle( pCall );
}

STATIC VOID
CmErrorDisc( 
	PISDN_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( 
	PISDN_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( 
	PISDN_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( 
	PISDN_CALL pCall )
/*++

Description:

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

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


STATIC VOID
CmClose( 
	PISDN_CALL pCall )
/*++

Description:

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

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


//
// 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).  

// Isdn 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_CONNECTING,		// XN_CONNECT and XN_CONNACK exchanged, waiting for XN_CACTIVE
	STATE_CUT_THROUGH,	// Outbound only, XN_CACTIVE received, waiting for XN_CONNACK
	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.  Isdn 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",
	"CONNECTING",
	"CUT_THROUGH",
	"CONNECTED",
	"DISCONNECTING",
	"DISCONNECTED",
	"DROPPING",
	"DROPPED",
	"CLOSING",
	"ERROR"
};
#endif

// Isdn 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_INCOMING,	// XN_CONNECT received on listen stream.  Offer call to Tapi.
	INPUT_LINE_CONNACK,	// Ack that outbound call has been accepted by remote station
	INPUT_LINE_CACTIVE,	// Channel active.  Data may be sent and received.
	INPUT_LINE_DISCONN,	// Remote has initiated disconnect
	INPUT_LINE_CONNEND,	// Disconnect is complete
	INPUT_LINE_CONNEND_FALLBACK, // Connend, retry the outbound call at a lower speed
	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_INCOMING",
	"LINE_CONNACK",
	"LINE_CACTIVE",
	"LINE_DISCONN",
	"LINE_CONNEND",
	"LINE_CONNEND_FALLBACK"
};
#endif

STATIC INLINE VOID
SetState( 
	PISDN_CALL pCall, 
	STATE NewState )
{
	UCHAR i;

	// Keep state history for debugging purposes.  Advance all states forward and move
	// current state (pCall->State) into history array.

	for ( i = ARRAY_SIZE(pCall->StateHistory)-1; i > 0; i-- )
		pCall->StateHistory[i] = pCall->StateHistory[i-1];

	pCall->StateHistory[0] = pCall->State;
	pCall->State = NewState;
}

STATIC INLINE STATE
GetState( 
	PISDN_CALL const pCall )
{
	if ( pCall->State >= STATE_COUNT )
	{
		DigiDump( DIGIERROR|DIGIBREAK, ( "**** Invalid Call object State, pCall 0x%x, State 0x%x ****\n", pCall, pCall->State ) );
		return STATE_ERROR;
	}
	return pCall->State;
}


// 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_INCOMING
		{CmErrorIdle, 	STATE_ERROR },						// INPUT_LINE_CONNACK
		{CmErrorIdle, 	STATE_ERROR },						// INPUT_LINE_CACTIVE
		{CmErrorIdle, 	STATE_ERROR },						// INPUT_LINE_DISCONN
		{CmErrorIdle, 	STATE_ERROR },						// INPUT_LINE_CONNEND
		{CmErrorIdle, 	STATE_ERROR }						// INPUT_LINE_CONNEND_FALLBACK
	},
	//	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	
		{CmErrorInProgress,	STATE_ERROR },				// INPUT_LINE_INCOMING
		{CmIgnore,				STATE_CONNECTING },		// INPUT_LINE_CONNACK
		{CmIgnore,				STATE_CUT_THROUGH },		// INPUT_LINE_CACTIVE
		{CmDisc,					STATE_DISCONNECTING },	// INPUT_LINE_DISCONN
		{CmEarlyConnend,		STATE_DISCONNECTED },	// INPUT_LINE_CONNEND
		{CmRedial,				STATE_DIALING }			// INPUT_LINE_CONNEND_FALLBACK
	},
	//	STATE_OFFERING    
	{	{CmErrorInProgress,	STATE_ERROR },				// INPUT_TAPI_DIAL	
		{CmAnswer,				STATE_CONNECTING },		// 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_INCOMING
		{CmErrorInProgress,	STATE_ERROR },				// INPUT_LINE_CONNACK
		{CmErrorInProgress,	STATE_ERROR },				// INPUT_LINE_CACTIVE
		{CmErrorInProgress,	STATE_ERROR },				// INPUT_LINE_DISCONN
		{CmDisc,					STATE_DISCONNECTED },	// INPUT_LINE_CONNEND
		{CmErrorInProgress,	STATE_DISCONNECTED }		// INPUT_LINE_CONNEND_FALLBACK
	},
	//	STATE_CONNECTING  
	{	{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	
		{CmErrorInProgress,	STATE_ERROR },				// INPUT_LINE_INCOMING
		{CmErrorInProgress,	STATE_ERROR },				// INPUT_LINE_CONNACK
		{CmConnected,			STATE_CONNECTED },		// INPUT_LINE_CACTIVE
		{CmDisc,					STATE_DISCONNECTING },	// INPUT_LINE_DISCONN
		{CmEarlyConnend,		STATE_DISCONNECTED },	// INPUT_LINE_CONNEND
		{CmEarlyConnend,		STATE_DISCONNECTED }		// INPUT_LINE_CONNEND_FALLBACK
	},
	//	STATE_CUT_THROUGH 
	{	{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	
		{CmErrorInProgress,	STATE_ERROR },				// INPUT_LINE_INCOMING
		{CmConnected,			STATE_CONNECTED },		// INPUT_LINE_CONNACK
		{CmErrorInProgress,	STATE_ERROR },				// INPUT_LINE_CACTIVE
		{CmErrorInProgress,	STATE_DISCONNECTING },	// INPUT_LINE_DISCONN
		{CmEarlyConnend,		STATE_DISCONNECTED },	// INPUT_LINE_CONNEND
		{CmRedial,				STATE_DIALING }			// INPUT_LINE_CONNEND_FALLBACK
	},
	//	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_INCOMING
		{CmErrorConn,			STATE_ERROR },				// INPUT_LINE_CONNACK
		{CmErrorConn,			STATE_ERROR },				// INPUT_LINE_CACTIVE
		{CmDisc,					STATE_DISCONNECTING },	// INPUT_LINE_DISCONN
		{CmConnectedConnend,	STATE_DISCONNECTED },	// INPUT_LINE_CONNEND
		{CmConnectedConnend,	STATE_DISCONNECTED }		// INPUT_LINE_CONNEND_FALLBACK
	},
	//	STATE_DISCONNECTING     
	{	{CmErrorDisc,	STATE_ERROR },						// INPUT_TAPI_DIAL	
		{CmErrorDisc,	STATE_ERROR },						// INPUT_TAPI_ANSWER	
		{CmLinedown,	STATE_DROPPING },					// INPUT_TAPI_DROP	
		{CmCloseCall,	STATE_CLOSING },					// INPUT_TAPI_CLOSECALL	
		{CmClose,		STATE_CLOSING },					// INPUT_TAPI_CLOSE	
		{CmErrorDisc,	STATE_ERROR },						// INPUT_LINE_INCOMING
		{CmErrorDisc,	STATE_ERROR },						// INPUT_LINE_CONNACK
		{CmErrorDisc,	STATE_ERROR },						// INPUT_LINE_CACTIVE
		{CmErrorDisc,	STATE_ERROR },						// INPUT_LINE_DISCONN
		{CmConnend,		STATE_DISCONNECTED },			// INPUT_LINE_CONNEND
		{CmConnend,		STATE_DISCONNECTED }				// INPUT_LINE_CONNEND_FALLBACK
	},
	//	STATE_DISCONNECTED
	{	{CmErrorIdle,	STATE_ERROR },						// INPUT_TAPI_DIAL	
		{CmErrorIdle,	STATE_ERROR },						// INPUT_TAPI_ANSWER	
		{CmLinedown,	STATE_DROPPED },					// INPUT_TAPI_DROP	
		{CmCloseCall,	STATE_CLOSING },					// INPUT_TAPI_CLOSECALL	
		{CmClose,		STATE_CLOSING },					// INPUT_TAPI_CLOSE	
		{CmErrorIdle,	STATE_ERROR },						// INPUT_LINE_INCOMING
		{CmErrorIdle,	STATE_ERROR },						// INPUT_LINE_CONNACK
		{CmErrorIdle,	STATE_ERROR },						// INPUT_LINE_CACTIVE
		{CmErrorIdle,	STATE_ERROR },						// INPUT_LINE_DISCONN
		{CmErrorIdle,	STATE_ERROR },						// INPUT_LINE_CONNEND
		{CmErrorIdle,	STATE_ERROR }						// INPUT_LINE_CONNEND_FALLBACK
	},
	//	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	
		{CmErrorIdle,		STATE_ERROR },					// INPUT_LINE_INCOMING
		{CmErrorIdle,		STATE_ERROR },					// INPUT_LINE_CONNACK
		{CmErrorDisc,		STATE_ERROR },					// INPUT_LINE_CACTIVE
		{CmIgnore,			STATE_DROPPING },				// INPUT_LINE_DISCONN
		{CmConnend,			STATE_DROPPED },				// INPUT_LINE_CONNEND
		{CmConnend,			STATE_DROPPED }				// INPUT_LINE_CONNEND_FALLBACK
	},
	//	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_INCOMING
		{CmErrorIdle,		STATE_ERROR },					// INPUT_LINE_CONNACK
		{CmErrorIdle,		STATE_ERROR },					// INPUT_LINE_CACTIVE
		{CmErrorIdle,		STATE_ERROR },					// INPUT_LINE_DISCONN
		{CmErrorIdle,		STATE_ERROR },					// INPUT_LINE_CONNEND
		{CmErrorIdle,		STATE_ERROR }					// INPUT_LINE_CONNEND_FALLBACK
	},
	// STATE_CLOSING      
	{	{CmErrorIdle,	STATE_CLOSING },					// INPUT_TAPI_DIAL	
		{CmErrorIdle,	STATE_CLOSING },					// INPUT_TAPI_ANSWER	
		{CmErrorIdle,	STATE_CLOSING },					// INPUT_TAPI_DROP	
		{CmIgnore,		STATE_CLOSING },					// INPUT_TAPI_CLOSECALL	
		{CmIgnore,		STATE_CLOSING },					// INPUT_TAPI_CLOSE	
		{CmIgnore,		STATE_CLOSING },					// INPUT_LINE_INCOMING
		{CmIgnore,		STATE_CLOSING },					// INPUT_LINE_CONNACK
		{CmIgnore,		STATE_CLOSING },					// INPUT_LINE_CACTIVE
		{CmIgnore,		STATE_CLOSING },					// INPUT_LINE_DISCONN
		{CmIgnore,		STATE_CLOSING },					// INPUT_LINE_CONNEND
		{CmIgnore,		STATE_CLOSING }					// INPUT_LINE_CONNEND_FALLBACK
	},
	// STATE_ERROR      
	{	{CmIgnore,		STATE_ERROR },						// INPUT_TAPI_DIAL	
		{CmIgnore,		STATE_ERROR },						// INPUT_TAPI_ANSWER	
		{CmLinedown,	STATE_ERROR },						// INPUT_TAPI_DROP	
		{CmCloseCall,	STATE_CLOSING },					// INPUT_TAPI_CLOSECALL	
		{CmClose,		STATE_CLOSING },					// INPUT_TAPI_CLOSE	
		{CmIgnore,		STATE_ERROR },						// INPUT_LINE_INCOMING
		{CmIgnore,		STATE_ERROR },						// INPUT_LINE_CONNACK
		{CmIgnore,		STATE_ERROR },						// INPUT_LINE_CACTIVE
		{CmIgnore,		STATE_ERROR },						// INPUT_LINE_DISCONN
		{CmIgnore,		STATE_ERROR },						// INPUT_LINE_CONNEND
		{CmIgnore,		STATE_ERROR }						// INPUT_LINE_CONNEND_FALLBACK
	}
};


STATIC VOID 
IsdnStateMachine( 
	PISDN_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, ( "IsdnStateMachine: 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, ("IsdnStateMachine: 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 );
}


// Macros to help parse dial string.  
//    IsDigit checks for valid phone number digits
//    IsSpecial checks for other phone buttons
//    IsDialable defines what chars are acceptable in a dialable address
//    IsDelim checks for the deliminator between address and subaddress.
//    *** Users might need to dial using subaddresses, if so, they seperate the
//    *** address from the subaddress using one of these digits.  Defined in the 
//    *** TAPI spec.

#define IsDigit( ch )		( (ch) >= '0' && (ch) <= '9' )
#define IsSpecial( ch )		( (ch) == '*' || (ch) == '#' )
#define IsDialable( ch )	( IsDigit(ch) || IsSpecial(ch) )
#define IsDelim( ch )		( (ch) == '|' || (ch) == 's' || (ch) == 'S' )


STATIC VOID
ParseDialString( 
	IN PCHAR       pDialString,
	IN ULONG       cbDialString,
	IN OUT PSTRING pAddress,
	IN OUT PSTRING pSubAddress )
{
	ULONG cb = cbDialString+1;	// Add null terminator
	PCHAR pAddrBuf = NULL;
	PCHAR pSubAddrBuf = NULL;

	// Alloc temp buffers for both strings
	pAddrBuf = DigiAllocMem( NonPagedPool, cb );
	pSubAddrBuf = DigiAllocMem( NonPagedPool, cb );
	if ( pAddrBuf && pSubAddrBuf )
	{
		ULONG i;
		PCHAR pOut;
		BOOLEAN fSubAddr;

		RtlZeroMemory( pAddrBuf, cb );
		RtlZeroMemory( pSubAddrBuf, cb );

		for ( i = 0, pOut = pAddrBuf, fSubAddr = FALSE; i < cbDialString; i++ )
		{
			if ( IsDialable( pDialString[i] ) )
			{
				*pOut++ = pDialString[i];
			}
			else if ( IsDelim( pDialString[i] ) )
			{
				// Found a delim char.  If we're still writing the address string, 
				// start filling in the SubAddress string.  If we were already doing the
				// subaddress string, simply stop.
				if ( !fSubAddr )
				{
					fSubAddr = TRUE;
					pOut = pSubAddrBuf;
				}
				else
					break;
			}
			else
			{
				// Otherwise, it's a non-dialable digit, ignore it.
				continue;
			}
		}

		if ( pAddress && pAddrBuf[0] )
		{
			STRING Source;
			RtlInitString( &Source, pAddrBuf );
			RtlCopyString( pAddress, &Source );
		}

		if ( pSubAddress && pSubAddrBuf[0] )
		{
			STRING Source;
			RtlInitString( &Source, pSubAddrBuf );
			RtlCopyString( pSubAddress, &Source );
		}
	}

	if ( pAddrBuf )
		DigiFreeMem( pAddrBuf );
	if ( pSubAddrBuf )
		DigiFreeMem( pSubAddrBuf );
}



///////////////////////////////////////////////////////////////////////////////
//
// External Api
// 
// The state machine table, proc, and helper routines are all internal to the 
// isdncall 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.
//

NTSTATUS 
IsdnCallDial( 
	IN PISDN_ADAPTER pAdapter,
	IN HDRV_LINE     hdLine,
	IN HTAPI_CALL    htCall,
	IN ULONG         MediaMode,
	IN ULONG         BearerMode,
	IN ULONG         MinRate,
	IN ULONG         MaxRate,
	IN PCHAR         pDestAddr,
	IN ULONG         cbDestAddr,
	OUT PISDN_CALL   *ppCall )
/*++

Description

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

--*/
{
	NTSTATUS Status;
	PISDN_CALL pCall;
	ASSERT( MediaMode != 0 );
	ASSERT( BearerMode != 0 );
	ASSERT( MinRate != 0 );
	ASSERT( MaxRate != 0 );
	ASSERT( MinRate <= MaxRate );
	ASSERT( pDestAddr != NULL );
	ASSERT( cbDestAddr != 0 );
	ASSERT( cbDestAddr < MAXUSHORT );
	ASSERT( ppCall != NULL );

	pCall = IsdnCallConstruct( pAdapter, hdLine );

	if ( pCall == NULL )
	{
		Status = NDIS_STATUS_RESOURCES;
		goto Exit;
	}

	pCall->htCall     = htCall;
	pCall->MediaMode  = MediaMode;
	pCall->BearerMode = BearerMode;
	pCall->RateMin    = MinRate;
	pCall->RateMax    = MaxRate;
	pCall->ConnType = BearerMode == LINEBEARERMODE_VOICE ? ISDN_CONN_TOLLSAVER :
		               MaxRate < 64000 ? ISDN_CONN_D56 : ISDN_CONN_D64;

	ParseDialString( pDestAddr, cbDestAddr, &pCall->CalledID, NULL );
	IsdnStateMachine( pCall, INPUT_TAPI_DIAL );

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

Exit:
	return Status;
}


NTSTATUS
IsdnCallOffer( 
	PISDN_CALL pCall )
/*++

Description

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


NTSTATUS
IsdnCallAnswer( 
	PISDN_CALL pCall )
/*++

Description

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

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

NTSTATUS
IsdnCallDrop( 
	PISDN_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.
	IsdnStateMachine( pCall, INPUT_TAPI_DROP );
	return STATUS_SUCCESS;
}

NTSTATUS 
IsdnCallCloseCall( 
	PISDN_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.
	IsdnStateMachine( pCall, INPUT_TAPI_CLOSECALL );
	return STATUS_PENDING;	// Completed by CallMgrThread 
}


NTSTATUS 
IsdnCallClose( 
	PISDN_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.
	IsdnStateMachine( pCall, INPUT_TAPI_CLOSE );
	return STATUS_PENDING;	// Completed by CallMgrThread 
}


VOID
IsdnSendComplete( 
	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.

--*/
{
	PISDN_CALL CONST pCall = hOwner;
	PNDIS_WAN_PACKET CONST pPacket = Context;
	ASSERT( pPacket != NULL );

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

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

	IsdnQueueSendComplete( pCall, pPacket, IoStatus );
}


NTSTATUS 
IsdnCallSend( 
	PISDN_CALL       pCall, 
	PNDIS_WAN_PACKET pPacket )
/*++

Description

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

--*/
{
	NTSTATUS Status;

	CallAcquireLock( pCall );
	if ( GetState( pCall ) == STATE_CONNECTED )
	{
		DigiDump( DIGITXDATA|DIGITX, ( "IsdnCallSend: pCall 0x%08x, pPacket 0x%08x, Length 0x%08x\n", 
		          pCall, pPacket, pPacket->CurrentLength ) );
		DigiDumpData( DIGITXDATA, pPacket->CurrentBuffer, pPacket->CurrentLength );
		Status = DsPutMsgEx( pCall->hStream, NULL, 0, pPacket->CurrentBuffer, 
		                     pPacket->CurrentLength, pPacket, DS_PRIORITY_LOW, 
		                     IsdnSendComplete );

		if ( Status == STATUS_SUCCESS )
			Status = STATUS_PENDING;
	}
	else
		Status = NDIS_STATUS_FAILURE;

	CallReleaseLock( pCall );

	return Status;
}


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

VOID
IsdnCallOnConnect( 
	IN PVOID          pOwner,
	IN ULONG          ChanIndex,
	IN ULONG          Channel,
	IN ISDN_CONN_TYPE ConnType )
/*++

Description

	Not even input to state machine.  This message can never be received on a call.  
	Only received on the listening stream.  Simply return.

--*/
{
	PISDN_CALL pCall = pOwner;
	ASSERT( MmIsAddressValid( pCall ) );
	ASSERT( pCall->Type == ISDN_TYPE_CALL );
	DigiDump( DIGICALL|DIGIERROR, ( "ISDN!CallOnConnect: Error unexpected XnConnect received on pCall 0x%x\n"
	                                "                    ChanIndex 0x%x, Channel 0x%x\n", 
	                                pCall, ChanIndex, Channel ) );
}

VOID
IsdnCallOnConnack( 
	IN PVOID pOwner,
	IN ULONG ChanIndex,
	IN ULONG Channel )
/*++

Description

	Outbound calls only.  Connack is received when the remote endpoint accepts the call.
	Now, an XN_CACTIVE must be received before considering the call connected.

--*/
{
	PISDN_CALL pCall = pOwner;
	ASSERT( MmIsAddressValid( pCall ) );
	ASSERT( pCall->Type == ISDN_TYPE_CALL );
	DigiDump( DIGICALL, ( "ISDN!CallOnConnack: pCall 0x%x\n", pCall ) );
	IsdnStateMachine( pCall, INPUT_LINE_CONNACK );
}


VOID
IsdnCallOnCActive( 
	IN PVOID pOwner )
/*++

Description

	All calls.  Called when an XN_CACTIVE message arrives from the board.  It means 
	"B-Channel is Active".  Received when the switch has setup the b-channel for a call.
	Once received, we may receive rx data, but cannot send data until we subsequently 
	receive an XN_CONNACK message.

--*/
{
	PISDN_CALL pCall = pOwner;
	ASSERT( MmIsAddressValid( pCall ) );
	ASSERT( pCall->Type == ISDN_TYPE_CALL );
	DigiDump( DIGICALL, ( "ISDN!CallOnCActive: pCall 0x%x\n", pCall ) );
	IsdnStateMachine( pCall, INPUT_LINE_CACTIVE );
}


VOID
IsdnCallOnDisconn( 
	IN PVOID pOwner,
	IN ULONG ChanIndex,
	IN ULONG Channel )
/*++

Description

	Called when an XN_DISCONN message arrives from the board, indicating the remote has
	initiated disconnection of the call.  The call is completely disconnected when an
	XN_CONNEND has also been received.

--*/
{
	PISDN_CALL pCall = pOwner;
	ASSERT( MmIsAddressValid( pCall ) );
	ASSERT( pCall->Type == ISDN_TYPE_CALL );
	DigiDump( DIGICALL, ( "ISDN!CallOnDisconn: pCall 0x%x\n", pCall ) );
	IsdnStateMachine( pCall, INPUT_LINE_DISCONN );
}


VOID
IsdnCallOnConnend( 
	IN PVOID pOwner,
	IN ULONG ChanIndex,
	IN ULONG Channel )
/*++

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).

--*/
{
	PISDN_CALL pCall = pOwner;
	INPUT Input;
	ASSERT( MmIsAddressValid( pCall ) );
	ASSERT( pCall->Type == ISDN_TYPE_CALL );
	DigiDump( DIGICALL, ( "ISDN!CallOnConnend: pCall 0x%x\n", pCall ) );
	if ( pCall->RateMax > pCall->RateMin && pCall->ConnType > ISDN_CONN_TOLLSAVER )
	{
		Input = INPUT_LINE_CONNEND_FALLBACK;
		pCall->ConnType--;
	}
	else
		Input = INPUT_LINE_CONNEND;

	IsdnStateMachine( pCall, Input );
}


VOID
IsdnCallOnListen( 
	IN PVOID pOwner )
/*++

Description

	Not even input to state machine.  This message can never be received on a call.  
	Only received on the listening stream.  Simply return.

--*/
{
	PISDN_CALL pCall = pOwner;
	ASSERT( MmIsAddressValid( pCall ) );
	ASSERT( pCall->Type == ISDN_TYPE_CALL );
	DigiDump( DIGICALL|DIGIERROR, ( "ISDN!CallOnListen: Error unexpected XnListen received on pCall 0x%x\n", pCall ) );
}


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

Description:

	Called to process an incoming data packet.

--*/
{
	PISDN_CALL pCall = pOwner;
	ASSERT( MmIsAddressValid( pCall ) );
	ASSERT( pCall->Type == ISDN_TYPE_CALL );

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

	CallAcquireLock( pCall );
	if ( GetState( pCall ) == STATE_CONNECTED )
		IsdnQueueIndicateReceive( pCall, pData, cbData );
	else
		RxEngineFreeData( pData );
	CallReleaseLock( pCall );
}


STATIC ISDN_EVENT_HANDLERS CallMsgHandlers = 
{
	IsdnCallOnConnect,
	IsdnCallOnConnack,
	IsdnCallOnCActive,
	IsdnCallOnDisconn,
	IsdnCallOnConnend,
	IsdnCallOnListen,
	IsdnCallOnData
};
