/*++

Copyright (c) 1992  Microsoft Corporation

Module Name:

	atktdi.c

Abstract:

	This module contains the code providing the tdi interface.

Author:

	Jameel Hyder (jameelh@microsoft.com)
	Nikhil Kamkolkar (nikhilk@microsoft.com)

Revision History:
	19 Jun 1992		Initial Version

Notes:	Tab stop: 4
--*/

#define	ATKTDI_LOCALS
#define	FILENUM		ATKTDI
#include <atalk.h>
#pragma hdrstop

#ifdef	ALLOC_PRAGMA
#pragma alloc_text(INIT, AtalkLockInit)
#pragma alloc_text(PAGE, AtalkTdiOpenAddress)
#pragma alloc_text(PAGE, AtalkTdiOpenConnection)
#pragma alloc_text(PAGE, AtalkTdiOpenControlChannel)
#pragma alloc_text(PAGE, AtalkTdiAction)
#pragma alloc_text(PAGE, atalkQueuedLockUnlock)
#pragma alloc_text(PAGE_TDI, AtalkTdiCleanupAddress)
#pragma alloc_text(PAGE_TDI, AtalkTdiCleanupConnection)
#pragma alloc_text(PAGE_TDI, AtalkTdiCloseAddress)
#pragma alloc_text(PAGE_TDI, AtalkTdiCloseConnection)
#pragma alloc_text(PAGE_TDI, AtalkTdiCloseControlChannel)
#pragma alloc_text(PAGE_TDI, AtalkTdiAssociateAddress)
#pragma alloc_text(PAGE_TDI, AtalkTdiDisassociateAddress)
#pragma alloc_text(PAGE_TDI, AtalkTdiConnect)
#pragma alloc_text(PAGE_TDI, AtalkTdiDisconnect)
#pragma alloc_text(PAGE_TDI, AtalkTdiAccept)
#pragma alloc_text(PAGE_TDI, AtalkTdiListen)
#pragma alloc_text(PAGE_TDI, AtalkTdiSend)
#pragma alloc_text(PAGE_TDI, AtalkTdiSendDgram)
#pragma alloc_text(PAGE_TDI, AtalkTdiReceive)
#pragma alloc_text(PAGE_TDI, AtalkTdiReceiveDgram)
#pragma alloc_text(PAGE_TDI, AtalkTdiQueryInformation)
#pragma alloc_text(PAGE_TDI, AtalkTdiSetInformation)
#pragma alloc_text(PAGE_TDI, AtalkTdiSetEventHandler)
#pragma alloc_text(PAGE_TDI, AtalkTdiCancel)
#pragma alloc_text(PAGE_TDI, AtalkQueryInitProviderInfo)
#pragma alloc_text(PAGE_TDI, atalkTdiActionComplete)
#pragma alloc_text(PAGE_TDI, atalkTdiGenericComplete)
#pragma alloc_text(PAGE_TDI, atalkTdiGenericReadComplete)
#pragma alloc_text(PAGE_TDI, atalkTdiGenericWriteComplete)
#endif

// Primary TDI Functions for appletalk stack

NTSTATUS
AtalkTdiOpenAddress(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN		PTA_APPLETALK_ADDRESS	pTdiAddr,
	IN		BYTE					ProtoType,
	IN		BYTE					SocketType,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine is used to create an address object. It will also the
	create the appropriate socket with the portable stack.

Arguments:


Return Value:

	STATUS_SUCCESS if address was successfully opened
	Error otherwise.

--*/
{
	PVOID			FsContext;
	ATALK_ADDR		atalkAddr;
	ATALK_ERROR		error;

	do
	{
		atalkWaitDefaultPort();
		// We honor only if count/type and length are as we expect. And only if the
		// default port is valid.
		if ((AtalkDefaultPort == NULL)												||
			(pTdiAddr->TAAddressCount != 1)											||
			(pTdiAddr->Address[0].AddressLength != sizeof(TDI_ADDRESS_APPLETALK))	||
			(pTdiAddr->Address[0].AddressType != TDI_ADDRESS_TYPE_APPLETALK))
		{
			error = ATALK_NEW_SOCKET; // maps to STATUS_INVALID_ADDRESS
			break;
		}

		TDI_TO_ATALKADDR(&atalkAddr, pTdiAddr);

		// Now depending on the requested device...
		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_DDP:
			error = AtalkDdpOpenAddress(AtalkDefaultPort,
										atalkAddr.ata_Socket,
										NULL,					// Desired node (any node)
										NULL,					// NULL Socket Handler
										NULL,					// Context for handler
										ProtoType,
										pCtx,
										(PDDP_ADDROBJ *)(&FsContext));
			break;

		case ATALK_DEV_ASPC:
			AtalkLockAspCIfNecessary();
			error = AtalkAspCCreateAddress(pCtx, (PASPC_ADDROBJ *)(&FsContext));
			break;

		  case ATALK_DEV_ASP:
			AtalkLockAspIfNecessary();
			error = AtalkAspCreateAddress((PASP_ADDROBJ *)(&FsContext));
			break;

		  case ATALK_DEV_PAP:
			AtalkLockPapIfNecessary();
			error = AtalkPapCreateAddress(pCtx, (PPAP_ADDROBJ *)(&FsContext));
			break;

		  case ATALK_DEV_ADSP:
			AtalkLockAdspIfNecessary();
			error = AtalkAdspCreateAddress(pCtx,
										   SocketType,
										   (PADSP_ADDROBJ *)(&FsContext));
			break;

		  default:
			// The device type in the Ctx field can never be anything
			// other than the above! Internal protocol error. KeBugCheck.
            error = ATALK_INVALID_REQUEST;
			DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_ERR,
					("AtalkTdiOpenAddress: Invalid device type\n"));
			break;
		}

		if (ATALK_SUCCESS(error))
		{
			pIrpSp->FileObject->FsContext2 =
				(PVOID)(TDI_TRANSPORT_ADDRESS_FILE + (pCtx->adc_DevType << 16));

			pIrpSp->FileObject->FsContext = FsContext;
			AtalkLockTdiIfNecessary();
		}
	} while (FALSE);

	ASSERT(error == ATALK_NO_ERROR);

	return AtalkErrorToNtStatus(error);
}




NTSTATUS
AtalkTdiOpenConnection(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN		CONNECTION_CONTEXT		ConnCtx,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine is used to create a connection object and associate the
	passed ConnectionContext with it.

Arguments:

	ConnectionContext - The TDI ConnectionContext to be associated with object
	Context - The DeviceContext of the device on which open is happening

Return Value:

	STATUS_SUCCESS if connection was successfully opened
	Error otherwise.

--*/
{
	ATALK_ERROR	error = ATALK_INVALID_REQUEST;
	PVOID		FsContext;

	if ((pCtx->adc_DevType == ATALK_DEV_PAP) ||
		(pCtx->adc_DevType == ATALK_DEV_ADSP)||
		(pCtx->adc_DevType == ATALK_DEV_ASPC))
	do
	{
		atalkWaitDefaultPort();
		// Only if the default port is valid.
		if (AtalkDefaultPort == NULL)
		{
			error = ATALK_NEW_SOCKET; // maps to STATUS_INVALID_ADDRESS
			break;
		}

		// Now depending on the requested device...
		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_PAP:
			AtalkLockPapIfNecessary();
			error = AtalkPapCreateConnection(ConnCtx,
											 pCtx,
											 (PPAP_CONNOBJ *)(&FsContext));
			break;

		  case ATALK_DEV_ADSP:
			AtalkLockAdspIfNecessary();
			error = AtalkAdspCreateConnection(ConnCtx,
											  pCtx,
											  (PADSP_CONNOBJ *)(&FsContext));
			break;

		  case ATALK_DEV_ASPC:
			AtalkLockAspCIfNecessary();
			error = AtalkAspCCreateConnection(ConnCtx,
											  pCtx,
											  (PASPC_CONNOBJ *)(&FsContext));
			break;
		}

		if (ATALK_SUCCESS(error))
		{
			pIrpSp->FileObject->FsContext2 = (PVOID)(TDI_CONNECTION_FILE +
													 (pCtx->adc_DevType << 16));
			pIrpSp->FileObject->FsContext = FsContext;
			AtalkLockTdiIfNecessary();
		}
		
		ASSERT(error == ATALK_NO_ERROR);

	} while (FALSE);

	return AtalkErrorToNtStatus(error);
}




NTSTATUS
AtalkTdiOpenControlChannel(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine is used to create a control channel

Arguments:

	Context - The DeviceContext of the device on which open is happening

Return Value:

	STATUS_SUCCESS if controlchannel was successfully opened
	Error otherwise.

--*/
{
	PDDP_ADDROBJ	pDdpAddr;
	ATALK_ERROR		error = ATALK_INVALID_REQUEST;

	do
	{
		atalkWaitDefaultPort();
		// Only if the default port is valid.
		if (AtalkDefaultPort == NULL)
		{
			error = ATALK_NEW_SOCKET; // maps to STATUS_INVALID_ADDRESS
			break;
		}

		// Now depending on the requested device...
		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_DDP:
		  case ATALK_DEV_ASP:
		  case ATALK_DEV_PAP:
		  case ATALK_DEV_ADSP:
		  case ATALK_DEV_ASPC:
			error = AtalkDdpOpenAddress(AtalkDefaultPort,
										UNKNOWN_SOCKET,
										NULL,
										NULL,
										NULL,
										0,
										pCtx,
										&pDdpAddr);
			break;

		  default:
			// The device type in the Ctx field can never be anything
			// other than the above! Internal protocol error. KeBugCheck.
			DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_ERR,
					("AtalkTdiOpenControlChannel: Invalid device type\n"));
			break;
		}

		if (ATALK_SUCCESS(error))
		{
			pIrpSp->FileObject->FsContext2 =
					(PVOID)(TDI_CONTROL_CHANNEL_FILE + (pCtx->adc_DevType << 16));
			pIrpSp->FileObject->FsContext = pDdpAddr;
			AtalkLockTdiIfNecessary();
		}
		else
		{
			DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_ERR,
					("AtalkTdiOpenControlChannel: Failed %ld\n", error));
		}
	} while (FALSE);

	return AtalkErrorToNtStatus(error);
}




