/*++
*******************************************************************************
* Copyright (c) 1990-98 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:

   x25call.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 "digix25.h"

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

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

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

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

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

#define QueueWanLineUp( pCall )	\
	(void)X25QueueWanLineup( pCall, pCall->htCall, pCall->LocalLinkSpeed )
		
#define QueueWanLineDown( pCall )	\
	(void)X25QueueWanLinedown( pCall )

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

NTSTATUS X25QueueIndicateFragment(PX25_CALL pCall, ULONG error );

VOID AssemblePPPFrame( PX25_CALL CallObject,
                       PNDIS_WAN_PACKET Packet,
                       PNDIS_WAN_PACKET OriginalPacket );



VOID QueuePadByteStream( PX25_CALL CallObject,
                         PUCHAR ByteBuffer,
                         ULONG Length );



STATIC X25_EVENT_HANDLERS CallMsgHandlers;

//
// 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_CONNECTED,		// Connected.  Only state in which we can pass data
	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_DISCONNECTING,    // Removed by Stratus 26/3/02
	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;


STATIC INLINE NTSTATUS
FindListeningLine( 
	PX25_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 ) )
		if ( pAdapter->pLines[i].pCall == NULL ) 			
		{
			Line = i;
			break;
		}
	}
	if ( Line < pAdapter->nLines )
	{
		Status = STATUS_SUCCESS;
		*pLine = Line;
	}
	return Status;
}


PX25_CALL 
X25CallConstruct( 
	IN PX25_ADAPTER pAdapter,
	IN ULONG         iLine )
/*++

Description 

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

--*/
{
	PX25_CALL pCall;
	DigiDump( DIGICALL, ( "DigiX25:x25CallConstruct - pAdapter 0x%x, iLine 0x%x\n", pAdapter, iLine ) );
	pCall = DigiAllocMem( NonPagedPool, sizeof *pCall );

	if ( pCall )
	{
		NdisZeroMemory( pCall, sizeof *pCall );
		pCall->Type     = X25_TYPE_CALL;
		pCall->pAdapter = pAdapter;

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

		pCall->MaxSendFrameSize    = X25_MAX_FRAME_SIZE;
		pCall->MaxRecvFrameSize    = X25_MAX_FRAME_SIZE;
		pCall->HeaderPadding       = X25_HEADER_PADDING;
		pCall->TailPadding         = X25_TAIL_PADDING;
		pCall->SendFramingBits     = X25_FRAMING_BITS;
		pCall->RecvFramingBits     = X25_FRAMING_BITS;
		pCall->SendCompressionBits = X25_COMP_BITS;
		pCall->RecvCompressionBits = X25_COMP_BITS;
		pCall->CurrentMaxMtuSize = MAX_MTU_SIZE;

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

		AdapterAcquireLock( pAdapter );
		{
			ULONG Line = iLine;
			NTSTATUS Status = STATUS_SUCCESS;
			if ( Line == X25_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
X25CallDestruct( 
	IN PX25_CALL pCall )
/*++

Description 

	Free the specified call object.

--*/
{
	NDIS_MAC_LINE_DOWN Linedown = { 0 };
    PX25_ADAPTER pAdapter = NULL;

    ASSERT( pCall->hStream == NULL );
	AdapterAcquireLock( pCall->pAdapter );
	pAdapter = pCall->pAdapter;

    ASSERT( pCall->pAdapter->pLines[pCall->hdLine].pCall == pCall );

    // Tell NDIS that the call is down before freeing the call object.
    
	DigiDump( DIGIWANEVENT, ( "X25!X25CallDestruct: closing pCall 0x%x, htCall 0x%x, hLink 0x%x\n", pCall, pCall->htCall, pCall->hLink ) );

	if (pCall->hLink)
    {
        Linedown.NdisLinkContext = pCall->hLink;
    	NdisMIndicateStatus( pCall->pAdapter->hMiniport, NDIS_STATUS_WAN_LINE_DOWN, &Linedown, sizeof Linedown );
    	pCall->hLink = NULL;
    	NdisMIndicateStatusComplete( pCall->pAdapter->hMiniport );
    }
	pCall->pAdapter->pLines[pCall->hdLine].pCall = NULL;

	if( pCall->RxBuffer != NULL )
    {
		 DigiFreeMem( pCall->RxBuffer );
         pCall->RxBuffer = NULL;
    }
    
    if ( pCall )
    {
	    DigiFreeMem( pCall );
    }

    AdapterReleaseLock( pAdapter );

}


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

--*/
{
	PX25_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 )
	{
		PX25_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, 
						          ("DigiX25: pCall 0x%x Open failed, Status 0x%x, Error 0x%x\n",
						          pCall, Status, Error ) );
						X25CallDestruct( pCall );
						X25QueueQueryComplete( pAdapter, Status );
					}
					else
					{
						X25QueueQueryComplete( pAdapter, Status );
						QueueTapiDialing( pCall );
						ASSERT( pCall->MaxSendFrameSize == pCall->MaxRecvFrameSize );
						//						Send N_CI down
						XnConnectIndication( pCall->pAdapter, pCall->hStream, pCall->pAdapter->Snid,  pCall->RemoteAddress.Buffer, pCall->RemoteAddressLength);
						RxEngineStart( &pCall->RxEngine, pCall->pAdapter->hMiniport, pCall->hStream, pCall, &CallMsgHandlers, pCall->pAdapter->XnDispatch.XnInterpreter, XnCbControl(pAdapter), DEFAULT_EXTRA_DATA_SIZE );
					}
					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, 
						          ("X25: pCall 0x%x Open failed, Status 0x%x, Error 0x%x\n",
						          pCall, Status, Error ) );
						X25CallDestruct( pCall );
						X25QueueSetComplete( pAdapter, Status );
					}
					else
					{
						X25QueueSetComplete( pAdapter, Status );
//						Send N_CC down	
						XnConnectConfirm( 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), DEFAULT_EXTRA_DATA_SIZE );
					}
					break;
				}

				case CALL_THREAD_CLOSECALL:
				case CALL_THREAD_CLOSE:

					RxEngineStop( &pCall->RxEngine );
					(void)DsPurge( pCall->hStream, DS_PURGE_ALL );
					(void)DsClose( pCall->hStream );
					pCall->hStream = NULL;
					if ( Action == CALL_THREAD_CLOSE )
					{
						pAdapter->pLines[pCall->hdLine].htLine = 0;
						pAdapter->pLines[pCall->hdLine].Listen = FALSE;
					}

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

					X25CallDestruct( pCall );
					X25QueueSetComplete( pAdapter, STATUS_SUCCESS );
					break;

				default:
					
					DigiDump( DIGIERROR, ( "X25 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;
				X25CallDestruct( pAdapter->pLines[i].pCall );
			}
		}
	}

	(void)PsTerminateSystemThread( STATUS_SUCCESS );
}