NTSTATUS
AtalkTdiCleanupAddress(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine removes the creation reference on the object. It also
	sets up the closepIrp for completion.

Arguments:

	pIrp - The close irp
	Context - The DeviceContext of the device on which close is happening

Return Value:

	STATUS_SUCCESS if successfully setup
	Error otherwise.

--*/
{
	ATALK_ERROR	error;
	PVOID		pAddrObj = pIrpSp->FileObject->FsContext;

	do
	{
		// Now depending on the requested device...
		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_DDP:
			ASSERT(VALID_DDP_ADDROBJ(((PDDP_ADDROBJ)pAddrObj)));
			AtalkDdpReferenceByPtr(((PDDP_ADDROBJ)pAddrObj), &error);
			if (ATALK_SUCCESS(error))
			{
				AtalkDdpCleanupAddress((PDDP_ADDROBJ)pAddrObj);
				AtalkDdpDereference(((PDDP_ADDROBJ)pAddrObj));
			}
			break;

		  case ATALK_DEV_ASPC:
			ASSERT(VALID_ASPCAO((PASPC_ADDROBJ)pAddrObj));
			AtalkAspCAddrReference((PASPC_ADDROBJ)pAddrObj, &error);
			if (ATALK_SUCCESS(error))
			{
				AtalkAspCCleanupAddress((PASPC_ADDROBJ)pAddrObj);
				AtalkAspCAddrDereference((PASPC_ADDROBJ)pAddrObj);
			}
			break;

		  case ATALK_DEV_ASP:
			error = ATALK_NO_ERROR;
			break;

		  case ATALK_DEV_PAP:
			ASSERT(VALID_PAPAO((PPAP_ADDROBJ)pAddrObj));
			AtalkPapAddrReference((PPAP_ADDROBJ)pAddrObj, &error);
			if (ATALK_SUCCESS(error))
			{
				AtalkPapCleanupAddress((PPAP_ADDROBJ)pAddrObj);
				AtalkPapAddrDereference((PPAP_ADDROBJ)pAddrObj);
			}
			break;

		  case ATALK_DEV_ADSP:
			ASSERT(VALID_ADSPAO((PADSP_ADDROBJ)pAddrObj));
			AtalkAdspAddrReference((PADSP_ADDROBJ)pAddrObj, &error);
			if (ATALK_SUCCESS(error))
			{
				AtalkAdspCleanupAddress((PADSP_ADDROBJ)pAddrObj);
				AtalkAdspAddrDereference((PADSP_ADDROBJ)pAddrObj);
			}
			break;

		  default:
			// The device type in the Ctx field can never be anything
			// other than the above! Internal protocol error. KeBugCheck.
			DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_ERR,
					("AtalkTdiCleanupAddress: Invalid device type\n"));
			error = ATALK_INVALID_REQUEST;
			break;
		}

	} while (FALSE);

	return AtalkErrorToNtStatus(error);
}




NTSTATUS
AtalkTdiCleanupConnection(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine removes the creation reference on the object. It also
	sets up the closepIrp for completion.

Arguments:

	pIrp - The close irp
	Context - The DeviceContext of the device on which close is happening

Return Value:

	STATUS_SUCCESS if successfully setup
	Error otherwise.

--*/
{
	ATALK_ERROR	error;
	PVOID		pConnObj = pIrpSp->FileObject->FsContext;

	do
	{
		// Now depending on the requested device...
		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_PAP:

			ASSERT(VALID_PAPCO((PPAP_CONNOBJ)pConnObj));
			AtalkPapConnReferenceByPtr((PPAP_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				//	No need to have lock as we have a reference.
				((PPAP_CONNOBJ)pConnObj)->papco_CleanupComp = atalkTdiGenericComplete;
				((PPAP_CONNOBJ)pConnObj)->papco_CleanupCtx  = pIrp;

				DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_INFO,
						("AtalkTdiCleanupConnection: Cleanup %lx.%lx\n",
						pConnObj, pIrp));

				AtalkPapCleanupConnection((PPAP_CONNOBJ)pConnObj);
				AtalkPapConnDereference((PPAP_CONNOBJ)pConnObj);
			}
			error = ATALK_PENDING;
			break;

		  case ATALK_DEV_ADSP:
			ASSERT(VALID_ADSPCO((PADSP_CONNOBJ)pConnObj));
			AtalkAdspConnReferenceByPtr((PADSP_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				//	No need to have lock as we have a reference.
				((PADSP_CONNOBJ)pConnObj)->adspco_CleanupComp = atalkTdiGenericComplete;
				((PADSP_CONNOBJ)pConnObj)->adspco_CleanupCtx  = pIrp;
				AtalkAdspCleanupConnection((PADSP_CONNOBJ)pConnObj);
				AtalkAdspConnDereference((PADSP_CONNOBJ)pConnObj);
			}
			error = ATALK_PENDING;
			break;

		  case ATALK_DEV_ASPC:
			ASSERT(VALID_ASPCCO((PASPC_CONNOBJ)pConnObj));
			AtalkAspCConnReference((PASPC_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				//	No need to have lock as we have a reference.
				((PASPC_CONNOBJ)pConnObj)->aspcco_CleanupComp = atalkTdiGenericComplete;
				((PASPC_CONNOBJ)pConnObj)->aspcco_CleanupCtx  = pIrp;
				AtalkAspCCleanupConnection((PASPC_CONNOBJ)pConnObj);
				AtalkAspCConnDereference((PASPC_CONNOBJ)pConnObj);
			}
			error = ATALK_PENDING;
			break;

		  case ATALK_DEV_DDP:
		  default:

			// The device type in the Ctx field can never be anything
			// other than the above! Internal protocol error. KeBugCheck.
			DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_ERR,
					("AtalkTdiCleanupConnection: Invalid device type\n"));
			error = ATALK_INVALID_REQUEST;
			break;
		}

	} while (FALSE);

	return AtalkErrorToNtStatus(error);
}



NTSTATUS
AtalkTdiCloseAddress(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine removes the creation reference on the object. It also
	sets up the closepIrp for completion.

Arguments:

	pIrp - The close irp
	Context - The DeviceContext of the device on which close is happening

Return Value:

	STATUS_SUCCESS if successfully setup
	Error otherwise.

--*/
{
	ATALK_ERROR	error;
	PVOID		pAddrObj = pIrpSp->FileObject->FsContext;

	do
	{
		// Now depending on the requested device...
		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_DDP:
			ASSERT(VALID_DDP_ADDROBJ(((PDDP_ADDROBJ)pAddrObj)));
			AtalkDdpReferenceByPtr(((PDDP_ADDROBJ)pAddrObj), &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkDdpCloseAddress((PDDP_ADDROBJ)pAddrObj,
											  atalkTdiCloseAddressComplete,
											  pIrp);

				AtalkDdpDereference(((PDDP_ADDROBJ)pAddrObj));
			}
			break;

		  case ATALK_DEV_ASPC:
			ASSERT(VALID_ASPCAO((PASPC_ADDROBJ)pAddrObj));
			AtalkAspCAddrReference((PASPC_ADDROBJ)pAddrObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkAspCCloseAddress((PASPC_ADDROBJ)pAddrObj,
											  atalkTdiCloseAddressComplete,
											  pIrp);

				AtalkAspCAddrDereference((PASPC_ADDROBJ)pAddrObj);
			}
			break;

		  case ATALK_DEV_ASP:
			ASSERT(VALID_ASPAO((PASP_ADDROBJ)pAddrObj));
			if (AtalkAspReferenceAddr((PASP_ADDROBJ)pAddrObj) != NULL)
			{
				error = AtalkAspCloseAddress((PASP_ADDROBJ)pAddrObj,
											  atalkTdiCloseAddressComplete,
											  pIrp);
				AtalkAspDereferenceAddr((PASP_ADDROBJ)pAddrObj);
			}
			break;

		  case ATALK_DEV_PAP:
			ASSERT(VALID_PAPAO((PPAP_ADDROBJ)pAddrObj));
			AtalkPapAddrReference((PPAP_ADDROBJ)pAddrObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkPapCloseAddress((PPAP_ADDROBJ)pAddrObj,
											  atalkTdiCloseAddressComplete,
											  pIrp);

				AtalkPapAddrDereference((PPAP_ADDROBJ)pAddrObj);
			}
			break;

		  case ATALK_DEV_ADSP:
			ASSERT(VALID_ADSPAO((PADSP_ADDROBJ)pAddrObj));
			AtalkAdspAddrReference((PADSP_ADDROBJ)pAddrObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkAdspCloseAddress((PADSP_ADDROBJ)pAddrObj,
											  atalkTdiCloseAddressComplete,
											  pIrp);

				AtalkAdspAddrDereference((PADSP_ADDROBJ)pAddrObj);
			}
			break;

		  default:

			// The device type in the Ctx field can never be anything
			// other than the above! Internal protocol error. KeBugCheck.
			error = ATALK_INVALID_REQUEST;
			DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_ERR,
					("AtalkTdiCloseAddress: Invalid device type\n"));
			break;
		}

	} while (FALSE);

	if (error == ATALK_NO_ERROR)
		error = ATALK_PENDING;

	return AtalkErrorToNtStatus(error);
}




NTSTATUS
AtalkTdiCloseConnection(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine removes the creation reference on the object. It also
	sets up the closepIrp for completion.

Arguments:

	pIrp - The close irp
	Context - The DeviceContext of the device on which close is happening

Return Value:

	STATUS_SUCCESS if successfully setup
	Error otherwise.

--*/
{
	ATALK_ERROR		error = ATALK_INVALID_REQUEST;
	PVOID		pConnObj = pIrpSp->FileObject->FsContext;

	if ((pCtx->adc_DevType == ATALK_DEV_PAP) ||
		(pCtx->adc_DevType == ATALK_DEV_ADSP)||
		(pCtx->adc_DevType == ATALK_DEV_ASPC))
	do
	{
		// Now depending on the requested device...
		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_PAP:
			ASSERT(VALID_PAPCO((PPAP_CONNOBJ)pConnObj));
			AtalkPapConnReferenceByPtr((PPAP_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkPapCloseConnection((PPAP_CONNOBJ)pConnObj,
												atalkTdiCloseAddressComplete,
												pIrp);

				AtalkPapConnDereference((PPAP_CONNOBJ)pConnObj);
			}
			break;

		  case ATALK_DEV_ADSP:
			ASSERT(VALID_ADSPCO((PADSP_CONNOBJ)pConnObj));
			AtalkAdspConnReferenceByPtr((PADSP_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkAdspCloseConnection((PADSP_CONNOBJ)pConnObj,
												 atalkTdiCloseAddressComplete,
												 pIrp);

				AtalkAdspConnDereference((PADSP_CONNOBJ)pConnObj);
			}
			break;

		  case ATALK_DEV_ASPC:
			ASSERT(VALID_ASPCCO((PASPC_CONNOBJ)pConnObj));
			AtalkAspCConnReference((PASPC_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkAspCCloseConnection((PASPC_CONNOBJ)pConnObj,
												 atalkTdiCloseAddressComplete,
												 pIrp);

				AtalkAspCConnDereference((PASPC_CONNOBJ)pConnObj);
			}
			break;
		}

	} while (FALSE);

	if (error == ATALK_NO_ERROR)
		error = ATALK_PENDING;

	return AtalkErrorToNtStatus(error);
}




NTSTATUS
AtalkTdiCloseControlChannel(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine removes the creation reference on the object. It also
	sets up the closepIrp for completion.

Arguments:

	pIrp - The close irp
	Context - The DeviceContext of the device on which close is happening

Return Value:

	STATUS_SUCCESS if successfully setup
	Error otherwise.

--*/
{
	ATALK_ERROR		error;
	PVOID			pCtrlChnl = pIrpSp->FileObject->FsContext;

	ASSERT(VALID_DDP_ADDROBJ((PDDP_ADDROBJ)pCtrlChnl));
	AtalkDdpReferenceByPtr(((PDDP_ADDROBJ)pCtrlChnl), &error);
	if (ATALK_SUCCESS(error))
	{
		error = AtalkDdpCloseAddress(((PDDP_ADDROBJ)pCtrlChnl),
									  atalkTdiCloseAddressComplete,
									  pIrp);
		AtalkDdpDereference(((PDDP_ADDROBJ)pCtrlChnl));
	}

	return AtalkErrorToNtStatus(error);
}




NTSTATUS
AtalkTdiAssociateAddress(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine will associate the connection object with the specified
	address object.

	This routine is pretty much provider independent. All we check for is
	that the address object and the provider object belong to the same device.
	Also, this routine will complete synchronously.

Arguments:


Return Value:

	STATUS_SUCCESS if successfully completed
	Error otherwise.

--*/
{
	ATALK_ERROR		error = ATALK_INVALID_REQUEST;
	PVOID			pAddrObj;
	PVOID			pConnObj = pIrpSp->FileObject->FsContext;
	PFILE_OBJECT	pFileObj = NULL;
	HANDLE			AddrObjHandle =
			((PTDI_REQUEST_KERNEL_ASSOCIATE)(&pIrpSp->Parameters))->AddressHandle;

	if ((pCtx->adc_DevType == ATALK_DEV_PAP) ||
		(pCtx->adc_DevType == ATALK_DEV_ADSP)||
		(pCtx->adc_DevType == ATALK_DEV_ASPC))
	do
	{
		// Get the handle to the address object from the irp and map it to
		// the corres. file object.
		{
			NTSTATUS		status;
	
			status = ObReferenceObjectByHandle(AddrObjHandle,
											   0,
											   0,
											   KernelMode,
											   (PVOID *)&pFileObj,
										   NULL);
			ASSERT (NT_SUCCESS(status));
		}

		pAddrObj = pFileObj->FsContext;

		ASSERT(((LONG)pFileObj->FsContext2 >> 16) == pCtx->adc_DevType);

		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_PAP:

			ASSERT(VALID_PAPAO((PPAP_ADDROBJ)pAddrObj));
			AtalkPapAddrReference((PPAP_ADDROBJ)pAddrObj, &error);
			if (ATALK_SUCCESS(error))
			{
				ASSERT(VALID_PAPCO((PPAP_CONNOBJ)pConnObj));
				AtalkPapConnReferenceByPtr((PPAP_CONNOBJ)pConnObj, &error);
				if (ATALK_SUCCESS(error))
				{
					error = AtalkPapAssociateAddress((PPAP_ADDROBJ)pAddrObj,
													 (PPAP_CONNOBJ)pConnObj);
					AtalkPapConnDereference((PPAP_CONNOBJ)pConnObj);
				}

				AtalkPapAddrDereference((PPAP_ADDROBJ)pAddrObj);
			}
			break;

		  case ATALK_DEV_ADSP:
			ASSERT(VALID_ADSPAO((PADSP_ADDROBJ)pAddrObj));
			AtalkAdspAddrReference((PADSP_ADDROBJ)pAddrObj, &error);
			if (ATALK_SUCCESS(error))
			{
				ASSERT(VALID_ADSPCO((PADSP_CONNOBJ)pConnObj));
				AtalkAdspConnReferenceByPtr((PADSP_CONNOBJ)pConnObj, &error);
				if (ATALK_SUCCESS(error))
				{
					error = AtalkAdspAssociateAddress((PADSP_ADDROBJ)pAddrObj,
													  (PADSP_CONNOBJ)pConnObj);
					AtalkAdspConnDereference((PADSP_CONNOBJ)pConnObj);
				}

				AtalkAdspAddrDereference((PADSP_ADDROBJ)pAddrObj);
			}
			break;

		  case ATALK_DEV_ASPC:
			ASSERT(VALID_ASPCAO((PASPC_ADDROBJ)pAddrObj));
			AtalkAspCAddrReference((PASPC_ADDROBJ)pAddrObj, &error);
			if (ATALK_SUCCESS(error))
			{
				ASSERT(VALID_ASPCCO((PASPC_CONNOBJ)pConnObj));
				AtalkAspCConnReference((PASPC_CONNOBJ)pConnObj, &error);
				if (ATALK_SUCCESS(error))
				{
					error = AtalkAspCAssociateAddress((PASPC_ADDROBJ)pAddrObj,
													  (PASPC_CONNOBJ)pConnObj);
					AtalkAspCConnDereference((PASPC_CONNOBJ)pConnObj);
				}

				AtalkAspCAddrDereference((PASPC_ADDROBJ)pAddrObj);
			}
			break;
		}

		// Dereference the file object corres. to the address object
		ObDereferenceObject(pFileObj);

	} while (FALSE);

	return AtalkErrorToNtStatus(error);
}




NTSTATUS
AtalkTdiDisassociateAddress(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine performs a disassociate. This request is only valid when
	the connection is in a purely ASSOCIATED state.

Arguments:


Return Value:

	STATUS_SUCCESS if successfully completed
	Error otherwise.

--*/
{
	ATALK_ERROR	error = ATALK_INVALID_REQUEST;
	PVOID		pConnObj = pIrpSp->FileObject->FsContext;

	if ((pCtx->adc_DevType == ATALK_DEV_PAP) ||
		(pCtx->adc_DevType == ATALK_DEV_ADSP)||
		(pCtx->adc_DevType == ATALK_DEV_ASPC))
	do
	{
		ASSERT(((LONG)pIrpSp->FileObject->FsContext2 >> 16) == pCtx->adc_DevType);

		// Now depending on the requested device...
		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_PAP:
			// Reference the connection object
			ASSERT(VALID_PAPCO((PPAP_CONNOBJ)pConnObj));
			AtalkPapConnReferenceByPtr((PPAP_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkPapDissociateAddress(pConnObj);

				AtalkPapConnDereference((PPAP_CONNOBJ)pConnObj);
			}
			break;

		  case ATALK_DEV_ADSP:
			// Reference the connection object
			ASSERT(VALID_ADSPCO((PADSP_CONNOBJ)pConnObj));
			AtalkAdspConnReferenceByPtr((PADSP_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkAdspDissociateAddress(pConnObj);

				AtalkAdspConnDereference((PADSP_CONNOBJ)pConnObj);
			}
			break;

		  case ATALK_DEV_ASPC:
			// Reference the connection object
			ASSERT(VALID_ASPCCO((PASPC_CONNOBJ)pConnObj));
			AtalkAspCConnReference((PASPC_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkAspCDissociateAddress(pConnObj);

				AtalkAspCConnDereference((PASPC_CONNOBJ)pConnObj);
			}
			break;
		}

	} while (FALSE);

	return AtalkErrorToNtStatus(error);
}




NTSTATUS
AtalkTdiConnect(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine will post a connect request with the portable stack.

Arguments:

Return Value:

	STATUS_PENDING if successfully started
	Error otherwise.

--*/
{
	PTDI_REQUEST_KERNEL_CONNECT	parameters;
	PTA_APPLETALK_ADDRESS		remoteTdiAddr;
	ATALK_ADDR					remoteAddr;
	ATALK_ERROR					error = ATALK_INVALID_REQUEST;
	PVOID						pConnObj = pIrpSp->FileObject->FsContext;

	parameters 		= (PTDI_REQUEST_KERNEL_CONNECT)&pIrpSp->Parameters;
	remoteTdiAddr  	= (PTA_APPLETALK_ADDRESS)
						parameters->RequestConnectionInformation->RemoteAddress;

	DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_INFO,
			("AtalkTdiConnect: Net %x Node %x Socket %x\n",
			remoteTdiAddr->Address[0].Address[0].Network,
			remoteTdiAddr->Address[0].Address[0].Node,
			remoteTdiAddr->Address[0].Address[0].Socket));

	DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_INFO,
			("AtalkConnPostConnect: Cnt %x\n", remoteTdiAddr->TAAddressCount));

	TDI_TO_ATALKADDR(&remoteAddr, remoteTdiAddr);

	DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_INFO,
			("AtalkTdiConnect: Portable Net %x Node %x Socket %x\n",
			remoteAddr.ata_Network, remoteAddr.ata_Node, remoteAddr.ata_Socket));

	if ((pCtx->adc_DevType == ATALK_DEV_PAP) ||
		(pCtx->adc_DevType == ATALK_DEV_ADSP)||
		(pCtx->adc_DevType == ATALK_DEV_ASPC))
	do
	{
		ASSERT(((LONG)pIrpSp->FileObject->FsContext2 >> 16) == pCtx->adc_DevType);

		// Now depending on the requested device...
		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_PAP:
			ASSERT(VALID_PAPCO((PPAP_CONNOBJ)pConnObj));
			AtalkPapConnReferenceByPtr((PPAP_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkPapPostConnect((PPAP_CONNOBJ)pConnObj,
											&remoteAddr,
											pIrp,
											atalkTdiGenericComplete);

				AtalkPapConnDereference((PPAP_CONNOBJ)pConnObj);
			}
			break;

		  case ATALK_DEV_ADSP:
			ASSERT(VALID_ADSPCO((PADSP_CONNOBJ)pConnObj));
			AtalkAdspConnReferenceByPtr((PADSP_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkAdspPostConnect((PADSP_CONNOBJ)pConnObj,
											 &remoteAddr,
											 pIrp,
											 atalkTdiGenericComplete);

				AtalkAdspConnDereference((PADSP_CONNOBJ)pConnObj);
			}
			break;

		  case ATALK_DEV_ASPC:
			ASSERT(VALID_ASPCCO((PASPC_CONNOBJ)pConnObj));
			AtalkAspCConnReference((PASPC_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkAspCPostConnect((PASPC_CONNOBJ)pConnObj,
											 &remoteAddr,
											 pIrp,
											 atalkTdiGenericComplete);

				AtalkAspCConnDereference((PASPC_CONNOBJ)pConnObj);
			}
			break;
		}
	} while (FALSE);

	return AtalkErrorToNtStatus(error);
}




NTSTATUS
AtalkTdiDisconnect(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine will disconnect an active connection or cancel a posted
	listen/connect

Arguments:

				
Return Value:

	STATUS_SUCCESS if successfully completed
	STATUS_PENDING if successfully started
	Error otherwise.

--*/
{
	ATALK_ERROR	error = ATALK_INVALID_REQUEST;
	PVOID		pConnObj = pIrpSp->FileObject->FsContext;

	if ((pCtx->adc_DevType == ATALK_DEV_PAP) ||
		(pCtx->adc_DevType == ATALK_DEV_ADSP)||
		(pCtx->adc_DevType == ATALK_DEV_ASPC))
	do
	{
		ASSERT(((LONG)pIrpSp->FileObject->FsContext2 >> 16) == pCtx->adc_DevType);

		// Now depending on the requested device...
		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_PAP:
			ASSERT(VALID_PAPCO((PPAP_CONNOBJ)pConnObj));
			AtalkPapConnReferenceByPtr((PPAP_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkPapDisconnect((PPAP_CONNOBJ)pConnObj,
											ATALK_LOCAL_DISCONNECT,
											pIrp,
											atalkTdiGenericComplete);

				AtalkPapConnDereference((PPAP_CONNOBJ)pConnObj);
			}
			break;

		  case ATALK_DEV_ADSP:
			ASSERT(VALID_ADSPCO((PADSP_CONNOBJ)pConnObj));
			AtalkAdspConnReferenceByPtr((PADSP_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkAdspDisconnect((PADSP_CONNOBJ)pConnObj,
											ATALK_LOCAL_DISCONNECT,
											pIrp,
											atalkTdiGenericComplete);

				AtalkAdspConnDereference((PADSP_CONNOBJ)pConnObj);
			}
			break;

		  case ATALK_DEV_ASPC:
			ASSERT(VALID_ASPCCO((PASPC_CONNOBJ)pConnObj));
			AtalkAspCConnReference((PASPC_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkAspCDisconnect((PASPC_CONNOBJ)pConnObj,
											ATALK_LOCAL_DISCONNECT,
											pIrp,
											atalkTdiGenericComplete);

				AtalkAspCConnDereference((PASPC_CONNOBJ)pConnObj);
			}
			break;
		}
	} while (FALSE);

	return AtalkErrorToNtStatus(error);
}




NTSTATUS
AtalkTdiAccept(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine

Arguments:

				
Return Value:

	STATUS_SUCCESS if successfully completed
	STATUS_PENDING if successfully started
	Error otherwise.

--*/
{
	NTSTATUS	status = STATUS_SUCCESS;

	do
	{
		ASSERT(((LONG)pIrpSp->FileObject->FsContext2 >> 16) == pCtx->adc_DevType);

		// Now depending on the requested device...
		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_ADSP:
		  case ATALK_DEV_PAP:
			break;

		  default:
			// The device type in the Ctx field can never be anything
			// other than the above! Internal protocol error. KeBugCheck.
			DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_ERR,
					("AtalkTdiAccept: Invalid device type\n"));
		  case ATALK_DEV_DDP:
		  case ATALK_DEV_ASPC:
		  case ATALK_DEV_ASP:
			status = STATUS_INVALID_DEVICE_REQUEST;
			break;
		}
	} while (FALSE);

	return status;
}




NTSTATUS
AtalkTdiListen(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine

Arguments:

				
Return Value:

	STATUS_PENDING if successfully started
	Error otherwise.

--*/
{
	ATALK_ERROR	error = ATALK_INVALID_REQUEST;
	PVOID		pConnObj = pIrpSp->FileObject->FsContext;

	if ((pCtx->adc_DevType == ATALK_DEV_PAP) ||
		(pCtx->adc_DevType == ATALK_DEV_ADSP))
	do
	{
		ASSERT(((LONG)pIrpSp->FileObject->FsContext2 >> 16) == pCtx->adc_DevType);

		// Now depending on the requested device...
		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_PAP:
			ASSERT(VALID_PAPCO((PPAP_CONNOBJ)pConnObj));
			AtalkPapConnReferenceByPtr((PPAP_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkPapPostListen((PPAP_CONNOBJ)pConnObj,
											pIrp,
											atalkTdiGenericComplete);
				AtalkPapConnDereference((PPAP_CONNOBJ)pConnObj);
			}
			break;

		  case ATALK_DEV_ADSP:
			ASSERT(VALID_ADSPCO((PADSP_CONNOBJ)pConnObj));
			AtalkAdspConnReferenceByPtr((PADSP_CONNOBJ)pConnObj, &error);
			if (ATALK_SUCCESS(error))
			{
				error = AtalkAdspPostListen((PADSP_CONNOBJ)pConnObj,
											pIrp,
											atalkTdiGenericComplete);
				AtalkAdspConnDereference((PADSP_CONNOBJ)pConnObj);
			}
			break;
		}

	} while (FALSE);

	if (error == ATALK_NO_ERROR)
		error = ATALK_PENDING;

	return AtalkErrorToNtStatus(error);
}




NTSTATUS
AtalkTdiSendDgram(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine sends a datagram.

Arguments:

				
Return Value:

	STATUS_PENDING if successfully started
	Error otherwise.

--*/
{
	NTSTATUS		status = STATUS_SUCCESS;
	SEND_COMPL_INFO	SendInfo;

	do
	{
		// Now depending on the requested device...
		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_DDP:
			{
				PTDI_REQUEST_KERNEL_SENDDG	pParam;
				PBUFFER_DESC				pBufDesc;
				ATALK_ERROR					error;
				PTA_APPLETALK_ADDRESS		pTaDest;
				ATALK_ADDR					AtalkAddr;
				PDDP_ADDROBJ				pDdpAddr;

				pDdpAddr  = (PDDP_ADDROBJ)pIrpSp->FileObject->FsContext;
				ASSERT(VALID_DDP_ADDROBJ(pDdpAddr));
				
				pParam = (PTDI_REQUEST_KERNEL_SENDDG)&pIrpSp->Parameters;
				pTaDest = (PTA_APPLETALK_ADDRESS)
								pParam->SendDatagramInformation->RemoteAddress;
			
				DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO,
						("DDP: SendDatagram - Net %x Node %x Socket %x\n",
							pTaDest->Address[0].Address[0].Network,
							pTaDest->Address[0].Address[0].Node,
							pTaDest->Address[0].Address[0].Socket));
			
				if ((pTaDest->Address[0].AddressType != TDI_ADDRESS_TYPE_APPLETALK) ||
					(pTaDest->Address[0].AddressLength != sizeof(TDI_ADDRESS_APPLETALK)))
				{
					DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO,
							("DDP: Error SendDatagram - Type %x\n Len %d\n",
								pTaDest->Address[0].AddressType,
								pTaDest->Address[0].AddressLength));
			
					status = STATUS_INVALID_ADDRESS;
				}
				else
				{
					ULONG	sendLength;
			
					AtalkAddr.ata_Network = pTaDest->Address[0].Address[0].Network;
					AtalkAddr.ata_Node = pTaDest->Address[0].Address[0].Node;
					AtalkAddr.ata_Socket = pTaDest->Address[0].Address[0].Socket;
			
					// Get the length of the send mdl
					sendLength = AtalkSizeMdlChain(pIrp->MdlAddress);

					//	Check destination address
					if (INVALID_ADDRESS(&AtalkAddr))
					{
						error = ATALK_DDP_INVALID_ADDR;
					}
				
					if (sendLength > MAX_DGRAM_SIZE)
					{
						error = ATALK_BUFFER_TOO_BIG;
					}

					else if ((pBufDesc = AtalkAllocBuffDesc(pIrp->MdlAddress,
															(USHORT)sendLength,
															0)) != NULL)
					{
						SendInfo.sc_TransmitCompletion = atalkTdiSendDgramComplete;
						SendInfo.sc_Ctx1 = pDdpAddr;
						SendInfo.sc_Ctx2 = pBufDesc;
						SendInfo.sc_Ctx3 = pIrp;
						error = AtalkDdpSend(pDdpAddr,
											 &AtalkAddr,
											 pDdpAddr->ddpao_Protocol,
											 FALSE,
											 pBufDesc,
											 NULL,	// OptHdr
											 0,		// OptHdrLen
											 NULL,	// ZoneMcastAddr
											 &SendInfo);

						if (!ATALK_SUCCESS(error))
						{
							atalkTdiSendDgramComplete(NDIS_STATUS_FAILURE,
													  &SendInfo);

							error = ATALK_PENDING;
						}
					}

					else error	= ATALK_RESR_MEM;
								
					status = AtalkErrorToNtStatus(error);
				}
			}
			break;

		  case ATALK_DEV_ASPC:
		  case ATALK_DEV_ADSP:
		  case ATALK_DEV_ASP:
		  case ATALK_DEV_PAP:
			status = STATUS_INVALID_DEVICE_REQUEST;
			break;

		  default:
			// The device type in the Ctx field can never be anything
			// other than the above! Internal protocol error. KeBugCheck.
			DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_ERR,
					("AtalkTdiSendDatagram: Invalid device type\n"));
			break;
		}

	} while (FALSE);

	return status;
}




NTSTATUS
AtalkTdiReceiveDgram(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine receives a datagram.

Arguments:

				
Return Value:

	STATUS_PENDING if successfully started
	Error otherwise.

--*/
{
	ATALK_ERROR	error = ATALK_INVALID_REQUEST;

	if (pCtx->adc_DevType == ATALK_DEV_DDP)
	{
		PDDP_ADDROBJ					pDdpAddr;
		PTDI_REQUEST_KERNEL_RECEIVEDG	parameters =
										(PTDI_REQUEST_KERNEL_RECEIVEDG)&pIrpSp->Parameters;

		pDdpAddr  = (PDDP_ADDROBJ)pIrpSp->FileObject->FsContext;
		ASSERT(VALID_DDP_ADDROBJ(pDdpAddr));

		error = AtalkDdpReceive(pDdpAddr,
								pIrp->MdlAddress,
								(USHORT)AtalkSizeMdlChain(pIrp->MdlAddress),
								parameters->ReceiveFlags,
								atalkTdiRecvDgramComplete,
								pIrp);

	}

	return AtalkErrorToNtStatus(error);
}




NTSTATUS
AtalkTdiSend(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine sends the data specified. (used by PAP/ADSP only)

Arguments:

				
Return Value:

	STATUS_SUCCESS if successfully completed
	STATUS_PENDING if successfully started
	Error otherwise.

--*/
{
	PTDI_REQUEST_KERNEL_SEND	parameters;
	ATALK_ERROR					error = ATALK_INVALID_REQUEST;
	PVOID						pConnObj= pIrpSp->FileObject->FsContext;

	parameters = (PTDI_REQUEST_KERNEL_SEND)&pIrpSp->Parameters;

	if ((pCtx->adc_DevType == ATALK_DEV_PAP) ||
		(pCtx->adc_DevType == ATALK_DEV_ADSP))
	do
	{
		// Now depending on the requested device...
		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_ADSP:
			error = AtalkAdspWrite(pConnObj,
								   pIrp->MdlAddress,
								   (USHORT)parameters->SendLength,
								   parameters->SendFlags,
								   pIrp,
								   atalkTdiGenericWriteComplete);

			if (!ATALK_SUCCESS(error))
			{
				DBGPRINT(DBG_COMP_ADSP, DBG_LEVEL_INFO,
						("AtalkAdspWrite: Failed for conn %lx.%lx error %lx\n",
						pConnObj, ((PADSP_CONNOBJ)pConnObj)->adspco_Flags, error));
			}
			break;

		  case ATALK_DEV_PAP:
			error = AtalkPapWrite(pConnObj,
								pIrp->MdlAddress,
								(USHORT)parameters->SendLength,
								parameters->SendFlags,
								pIrp,
								atalkTdiGenericWriteComplete);

			if (!ATALK_SUCCESS(error))
			{
				DBGPRINT(DBG_COMP_PAP, DBG_LEVEL_INFO,
						("AtalkPapWrite: Failed for conn %lx.%lx error %lx\n",
						pConnObj, ((PPAP_CONNOBJ)pConnObj)->papco_Flags, error));
			}
			break;
		}

	} while (FALSE);

	return AtalkErrorToNtStatus(error);
}




NTSTATUS
AtalkTdiReceive(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine receives data. (used by PAP/ADSP only)

Arguments:

				
Return Value:

	STATUS_SUCCESS if successfully completed
	STATUS_PENDING if successfully started
	Error otherwise.

--*/
{
	ATALK_ERROR					error = ATALK_INVALID_REQUEST;
	PVOID						pConnObj= pIrpSp->FileObject->FsContext;
	PTDI_REQUEST_KERNEL_RECEIVE	parameters =
									(PTDI_REQUEST_KERNEL_RECEIVE)&pIrpSp->Parameters;

	if ((pCtx->adc_DevType == ATALK_DEV_PAP) ||
		(pCtx->adc_DevType == ATALK_DEV_ASPC)||
		(pCtx->adc_DevType == ATALK_DEV_ADSP))
	do
	{
		// Now depending on the requested device...
		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_PAP:
			error = AtalkPapRead(pConnObj,
								 pIrp->MdlAddress,
								 (USHORT)parameters->ReceiveLength,
								 parameters->ReceiveFlags,
								 pIrp,
								 atalkTdiGenericReadComplete);

			if (!ATALK_SUCCESS(error))
			{
				DBGPRINT(DBG_COMP_PAP, DBG_LEVEL_INFO,
						("AtalkPapRead: Failed for conn %lx.%lx error %lx\n",
						pConnObj, ((PPAP_CONNOBJ)pConnObj)->papco_Flags, error));
			}
			break;

		  case ATALK_DEV_ADSP:
			error = AtalkAdspRead(pConnObj,
								  pIrp->MdlAddress,
								  (USHORT)parameters->ReceiveLength,
								  parameters->ReceiveFlags,
								  pIrp,
								  atalkTdiGenericReadComplete);

			if (!ATALK_SUCCESS(error))
			{
				DBGPRINT(DBG_COMP_ADSP, DBG_LEVEL_INFO,
						("AtalkAdspRead: Failed for conn %lx.%lx error %lx\n",
						pConnObj, ((PADSP_CONNOBJ)pConnObj)->adspco_Flags, error));
			}
			break;

		  case ATALK_DEV_ASPC:
			error = AtalkAspCGetAttn(pConnObj,
									 pIrp->MdlAddress,
									 (USHORT)parameters->ReceiveLength,
									 parameters->ReceiveFlags,
									 pIrp,
									 atalkTdiGenericReadComplete);

			if (!ATALK_SUCCESS(error))
			{
				DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
						("AtalkAspCGetAttn: Failed for conn %lx.%lx error %lx\n",
						pConnObj, ((PASPC_CONNOBJ)pConnObj)->aspcco_Flags, error));
			}
			break;
		}
	
	} while (FALSE);

	return AtalkErrorToNtStatus(error);
}



NTSTATUS
AtalkTdiAction(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine is the dispatch routine for all the TdiAction primitives
	for all the providers

Arguments:

				
Return Value:

	STATUS_SUCCESS if successfully completed
	STATUS_PENDING if successfully started
	Error otherwise.

--*/
{
	NTSTATUS			status = STATUS_SUCCESS;
	ATALK_ERROR			error  = ATALK_NO_ERROR;
	USHORT				bufLen;
	USHORT				actionCode, Flags;
	PTDI_ACTION_HEADER	pActionHdr;
	PMDL				pMdl = pIrp->MdlAddress;
	PVOID				pObject;
	USHORT				ObjectType;
	USHORT				DevType;
	BOOLEAN				freeHdr = FALSE;

	do
	{
		if (pMdl == NULL)
		{
			status = STATUS_INVALID_PARAMETER;
			break;
		}
	
		bufLen = (USHORT)AtalkSizeMdlChain(pIrp->MdlAddress);
	
		// If we atleast do not have the action header, return
		if (bufLen < sizeof(TDI_ACTION_HEADER))
		{
			status = STATUS_INVALID_PARAMETER;
		}
	
		if (AtalkIsMdlFragmented(pMdl))
		{
			ULONG	bytesCopied;

			if ((pActionHdr = AtalkAllocMemory(sizeof(TDI_ACTION_HEADER))) == NULL)
			{
				status = STATUS_INSUFFICIENT_RESOURCES;
				break;
			}

			freeHdr = TRUE;

			//	Copy the header to this buffer
			status = TdiCopyMdlToBuffer(pMdl,
										0,							// SrcOff
										pActionHdr,
										0,							// Dest Off
										sizeof(TDI_ACTION_HEADER),
										&bytesCopied);

			ASSERT(NT_SUCCESS(status) && (bytesCopied == sizeof(TDI_ACTION_HEADER)));

			if (!NT_SUCCESS(status))
				break;
		}
		else
		{
			pActionHdr = (PTDI_ACTION_HEADER)MmGetSystemAddressForMdl(pMdl);
		}
	
		DBGPRINT(DBG_COMP_ACTION, DBG_LEVEL_INFO,
				("AtalkTdiAction - code %lx BufLen %d SysAddress %lx\n",
				pActionHdr->ActionCode, bufLen, pActionHdr));
	
		// If the MATK identifier is not present, we return
		if (pActionHdr->TransportId != MATK)
		{
			status = STATUS_INVALID_DEVICE_REQUEST;
			break;
		}
	
		actionCode = pActionHdr->ActionCode;
		if ((actionCode < MIN_COMMON_ACTIONCODE) ||
			(actionCode > MAX_ALLACTIONCODES))
		{
			DBGPRINT(DBG_COMP_ACTION, DBG_LEVEL_ERR,
					("AtalkTdiAction - Invalid action code %d\n", actionCode));
	
			status = STATUS_INVALID_PARAMETER;
			break;
		}

		if (bufLen < AtalkActionDispatch[actionCode]._MinBufLen)
		{
			DBGPRINT(DBG_COMP_ACTION, DBG_LEVEL_ERR,
					("AtalkTdiAction - Minbuflen %d Expected %d\n",
					bufLen, AtalkActionDispatch[actionCode]._MinBufLen));

			status = STATUS_BUFFER_TOO_SMALL;
			break;
		}

		Flags = AtalkActionDispatch[actionCode]._Flags;

		pObject = (PVOID)pIrpSp->FileObject->FsContext;
		ObjectType = (USHORT)((ULONG)(pIrpSp->FileObject->FsContext2) & 0xFF);
		DevType = (USHORT)((ULONG)(pIrpSp->FileObject->FsContext2) >> 16);
		// Convert control channel operations to Ddp
		if (ObjectType == TDI_CONTROL_CHANNEL_FILE)
			DevType = ATALK_DEV_DDP;

		// Verify the device type is that expected. Either the request
		// should be valid for any device or the type of device for the
		// request should match the type of device expected.
		if ((AtalkActionDispatch[actionCode]._DeviceType != ATALK_DEV_ANY) &&
			((pCtx->adc_DevType != AtalkActionDispatch[actionCode]._DeviceType) ||
			 (DevType != AtalkActionDispatch[actionCode]._DeviceType)))
		{
			status = STATUS_INVALID_DEVICE_REQUEST;
			break;
		}

		// Verify the object - it has to be one of those specified as valid
		// in the dispatch table for this action call.
		ASSERT(ObjectType & (DFLAG_ADDR | DFLAG_CNTR | DFLAG_CONN));

		switch (ObjectType)
		{
		  case TDI_TRANSPORT_ADDRESS_FILE:
			if (!(Flags & DFLAG_ADDR))
				status = STATUS_INVALID_HANDLE;
			break;

		  case TDI_CONNECTION_FILE:
			if (!(Flags & DFLAG_CONN))
				status = STATUS_INVALID_HANDLE;
			break;

		  case TDI_CONTROL_CHANNEL_FILE:
			if (!(Flags & DFLAG_CNTR))
				status = STATUS_INVALID_HANDLE;
			break;

		  default:
			status = STATUS_INVALID_HANDLE;
			break;
		}

	} while (FALSE);

	if (!NT_SUCCESS(status))
	{
		if (freeHdr)
		{
			AtalkFreeMemory(pActionHdr);
		}
		return status;
	}


	// Handle the requests based on the action code.
	// Use the table to call the appropriate routine

	do
	{
		PACTREQ				pActReq;
		USHORT				offset = AtalkActionDispatch[actionCode]._ActionBufSize;
		USHORT				size = bufLen - offset;

		// If DFLAG_MDL is set, then we know we have to create the mdl.
		//
		// NOTE:	User can pass in invalid sizes...
		//			Also, it is assumed that BuildMdl will not change
		//			value of the mdl unless it can successfully build
		//			all of it. Therefore, error cases must preserve
		//			value of NULL.
		//

		// First allocate an action request structure.
		// !!!This memory should be zeroed out as we depend on extra mdl pointer to
		//	be NULL!!!
		if ((pActReq = AtalkAllocZeroedMemory(sizeof(ACTREQ))) == NULL)
		{
			status = STATUS_INSUFFICIENT_RESOURCES;
			break;
		}
#if	DBG
		pActReq->ar_Signature = ACTREQ_SIGNATURE;
#endif
		pActReq->ar_pIrp = pIrp;
		pActReq->ar_DevType = DevType;
		pActReq->ar_pParms = (PBYTE)pActionHdr + sizeof(TDI_ACTION_HEADER);
		pActReq->ar_Completion = atalkTdiActionComplete;
		pActReq->ar_ActionCode = actionCode;
		pActReq->ar_pAMdl =	NULL;
		pActReq->ar_MdlSize = 0;

		if (Flags & DFLAG_MDL)
		{
			ASSERT((size >= 0) && ((offset+size) <= bufLen));
			pActReq->ar_MdlSize = size;

			if ((size < 0) || ((offset+size) > bufLen))
			{
				AtalkFreeMemory(pActReq);
				status = STATUS_INVALID_PARAMETER;
				break;
			}

			// If size is zero, we go on to the next mdl.
			// IoAllocateMdl will fail for a 0-length mdl
			// If size < 0, we will hit the error later.
			if (size != 0)
			{
				DBGPRINT(DBG_COMP_ACTION, DBG_LEVEL_INFO,
						("AtalkTdiAction - Size of mdl %lx\n", size));
		
				pActReq->ar_pAMdl =	AtalkSubsetAmdl(pMdl,	// MasterMdl
													offset,	// ByteOffset,
													size);	// SubsetMdlSize,
		
				if (pActReq->ar_pAMdl == NULL)
				{
					AtalkFreeMemory(pActReq);
					status = STATUS_INSUFFICIENT_RESOURCES;
					break;
				}
			}
		}

		//	Now call the dispatch routine
		error = (*AtalkActionDispatch[actionCode]._Dispatch)(pObject, pActReq);
		if (!ATALK_SUCCESS(error))
		{
			//	Call the generic completion routine and then return
			//	pending. That will free up the mdl's and the actreq.
			atalkTdiActionComplete(error, pActReq);
		}
		status = STATUS_PENDING;
	} while (FALSE);

	return status;
}




NTSTATUS
AtalkTdiQueryInformation(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine will satisfy the query for the object indicated in the Request. It
	supports the following query types-

	TDI_QUERY_PROVIDER_INFO
		The provider information structure for the provider that the object belongs to.

	TDI_QUERY_ADDRESS_INFO
		The address information for the address object passed in.

	TDI_QUERY_CONNECTION_INFO			**NOT SUPPORTED**
		The connection information for the connection object passed in.

	TDI_QUERY_PROVIDER_STATISTICS		**NOT SUPPORTED**
		The provider statistics - per provider statistics. All actions on a particular
		file object corresponds to activity on the provider of that file object. So each
		provider context structure will have the provider statistics structure which will
		be returned in this call.

Arguments:

				
Return Value:

	STATUS_SUCCESS if successfully completed
	STATUS_PENDING if successfully started
	Error otherwise.

--*/
{
	PVOID				pObject;
	USHORT				ObjectType;
	USHORT				DevType;
	USHORT				bufLen;
	NTSTATUS			status = STATUS_SUCCESS;

	PTDI_REQUEST_KERNEL_QUERY_INFORMATION	pQuery;

	pObject = (PVOID)pIrpSp->FileObject->FsContext;
	ObjectType = (USHORT)((ULONG)(pIrpSp->FileObject->FsContext2) & 0xFF);
	DevType = (USHORT)((ULONG)(pIrpSp->FileObject->FsContext2) >> 16);

	pQuery = (PTDI_REQUEST_KERNEL_QUERY_INFORMATION)&pIrpSp->Parameters;
	pIrp->IoStatus.Information	= 0;

	bufLen = (USHORT)AtalkSizeMdlChain(pIrp->MdlAddress);
	
	switch (pQuery->QueryType)
	{

	case TDI_QUERY_ADDRESS_INFO:
		if (bufLen < sizeof(TDI_ADDRESS_INFO))
		{
			status	= STATUS_BUFFER_TOO_SMALL;
			break;
		}

		switch (DevType)
		{
		  case ATALK_DEV_DDP:
			ASSERT(ObjectType == TDI_TRANSPORT_ADDRESS_FILE);
			AtalkDdpQuery(pObject,
						  pIrp->MdlAddress,
						  &pIrp->IoStatus.Information);

			break;

		  case ATALK_DEV_PAP:
			AtalkPapQuery(pObject,
						  ObjectType,
						  pIrp->MdlAddress,
						  &pIrp->IoStatus.Information);
			break;

		  case ATALK_DEV_ADSP:
			AtalkAdspQuery(pObject,
						   ObjectType,
						   pIrp->MdlAddress,
						   &pIrp->IoStatus.Information);
			break;

		  case ATALK_DEV_ASPC:
		  case ATALK_DEV_ASP:
			status = STATUS_INVALID_DEVICE_REQUEST;
			break;

		  default:
			// The device type in the Ctx field can never be anything
			// other than the above! Internal protocol error. KeBugCheck.
			DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_ERR,
					("AtalkTdiQueryInformation: Invalid device type\n"));

			status = STATUS_INVALID_DEVICE_REQUEST;
			break;
		}
		break;

	  case TDI_QUERY_CONNECTION_INFO:
		//	Statistics on a connection. Not supported.
		status = STATUS_NOT_IMPLEMENTED;
		break;

	  case TDI_QUERY_PROVIDER_INFO:

		if (bufLen < sizeof(TDI_PROVIDER_INFO))
		{
			status	= STATUS_BUFFER_TOO_SMALL;
			break;
		}

		status = TdiCopyBufferToMdl(&pCtx->adc_ProvInfo,
									0,
									sizeof (TDI_PROVIDER_INFO),
									pIrp->MdlAddress,
									0,
									&pIrp->IoStatus.Information);
		break;

	  case TDI_QUERY_PROVIDER_STATISTICS:
		status = STATUS_NOT_IMPLEMENTED;
		break;

	  default:
		status = STATUS_INVALID_DEVICE_REQUEST;
		break;
	}
	
	return status;
}




NTSTATUS
AtalkTdiSetInformation(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine

Arguments:

				
Return Value:

	STATUS_SUCCESS if successfully completed
	STATUS_PENDING if successfully started
	Error otherwise.

--*/
{
	NTSTATUS	status;


	do
	{
		// Now depending on the requested device...
		switch (pCtx->adc_DevType)
		{
		  case ATALK_DEV_DDP:
		  case ATALK_DEV_PAP:
		  case ATALK_DEV_ADSP:
		  case ATALK_DEV_ASPC:
	        status = STATUS_SUCCESS;
			break;

		  case ATALK_DEV_ASP:
			status = STATUS_INVALID_DEVICE_REQUEST;
			break;

		  default:
			// The device type in the Ctx field can never be anything
			// other than the above! Internal protocol error. KeBugCheck.
			status = STATUS_INVALID_DEVICE_REQUEST;
			DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_ERR,
					("AtalkTdiSetInformation: Invalid device type\n"));
			break;
		}

	} while (FALSE);

	return status;
}




NTSTATUS
AtalkTdiSetEventHandler(
	IN		PIRP					pIrp,
	IN		PIO_STACK_LOCATION		pIrpSp,
	IN OUT	PATALK_DEV_CTX			pCtx
	)
/*++

Routine Description:

	This routine

Arguments:


Return Value:

	STATUS_SUCCESS if successfully completed
	STATUS_PENDING if successfully started
	Error otherwise.

--*/
{
	PVOID				pObject;
	PDDP_ADDROBJ		pDdpAddr;
	PADSP_ADDROBJ		pAdspAddr;
	PPAP_ADDROBJ		pPapAddr;
	PASPC_ADDROBJ		pAspAddr;
	USHORT				objectType;
	USHORT				devType;
	KIRQL				OldIrql;
	NTSTATUS			status = STATUS_SUCCESS;

	do
	{
		PTDI_REQUEST_KERNEL_SET_EVENT parameters = (PTDI_REQUEST_KERNEL_SET_EVENT)&pIrpSp->Parameters;
	
		pObject 	= (PVOID)pIrpSp->FileObject->FsContext;
		objectType 	= (USHORT)((ULONG)(pIrpSp->FileObject->FsContext2) & 0xFF);
		devType 	= (USHORT)((ULONG)(pIrpSp->FileObject->FsContext2) >> 16);

		if (objectType != TDI_TRANSPORT_ADDRESS_FILE)
		{
			status = STATUS_INVALID_ADDRESS;
			break;
		}

		switch (parameters->EventType)
		{
		  case TDI_EVENT_RECEIVE_DATAGRAM:
			if (devType != ATALK_DEV_DDP)
			{
				status = STATUS_INVALID_DEVICE_REQUEST;
				break;
			}

			pDdpAddr = (PDDP_ADDROBJ)pObject;
			ASSERT(VALID_DDP_ADDROBJ(pDdpAddr));

			ACQUIRE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, &OldIrql);

			//	Allocate event info if null.
			if (pDdpAddr->ddpao_EventInfo == NULL)
			{
				pDdpAddr->ddpao_EventInfo =
					AtalkAllocZeroedMemory(sizeof(DDPEVENT_INFO));
			}

			if (pDdpAddr->ddpao_EventInfo != NULL)
			{
				pDdpAddr->ddpao_Flags |= DDPAO_DGRAM_EVENT;
				if ((pDdpAddr->ddpao_EventInfo->ev_RcvDgramHandler =
							(PTDI_IND_RECEIVE_DATAGRAM)parameters->EventHandler) == NULL)
				{
					pDdpAddr->ddpao_Flags	&= ~DDPAO_DGRAM_EVENT;
				}

				pDdpAddr->ddpao_EventInfo->ev_RcvDgramCtx = parameters->EventContext;
			}
			else
			{
				status = STATUS_INSUFFICIENT_RESOURCES;
			}
			RELEASE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, OldIrql);
			break;

		  case TDI_EVENT_ERROR:
			break;

		  case TDI_EVENT_CONNECT:
			switch (devType)
			{
			  case ATALK_DEV_ADSP:
				pAdspAddr = (PADSP_ADDROBJ)pObject;
				ASSERT(VALID_ADSPAO(pAdspAddr));

				ACQUIRE_SPIN_LOCK(&pAdspAddr->adspao_Lock, &OldIrql);
				if (pAdspAddr->adspao_Flags & ADSPAO_CONNECT)
				{
					status = STATUS_INVALID_ADDRESS;
				}
				else
				{
					pAdspAddr->adspao_ConnHandler = (PTDI_IND_CONNECT)parameters->EventHandler;
					pAdspAddr->adspao_ConnHandlerCtx = parameters->EventContext;
					pAdspAddr->adspao_Flags	|= ADSPAO_LISTENER;
				}
				RELEASE_SPIN_LOCK(&pAdspAddr->adspao_Lock, OldIrql);
				break;

			  case ATALK_DEV_PAP:
				pPapAddr = (PPAP_ADDROBJ)pObject;
				ASSERT(VALID_PAPAO(pPapAddr));

				ACQUIRE_SPIN_LOCK(&pPapAddr->papao_Lock, &OldIrql);
				if (pPapAddr->papao_Flags & PAPAO_CONNECT)
				{
					status = STATUS_INVALID_ADDRESS;
				}
				else
				{
					pPapAddr->papao_Flags	|= (PAPAO_LISTENER | PAPAO_UNBLOCKED);

					//	If we are setting a null handler, set it to blocked.
					if ((pPapAddr->papao_ConnHandler = (PTDI_IND_CONNECT)parameters->EventHandler) == NULL)
					{
						//	Oops. block. Dont care about listens being posted here.
						pPapAddr->papao_Flags &= ~PAPAO_UNBLOCKED;
					}

					pPapAddr->papao_ConnHandlerCtx = parameters->EventContext;
				}
				RELEASE_SPIN_LOCK(&pPapAddr->papao_Lock, OldIrql);

				if (NT_SUCCESS(status))
				{
					//	Prime the listener.
					if (!ATALK_SUCCESS(AtalkPapPrimeListener(pPapAddr)))
					{
						TMPLOGERR();
					}
				}
				break;

			  case ATALK_DEV_ASPC:
				// No listens here. Client side only.
				status = STATUS_INVALID_ADDRESS;
				break;

			  default:
				status = STATUS_INVALID_DEVICE_REQUEST;
				break;

			}
			break;

		  case TDI_EVENT_RECEIVE:

			switch (devType)
			{
			  case ATALK_DEV_ADSP:
				pAdspAddr = (PADSP_ADDROBJ)pObject;
				ASSERT(VALID_ADSPAO(pAdspAddr));

				ACQUIRE_SPIN_LOCK(&pAdspAddr->adspao_Lock, &OldIrql);
				pAdspAddr->adspao_RecvHandler = (PTDI_IND_RECEIVE)parameters->EventHandler;
				pAdspAddr->adspao_RecvHandlerCtx = parameters->EventContext;
				RELEASE_SPIN_LOCK(&pAdspAddr->adspao_Lock, OldIrql);
				break;

			  case ATALK_DEV_PAP:
				pPapAddr = (PPAP_ADDROBJ)pObject;
				ASSERT(VALID_PAPAO(pPapAddr));

				ACQUIRE_SPIN_LOCK(&pPapAddr->papao_Lock, &OldIrql);
				pPapAddr->papao_RecvHandler = (PTDI_IND_RECEIVE)parameters->EventHandler;
				pPapAddr->papao_RecvHandlerCtx = parameters->EventContext;
				RELEASE_SPIN_LOCK(&pPapAddr->papao_Lock, OldIrql);
				break;

			  case ATALK_DEV_ASPC:
		  		// No receives in asp client
				status	= STATUS_SUCCESS;
				break;

			  default:
				status = STATUS_INVALID_DEVICE_REQUEST;
				break;
			}
			break;

		  case TDI_EVENT_RECEIVE_EXPEDITED:
			switch (devType)
			{
			  case ATALK_DEV_ADSP:
				pAdspAddr = (PADSP_ADDROBJ)pObject;
				ASSERT(VALID_ADSPAO(pAdspAddr));
	
				ACQUIRE_SPIN_LOCK(&pAdspAddr->adspao_Lock, &OldIrql);
				pAdspAddr->adspao_ExpRecvHandler = (PTDI_IND_RECEIVE_EXPEDITED)parameters->EventHandler;
				pAdspAddr->adspao_ExpRecvHandlerCtx = parameters->EventContext;
				RELEASE_SPIN_LOCK(&pAdspAddr->adspao_Lock, OldIrql);
				break;

			  case ATALK_DEV_ASPC:
				pAspAddr = (PASPC_ADDROBJ)pObject;
				ASSERT(VALID_ASPCAO(pAspAddr));
	
				ACQUIRE_SPIN_LOCK(&pAspAddr->aspcao_Lock, &OldIrql);
				pAspAddr->aspcao_ExpRecvHandler = (PTDI_IND_RECEIVE_EXPEDITED)parameters->EventHandler;
				pAspAddr->aspcao_ExpRecvHandlerCtx = parameters->EventContext;
				RELEASE_SPIN_LOCK(&pAspAddr->aspcao_Lock, OldIrql);
				break;

			  default:
				status = STATUS_INVALID_DEVICE_REQUEST;
				break;
			}
			break;

		  case TDI_EVENT_DISCONNECT:

			switch (devType)
			{
			  case ATALK_DEV_ADSP:
				pAdspAddr = (PADSP_ADDROBJ)pObject;
				ASSERT(VALID_ADSPAO(pAdspAddr));

				ACQUIRE_SPIN_LOCK(&pAdspAddr->adspao_Lock, &OldIrql);
				pAdspAddr->adspao_DisconnectHandler = (PTDI_IND_DISCONNECT)parameters->EventHandler;
				pAdspAddr->adspao_DisconnectHandlerCtx = parameters->EventContext;
				RELEASE_SPIN_LOCK(&pAdspAddr->adspao_Lock, OldIrql);
				break;

			  case ATALK_DEV_PAP:
				pPapAddr = (PPAP_ADDROBJ)pObject;
				ASSERT(VALID_PAPAO(pPapAddr));

				ACQUIRE_SPIN_LOCK(&pPapAddr->papao_Lock, &OldIrql);
				pPapAddr->papao_DisconnectHandler = (PTDI_IND_DISCONNECT)parameters->EventHandler;
				pPapAddr->papao_DisconnectHandlerCtx = parameters->EventContext;
				RELEASE_SPIN_LOCK(&pPapAddr->papao_Lock, OldIrql);
				break;

			  case ATALK_DEV_ASPC:
				pAspAddr = (PASPC_ADDROBJ)pObject;
				ASSERT(VALID_ASPCAO(pAspAddr));

				ACQUIRE_SPIN_LOCK(&pAspAddr->aspcao_Lock, &OldIrql);
				pAspAddr->aspcao_DisconnectHandler = (PTDI_IND_DISCONNECT)parameters->EventHandler;
				pAspAddr->aspcao_DisconnectHandlerCtx= parameters->EventContext;
				RELEASE_SPIN_LOCK(&pAspAddr->aspcao_Lock, OldIrql);
				break;

			  default:
				status = STATUS_INVALID_DEVICE_REQUEST;
				break;
			}
			break;

		  case TDI_EVENT_SEND_POSSIBLE:

			switch (devType)
			{
			  case ATALK_DEV_ADSP:
				pAdspAddr = (PADSP_ADDROBJ)pObject;
				ASSERT(VALID_ADSPAO(pAdspAddr));

				ACQUIRE_SPIN_LOCK(&pAdspAddr->adspao_Lock, &OldIrql);
				pAdspAddr->adspao_SendPossibleHandler = (PTDI_IND_SEND_POSSIBLE)parameters->EventHandler;
				pAdspAddr->adspao_SendPossibleHandlerCtx = parameters->EventContext;
				RELEASE_SPIN_LOCK(&pAdspAddr->adspao_Lock, OldIrql);
				break;

			  case ATALK_DEV_PAP:
				pPapAddr = (PPAP_ADDROBJ)pObject;
				ASSERT(VALID_PAPAO(pPapAddr));

				ACQUIRE_SPIN_LOCK(&pPapAddr->papao_Lock, &OldIrql);
				pPapAddr->papao_SendPossibleHandler	= (PTDI_IND_SEND_POSSIBLE)parameters->EventHandler;
				pPapAddr->papao_SendPossibleHandlerCtx = parameters->EventContext;
				RELEASE_SPIN_LOCK(&pPapAddr->papao_Lock, OldIrql);
				break;

			  case ATALK_DEV_ASPC:
		  		// No sends in asp client
				status	= STATUS_SUCCESS;
				break;

			  default:
				status = STATUS_INVALID_DEVICE_REQUEST;
				break;

			}
			break;

		  default:
			status = STATUS_INVALID_PARAMETER;
		}
	
#if DBG
		//	Avoid assertions in AFD.
		status	= STATUS_SUCCESS;
#endif

	} while (FALSE);

	return status;
}




VOID
AtalkTdiCancel(
	IN OUT	PATALK_DEV_OBJ			pDevObj,
	IN		PIRP					pIrp
	)
/*++

Routine Description:

	This routine handles cancellation of IO requests

Arguments:


Return Value:
--*/
{
	PIO_STACK_LOCATION		pIrpSp;
	PVOID					pObject;
	PATALK_DEV_CTX			pCtx;

	pIrpSp 	= IoGetCurrentIrpStackLocation(pIrp);
	pObject = pIrpSp->FileObject->FsContext;
	pCtx	= &pDevObj->Ctx;

	ASSERT(((LONG)pIrpSp->FileObject->FsContext2 >> 16) == pCtx->adc_DevType);

	switch (pCtx->adc_DevType)
	{
	  case ATALK_DEV_DDP:
		break;

	  case ATALK_DEV_ASPC:
		if (pIrpSp->FileObject->FsContext2 == (PVOID)(TDI_CONNECTION_FILE + (pCtx->adc_DevType << 16)))
			 AtalkAspCCleanupConnection((PASPC_CONNOBJ)pObject);
		else AtalkAspCCleanupAddress((PASPC_ADDROBJ)pObject);
		break;

	  case ATALK_DEV_ASP:
		// We only handle cancellation of IO requests on connection objects.
		if (pIrpSp->FileObject->FsContext2 == (PVOID)(TDI_CONNECTION_FILE + (pCtx->adc_DevType << 16)))
			AtalkAspCleanupConnection((PASP_CONNOBJ)pObject);
		break;

	  case ATALK_DEV_PAP:
		if (pIrpSp->FileObject->FsContext2 == (PVOID)(TDI_CONNECTION_FILE + (pCtx->adc_DevType << 16)))
			 AtalkPapCleanupConnection((PPAP_CONNOBJ)pObject);
		else AtalkPapCleanupAddress((PPAP_ADDROBJ)pObject);
		break;

	  case ATALK_DEV_ADSP:
		if (pIrpSp->FileObject->FsContext2 == (PVOID)(TDI_CONNECTION_FILE + (pCtx->adc_DevType << 16)))
			 AtalkAdspCleanupConnection((PADSP_CONNOBJ)pObject);
		else AtalkAdspCleanupAddress((PADSP_ADDROBJ)pObject);
		break;

	  default:
		// The device type in the Ctx field can never be anything
		// other than the above! Internal protocol error.
		DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_ERR,
				("AtalkTdiCancel: Invalid device type\n"));
		break;
	}
	IoReleaseCancelSpinLock (pIrp->CancelIrql);
}




VOID
AtalkQueryInitProviderInfo(
	IN		ATALK_DEV_TYPE		DeviceType,
	IN OUT	PTDI_PROVIDER_INFO  ProviderInfo
	)
{
	//
	//  Initialize to defaults first
	//

	RtlZeroMemory((PVOID)ProviderInfo, sizeof(TDI_PROVIDER_INFO));

	ProviderInfo->Version = ATALK_TDI_PROVIDERINFO_VERSION;
	KeQuerySystemTime (&ProviderInfo->StartTime);

	switch (DeviceType)
	{
	  case ATALK_DEV_DDP:
		ProviderInfo->MaxDatagramSize = ATALK_DDP_PINFODGRAMSIZE;
		ProviderInfo->ServiceFlags = ATALK_DDP_PINFOSERVICEFLAGS;
		break;

	  case ATALK_DEV_PAP:
		ProviderInfo->MaxSendSize =  ATALK_PAP_PINFOSENDSIZE;
		ProviderInfo->ServiceFlags = ATALK_PAP_PINFOSERVICEFLAGS;
		break;

	  case ATALK_DEV_ADSP:
		ProviderInfo->MaxSendSize =  ATALK_ADSP_PINFOSENDSIZE;
		ProviderInfo->ServiceFlags = ATALK_ADSP_PINFOSERVICEFLAGS;
		break;

	  case ATALK_DEV_ASP:
		ProviderInfo->MaxSendSize =  ATALK_ASP_PINFOSENDSIZE;
		ProviderInfo->ServiceFlags = ATALK_ASP_PINFOSERVICEFLAGS;
		break;

	  case ATALK_DEV_ASPC:
		ProviderInfo->MaxSendSize =  ATALK_ASP_PINFOSENDSIZE;
		ProviderInfo->ServiceFlags = ATALK_ASP_PINFOSERVICEFLAGS;
		break;


	  default:
		KeBugCheck(0);
	}
}


LOCAL VOID FASTCALL
atalkTdiSendDgramComplete(
	IN	NDIS_STATUS			Status,
	IN	PSEND_COMPL_INFO	pSendInfo
	)
{
	PDDP_ADDROBJ	pAddr = (PDDP_ADDROBJ)(pSendInfo->sc_Ctx1);
	PBUFFER_DESC	pBufDesc = (PBUFFER_DESC)(pSendInfo->sc_Ctx2);
	PIRP			pIrp = (PIRP)(pSendInfo->sc_Ctx3);

	ASSERT(VALID_DDP_ADDROBJ(pAddr));

	DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_ERR,
			("atalkTdiSendDgramComplete: Status %lx, addr %lx\n", Status, pAddr));

	AtalkFreeBuffDesc(pBufDesc);
	pIrp->CancelRoutine = NULL;
	TdiCompleteRequest(pIrp,
					   ((Status == NDIS_STATUS_SUCCESS) ?
						STATUS_SUCCESS: STATUS_UNSUCCESSFUL));
}




LOCAL VOID
atalkTdiRecvDgramComplete(
	IN	ATALK_ERROR		ErrorCode,
	IN	PAMDL			pReadBuf,
	IN	USHORT			ReadLen,
	IN	PATALK_ADDR		pSrcAddr,
	IN	PIRP			pIrp
	)
{
	PIO_STACK_LOCATION 				pIrpSp;
	PTDI_REQUEST_KERNEL_RECEIVEDG	parameters;
	PTDI_CONNECTION_INFORMATION		returnInfo;
	PTA_APPLETALK_ADDRESS			remoteAddress;


	DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_ERR,
			("atalkTdiRecvDgramComplete: %lx\n", ErrorCode));

	pIrpSp 		= IoGetCurrentIrpStackLocation(pIrp);
	parameters 	= (PTDI_REQUEST_KERNEL_RECEIVEDG)&pIrpSp->Parameters;

	//	Set length in the info field and call the completion routine.
	pIrp->CancelRoutine = NULL;
	pIrp->IoStatus.Information	= (ULONG)ReadLen;

	if (ATALK_SUCCESS(ErrorCode))
	{
		ASSERT(parameters != NULL);

		if (parameters != NULL)
		{
			parameters->ReceiveLength = (ULONG)ReadLen;
			returnInfo =
				(PTDI_CONNECTION_INFORMATION)parameters->ReturnDatagramInformation;

			ASSERT(returnInfo != NULL);
			if (returnInfo != NULL)
			{
				if (returnInfo->RemoteAddressLength >= sizeof(TA_APPLETALK_ADDRESS))
				{
					//  Fill in the remote address
					remoteAddress = (PTA_APPLETALK_ADDRESS)returnInfo->RemoteAddress;

					ASSERT(remoteAddress != NULL);
					if (remoteAddress != NULL)
					{
						//	Copy the remote address from where the datagram was received
						ATALKADDR_TO_TDI(
							remoteAddress,
							pSrcAddr);

						DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_ERR,
								("AtalkAddrRecvDgComp - Net %x Node %x Socket %x\n",
									remoteAddress->Address[0].Address[0].Network,
									remoteAddress->Address[0].Address[0].Node,
									remoteAddress->Address[0].Address[0].Socket));
					}
				}
			}
		}
	}

	ASSERT (ErrorCode != ATALK_PENDING);
	TdiCompleteRequest(pIrp, AtalkErrorToNtStatus(ErrorCode));
}




LOCAL VOID
atalkTdiActionComplete(
	IN	ATALK_ERROR	ErrorCode,
	IN	PACTREQ		pActReq
	)
{
	PIRP	pIrp = pActReq->ar_pIrp;

	ASSERT (VALID_ACTREQ(pActReq));

	if (pActReq->ar_pAMdl != NULL)
		AtalkFreeAMdl(pActReq->ar_pAMdl);
	AtalkFreeMemory(pActReq);

	pIrp->CancelRoutine = NULL;
	ASSERT (ErrorCode != ATALK_PENDING);
	TdiCompleteRequest(pIrp, AtalkErrorToNtStatus(ErrorCode));
}




LOCAL VOID
atalkTdiGenericComplete(
	IN	ATALK_ERROR	ErrorCode,
	IN	PIRP		pIrp
	)
{
	DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_INFO,
			("atalkTdiGenericComplete: Completing %lx with %lx\n",
				pIrp, AtalkErrorToNtStatus(ErrorCode)));

	pIrp->CancelRoutine = NULL;
	ASSERT (ErrorCode != ATALK_PENDING);
	TdiCompleteRequest(pIrp, AtalkErrorToNtStatus(ErrorCode));
}



LOCAL VOID
atalkTdiCloseAddressComplete(
	IN	ATALK_ERROR	ErrorCode,
	IN	PIRP		pIrp
	)
{
	DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_INFO,
			("atalkTdiCloseAddressComplete: Completing %lx with %lx\n",
			pIrp, AtalkErrorToNtStatus(ErrorCode)));

	pIrp->CancelRoutine = NULL;
	ASSERT (ErrorCode != ATALK_PENDING);
	AtalkUnlockTdiIfNecessary();
	TdiCompleteRequest(pIrp, AtalkErrorToNtStatus(ErrorCode));
}