NTSTATUS
X25CallMgrThreadStart( 
	IN PX25_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
X25CallMgrThreadStop( 
	IN PX25_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( 
	PX25_CALL pCall )
/*++

Description:

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

--*/
{
	QueueTapiNewCall( pCall );
	QueueTapiOffering( pCall );
	DigiDump( DIGICALL,( "CmOffer: call state after CmOffer state 0x%x Tapi State 0x%x\n",
	          pCall->State,pCall->TapiState ) );

}

STATIC VOID
CmDial( 
	PX25_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( 
	PX25_CALL pCall )
/*++

Description:

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

--*/
{
	XnConnectIndication( pCall->pAdapter, pCall->hStream, pCall->pAdapter->Snid,  pCall->RemoteAddress.Buffer, pCall->RemoteAddress.MaximumLength);
				
}
STATIC VOID
CmConnected( 
	PX25_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( 
	PX25_CALL pCall )
/*++

Description:

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

--*/
{
	DigiDump( DIGICALL,( "CmAnswer: call state beforeCmAnswer state 0x%x Tapi State 0x%x\n",
	          pCall->State,pCall->TapiState ) );

	MgrNotifyThread( pCall, CALL_THREAD_ANSWER );
	DigiDump( DIGICALL,( "CmAnswer: call state after CmAnswer state 0x%x Tapi State 0x%x\n",
	          pCall->State,pCall->TapiState ) );

	CmConnected(pCall);

	
}

STATIC VOID
CmReject(
	PX25_CALL pCall )
/*++

Description:

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

--*/
{
	XnDisconn( pCall, pCall->pAdapter->Listen.hStream, pCall->conn_id );
}


STATIC VOID
CmEarlyDrop( 
	PX25_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, pCall->hStream, pCall->conn_id );
}

STATIC VOID
CmDrop( 
	PX25_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, pCall->hStream, pCall->conn_id );
	QueueTapiIdle( pCall );
	QueueWanLineDown( pCall );
}



STATIC VOID
CmEarlyConnend( 
	PX25_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
CmDisc( 
	PX25_CALL pCall )
/*++

Description:

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

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


STATIC VOID
CmConnend( 
	PX25_CALL pCall )
/*++

Description:

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

--*/
{
	QueueTapiIdle( pCall );
}



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

Description:

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

--*/
{
	UNREFERENCED_PARAMETER( pCall );
}

STATIC VOID
CmErrorConn( 
	PX25_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( 
	PX25_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( 
	PX25_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( 
	PX25_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( 
	PX25_CALL pCall )
/*++

Description:

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

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


STATIC VOID
CmClose( 
	PX25_CALL pCall )
/*++

Description:

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

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



////////////////////////////////////////////////////////
// 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",
	"DISCONNECTED",
	"DROPPING",
	"DROPPED",
	"DISCONNECTING",
	"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_NCI,			// NCI received on listen stream.  Offer call to Tapi.
	INPUT_LINE_NCC,			// Ack that outbound call has been accepted by remote station
	INPUT_LINE_NDI,			// Remote has initiated disconnect
	INPUT_LINE_NDC,			// Disconnect is complete
	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_NCI",
	"LINE_NCC",
	"LINE_NDI",
	"LINE_NDC"
};
#endif

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

STATIC INLINE STATE
GetState( PX25_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_NCI
		{CmErrorIdle, 	STATE_ERROR },						// INPUT_LINE_NCC
		{CmErrorIdle, 	STATE_ERROR },						// INPUT_LINE_NDI
		{CmErrorIdle, 	STATE_ERROR }						// INPUT_LINE_NDC

	},
	//	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_NCI
		{CmConnected,				STATE_CONNECTED },		// INPUT_LINE_NCC - call confirm
		{CmEarlyConnend,			STATE_DISCONNECTED },	// INPUT_LINE_NDI - disconnect indication
		{CmEarlyConnend,		STATE_DISCONNECTED }	// INPUT_LINE_NDC - disconnect confirm

	},
	//	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_NCI
		{CmErrorInProgress,	STATE_ERROR },				// INPUT_LINE_NCC
		{CmErrorInProgress,	STATE_DISCONNECTED },				// INPUT_LINE_NDI
		{CmEarlyConnend,			STATE_ERROR }			// INPUT_LINE_NDC

	},
	
	//	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_NCI
		{CmErrorConn,			STATE_ERROR },				// INPUT_LINE_NCC
		{CmErrorConn,			STATE_DISCONNECTED },				// INPUT_LINE_DI
		{CmDisc,					STATE_ERROR }	// INPUT_LINE_NDC
		},
		//	STATE_DISCONNECTED
	{	{CmErrorIdle,	STATE_ERROR },						// INPUT_TAPI_DIAL	
		{CmErrorIdle,	STATE_ERROR },						// INPUT_TAPI_ANSWER	
		{CmIgnore,		STATE_DROPPED },					// INPUT_TAPI_DROP	
		{CmCloseCall,	STATE_CLOSING },					// INPUT_TAPI_CLOSECALL	
		{CmClose,		STATE_CLOSING },					// INPUT_TAPI_CLOSE	
		{CmErrorIdle,	STATE_ERROR },						// INPUT_LINE_NCC
		{CmErrorIdle,	STATE_ERROR },						// INPUT_LINE_NCI
		{CmErrorIdle,	STATE_ERROR },						// INPUT_LINE_NDI
		{CmErrorIdle,	STATE_ERROR }						// INPUT_LINE_NDC
	},
	//	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_NCI
		{CmErrorIdle,		STATE_ERROR },					// INPUT_LINE_NCC
		{CmErrorDisc,		STATE_ERROR },					// INPUT_LINE_NDI
		{CmConnend,			STATE_DROPPED }					// INPUT_LINE_NDC

	   
	},
	//	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_NCI
		{CmErrorIdle,		STATE_ERROR },					// INPUT_LINE_NCC
		{CmErrorIdle,		STATE_ERROR },					// INPUT_LINE_NDI
		{CmErrorIdle,		STATE_ERROR }					// INPUT_LINE_NDC
	},
	// 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_NCI
		{CmIgnore,		STATE_CLOSING },					// INPUT_LINE_NCC
		{CmIgnore,		STATE_CLOSING },					// INPUT_LINE_NDI
		{CmIgnore,		STATE_CLOSING }						// INPUT_LINE_NDC

	},
	// STATE_ERROR      
	{	{CmIgnore,   	STATE_DISCONNECTED },				// INPUT_TAPI_DIAL	
		{CmIgnore,		STATE_DISCONNECTED },				// INPUT_TAPI_ANSWER	
		{CmClose,   	STATE_DISCONNECTED },				// INPUT_TAPI_DROP	
		{CmCloseCall,	STATE_CLOSING },					// INPUT_TAPI_CLOSECALL	
		{CmClose,		STATE_CLOSING },					// INPUT_TAPI_CLOSE	
		{CmClose,   	STATE_DISCONNECTED },				// INPUT_LINE_NCI
		{CmClose,   	STATE_DISCONNECTED },				// INPUT_LINE_NCC
		{CmClose,	    STATE_DISCONNECTED },				// INPUT_LINE_NDI
		{CmIgnore,   	STATE_DISCONNECTED }				// INPUT_LINE_NDC

	}
};


STATIC VOID 
X25StateMachine( 
	PX25_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, ( "X25StateMachine: 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, ("X25StateMachine: 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 );
	DigiDump( DIGISTATE, ( "X25StateMachine: pCall 0x%x, State 0x%x, TapiState 0x%x\n", pCall, pCall->State, pCall->TapiState ) );
	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 
X25CallDial( 
	IN PX25_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 PX25_CALL   *ppCall )
/*++

Description

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

--*/
{
	NTSTATUS Status;
	PX25_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 );

	DigiDump( DIGICALL, ( "DigiX25:CallDial pAdapter 0x%x\nMediaMode 0x%x\nMaxrate %d\nMinRate %d\nhdLine %d\n",
			pAdapter,MediaMode,MaxRate,MinRate,hdLine) );

	DigiDump(DIGICALL, ("htCall 0x%d\nDestAddr %s\ncbDestAddr %d\n",htCall,pDestAddr,cbDestAddr));

	pCall = X25CallConstruct( pAdapter, (ULONG)hdLine );
	if ( pCall == NULL )
	{
		Status = NDIS_STATUS_RESOURCES;
		goto Exit;
	}


	pCall->RemoteAddressLength = cbDestAddr;
	NdisMoveMemory( pCall->RemoteAddressBuffer, 
	                pDestAddr,cbDestAddr);



	pCall->htCall     = htCall;
	pCall->MediaMode  = MediaMode;
	pCall->BearerMode = BearerMode;
	pCall->RateMin    = MinRate;
	pCall->RateMax    = MaxRate;
	pCall->LocalLinkSpeed = pAdapter->LinkSpeed;
	pCall->RemoteLinkSpeed = pAdapter->LinkSpeed;
	pCall->ConnType = BearerMode == LINEBEARERMODE_VOICE ? X25_CONN_TOLLSAVER :
		               MaxRate < 64000 ? X25_CONN_D56 : X25_CONN_D64;
					   

//	ParseDialString( pDestAddr, cbDestAddr, &pCall->RemoteAddress, NULL );
	X25StateMachine( pCall, INPUT_TAPI_DIAL );

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

Exit:
	return Status;
}


NTSTATUS
X25CallOffer( 
	PX25_CALL pCall )
/*++

Description

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


NTSTATUS
X25CallAnswer( 
	PX25_CALL pCall )
/*++

Description

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

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

NTSTATUS
X25CallDrop( 
	PX25_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.
	X25StateMachine( pCall, INPUT_TAPI_DROP );
	return STATUS_SUCCESS;
}

NTSTATUS 
X25CallCloseCall( 
	PX25_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.
	X25StateMachine( pCall, INPUT_TAPI_CLOSECALL );
	return STATUS_PENDING;	// Completed by CallMgrThread 
}


NTSTATUS 
X25CallClose( 
	PX25_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.
	X25StateMachine( pCall, INPUT_TAPI_CLOSE );
	return STATUS_PENDING;	// Completed by CallMgrThread 
}


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

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

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

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

	if( pCall->SpecialFlags & CALL_OBJ_PAD_CONNECTION )
	{
		// Cleanup and restore packet original pointer

		PNDIS_WAN_PACKET OriginalPacket = pPacket->MacReserved1;

		DigiFreeMem( pPacket->StartBuffer );
		DigiFreeMem( pPacket );
		pPacket = OriginalPacket;
	}


	X25QueueSendComplete( pCall, pPacket, IoStatus );
}


NTSTATUS 
X25CallSend( 
	PX25_CALL       pCall, 
	PNDIS_WAN_PACKET pPacket )
/*++

Description

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

--*/
{
	NTSTATUS Status;
	struct xdataf NData;
	PNDIS_WAN_PACKET OriginalPacket;


	CallAcquireLock( pCall );
	if ( GetState( pCall ) == STATE_CONNECTED )
	{
		DigiDump( DIGITXDATA|DIGITX, ( "X25CallSend: pCall 0x%08x, pPacket 0x%08x, Length 0x%08x\n", 
		          pCall, pPacket, pPacket->CurrentLength ) );
		DigiDumpData( DIGITXDATA, pPacket->CurrentBuffer, pPacket->CurrentLength );

		pPacket->MacReserved1 = NULL;
		pPacket->MacReserved2 = NULL;
		OriginalPacket = pPacket;

		if( pCall->SpecialFlags & CALL_OBJ_PAD_CONNECTION )
	   {
    		PUCHAR NewCurrentBuffer;
			PNDIS_WAN_PACKET NewPacket;
			ULONG AllocSize;
      
      //
      // Okay, we will allocate enough memory for a worst case scenerio of
      // all the data having transparency applied, which increases the buffer
      // size * 2.  We also add 16-bits for the CRC and 2 8-bit Flag Sequences
      // to make up the rest of the frame.
      //
      
			AllocSize = (ULONG)(pPacket->CurrentBuffer - pPacket->StartBuffer) +
                  (pPacket->CurrentLength * 2) +
                  (pCall->TailPadding * 2) +
                  (2 * sizeof(UCHAR));
      
			// We save a copy of the original Wan Packet in the MacReserved1 field.
			NewPacket = (PNDIS_WAN_PACKET)DigiAllocMem( NonPagedPool, sizeof(NDIS_WAN_PACKET) );
			NewPacket->MacReserved1 = pPacket;
			NewPacket->MacReserved2 = pPacket->MacReserved2;
			NewPacket->MacReserved3 = pPacket->MacReserved3;
			NewPacket->MacReserved4 = pPacket->MacReserved4;
      
			NewCurrentBuffer = DigiAllocMem( NonPagedPool, AllocSize );
      
			NewPacket->CurrentBuffer = NewCurrentBuffer + (pPacket->CurrentBuffer - pPacket->StartBuffer);
			NewPacket->StartBuffer = NewCurrentBuffer;
			NewPacket->EndBuffer = NewCurrentBuffer + AllocSize;

      //
      // Okay, the client is an async PAD connection.  Which
      // framing should we use??
      //
			if( pCall->SpecialFlags & CALL_OBJ_PPP_FRAMING )
			{
		         AssemblePPPFrame( pCall,
                           NewPacket,
                           pPacket );
			}
			else
			{
			//
			// We should have never gotten this!!!!
			//
				ASSERT( FALSE && "Unknown Framing type on a send!!!!" );
			}
			pPacket = NewPacket;
	}

	//
	// Send it!
	//

	NdisZeroMemory( &NData, sizeof( NData ) );

	NData.xl_type = XL_DAT;
	NData.xl_command = N_Data;
	Status = DsPutMsgEx( pCall->hStream, (PVOID)&NData, sizeof( NData ), 
						pPacket->CurrentBuffer, pPacket->CurrentLength, 
						pPacket, DS_PRIORITY_LOW, X25SendComplete );


	if ( Status == STATUS_SUCCESS )
		Status = STATUS_PENDING;
   
	}
	CallReleaseLock( pCall );

	DigiDump( DIGITXDATA|DIGITX, ( "X25CallSend: returning - Status 0x%x\n", 
		          Status ) );
		
	return Status;
}



VOID
X25CallOnConnectConfirm( 
	IN PVOID pOwner,
	IN PVOID pControl,
	IN PVOID pData )
/*++

Description

	Outbound calls only.

--*/
{
	PX25_CALL pCall = pOwner;
	ASSERT( MmIsAddressValid( pCall ) );
	ASSERT( pCall->Type == X25_TYPE_CALL );
	DigiDump( DIGICALL, ( "X25!CallOnConnectConfirm: pCall 0x%x\n", pCall ) );

	X25StateMachine( pCall, INPUT_LINE_NCC );
}

VOID
X25CallOnDisconnectConfirm( 
	IN PVOID pOwner,
	IN PVOID pControl,
	IN PVOID pData )
/*++

Description

	

--*/
{
	PX25_CALL pCall = pOwner;
	ASSERT( MmIsAddressValid( pCall ) );
	ASSERT( pCall->Type == X25_TYPE_CALL );
	DigiDump( DIGICALL, ( "X25!CallOnDisconnectConfirm: pCall 0x%x\n", pCall ) );
	X25StateMachine( pCall, INPUT_LINE_NDC );
}


 
VOID
X25CallOnDisconnectIndication( 
	IN PVOID pOwner,
	PVOID pControl,
	PVOID pData)
/*++

Description

	Called when an NDI message arrives from the board, indicating a call has been
	disconnected.  
--*/
{
	PX25_CALL pCall = pOwner;
   	ASSERT( MmIsAddressValid( pCall ) );
	ASSERT( pCall->Type == X25_TYPE_CALL );
	DigiDump( DIGICALL, ( "DigiX25!CallOnConnend: pCall 0x%x\n", pCall ) );

	X25StateMachine( pCall, INPUT_LINE_NDI );
}


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

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


STATIC VOID
X25CallOnConnect(	
	IN PVOID          pOwner,
	IN PVOID		pControl,
	IN PVOID 		pData )
/*++

Description:

	Called to process a received XN_CONNECT message which signals an incoming call.  If
	we like the bearer caps, etc., offer the call up to Tapi.

--*/
{
	PX25_ADAPTER CONST pAdapter = pOwner;
	
	ASSERT( MmIsAddressValid( pAdapter ) );
	ASSERT( pAdapter->Type == X25_TYPE_ADAPTER );
	DigiDump( DIGICALL|DIGIERROR, ( "X25!CallOnListen: Error unexpected N_CI received on \n"  ) );

}


NTSTATUS
PPPAppendFrame(
	IN PX25_CALL CallObject,
	IN PUCHAR pData,
	IN ULONG DataLength )
/*++

Description:

	This routine moves received data into rx frame buffer.  If rx frame buffer
	does not exist, a new one is alloc'd.

Return Value:

	STATUS_SUCCESS if successful, or error.

--*/
{
	NDIS_STATUS Status = NDIS_STATUS_SUCCESS;

	if ( !CallObject->RxBuffer )
	{
		CallObject->RxBuffer = DigiAllocMem( NonPagedPool, 
		                                     CallObject->CurrentMaxMtuSize );
		CallObject->RxOffset = 0;

		if ( !CallObject->RxBuffer )
			return( NDIS_STATUS_RESOURCES );
	}

	if ( CallObject->RxOffset + DataLength < CallObject->CurrentMaxMtuSize )
	{
		NdisMoveMemory( CallObject->RxBuffer + CallObject->RxOffset, pData, DataLength );
		CallObject->RxOffset += DataLength;
	}
	else
	{
		// Framing error!  Indicate error and reset buffer.
		CallObject->RxOffset = 0;
		CallObject->MtuOverrun++;
		X25QueueIndicateFragment( CallObject, WAN_ERROR_ALIGNMENT );
		Status = NDIS_STATUS_FAILURE;
	}

	return( Status );
}









VOID
X25ProcessRxData(
	 PX25_CALL pCall,
  	 IN PVOID pControl,
	 IN ULONG ControlLength,
	 IN PVOID pData,
	 IN ULONG DataLength
	 )
/*++

Routine Description:
	Completion routine

Arguments:


Return Value:


--*/
{
	S_X25_HDR *pMsgHeader = (PVOID)pControl;

	DigiDump( DIGIDIAG, ("XL_DAT: ") );
	switch( pMsgHeader->xl_command  )
	{
            case N_Data:	// X.25 Data packet
            {
               struct xdataf *pNData = (PVOID)pControl;

					DigiDump( DIGIDIAG, ("N_Data\n"
					                        "   Dbit 0x%x, Qbit 0x%x, More 0x%x\n",
					                        pNData->setDbit,
					                        pNData->setQbit,
					                        pNData->More) );

               // Using delivery-bit?
               if ( pNData->setDbit )
               {
						// Yes, acknowledge the packet.
	               struct xdatacf DataAck;

	               RtlZeroMemory( &DataAck, sizeof(struct xdatacf) );
	               DataAck.xl_type = XL_DAT;
	               DataAck.xl_command = N_DAck;

						DsPutMsg( pCall->hStream,
						          (PVOID)&DataAck, sizeof( DataAck ),
						          NULL, 0, DS_PRIORITY_LOW );
               }


			// Drop and ignore received data if we're not in the connected state.
			 	if ( pCall->State != STATE_CONNECTED )
						break;

				if( pCall->SpecialFlags & CALL_OBJ_PAD_CONNECTION )
					{
						// PAD Connection
						if( pNData->setQbit )
						{							
							DigiDump( DIGIWARN, ("Received X29 packet on CallObj (0x%x)\n", pCall));
						}
						else	// Data packet
							// Process received bytes
							QueuePadByteStream( pCall, pData, DataLength );
					}
					else
					{
						// X25 Connection - remote is *not* a PAD
						//
						// Received X.25 packet.  If the More bit is clear, then we 
						// have a complete PPP Frame, indicate a receive.  Otherwise, 
						// we've got only a portion of the PPP frame, store it and wait.
						//

						DigiDump( DIGIDIAG,
						          ("NData recv: length=%d, RxOffset=%d\n",
						          DataLength,
						          pCall->RxOffset) );

						
						// Don't waste time copying if we must drop the frame
						if ( !( pCall->SpecialFlags & CALL_OBJ_DROP_FRAME ) )
						{
							// This frame's a keeper, process it.
							NTSTATUS Status;
							
							// There could be previous data marked with the More-bit stored
							// in the CallObject RxBuffer, append this packet to it.  If no 
							// previous data, insert at the beginning of RxBuffer.
							Status = PPPAppendFrame( pCall, pData, DataLength );

							if ( !NT_SUCCESS( Status ) )
								// Rx buffer has overrun!
								pCall->SpecialFlags |= CALL_OBJ_DROP_FRAME;

							ASSERT( pCall->RxOffset < pCall->CurrentMaxMtuSize );
						}

					   

	               if( !pNData->More )
	               {
	                  // We've reached the end of a packet.  Indicate it.
							if ( pCall->SpecialFlags & CALL_OBJ_DROP_FRAME )
							{
								// It's a bad packet. Tell Ndis we're dropping it.
								X25QueueIndicateFragment( pCall, WAN_ERROR_ALIGNMENT );
								pCall->SpecialFlags &= ~CALL_OBJ_DROP_FRAME;
								ASSERT( pCall->RxOffset == 0 );
								pCall->RxOffset = 0;
							}
							else
							{
				                  ASSERT( pCall->RxOffset < pCall->CurrentMaxMtuSize );
				                  X25QueueIndicateReceive( pCall,
												pCall->RxBuffer,
												pCall->RxOffset );
							// AdapterContext now owns that buffer.  Must alloc a new one.
								pCall->RxOffset = 0;
		               			pCall->RxBuffer = DigiAllocMem( NonPagedPool, 
		               	                       pCall->CurrentMaxMtuSize );
							}
	               }
					DigiFreeMem( pData );
				}
               break;

            }  // end case N_Data

            case N_DAck:	// Dbit data acknowledge
					DigiDump( DIGIERROR, ("*** X25GetMsgComplete: "
					                      "Unexpected N_DAck indication! ***\n") );
					break;

				// Expedited data, this is the user data from an interrupt packet.
				// Apparently the only time these would be received is when the user
				// on a remote PAD hits BREAK. For now discard the data and return
				// N_EAck, which will cause the FEP to send an interrupt confirm packet.
            case N_EData:	// X.25 Interrupt Packet
            {
               NTSTATUS Status;
               struct xedatacf EDataConfirm;

               DigiDump( DIGIDIAG, ("N_EData\n") );

               RtlZeroMemory( &EDataConfirm, sizeof(struct xedatacf) );
               EDataConfirm.xl_type = XL_DAT;
               EDataConfirm.xl_command = N_EAck;

				Status = DsPutMsg( pCall->hStream,
					                   (PVOID)&EDataConfirm, sizeof( EDataConfirm ),
					                   NULL, 0, DS_PRIORITY_LOW );
               break;
            }  // end case N_EData

            case N_EAck:
					DigiDump( DIGIERROR, ("*** X25GetMsgComplete: "
					                      "Unexpected N_Xlisten indication! ***\n") );
					break;

            default:
               DigiDump( DIGIERROR, 
                         ("Unknown XL_DAT command from controller (0x%x)\n",
                         pMsgHeader->xl_command) );
               ASSERT( FALSE && "Unknown XL_DAT command" );
               break;
         }  // end switch( pMsgHeader->xl_command )


}  // end X25GetMsgComplete


VOID
X25CallOnData(
	IN PVOID pOwner,
	IN PVOID pControl,
	IN ULONG	 cbControl,
	IN PVOID pData, 
	IN ULONG cbData )
/*++

Description:

	Called to process an incoming data packet.

--*/
{
	PX25_CALL pCall = (PX25_CALL) pOwner;
	ASSERT( MmIsAddressValid( pCall ) );
	ASSERT( pCall->Type == X25_TYPE_CALL );

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

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




STATIC X25_EVENT_HANDLERS CallMsgHandlers = 
{
	X25CallOnConnect,									// N_CI
	X25CallOnConnectConfirm,							// N_CC
	X25CallOnDisconnectIndication,						// N_DI
	X25CallOnDisconnectConfirm,							// N_DC
	X25CallOnListen,									// Should not come on data line
	X25CallOnData
};