LOCAL VOID
atalkTdiGenericReadComplete(
	IN	ATALK_ERROR	ErrorCode,
	IN 	PAMDL		ReadBuf,
	IN 	USHORT		ReadLen,
	IN 	ULONG		ReadFlags,
	IN 	PIRP		pIrp
	)
{
	ASSERT(pIrp->IoStatus.Status != STATUS_UNSUCCESSFUL);

	DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_INFO,
			("atalkTdiGenericReadComplete: Irp %lx Status %lx Info %lx\n",
				pIrp, pIrp->IoStatus.Status, ReadLen));

	pIrp->CancelRoutine = NULL;
	pIrp->IoStatus.Information	= (ULONG)ReadLen;
	ASSERT (ErrorCode != ATALK_PENDING);
	TdiCompleteRequest(pIrp, AtalkErrorToNtStatus(ErrorCode));
}




VOID
atalkTdiGenericWriteComplete(
	IN	ATALK_ERROR	ErrorCode,
	IN 	PAMDL		WriteBuf,
	IN 	USHORT		WriteLen,
	IN	PIRP		pIrp
	)
{
	ASSERT(pIrp->IoStatus.Status != STATUS_UNSUCCESSFUL);

	if (pIrp->IoStatus.Status == STATUS_UNSUCCESSFUL)
	{
		DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_ERR,
				("atalkTdiGenericWriteComplete: Irp %lx Status %lx Info %lx\n",
				pIrp, pIrp->IoStatus.Status, WriteLen));
	}

	DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_INFO,
			("atalkTdiGenericWriteComplete: Irp %lx Status %lx Info %lx\n",
				pIrp, pIrp->IoStatus.Status, WriteLen));

	pIrp->CancelRoutine = NULL;
	pIrp->IoStatus.Information	= (ULONG)WriteLen;
	ASSERT (ErrorCode != ATALK_PENDING);
	TdiCompleteRequest(pIrp, AtalkErrorToNtStatus(ErrorCode));
}


LOCAL VOID
atalkQueuedLockUnlock(
	IN	PQLU		pQLU
)
{
	ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);

	AtalkLockUnlock(FALSE,
					pQLU->qlu_pLockSection);
	AtalkPortDereference(pQLU->qlu_pPortDesc);
	AtalkFreeMemory(pQLU);
}


VOID
AtalkLockInit(
	IN	PLOCK_SECTION	pLs,
	IN	PVOID			Address
)
{
	pLs->ls_LockHandle = MmLockPagableCodeSection(Address);
	MmUnlockPagableImageSection(pLs->ls_LockHandle);
}


VOID
AtalkLockUnlock(
	IN	BOOLEAN			Lock,
	IN	PLOCK_SECTION	pLs
	)
{
	KIRQL	OldIrql;
	BOOLEAN	DoForReal;

	// We cannot call the MmLock/MmUnlock routines at Dpc. So if we are called at
	// DISPATCH, just queue ourselves. Also we only get unlock requests at DISPATCH,
	// Lock requests are only at LOW_LEVEL. So failure to allocate memory can be
	// IGNORED since that will only have the effect of failure to unlock.
	if (KeGetCurrentIrql() == DISPATCH_LEVEL)
	{
		PQLU		pQLU;
		ATALK_ERROR	Error;

		ASSERT (!Lock || (pLs->ls_LockCount > 0));

		if (Lock)
		{
			ASSERT (pLs->ls_LockCount > 0);
			ACQUIRE_SPIN_LOCK_DPC(&AtalkPgLkLock);
			pLs->ls_LockCount ++;
			RELEASE_SPIN_LOCK_DPC(&AtalkPgLkLock);
		}
		else
		{
			if ((pQLU = AtalkAllocMemory(sizeof(QLU))) != NULL)
			{
				pQLU->qlu_pPortDesc = AtalkPortList;
				AtalkPortReferenceByPtrDpc(pQLU->qlu_pPortDesc, &Error);
				if (ATALK_SUCCESS(Error))
				{
					pQLU->qlu_pLockSection = pLs;
		
					ExInitializeWorkItem(&pQLU->qlu_WQI, atalkQueuedLockUnlock, pQLU);
					ExQueueWorkItem(&pQLU->qlu_WQI, CriticalWorkQueue);
				}
				else
				{
					AtalkFreeMemory(pQLU);
				}
			}
		}
		return;									    
	}

	ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);

	// We need to serialize the operations here. Note that a spin-lock will not do the
	// job since the MmLock/MmUnlock routines cannot be called with the spin-lock held
	KeWaitForSingleObject(&AtalkPgLkMutex,
						  Executive,
						  KernelMode,
						  TRUE,
						  (PLARGE_INTEGER)NULL);

	ASSERT (pLs->ls_LockHandle != NULL);

	DoForReal = FALSE;
	ACQUIRE_SPIN_LOCK(&AtalkPgLkLock, &OldIrql);

	if (Lock)
	{
		if (pLs->ls_LockCount == 0)
		{
			DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_WARN,
					("AtalkLockUnlock: Locking %d\n", pLs - AtalkPgLkSection));
			DoForReal = TRUE;
		}
		pLs->ls_LockCount ++;
	}
	else
	{
		ASSERT (pLs->ls_LockCount > 0);

		pLs->ls_LockCount --;
		if (pLs->ls_LockCount == 0)
		{
			DBGPRINT(DBG_COMP_TDI, DBG_LEVEL_WARN,
					("AtalkLockUnlock: Unlocking %d\n", pLs - AtalkPgLkSection));
			DoForReal = TRUE;
		}
	}

	RELEASE_SPIN_LOCK(&AtalkPgLkLock, OldIrql);

	if (DoForReal)
	{
		if (Lock)
		{
			MmLockPagableSectionByHandle(pLs->ls_LockHandle);
		}
		else
		{
			MmUnlockPagableImageSection(pLs->ls_LockHandle);
		}
	}

	// LeaveCriticalSection
	KeReleaseMutex(&AtalkPgLkMutex, FALSE);
}


VOID
atalkWaitDefaultPort(
	VOID
)
{
	TIME		Time;
#define	ONE_SEC_IN_100ns		-10000000L		// 1sec in 100ns units

	if ((AtalkDefaultPort == NULL) ||
		((AtalkDefaultPort->pd_Flags & (PD_USER_NODE_1 | PD_USER_NODE_2)) == 0))
	{
		// Make sure we can indeed wait
		ASSERT (KeGetCurrentIrql() == LOW_LEVEL);
	
		Time.QuadPart = Int32x32To64((LONG)30, ONE_SEC_IN_100ns);
		KeWaitForSingleObject(&AtalkDefaultPortEvent, Executive, KernelMode, FALSE, &Time);
	}
}



