/*++

Copyright (c) 1992  Microsoft Corporation

Module Name:

	ddp.c

Abstract:

	This module implements the ddp protocol.

Author:

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

Revision History:
	19 Jun 1992		Initial Version

Notes:	Tab stop: 4
--*/

#define DDP_LOCALS
#define	FILENUM	DDP
#include <atalk.h>
#pragma hdrstop

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGEINIT, AtalkDdpInitCloseAddress)
#pragma alloc_text(PAGEINIT, atalkDdpInitCloseComplete)
#pragma alloc_text(PAGEINIT, AtalkInitDdpOpenStaticSockets)
#endif

//
//	AtalkDdpOpenAddress()
//	This opens a DDP address object and returns a pointer to it in
//	DdpAddrObject. The AppletalkSocket is created and will be the
//	address of this object.
//

ATALK_ERROR
AtalkDdpOpenAddress(
	IN		PPORT_DESCRIPTOR		pPortDesc,
	IN		BYTE					Socket,
	IN OUT	PATALK_NODEADDR			pDesiredNode	OPTIONAL,
	IN 		DDPAO_HANDLER			pSktHandler 	OPTIONAL,
	IN		PVOID					pSktCtx			OPTIONAL,
	IN		BYTE					Protocol		OPTIONAL,
	IN		PATALK_DEV_CTX			pDevCtx,
	OUT		PDDP_ADDROBJ	*		ppDdpAddr
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PATALK_NODE		pAtalkNode, pNextNode;

	PDDP_ADDROBJ 	pDdpAddr 	= NULL;
	ATALK_ERROR		error 		= ATALK_NO_ERROR;

	DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO,
			("AtalkDdpOpenAddress: Opening DDP socket %d on port %lx\n",
			Socket, pPortDesc));

	do
	{
		//	Verify the Appletalk socket number
		if (!IS_VALID_SOCKET(Socket))
		{
			error = ATALK_SOCKET_INVALID;
			break;
		}
		//	Allocate space for the address object
		if ((pDdpAddr = AtalkAllocZeroedMemory(sizeof(DDP_ADDROBJ))) == NULL)
		{
			error = ATALK_RESR_MEM;
			break;
		}
	
		if (pDesiredNode != NULL)
		{
			AtalkNodeReferenceByAddr(pPortDesc,
									 pDesiredNode,
									 &pAtalkNode,
									 &error);
	
			if (ATALK_SUCCESS(error))
			{
				ASSERT(VALID_ATALK_NODE(pAtalkNode));

				//	try to allocate the socket on this node.
				error = atalkDdpAllocSocketOnNode(pPortDesc,
												  Socket,
												  pAtalkNode,
												  pSktHandler,
												  pSktCtx,
												  Protocol,
												  pDevCtx,
												  pDdpAddr);
	
				//	Remove the reference on the node.
				AtalkNodeDereference(pAtalkNode);
			}

			break;
		}
		else
		{
			KIRQL	OldIrql;

			//	We can open the socket on any one of our
			//	nodes.

			//	We first get the port lock
			//	Then we go through all the nodes on the port
			//	reference a node, let go of the port lock
			//	acquire the node lock, try to open the socket
			//	on it. If we succeed, we return, else we fail.

			ACQUIRE_SPIN_LOCK(&pPortDesc->pd_Lock, &OldIrql);
			do
			{
				//	Try to get a referenced node. null if no non-closing node found.
				AtalkNodeReferenceNextNc(pPortDesc->pd_Nodes, &pAtalkNode, &error);
		
				while (ATALK_SUCCESS(error))
				{
					//	We do not use this node if it is orphaned or if
					//	it is a router node and we are trying to open a
					//	user socket (dynamic or non-reserved).
					if (((pAtalkNode->an_Flags & (AN_ORPHAN_NODE | AN_ROUTER_NODE)) == 0) ||
						((Socket != UNKNOWN_SOCKET) && (Socket <= LAST_APPLE_RESD_SOCKET)))
					{
						RELEASE_SPIN_LOCK(&pPortDesc->pd_Lock, OldIrql);
	
						//	try to allocate the socket on this node. PortLock held!
						error = atalkDdpAllocSocketOnNode(pPortDesc,
														  Socket,
														  pAtalkNode,
														  pSktHandler,
														  pSktCtx,
														  Protocol,
														  pDevCtx,
														  pDdpAddr);
	
						if (ATALK_SUCCESS(error))
						{
							//	Done! Break out of the loop. Remove the ref we added.
							AtalkNodeDereference(pAtalkNode);
							ACQUIRE_SPIN_LOCK(&pPortDesc->pd_Lock, &OldIrql);
							break;
						}

						ACQUIRE_SPIN_LOCK(&pPortDesc->pd_Lock, &OldIrql);
					}

					//	Gotta get to the next node.
					AtalkNodeReferenceNextNc(pAtalkNode->an_Next, &pNextNode, &error);

					RELEASE_SPIN_LOCK(&pPortDesc->pd_Lock, OldIrql);
					AtalkNodeDereference(pAtalkNode);
					ACQUIRE_SPIN_LOCK(&pPortDesc->pd_Lock, &OldIrql);

					pAtalkNode = pNextNode;
				}
		
			} while (FALSE);

			RELEASE_SPIN_LOCK(&pPortDesc->pd_Lock, OldIrql);
		}

	} while (FALSE);

	if (ATALK_SUCCESS(error))
	{
		if (ppDdpAddr != NULL)
			*ppDdpAddr = pDdpAddr;
	}
	else
	{
		DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_ERR,
				("AtalkDdpOpenAddress: failed with error %lx\n", error));
		ASSERTMSG("AtalkDdpOpenAddress: failed\n", 0);

		if (pDdpAddr)
			AtalkFreeMemory(pDdpAddr);
	}

	return error;
}




ATALK_ERROR
AtalkDdpCleanupAddress(
	IN	PDDP_ADDROBJ			pDdpAddr
	)
/*++

Routine Description:

	Releases any pending requests on the address.

Arguments:


Return Value:


--*/
{
	KIRQL			OldIrql;

	//	Free all pending ddp reads.
	ACQUIRE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, &OldIrql);

	while (!IsListEmpty(&pDdpAddr->ddpao_ReadLinkage))
	{
		PLIST_ENTRY	p;
		PDDP_READ	pRead;

		p = RemoveHeadList(&pDdpAddr->ddpao_ReadLinkage);
		RELEASE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, OldIrql);

		pRead 	= CONTAINING_RECORD(p, DDP_READ, dr_Linkage);

		(*pRead->dr_RcvCmp)(ATALK_FAILURE,
							pRead->dr_OpBuf,
							0,
							NULL,
							pRead->dr_RcvCtx);

		AtalkDdpDereference(pDdpAddr);
		AtalkFreeMemory(pRead);
		ACQUIRE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, &OldIrql);
	}

	RELEASE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, OldIrql);
	return ATALK_NO_ERROR;
}




ATALK_ERROR
AtalkDdpCloseAddress(
	IN	PDDP_ADDROBJ			pDdpAddr,
	IN	GENERIC_COMPLETION		pCloseCmp	OPTIONAL,	
	IN	PVOID					pCloseCtx	OPTIONAL
	)
/*++

Routine Description:

	Called to close an open ddp address object. This will complete after all
	requests on the object are done/cancelled, and the Appletalk Socket is
	closed.

Arguments:


Return Value:


--*/
{
	KIRQL			OldIrql;
	BOOLEAN			closing;

	ASSERT (VALID_DDP_ADDROBJ(pDdpAddr));

	ACQUIRE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, &OldIrql);
	closing = ((pDdpAddr->ddpao_Flags & DDPAO_CLOSING) != 0) ? TRUE : FALSE;

	ASSERTMSG("DdpAddr is already closing!\n", !closing);

	if (!closing)
	{
		//	Set the closing flag and remember the completion routines.
		pDdpAddr->ddpao_Flags |= DDPAO_CLOSING;
		pDdpAddr->ddpao_CloseComp = pCloseCmp;
		pDdpAddr->ddpao_CloseCtx  = pCloseCtx;
	}
	RELEASE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, OldIrql);

	if (!closing)
	{
		//	Release any pending reads
		AtalkDdpCleanupAddress(pDdpAddr);
		AtalkNbpCloseSocket(pDdpAddr);

		//	Remove reference for the creation
		AtalkDdpDereference(pDdpAddr);
	}

	return ATALK_PENDING;
}




ATALK_ERROR
AtalkDdpInitCloseAddress(
	IN	PPORT_DESCRIPTOR	pPortDesc,
	IN	PATALK_ADDR			pAtalkAddr
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ATALK_ERROR		error;
	PDDP_ADDROBJ	pDdpAddr;

	//	!!!This should only be called during initialization!!!
	KEVENT	Event	= {0};

	//	Try to see if the socket exists.
	AtalkDdpRefByAddr(pPortDesc, pAtalkAddr, &pDdpAddr, &error);
	if (ATALK_SUCCESS(error))
	{
		ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);

		KeInitializeEvent(&Event, NotificationEvent, FALSE);

		//	Call close with the appropriate completion routine.
		error = AtalkDdpCloseAddress(pDdpAddr,
									 atalkDdpInitCloseComplete,
									 (PVOID)&Event);

		//	Remove the reference we added.
		AtalkDdpDereference(pDdpAddr);

		if (error == ATALK_PENDING)
		{
			// 	Wait on event, completion routine will set NdisRequestEvent
			KeWaitForSingleObject(&Event,
								  Executive,
								  KernelMode,
								  TRUE,
								  NULL);

			//	Assume socket closed successfully.
			error = ATALK_NO_ERROR;
		}
	}

	return error;
}




VOID
atalkDdpInitCloseComplete(
	ATALK_ERROR 	Error,
	PVOID			Ctx
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PKEVENT		pEvent = (PKEVENT)Ctx;

	if (!ATALK_SUCCESS(Error))
	{
		DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO,
				("atalkDdpInitCloseComplete: Closed with error %lx\n", Error));
	}

	KeSetEvent(pEvent, 0L, FALSE);
}




ATALK_ERROR
AtalkInitDdpOpenStaticSockets(
	IN		PPORT_DESCRIPTOR		pPortDesc,
	IN	OUT PATALK_NODE				pNode
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PDDP_ADDROBJ	pDdpAddr, pDdpAddr1, pDdpAddr2, pDdpAddr3;
	ATALK_ERROR		error = ATALK_NO_ERROR;

	//	This is called whenever a new node is created.
	do
	{
		error = AtalkDdpOpenAddress(pPortDesc,
									NAMESINFORMATION_SOCKET,
									&pNode->an_NodeAddr,
									AtalkNbpPacketIn,
									NULL,
									DDPPROTO_ANY,		
									NULL,
									&pDdpAddr);
	
		if (!ATALK_SUCCESS(error))
			break;
	
		// A lot of devices today work around the fact that a macintosh uses socket 254
		// for lookups from chooser. Agfa is one such beast. To make this work, we reserve
		// this socket for Nbp lookups ourselves.
		error = AtalkDdpOpenAddress(pPortDesc,
									LAST_DYNAMIC_SOCKET,
									&pNode->an_NodeAddr,
									AtalkNbpPacketIn,
									NULL,
									DDPPROTO_ANY,		
									NULL,
									&pDdpAddr1);
	
		if (!ATALK_SUCCESS(error))
		{
			AtalkDdpCloseAddress(pDdpAddr, NULL, NULL);
			break;
		}
	
		error = AtalkDdpOpenAddress(pPortDesc,
									ECHOER_SOCKET,
									&pNode->an_NodeAddr,
									AtalkAepPacketIn,
									NULL,
									DDPPROTO_ANY,		
									NULL,
									&pDdpAddr2);
	
		if (!ATALK_SUCCESS(error))
		{
			AtalkDdpCloseAddress(pDdpAddr, NULL, NULL);
			AtalkDdpCloseAddress(pDdpAddr1, NULL, NULL);
			break;
		}

		//	NOTE: RTMP uses two protocol types.
		error = AtalkDdpOpenAddress(pPortDesc,
									RTMP_SOCKET,
									&pNode->an_NodeAddr,
									AtalkRtmpPacketIn,
									NULL,
									DDPPROTO_ANY,		
									NULL,
									&pDdpAddr3);
	
		if (!ATALK_SUCCESS(error))
		{
			AtalkDdpCloseAddress(pDdpAddr, NULL, NULL);
			AtalkDdpCloseAddress(pDdpAddr1, NULL, NULL);
			AtalkDdpCloseAddress(pDdpAddr2, NULL, NULL);
		}
	} while (FALSE);

	return error;
}


//
//	AtalkDdpReceive()
//	Called by an external caller to the stack.
// 	PAMDL is an Appletalk Memory Descriptor List. On NT, it will be an MDL.
//


ATALK_ERROR
AtalkDdpReceive(
	IN		PDDP_ADDROBJ		pDdpAddr,
	IN		PAMDL				pAmdl,
	IN		USHORT				AmdlLen,
	IN		ULONG				RecvFlags,
	IN		RECEIVE_COMPLETION	pRcvCmp,
	IN		PVOID				pRcvCtx		OPTIONAL
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ATALK_ERROR		error;
	PDDP_READ		pRead;
	NTSTATUS		status;
	ULONG			bytesCopied;
	ATALK_ADDR		remoteAddr;
	KIRQL			OldIrql;
	BOOLEAN			completeRecv 	= FALSE,
					DerefAddr		= FALSE;
	BOOLEAN			pendingDgram 	= FALSE;

	do
	{
		if (pRcvCmp == NULL)
		{
			error = ATALK_DDP_INVALID_PARAM;
			break;
		}

		AtalkDdpReferenceByPtr(pDdpAddr, &error);
		if (!ATALK_SUCCESS(error))
		{
			break;
		}

		DerefAddr 	= TRUE;
		error		= ATALK_NO_ERROR;

		ACQUIRE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, &OldIrql);
		if (pDdpAddr->ddpao_Flags & DDPAO_DGRAM_PENDING)
		{
			if (AmdlLen < pDdpAddr->ddpao_EventInfo->ev_IndDgramLen)
			{
				error	= ATALK_BUFFER_TOO_SMALL;
			}

			AmdlLen = MIN(AmdlLen, pDdpAddr->ddpao_EventInfo->ev_IndDgramLen);
			status = TdiCopyBufferToMdl(
						pDdpAddr->ddpao_EventInfo->ev_IndDgram,
						0,
						AmdlLen,
						pAmdl,
						0,
						&bytesCopied);

			remoteAddr				= pDdpAddr->ddpao_EventInfo->ev_IndSrc;
			pDdpAddr->ddpao_Flags  &= ~DDPAO_DGRAM_PENDING;
			completeRecv			= TRUE;
		}
		else
		{
			//	This case never really will be executed for non-blocking sockets.
			//	Dont bother about this alloc with spinlock held for now.
			//	RACE CONDITION is with a packet coming in and setting DGRAM_PENDING.
			if ((pRead = AtalkAllocMemory(sizeof(DDP_READ))) == NULL)
			{
				RELEASE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, OldIrql);
				error = ATALK_RESR_MEM;
				break;
			}

			InsertTailList(&pDdpAddr->ddpao_ReadLinkage, &pRead->dr_Linkage);

			DerefAddr = FALSE;
			pRead->dr_OpBuf 	= pAmdl;
			pRead->dr_OpBufLen 	= AmdlLen;
			pRead->dr_RcvCmp 	= pRcvCmp;
			pRead->dr_RcvCtx 	= pRcvCtx;
			error 				= ATALK_PENDING;

		}
		RELEASE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, OldIrql);
	
	} while (FALSE);

	if (completeRecv)
	{
		ASSERT((error == ATALK_NO_ERROR) || (error == ATALK_BUFFER_TOO_SMALL));
		(*pRcvCmp)(error,
				   pAmdl,
				   AmdlLen,
				   &remoteAddr,
				   pRcvCtx);

		//	And return pending for sure!
		error		= ATALK_PENDING;
		DerefAddr	= TRUE;
	}


	if (DerefAddr)
	{
		AtalkDdpDereference(pDdpAddr);
	}

	return error;
}




//
//	DdpSend()
//	This function is used to deliver packets submitted by the ddp clients.
//	The packets are assummed to either be destined for one of the nodes on
//	the port, or need to be routed to another port (if router is on), or to
//	be transmitted onto the physical medium.
//
//	This takes a buffer descriptor as an input. This can contain either a
//	PAMDL or a PBYTE depending on where the data is coming from (user space
//	or router code respectively). In addition, it will take an optional header
//	buffer that will be appended to the ddp header. The buffer descriptor is
//	optional, that if NULL, it will be construed as a zero-length send.
//

ATALK_ERROR
AtalkDdpSend(
	IN	PDDP_ADDROBJ				pDdpAddr,
	IN	PATALK_ADDR					pDestAddr,
	IN	BYTE						Protocol,
	IN	BOOLEAN						DefinitelyRemoteAddr,
	IN	PBUFFER_DESC				pBuffDesc		OPTIONAL,
	IN	PBYTE						pOptHdr			OPTIONAL,
	IN	USHORT						OptHdrLen		OPTIONAL,
	IN	PBYTE						pMcastAddr		OPTIONAL,
	IN	PSEND_COMPL_INFO			pSendInfo		OPTIONAL
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ATALK_ERROR			error;
	BOOLEAN				shouldBeRouted;
	PPORT_DESCRIPTOR	pPortDesc;
	ATALK_ADDR			srcAddr;
	KIRQL				OldIrql;
	BOOLEAN				delivered 	= FALSE;

	//	NULL buffer descriptor => 0-length send.
	ASSERT((pBuffDesc == NULL) || (pBuffDesc->bd_Length > 0));

#ifdef DDP_STRICT
	//	Check destination address
	if (INVALID_ADDRESS(pDestAddr))
	{
		return ATALK_DDP_INVALID_ADDR;
	}
	
	//	Check the datagram length.
	if (pBuffDesc)
	{
		USHORT	dgramLen;

		AtalkSizeOfBuffDescData(pBuffDesc, &dgramLen);
		if (dgramLen > MAX_DGRAM_SIZE)
		{
			return ATALK_BUFFER_TOO_BIG;
		}
	}
#endif

	//	Get a pointer to the port on which the socket exists.
	pPortDesc = pDdpAddr->ddpao_Node->an_Port;

	//	Get the source address
	srcAddr = pDdpAddr->ddpao_Addr;

	if (!DefinitelyRemoteAddr)
	{
		// All socket handlers assume that they are called at DISPACTH. Make it so.
		KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);

		AtalkDdpOutBufToNodesOnPort(pPortDesc,
									&srcAddr,
									pDestAddr,
									Protocol,
									pBuffDesc,
									pOptHdr,
									OptHdrLen,
									&delivered);
		KeLowerIrql(OldIrql);

		if (delivered)
		{
			//	Ok, packet meant for one of our own nodes on this port,
			//	and we delivered it. Call the completion routine.
	
			if (pSendInfo != NULL)
			{
				(*pSendInfo->sc_TransmitCompletion)(NDIS_STATUS_SUCCESS, pSendInfo);
			}
			return ATALK_PENDING;
		}
	}

	ASSERT (!delivered);

	//	Can our router handle it?
	shouldBeRouted = ((pPortDesc->pd_Flags & PD_ROUTER_RUNNING)					&&
					  (pDestAddr->ata_Network != CABLEWIDE_BROADCAST_NETWORK)	&&
					  !(WITHIN_NETWORK_RANGE(pDestAddr->ata_Network,
											 &pPortDesc->pd_NetworkRange))		&&
					  !(WITHIN_NETWORK_RANGE(pDestAddr->ata_Network,
											 &AtalkStartupNetworkRange)));

	DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO,
			("AtalkDdpSend: destNet %lx shouldBeRouted %s\n",
			pDestAddr->ata_Network, shouldBeRouted ? "Yes" : "No"));

	if (shouldBeRouted)
	{
		ASSERT (!((WITHIN_NETWORK_RANGE(pDestAddr->ata_Network, &pPortDesc->pd_NetworkRange)) &&
				  (pDestAddr->ata_Node == ATALK_BROADCAST_NODE)));

		//	If we're a router and the packet isn't destined for the target ports
		//	local network, let our router handle it -- rather than sending to
		//	whatever the "best router" is or to "a router".
		do
		{
			// This algorithm is taken from the "Appletalk Phase 2 Specification".
		
			// If the destination network number is within the range of the reception
			// port's network range and the destination node number is broadcast, then
			// we can drop the packet on the floor -- it is a network specific broadcast
			// not for this router.  Note that we've already delivered the packet, and
			// thus not gotten here, if it was really addressed to the network of any
			// node owned by the reception port (in AtalkDdpPacketIn).
			// Also:
			// Try to find an entry in the routing table that contains the target
			// network.  If not found, discard the packet.

			PDDP_ADDROBJ		pRouteDdpAddr;
			PRTE				pRte;
			PPORT_DESCRIPTOR	pDestPortDesc;
			PATALK_NODE			pRouterNode;
			ATALK_ADDR			actualDest;
		
			if ((pRte = AtalkRtmpReferenceRte(pDestAddr->ata_Network)) == NULL)
			{
				DBGPRINT(DBG_COMP_ROUTER, DBG_LEVEL_FATAL,
						("AtalkDdpRouter: %lx RtmpRte/Not in ThisCableRange\n",
						pDestAddr->ata_Network));
		
				error = ATALK_RESR_MEM;
				break;
			}
		
			do
			{
				//	Get the port descriptor corres. to the RTE
				pDestPortDesc = pRte->rte_PortDesc;
		
				ASSERT(VALID_PORT(pDestPortDesc));
		
				//	If the target network's hop count is non-zero, we really need to send
				//	the beast, so, just do it!
				if (pRte->rte_NumHops != 0)
				{
					//	Too many hops?
					error = AtalkDdpTransmit(pDestPortDesc,
											 &srcAddr,
											 pDestAddr,
											 Protocol,
											 pBuffDesc,
											 pOptHdr,
											 OptHdrLen,
											 1,						//	HopCount
											 NULL,					//	pZoneMcastAddr
											 &pRte->rte_NextRouter,
											 pSendInfo);
					break;
				}
				
				//	If the destination node is zero, the packet is really destined for the
				//	router's node on this port.
				if (pDestAddr->ata_Node == ANY_ROUTER_NODE)
				{
					//	Try to reference this port, if not successful, its probably
					//	closing down. Grab the port lock and read the router node address.
					//	No need to reference, just ensure its not null.
					ACQUIRE_SPIN_LOCK(&pDestPortDesc->pd_Lock, &OldIrql);
		
					if ((pDestPortDesc->pd_Flags & PD_CLOSING) == 0)
					{
						ASSERT(pDestPortDesc->pd_RefCount > 0);
						pDestPortDesc->pd_RefCount++;
					}
					else
					{
						ASSERTMSG("AtalkDdpRouter: Could not ref port!\n", 0);
						error = ATALK_PORT_CLOSING;
						RELEASE_SPIN_LOCK(&pDestPortDesc->pd_Lock, OldIrql);
						break;
					}
		
					pRouterNode = pDestPortDesc->pd_RouterNode;
					if (pRouterNode != NULL)
					{
						actualDest.ata_Network = pRouterNode->an_NodeAddr.atn_Network;
						actualDest.ata_Node    = pRouterNode->an_NodeAddr.atn_Node;
		
						//	Set the actual destination socket.
						actualDest.ata_Socket  = pDestAddr->ata_Socket;
					}
					else
					{
						ASSERTMSG("AtalkDdpRouter: pRouter node is null!\n", 0);
						error = ATALK_DDP_NOTFOUND;
					}
		
					if (ATALK_SUCCESS(error))
					{
						AtalkDdpRefByAddrNode(pDestPortDesc,
											  &actualDest,
											  pRouterNode,
											  &pRouteDdpAddr,
											  &error);
					}
		
					RELEASE_SPIN_LOCK(&pDestPortDesc->pd_Lock, OldIrql);
		
					if (ATALK_SUCCESS(error))
					{
						KIRQL	OldIrql;

						// Socket handlers assume that they are called at DISPATCH. Make it so.
						KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);

						AtalkDdpInvokeHandlerBufDesc(pDestPortDesc,
													 pRouteDdpAddr,
													 &srcAddr,
													 pDestAddr,		// Pass in the actual destination
													 Protocol,
													 pBuffDesc,
													 pOptHdr,
													 OptHdrLen);
		
						//	Remove the reference on the socket
						AtalkDdpDereferenceDpc(pRouteDdpAddr);

						KeLowerIrql(OldIrql);
					}
					else
					{
						ASSERTMSG("AtalkDdpRouter: pSocket on router node is null!\n", 0);
					}
					break;
				}
		
				//	Okay, now walk through the nodes on the target port, looking for a
				//	home for this packet.
				if (!DefinitelyRemoteAddr)
				{
					// All socket handlers assume that they are called at DISPACTH. Make it so.
					KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);

					AtalkDdpOutBufToNodesOnPort(pDestPortDesc,
												&srcAddr,
												pDestAddr,
												Protocol,
												pBuffDesc,
												pOptHdr,
												OptHdrLen,
												&delivered);
					KeLowerIrql(OldIrql);

					if (delivered)
					{
			            if (pSendInfo != NULL)
			            {
				            (*pSendInfo->sc_TransmitCompletion)(NDIS_STATUS_SUCCESS, pSendInfo);
			            }
						error = ATALK_NO_ERROR;
						break;
					}
				}
			
				//	We need to deliver this packet to a local ports network.
				error = AtalkDdpTransmit(pDestPortDesc,
										 &srcAddr,
										 pDestAddr,
										 Protocol,
										 pBuffDesc,
										 pOptHdr,
										 OptHdrLen,
										 1,						//	HopCount
										 NULL,					//	pZoneMcastAddr,
										 NULL,	
										 pSendInfo);
			} while (FALSE);
		
			INTERLOCKED_INCREMENT_LONG_DPC(
					&pDestPortDesc->pd_PortStats.prtst_NumPktRoutedOut,
					&AtalkStatsLock.SpinLock);
		
			AtalkRtmpDereferenceRte(pRte, FALSE);				// Lock held?
		} while (FALSE);

		INTERLOCKED_INCREMENT_LONG_DPC(
			&pPortDesc->pd_PortStats.prtst_NumPktRoutedIn,
			&AtalkStatsLock.SpinLock);
	}
	else
	{
		error = AtalkDdpTransmit(pPortDesc,
								 &srcAddr,
								 pDestAddr,
								 Protocol,
								 pBuffDesc,
								 pOptHdr,
								 OptHdrLen,
								 0,					//	HopCnt,
								 pMcastAddr,
								 NULL,				//	pXmitDestNode,
								 pSendInfo);
	}

	return error;
}


//
//	DdpTransmit()
//	This function is called to build the headers for the packet and send it
//	out via the depend level functions. It is assumed at this point that the
//	packet is destined for nodes not currently controlled by this stack.
//
//	KnownMulticastAddress: Although the DDP destination is encoded using
//	'Destination', if this parameter is non-null, the packet is actually
//	sent to this address.
//
//	TransmitDestination: Again, as above, the router uses this to pass on the
//	packet to the next router it needs to go to, if 'Destination' is still one
//	or more hops away.
//
//	This is only called from within ddp send or by the router code (rtmp/zip/router).
//

ATALK_ERROR
AtalkDdpTransmit(
	IN	PPORT_DESCRIPTOR			pPortDesc,
	IN	PATALK_ADDR					pSrcAddr,
	IN	PATALK_ADDR					pDestAddr,
	IN	BYTE						Protocol,
	IN	PBUFFER_DESC				pBuffDesc		OPTIONAL,
	IN	PBYTE						pOptHdr			OPTIONAL,
	IN	USHORT						OptHdrLen		OPTIONAL,
	IN	USHORT						HopCnt,
	IN	PBYTE						pMcastAddr		OPTIONAL,	
	IN	PATALK_NODEADDR				pXmitDestNode	OPTIONAL,
	IN	PSEND_COMPL_INFO			pSendInfo		OPTIONAL
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PBYTE			pDgram, pDgramStart, pLinkDdpOptHdr;
	PBUFFER_DESC	pPktDesc;
	USHORT			linkLen;
	ATALK_NODEADDR	srcNode;
	ATALK_NODEADDR	destNode;
	USHORT			actualLength;
	ATALK_NODEADDR	actualDest;
	PBUFFER_DESC	probe;
	PBRE			routerNode;
	USHORT			bufLen				= 0;
	USHORT			checksum 			= 0;
	PBYTE			knownAddress 		= NULL;
	PBYTE			knownRouteInfo 		= NULL;
	USHORT	   		knownRouteInfoLen 	= 0;
	BOOLEAN			broadcast 			= FALSE;
	ATALK_ERROR		error 				= ATALK_NO_ERROR;
	BOOLEAN			shortDdpHeader		= FALSE;
	BOOLEAN			errorFreePkt		= FALSE;

	//
	// The basic transmit algorithum is:
	//
	//	   if (non-extended-network)
	//	   {
	//			if ((destination-network is 0 or
	//				 destination-network is NetworkRange.firstNetwork) and
	//				(source-network is 0 or
	//				 source-network is NetworkRange.firstNetwork))
	//			{
	//				 <send short form DDP packet to local network>
	//				 return-okay
	//			}
	//	   }
	//	   if (destination-network is CableWideBroadcastNetworkNumber or
	//		   destination-network in NetworkRange or
	//		   destination-network in SartupRange or
	//	   {
	//			<send long form DDP packet to local network>
	//			return-okay
	//	   }
	//	   if (destination-network-and-node in best-router-cache)
	//	   {
	//			<send long form DDP packet to best router>
	//			return-okay
	//	   }
	//	   if (seen-a-router-recently)
	//	   {
	//			<send long form DDP packet to a-router>
	//			return-okay
	//	   }
	//	   return-error
	
	destNode.atn_Network = pDestAddr->ata_Network;
	destNode.atn_Node = pDestAddr->ata_Node;
	actualDest.atn_Network = UNKNOWN_NETWORK;
	actualDest.atn_Node = UNKNOWN_NODE;

	do
	{
		if (pBuffDesc != NULL)
		{
			//	Get the buffer length. Check the datagram length.
			AtalkSizeOfBuffDescData(pBuffDesc, &bufLen);
			ASSERT(bufLen > 0);
		}

#ifdef DDP_STRICT
		//	Check destination address
		if (INVALID_ADDRESS(pDestAddr) || INVALID_ADDRESS(pSrcAddr))
		{
			error = ATALK_DDP_INVALID_ADDR;
			break;
		}
	
		if (pBuffDesc != NULL)
		{
			//	Ensure we do not have a chained datagram.
			if (pBuffDesc->bd_Next != NULL)
			{
				KeBugCheck(0);
			}

			if (bufLen > MAX_DGRAM_SIZE)
			{
				error = ATALK_BUFFER_TOO_BIG;
				break;
			}
		}

		if (OptHdrLen > MAX_OPTHDR_LEN)
		{
			error = ATALK_BUFFER_TOO_BIG;
			break;
		}
#endif
		//	For non-extended networks, we may want to send a short DDP header.
		if (!(EXT_NET(pPortDesc)) &&
			((pDestAddr->ata_Network == UNKNOWN_NETWORK) ||
			 (pDestAddr->ata_Network == pPortDesc->pd_NetworkRange.anr_FirstNetwork)) &&
			((pSrcAddr->ata_Network == UNKNOWN_NETWORK) ||
			 (pSrcAddr->ata_Network == pPortDesc->pd_NetworkRange.anr_FirstNetwork)))
		{
			//	Use a short ddp header. Call the port handler to first alloc
			//	the buffer that will hold both the link and ddp hdrs.
			shortDdpHeader	= TRUE;
			AtalkNdisAllocBuf(&pPktDesc);
			if (pPktDesc == NULL)
			{
				error = ATALK_FAILURE;
				break;
			}

			//	In cases of error, free the allocated packet.
			errorFreePkt = TRUE;

			//	pPkt will be the beginning of the packet and pDgram is where
			//	we fill in the ddp header.
			actualLength 		= bufLen + SDDP_HDR_LEN + OptHdrLen;
			pLinkDdpOptHdr		= pPktDesc->bd_CharBuffer;
			linkLen				= 0;
		
			ASSERT (pPortDesc->pd_NdisPortType == NdisMediumLocalTalk);

			//	Build the LAP header. This will build it from pDgram backwards,
			//	and set the pPkt pointer as the packet to be freed in the
			//	built buffer descriptor.
			linkLen = AtalkNdisBuildLTHdr(pLinkDdpOptHdr,
										  &pDestAddr->ata_Node,
										  pSrcAddr->ata_Node,
										  ALAP_SDDP_HDR_TYPE);
	
			DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO,
					("AtalkDdpTransmit: Sending short hdr on non-ext net! %ld\n",
					pDestAddr->ata_Node, pDestAddr->ata_Network));
			break;
		}

		//	LONG DDP HEADER
		// 	Compute the extended AppleTalk node number that we'll really need to
		//  send the packet to.

		DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO,
				("AtalkDdpTransmit: Building a long ddp header for bufdesc %lx on port %lx\n",
				pBuffDesc, pPortDesc));

		do
		{
			if (pMcastAddr != NULL)
			{
				knownAddress = pMcastAddr ;
				break;
			}

			if (pXmitDestNode != NULL)
			{
				actualDest = *pXmitDestNode;
				break;
			}

			if ((WITHIN_NETWORK_RANGE(pDestAddr->ata_Network,
									  &pPortDesc->pd_NetworkRange))	 ||
				(pDestAddr->ata_Network == CABLEWIDE_BROADCAST_NETWORK) ||
				(WITHIN_NETWORK_RANGE(pDestAddr->ata_Network,
									  &AtalkStartupNetworkRange)))
			{
				actualDest.atn_Node = pDestAddr->ata_Node;
				actualDest.atn_Network = pDestAddr->ata_Network;
				broadcast = (pDestAddr->ata_Node == ATALK_BROADCAST_NODE);
				break;
			}

			atalkDdpFindInBrc(pPortDesc, destNode.atn_Network, &routerNode);
			if (routerNode != NULL)
			{
				// Okay, we know where to go.
				knownAddress 		= routerNode->bre_RouterAddr;
				knownRouteInfo 		= (PBYTE)routerNode + sizeof(BRE);
				knownRouteInfoLen 	= routerNode->bre_RouteInfoLen;
				break;
			}

			if (pPortDesc->pd_Flags & PD_SEEN_ROUTER_RECENTLY)
			{
				actualDest = pPortDesc->pd_ARouter;
				break;
			}

			//	No router known. What do we do ? If its not an extended net,
			//  just send it - else return error.
			if (EXT_NET(pPortDesc))
			{
				error = ATALK_DDP_NO_ROUTER;
				break;
			}
			actualDest.atn_Node = pDestAddr->ata_Node;
			actualDest.atn_Network = pDestAddr->ata_Network;
			broadcast = (pDestAddr->ata_Node == ATALK_BROADCAST_NODE);
		} while (FALSE);

		if (error != ATALK_NO_ERROR)
		{
			break;
		}

		AtalkNdisAllocBuf(&pPktDesc);
		if (pPktDesc == NULL)
		{
			error = ATALK_FAILURE;
			break;
		}

		//	In cases of error, free the allocated packet.
		errorFreePkt = TRUE;

		pLinkDdpOptHdr		= pPktDesc->bd_CharBuffer;
		linkLen				= 0;
		actualLength 		= bufLen + LDDP_HDR_LEN + OptHdrLen;
	
		//	If we already know where we're headed, just blast it out.  Also,
		//	if we're broadcasting, just do it.  "knownAddress" will be NULL
		//	if we're broadcasting and that will cause the BuildHeader to make
		//	a broadcast packet.
		
		if (EXT_NET(pPortDesc) &&
			((knownAddress != NULL) ||
			  broadcast				||
			 (actualDest.atn_Network == CABLEWIDE_BROADCAST_NETWORK)))
		{
			//	Build the LAP header.
			AtalkNdisBuildHdr(pPortDesc,
							  pLinkDdpOptHdr,
							  linkLen,
							  actualLength,
							  knownAddress,
							  knownRouteInfo,
							  knownRouteInfoLen,
							  APPLETALK_PROTOCOL);
			break;
		}

		//	On non-extended networks, just send the packet to the desired node --
		//	no AARP games here.
		if (!EXT_NET(pPortDesc))
		{
			DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO,
					("AtalkDdpTransmit: Sending long hdr on non-ext net! %ld\n",
					actualDest.atn_Network, actualDest.atn_Node));
			
			ASSERT (pPortDesc->pd_NdisPortType == NdisMediumLocalTalk);

			linkLen = AtalkNdisBuildLTHdr(pLinkDdpOptHdr,
										  &actualDest.atn_Node,
										  pSrcAddr->ata_Node,
										  ALAP_LDDP_HDR_TYPE);
			break;
		}
	
		//	We're sending to a particular node on an extended network.
		//	Do we know its hardware address ? If so, send it out.
		{
			KIRQL		OldIrql;
			USHORT		index;
			PAMT		pAmt;
		
			//	Go through the AMT and find the entry for the destination
			//	address if present.
			index = HASH_ATALK_NODE(&actualDest) % PORT_AMT_HASH_SIZE;
		
			ACQUIRE_SPIN_LOCK(&pPortDesc->pd_Lock, &OldIrql);
			
			for (pAmt = pPortDesc->pd_Amt[index];
				 pAmt != NULL;
				 pAmt = pAmt->amt_Next)
			{
				if (ATALK_NODES_EQUAL(&pAmt->amt_Target, &actualDest))
				{
					ASSERT(EXT_NET(pPortDesc));
					AtalkNdisBuildHdr(pPortDesc,
									  pLinkDdpOptHdr,
									  linkLen,
									  actualLength,
									  pAmt->amt_HardwareAddr,
									  (PBYTE)pAmt+sizeof(AMT),
									  pAmt->amt_RouteInfoLen,
									  APPLETALK_PROTOCOL);
					error = ATALK_NO_ERROR;
					break;
				}
			}
		
			RELEASE_SPIN_LOCK(&pPortDesc->pd_Lock, OldIrql);

			if (pAmt == NULL)
			{
				DBGPRINT(DBG_COMP_AARP, DBG_LEVEL_WARN,
						("atalkDdpFindInAmt: Could not find %lx.%lx\n",
						actualDest.atn_Network, actualDest.atn_Node));
				error = ATALK_DDP_NO_AMT_ENTRY;
			}
			else break;				// Found the actual h/w address we want to go to.
		}

		//	Free up the allocated header buffer.
		errorFreePkt = TRUE;

		ASSERT(!ATALK_SUCCESS(error));

		//	We dont have the hardware address for the logical address that we
		//	need to send the packet to. Send out aarp requests and drop this packet.
		//	The higher layers can retry later if they have to.
		srcNode.atn_Network = pSrcAddr->ata_Network;
		srcNode.atn_Node	= pSrcAddr->ata_Node;

		probe = BUILD_AARPREQUEST(pPortDesc,
								  MAX_HW_ADDR_LEN,
								  srcNode,
								  actualDest);

		if (probe != NULL)
		{
#ifdef	PROFILING
			INTERLOCKED_INCREMENT_LONG(
				&pPortDesc->pd_PortStats.prtst_NumAarpProbesOut,
				&AtalkStatsLock.SpinLock);
#endif

			//	Send the aarp packet.
			error = AtalkNdisSendPacket(pPortDesc,
										probe,
										AtalkAarpSendComplete,
										NULL);
		
			if (!ATALK_SUCCESS(error))
			{
				TMPLOGERR()
				AtalkAarpSendComplete(NDIS_STATUS_FAILURE,
									  probe,
									  NULL);
			}
		}

		DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_WARN,
				("AMT Entry not found for %lx.%lx\n",
				pDestAddr->ata_Network, pDestAddr->ata_Node));

		error = ATALK_DDP_NO_AMT_ENTRY;
		break;

	} while (FALSE);

	//	Do we need to send the packet?
	if (ATALK_SUCCESS(error))
	{
		ASSERT(HopCnt <= RTMP_MAX_HOPS);

		//	Remember the beginning of the dgram
		pDgramStart = pDgram = pLinkDdpOptHdr + linkLen;

		if (!shortDdpHeader)
		{
			*pDgram++ = (DDP_HOP_COUNT(HopCnt) + DDP_MSB_LEN(actualLength));
		
			PUTSHORT2BYTE(pDgram, actualLength);
			pDgram++;
	
			ASSERT(checksum == 0);
			PUTSHORT2SHORT(pDgram, checksum);
			pDgram += sizeof(USHORT);
		
			PUTSHORT2SHORT(pDgram, pDestAddr->ata_Network);
			pDgram += sizeof(USHORT);
		
			PUTSHORT2SHORT(pDgram, pSrcAddr->ata_Network);
			pDgram += sizeof(USHORT);
		
			*pDgram++ = pDestAddr->ata_Node;
			*pDgram++ = pSrcAddr->ata_Node;
			*pDgram++ = pDestAddr->ata_Socket;
			*pDgram++ = pSrcAddr->ata_Socket;
			*pDgram++ = Protocol;
	
			//	Copy the optional header if present
			if (OptHdrLen > 0)
			{
				ASSERT(pOptHdr != NULL);
				RtlCopyMemory(pDgram, pOptHdr, OptHdrLen);
			}
	
			//	Set length in the buffer descriptor.
			AtalkSetSizeOfBuffDescData(pPktDesc,
									   linkLen + LDDP_HDR_LEN + OptHdrLen);
		}
		else
		{
			*pDgram++ 	= DDP_MSB_LEN(actualLength);

			PUTSHORT2BYTE(pDgram, actualLength);
			pDgram++;
	
			*pDgram++ = pDestAddr->ata_Socket;
			*pDgram++ = pSrcAddr->ata_Socket;
			*pDgram++ = Protocol;
	
			//	Copy the optional header if present
			if (OptHdrLen > 0)
			{
				ASSERT(pOptHdr != NULL);
				RtlCopyMemory(pDgram, pOptHdr, OptHdrLen);
			}

			//	Set length in the buffer descriptor.
			AtalkSetSizeOfBuffDescData(pPktDesc,
									   linkLen + SDDP_HDR_LEN + OptHdrLen);
		}

		//	Chain the passed in buffer desc onto the tail of the one
		//	returned above.
		AtalkPrependBuffDesc(pPktDesc, pBuffDesc);

		//	Okay, set checksum if needed.
		if (pPortDesc->pd_Flags & PD_SEND_CHECKSUMS)
		{
			// 	Temporary skip over the leading unchecksumed bytes.
			checksum = AtalkDdpCheckSumBufferDesc(pPktDesc,
												  (USHORT)(linkLen + LEADING_UNCHECKSUMED_BYTES));
									

			DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO,
					("AtalkDdpTransmit: checksum %lx\n", checksum));

			PUTSHORT2SHORT(&pDgramStart[LDDP_CHECKSUM_OFFSET], checksum);
		}
		
		INTERLOCKED_ADD_STATISTICS(&pPortDesc->pd_PortStats.prtst_DataOut,
								   AtalkSizeBuffDesc(pPktDesc),
								   &AtalkStatsLock.SpinLock);

		//	Send the packet.  The completion routine will handle freeing the buffer chain.
		error = AtalkNdisSendPacket(pPortDesc,
									pPktDesc,
									AtalkDdpSendComplete,
									pSendInfo);
			
		if (!ATALK_SUCCESS(error))
		{
			AtalkDdpSendComplete(NDIS_STATUS_FAILURE,
								 pPktDesc,
								 pSendInfo);

			//	Return pending. We've alredy called the completion
			//	routine here, which will have called the callers
			//	completion routine.
			error = ATALK_PENDING;
		}
	}

	//	Do we need to free the allocated header packet?
	if (!ATALK_SUCCESS(error) && (errorFreePkt))
	{
		AtalkNdisFreeBuf(pPktDesc);
	}

	return error;
}



VOID
AtalkDdpSendComplete(
	NDIS_STATUS				Status,
	PBUFFER_DESC			pBuffDesc,
	PSEND_COMPL_INFO		pInfo
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	//	Free up the buffer descriptor for the first part
	//	and call the specified completion. One of the contexts
	//	should be the remaining part of the buffer descriptor
	//	chain.

	//	There will always be atleast the ddp header, although the next
	//	part could be null. Thats upto the completion routine to care
	//	about.

	ASSERT(pBuffDesc != NULL);
	pBuffDesc->bd_Next = NULL;

	ASSERT(pBuffDesc->bd_Flags & BD_CHAR_BUFFER);
	AtalkNdisFreeBuf(pBuffDesc);

	//	If null, just return.
	if (pInfo != NULL)
	{
		//	Call the completion routine for the transmit if present
		if (pInfo->sc_TransmitCompletion)
			(pInfo->sc_TransmitCompletion)(Status, pInfo);
	}
}
	



VOID
AtalkDdpInvokeHandlerBufDesc(
	IN		PPORT_DESCRIPTOR	pPortDesc,
	IN		PDDP_ADDROBJ		pDdpAddr,
	IN		PATALK_ADDR			pSrc,
	IN		PATALK_ADDR			pDest,
	IN		BYTE				Protocol,
	IN		PBUFFER_DESC		pBuffDesc		OPTIONAL,
	IN		PBYTE				pOptHdr			OPTIONAL,
	IN		USHORT				OptHdrLen		OPTIONAL
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	USHORT			pktLen	= 0;
	PBYTE			pPkt	= NULL;
	BOOLEAN			freePkt = FALSE;

	//	This is only called from directly or indirectly throught
	//	the router in AtalkDdpSend. Both of these cases indicate
	//	that we have completion routines to deal with. We just make
	//	a copy and assume caller will deal with its buffer descriptor.


	//	Alloc and copy the buffer descriptor data into pPkt.
	//	optimization: If the buffer descriptor is not a chain
	//				  and contains a PBYTE and OptHdrLen = 0,
	//				  then pass that directly.
	//				  Or if buffer descriptor is NULL indicating 0-length
	//				  sends.

	do
	{
		if ((pBuffDesc != NULL) &&
			(pBuffDesc->bd_Next == NULL) &&
			(pBuffDesc->bd_Flags & BD_CHAR_BUFFER) &&
			(OptHdrLen == 0))
		{
			DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO,
					("AtalkDdpInvokeHandlerBufDesc: one element, opt hdr null %ld\n",
					pBuffDesc->bd_Length));
	
			pPkt 	= pBuffDesc->bd_CharBuffer;
			pktLen 	= pBuffDesc->bd_Length;
		}
		else if ((pBuffDesc != NULL) || (OptHdrLen != 0))
		{
			//	Make a copy! Either the buffer descriptor of the Optional Header
			//	is non null. Or both or non-null.
			if (pBuffDesc != NULL)
			{
				AtalkSizeOfBuffDescData(pBuffDesc, &pktLen);
				ASSERT(pktLen > 0);
			}
	
			//	Add the optHdrLen
			pktLen += OptHdrLen;
	
			DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO,
					("AtalkDdpInvokeHandlerBufDesc: Size (incl opt hdr len) %ld\n",
					pktLen));
	
			if ((pPkt = AtalkAllocMemory(pktLen)) != NULL)
			{
				//	First copy the OptHdr if present
				if (pOptHdr != NULL)
				{
					RtlCopyMemory(pPkt, pOptHdr, OptHdrLen);
				}

				if (pBuffDesc != NULL)
				{
					AtalkCopyBuffDescToBuffer(pBuffDesc,
											  0,						// SrcOff
											  pktLen - OptHdrLen,
											  pPkt + OptHdrLen);
				}

				freePkt = TRUE;
			}
			else
			{
				break;
			}
		}
		else
		{
			ASSERT((pBuffDesc == NULL) && (OptHdrLen == 0));
			ASSERT(pPkt == NULL);
			ASSERT(pktLen == 0);
		}
	
		AtalkDdpInvokeHandler(pPortDesc,
							  pDdpAddr,
							  pSrc,
							  pDest,
							  Protocol,
							  pPkt,
							  pktLen);

	} while (FALSE);

	if (freePkt)
	{
        AtalkFreeMemory(pPkt);
	}
}




VOID
AtalkDdpInvokeHandler(
	IN		PPORT_DESCRIPTOR	pPortDesc,
	IN		PDDP_ADDROBJ		pDdpAddr,
	IN		PATALK_ADDR			pSrc,
	IN		PATALK_ADDR			pDest,
	IN		BYTE				Protocol,
	IN		PBYTE				pPkt		OPTIONAL,
	IN		USHORT				PktLen		OPTIONAL
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PLIST_ENTRY	p;
	PDDP_READ	pRead;
	NTSTATUS	status;
	ATALK_ERROR	error;
	ULONG		bytesCopied;
	BOOLEAN		eventDone = FALSE;

	ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

	//	The address object should be referenced, and we just assume
	//	it will be valid during the lifetime of this call.

	//	Check if protocol type is valid.
	if ((pDdpAddr->ddpao_Protocol != Protocol) &&
		(pDdpAddr->ddpao_Protocol != DDPPROTO_ANY))
	{
		return;
	}

	//	First check for queued ddp reads
	ACQUIRE_SPIN_LOCK_DPC(&pDdpAddr->ddpao_Lock);
	if (!IsListEmpty(&pDdpAddr->ddpao_ReadLinkage))
	{
		p = RemoveHeadList(&pDdpAddr->ddpao_ReadLinkage);
		RELEASE_SPIN_LOCK_DPC(&pDdpAddr->ddpao_Lock);

		error	= ATALK_NO_ERROR;
		pRead 	= CONTAINING_RECORD(p, DDP_READ, dr_Linkage);

		//	Do copy if > 0 bytes
		if (PktLen > 0)
		{
			if (PktLen > pRead->dr_OpBufLen)
			{
				error 	= ATALK_BUFFER_TOO_SMALL;
			}

			PktLen 	= MIN(PktLen, pRead->dr_OpBufLen);
			status = TdiCopyBufferToMdl(pPkt,
										0,
										PktLen,
										GET_MDL_FROM_OPAQUE(pRead->dr_OpBuf),
										0,
										&bytesCopied);

			ASSERT(status == STATUS_SUCCESS);
		}


		(*pRead->dr_RcvCmp)(error, pRead->dr_OpBuf, PktLen, pSrc, pRead->dr_RcvCtx);

		AtalkFreeMemory(pRead);
		return;
	}

	//	If a handler was set on this socket,call it.
	else if (pDdpAddr->ddpao_Handler != NULL)
	{
		RELEASE_SPIN_LOCK_DPC(&pDdpAddr->ddpao_Lock);
		(*pDdpAddr->ddpao_Handler)(pPortDesc,
								   pDdpAddr,
								   pPkt,
								   PktLen,
								   pSrc,
								   pDest,
								   ATALK_NO_ERROR,
								   Protocol,
								   pDdpAddr->ddpao_HandlerCtx,
								   FALSE,
								   NULL);
	}
	else
	{
		//	if there is an event handler on this address object call it.
		//	If there is already a buffered datagram, drop this packet.
		//	If not, save this datagram as the buffered one, and then
		//	indicate,

		if (pDdpAddr->ddpao_Flags & DDPAO_DGRAM_EVENT)
		{
			do
			{
				//	We have datagram event handler set on this AO.
				if (pDdpAddr->ddpao_Flags & (DDPAO_DGRAM_ACTIVE |
											 DDPAO_DGRAM_PENDING))
				{
					//	We are already indicating an event. Or we
					//	have a buffered datagram. Drop this pkt.
					break;
				}
				else
				{
					PTDI_IND_RECEIVE_DATAGRAM 	RcvHandler;
					PVOID 						RcvCtx;
					ULONG						bytesTaken;
					PIRP						rcvDgramIrp;
					TA_APPLETALK_ADDRESS		srcTdiAddr;
					NTSTATUS					status;
				
					ASSERT(pDdpAddr->ddpao_EventInfo != NULL);

					pDdpAddr->ddpao_Flags |= (DDPAO_DGRAM_ACTIVE	|
											  DDPAO_DGRAM_PENDING);

					RcvHandler = pDdpAddr->ddpao_EventInfo->ev_RcvDgramHandler;
					RcvCtx	 = pDdpAddr->ddpao_EventInfo->ev_RcvDgramCtx;

					ATALKADDR_TO_TDI(&srcTdiAddr, pSrc);

					//	Save the dgram in the event info.
					RtlCopyMemory(pDdpAddr->ddpao_EventInfo->ev_IndDgram, pPkt, PktLen);

					pDdpAddr->ddpao_EventInfo->ev_IndDgramLen 	= PktLen;
					pDdpAddr->ddpao_EventInfo->ev_IndSrc 		= *pSrc;
					pDdpAddr->ddpao_EventInfo->ev_IndProto 		= Protocol;
					RELEASE_SPIN_LOCK_DPC(&pDdpAddr->ddpao_Lock);

					status = (*RcvHandler)(RcvCtx,
										   sizeof(TA_APPLETALK_ADDRESS),
										   &srcTdiAddr,
										   0,					  	// Options length
										   NULL,				   	// Options
										   0,						// Datagram flags
										   (ULONG)PktLen,  		// Bytes indicated
										   (ULONG)PktLen,  		// Bytes available
										   (ULONG *)&bytesTaken,
										   pPkt,
										   &rcvDgramIrp);
		
					ASSERT((bytesTaken == 0) || (bytesTaken == PktLen));
		
					if (status == STATUS_MORE_PROCESSING_REQUIRED)
					{
						if (rcvDgramIrp != NULL)
						{
							//  Post the receive as if it came from the io system
							status= AtalkDispatchInternalDeviceControl(
									(PDEVICE_OBJECT)AtalkDeviceObject[ATALK_DEV_DDP],
									 rcvDgramIrp);
			
							ASSERT(status == STATUS_PENDING);
						}
					}	
					else if (status == STATUS_SUCCESS)
					{
						if (bytesTaken != 0)
						{
							//	Assume all of the data was read.
							pDdpAddr->ddpao_Flags &= ~DDPAO_DGRAM_PENDING;
						}
					}
					else if (status == STATUS_DATA_NOT_ACCEPTED)
					{
						//	Client may have posted a receive in the indication. Or
						//	it will post a receive later on. Do nothing here.
						DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_ERR,
								("atalkDdpRecvData: Indication status %lx\n", status));
					}

					ACQUIRE_SPIN_LOCK_DPC(&pDdpAddr->ddpao_Lock);
				}

			} while (FALSE);

			//	reset the event flags
			pDdpAddr->ddpao_Flags &= ~DDPAO_DGRAM_ACTIVE;
		}
		RELEASE_SPIN_LOCK_DPC(&pDdpAddr->ddpao_Lock);
	}
}




VOID
AtalkDdpPacketIn(
	IN	PPORT_DESCRIPTOR	pPortDesc,
	IN	PBYTE				pLinkHdr,
	IN	PBYTE				pPkt,
	IN	USHORT				PktLen
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	USHORT			dgramLen, ddpHdrLen;
	USHORT			hopCnt, checksum;
	BYTE			Protocol;
	ATALK_ADDR		destAddr, srcAddr;
	PBYTE			pDdpHdr;

	//	Only for localtalk
	BYTE			alapSrcNode;
	BYTE			alapDestNode;

	USHORT			srcOffset;
	BOOLEAN			extHdr	  = TRUE;
	PBYTE			pRouteInfo;
	USHORT			routeLen  = 0;
	BOOLEAN			delivered = FALSE;
	BOOLEAN			broadcast = FALSE;
	BOOLEAN			shouldBeRouted = FALSE;
	ATALK_ERROR		error = ATALK_NO_ERROR;
	TIME			TimeS, TimeE, TimeD;

	TimeS = KeQueryPerformanceCounter(NULL);

	if (PORT_CLOSING(pPortDesc))
	{
		//	If we are not active, return!
		return;
	}

	do
	{
		ASSERT((PktLen > 0) || ((PktLen == 0) && (pPkt == NULL)));

		if (PktLen > (MAX_DGRAM_SIZE + LDDP_HDR_LEN))
		{
			DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_WARN,
					("AtalkDdpPacketIn: Invalid size %lx\n", PktLen));
			error = ATALK_DDP_INVALID_LEN;
			break;
		}

		//	Get to the ddp header
		pDdpHdr		= pPkt;

		//	Short and long header formats have the length in the same place,
		dgramLen = DDP_GET_LEN(pDdpHdr);
		hopCnt   = DDP_GET_HOP_COUNT(pDdpHdr);

		//	Is the packet too long?
		if ((hopCnt > RTMP_MAX_HOPS) || (dgramLen > PktLen))
		{
			error = ATALK_DDP_INVALID_LEN;
			break;
		}

		//	First glean the information. Check for route info if
		//	tokenring network.
		switch (pPortDesc->pd_NdisPortType)
		{
		  case NdisMedium802_5:
	
			if (pLinkHdr[TLAP_SRC_OFFSET] & TLAP_SRC_ROUTING_MASK)
			{
				routeLen = (pLinkHdr[TLAP_ROUTE_INFO_OFFSET] & TLAP_ROUTE_INFO_SIZE_MASK);

				//  First, glean any AARP information that we can, then handle the DDP
				//  packet.  This guy also makes sure we have a good 802.2 header...
				//
				//  Need to make a localcopy of the source address and then turn
				//  the source routing bit off before calling GleanAarpInfo
				//
			
				pLinkHdr[TLAP_SRC_OFFSET] &= ~TLAP_SRC_ROUTING_MASK;
				pRouteInfo = pLinkHdr + TLAP_ROUTE_INFO_OFFSET;
			}

			ddpHdrLen	 = LDDP_HDR_LEN;

			srcOffset = TLAP_SRC_OFFSET;
			break;
	
		  case NdisMedium802_3:

			//	Check the length.
			if ((dgramLen < LDDP_HDR_LEN) ||
				(dgramLen > MAX_DGRAM_SIZE + LDDP_HDR_LEN))
			{
				error = ATALK_DDP_INVALID_LEN;
				break;
			}

			ddpHdrLen	 = LDDP_HDR_LEN;

			srcOffset	 = ELAP_SRC_OFFSET;
			break;
	
		  case NdisMediumFddi:

			//	Check the length.
			if ((dgramLen < LDDP_HDR_LEN) ||
				(dgramLen > MAX_DGRAM_SIZE + LDDP_HDR_LEN))
			{
				error = ATALK_DDP_INVALID_LEN;
				break;
			}

			ddpHdrLen	 = LDDP_HDR_LEN;

			srcOffset	 = FDDI_SRC_OFFSET;
			break;
	
		  case NdisMediumLocalTalk:
	
			//	Do we have an extended header?
			extHdr = (BOOLEAN)(pLinkHdr[ALAP_TYPE_OFFSET] == ALAP_LDDP_HDR_TYPE);

			if (extHdr)
			{
				ddpHdrLen	 = LDDP_HDR_LEN;
			}
			else
			{
				alapDestNode 	= *(pLinkHdr + ALAP_DEST_OFFSET);
				alapSrcNode 	= *(pLinkHdr + ALAP_SRC_OFFSET);

				if ((dgramLen < SDDP_HDR_LEN) ||
					(dgramLen > (MAX_DGRAM_SIZE + SDDP_HDR_LEN)))
				{
					error = ATALK_DDP_INVALID_LEN;
					break;
				}
	
				ddpHdrLen	 = SDDP_HDR_LEN;
			}

			break;
	
		  default:
			//  Should never happen!
			DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_FATAL,
					("AtalkDdpPacketIn: Unknown media\n"));

			KeBugCheck(0);
			break;
		}

		if (!ATALK_SUCCESS(error))
		{
			break;
		}

		//	Advance packet to point to the data. Caller frees up packet.
		pPkt += ddpHdrLen;

		//	Glean aarp information for non-localtalk ports
		if (pPortDesc->pd_NdisPortType != NdisMediumLocalTalk)
		{
			AtalkAarpGleanInfo(pPortDesc,
							   pLinkHdr + srcOffset,
							   TLAP_ADDR_LEN,
							   pRouteInfo,
							   (USHORT)routeLen,
							   pDdpHdr,
							   (USHORT)ddpHdrLen);
		}

		pDdpHdr += 2;	// Past off-cable & len
	
		if (extHdr)		//	Long DDP header
		{
			//	Get checksum, verification, if needed.
			GETSHORT2SHORT(&checksum, pDdpHdr);
			pDdpHdr += 2;

			if (checksum != 0)
			{
				USHORT	calcCheckSum;

				//	pDdpHdr has already moved passed LEADING_UNCHECKSUMED_BYTES.
				//	So we just need to decrease the header length field. Use
				//	dgramLen, NOT PktLen!
				calcCheckSum = AtalkDdpCheckSumPacket(pDdpHdr,
													  (USHORT)(ddpHdrLen - LEADING_UNCHECKSUMED_BYTES),
													  pPkt,
													  (USHORT)(dgramLen - ddpHdrLen));
				if (checksum != calcCheckSum)
				{
					DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_ERR,
							("AtalkDdpPacketIn: Checksums dont match! %lx.%lx\n",
							checksum, calcCheckSum));

					AtalkLogBadPacket(pPortDesc,
									  &srcAddr,
									  &destAddr,
									  pDdpHdr,
									  (USHORT)(ddpHdrLen - LEADING_UNCHECKSUMED_BYTES));

					error = ATALK_DDP_PKT_DROPPED;
					break;
				}
			}
			

			//	Build full source and destination AppleTalk address structures
			//	from our DDP header.
			GETSHORT2SHORT(&destAddr.ata_Network, pDdpHdr);
			pDdpHdr += 2;

			GETSHORT2SHORT(&srcAddr.ata_Network, pDdpHdr);
			pDdpHdr += 2;

			destAddr.ata_Node 	= *pDdpHdr++;
			srcAddr.ata_Node 	= *pDdpHdr++;

			destAddr.ata_Socket	= *pDdpHdr++;
			srcAddr.ata_Socket 	= *pDdpHdr++;

			//	Get the protocol type.
			Protocol 			= *pDdpHdr;

			broadcast = (destAddr.ata_Node == ATALK_BROADCAST_NODE);
			
			//	Do we like what we see?  Note "nnnn00" is now allowed and used by
			//	NBP.
			
			if ((srcAddr.ata_Network > LAST_VALID_NETWORK) ||
				(srcAddr.ata_Network < FIRST_VALID_NETWORK) ||
				(srcAddr.ata_Node < MIN_USABLE_ATALKNODE) ||
				(srcAddr.ata_Node > MAX_USABLE_ATALKNODE))
			{
				error = ATALK_DDP_INVALID_SRC;
				break;
			}

			if ((destAddr.ata_Network > LAST_VALID_NETWORK) ||
				((destAddr.ata_Node > MAX_USABLE_ATALKNODE) &&
				  !broadcast))
			{
				error = ATALK_DDP_INVALID_DEST;
				break;
			}

			//	Loop through all nodes that are on the reception port and see if
			//	anybody wants this packet.  The algorithm is from the "AppleTalk
			//	Phase 2 Protocol Specification" with enhacements to support ports
			//	that have multiple nodes.
			
			//	"0000xx" (where "xx" isnt "FF") should not be accepted on an
			//	extended port... For some unknown reason, the spec would like
			//	us to pass this case onto the router (which will, no doubt,
			//	drop it on the floor because it won't find network zero in its
			//	routing table)... you know, bug-for-bug compatible!
			if ((destAddr.ata_Network == UNKNOWN_NETWORK) &&
				(pPortDesc->pd_Flags & PD_EXT_NET) &&
				(!broadcast))
			{
				DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_WARN,
						("DdpPacketIn: Received pkt with net/node %lx.%lx on ext\n",
						destAddr.ata_Network, destAddr.ata_Node));

				shouldBeRouted = TRUE;
			}
			else
			{
				//	Now, on the packet in path, we either deliver the packet
				//	to one of our nodes on this port, or we pass it on to the
				//	router. Even if the packet is a broadcast, the delivered
				//	flag will be set to true. shouldBeRouter will be set to
				//	true, only if the packet *DOES NOT* seem to be destined for
				//	this port. We route the packet *ONLY IF* both shouldBeRouter
				//	is true and delivered is false.
				AtalkDdpInPktToNodesOnPort(pPortDesc,
										   &destAddr,
										   &srcAddr,
										   Protocol,
										   pPkt,
										   (USHORT)(dgramLen - LDDP_HDR_LEN),
										   &delivered,
										   &shouldBeRouted);
			}


			//	If we're a router and we think that might help, give her a crack at it.
			if ((pPortDesc->pd_Flags & PD_ROUTER_RUNNING) &&
				!delivered &&
				shouldBeRouted)
			{
				AtalkDdpRouteInPkt(pPortDesc,
								   &srcAddr,
								   &destAddr,
								   Protocol,
								   pPkt,
								   (USHORT)(dgramLen - LDDP_HDR_LEN),
								   hopCnt);
			}
		}
		else		//	Short DDP header!
		{
			BYTE	ThisNode;

			ASSERT(!EXT_NET(pPortDesc));

			if (pPortDesc->pd_Flags & PD_EXT_NET)
			{
				error = ATALK_DDP_SHORT_HDR;
				break;
			}

			//	Use network number for the node on this port for source/destination
			//	network numbers. When we search for the socket/address
			//	object, the concept net = 0, matches anything will come
			//	into play.

			srcAddr.ata_Network = destAddr.ata_Network = NET_ON_NONEXTPORT(pPortDesc);
			srcAddr.ata_Node 	= alapSrcNode;

			ThisNode = NODE_ON_NONEXTPORT(pPortDesc);
			if (alapDestNode == ATALK_BROADCAST_NODE)
			{
				destAddr.ata_Node = ThisNode;
			}
			else if (alapDestNode != ThisNode)
			{
				error = ATALK_DDP_INVALID_DEST;
				break;
			}
			else
			{
				destAddr.ata_Node  	= alapDestNode;
			}

			DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_WARN,
					("AtalkDdpPacketIn: NonExtended Dest Net.Node %lx.%lx\n",
					destAddr.ata_Network, destAddr.ata_Node));

			//	Get the socket numbers from the ddp header.
			destAddr.ata_Socket = *pDdpHdr++;
			srcAddr.ata_Socket	= *pDdpHdr++;

			//	Get the protocol type
			Protocol 			= *pDdpHdr;

			//	If the protocol type is 0, we have an error.
			if (Protocol == 0)
			{
				error = ATALK_DDP_INVALID_PROTO;
				break;
			}

			//	Now the destination node address could be
			//	ALAP_BROADCAST_NODE (0xFF).
			if ((srcAddr.ata_Node < MIN_USABLE_ATALKNODE) ||
				(srcAddr.ata_Node > MAX_USABLE_ATALKNODE))
			{
				error = ATALK_DDP_INVALID_SRC;
				break;
			}

			if (((destAddr.ata_Node < MIN_USABLE_ATALKNODE) ||
				 (destAddr.ata_Node > MAX_USABLE_ATALKNODE)) &&
				(destAddr.ata_Node != ATALK_BROADCAST_NODE))
			{
				error = ATALK_DDP_INVALID_DEST;
				break;
			}

			//	On a non-extended port, there will be only one node.
			AtalkDdpInPktToNodesOnPort(pPortDesc,
									   &destAddr,
									   &srcAddr,
									   Protocol,
									   pPkt,
									   (USHORT)(dgramLen - SDDP_HDR_LEN),
									   &delivered,
									   &shouldBeRouted);		// This is a dud parameter
																// for non-ext nets
		}
	} while (FALSE);

	if (!ATALK_SUCCESS(error))
	{
		DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_WARN,
				("AtalkDdpPacketIn: Dropping packet %lx\n", error) );
	}

	TimeE = KeQueryPerformanceCounter(NULL);
	TimeD.QuadPart = TimeE.QuadPart - TimeS.QuadPart;

	INTERLOCKED_ADD_LARGE_INTGR_DPC(
		&pPortDesc->pd_PortStats.prtst_DdpPacketInProcessTime,
		TimeD,
		&AtalkStatsLock.SpinLock);

	INTERLOCKED_INCREMENT_LONG_DPC(
		&pPortDesc->pd_PortStats.prtst_NumDdpPacketsIn,
		&AtalkStatsLock.SpinLock);
}



VOID
AtalkDdpQuery(
	IN	PDDP_ADDROBJ	pDdpAddr,
	IN	PAMDL			pAmdl,
	OUT	PULONG			BytesWritten
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	TDI_ADDRESS_INFO		tdiInfo;
	PTA_APPLETALK_ADDRESS	pTaAddr;

	ASSERT (VALID_DDP_ADDROBJ(pDdpAddr));

	pTaAddr	= (PTA_APPLETALK_ADDRESS)&tdiInfo.Address;
	ATALKADDR_TO_TDI(pTaAddr, &pDdpAddr->ddpao_Addr);
	TdiCopyBufferToMdl ((PBYTE)&tdiInfo,
						0L,
						sizeof(tdiInfo),
						pAmdl,
						0,
						BytesWritten);
}




VOID
AtalkDdpOutBufToNodesOnPort(
	IN	PPORT_DESCRIPTOR	pPortDesc,
	IN	PATALK_ADDR			pSrc,
	IN	PATALK_ADDR			pDest,
	IN	BYTE				Protocol,
	IN	PBUFFER_DESC		pBuffDesc		OPTIONAL,
	IN	PBYTE				pOptHdr			OPTIONAL,
	IN	USHORT				OptHdrLen		OPTIONAL,
	OUT	PBOOLEAN			Delivered
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ATALK_ERROR		error;
	PATALK_NODE		pAtalkNode, pNextNode;
	PDDP_ADDROBJ	pDdpAddr;
	BOOLEAN			fDeliver, fSpecific, needToRef;
	BOOLEAN			lockHeld = FALSE;

	ASSERT (KeGetCurrentIrql() == DISPATCH_LEVEL);

	//	Do not internally loopback broadcast frames, these should come
	//	back to us from the mac.
	if (pDest->ata_Node == ATALK_BROADCAST_NODE)
	{
		*Delivered = FALSE;
		return;
	}

	fSpecific	= (pDest->ata_Network != CABLEWIDE_BROADCAST_NETWORK);

	//	Walk through our nodes to see if we can deliver this packet.
	//	OPTIMIZATIONS:
	//	In most cases, this will not be true. Optimize for returning false.
	//	Also, a node closing is a rare occurence. If we run into one that is
	//	closing, we abort trying to deliver this packet to a node on our port,
	//	and instead return delivered = FALSE. DDP - unreliable, and node closing
	//	should be a transient state. We avoid the acquire/release code.
	ACQUIRE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock);
	lockHeld	= TRUE;

	pNextNode	= pPortDesc->pd_Nodes;
	needToRef	= TRUE;
	for (; (pAtalkNode = pNextNode) != NULL; )
	{
		fDeliver	= FALSE;
		error		= ATALK_NO_ERROR;

		if (((pAtalkNode->an_NodeAddr.atn_Network == pDest->ata_Network) ||
			 !fSpecific) &&
			(pAtalkNode->an_NodeAddr.atn_Node == pDest->ata_Node))
		{
			//	Reference node. If we fail, we abort.
			if (needToRef)
			{
				AtalkNodeRefByPtr(pAtalkNode, &error);
			}

			if (ATALK_SUCCESS(error))
			{
				fDeliver	= TRUE;

				//	Set up for next node.
				if (fSpecific)
				{
					pNextNode	= NULL;
				}
				else
				{
					//	Get next eligible node.
					pNextNode	= pAtalkNode->an_Next;
					while (pNextNode != NULL)
					{
						if (pNextNode->an_NodeAddr.atn_Node == pDest->ata_Node)
						{
							AtalkNodeRefByPtr(pNextNode, &error);
							if (!ATALK_SUCCESS(error))
							{
								pNextNode	= NULL;
							}

							needToRef	= FALSE;
							break;
						}
						else
						{
							pNextNode	= pNextNode->an_Next;
						}
					}
				}
			}
			else
			{
				//	Break out of the for loop.
				break;
			}
		}
		else
		{
			pNextNode	= pAtalkNode->an_Next;
			needToRef	= TRUE;
		}

		if (fDeliver)
		{
			//	Release port lock, deliver packet, and Deref the node.
			//	Find the ddp address object on this node corresponding
			//	to this address. This will get the node lock.
			AtalkDdpRefByAddrNode(pPortDesc,
								  pDest,
								  pAtalkNode,
								  &pDdpAddr,
								  &error);

			RELEASE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock);
			lockHeld	= FALSE;

			if (ATALK_SUCCESS(error))
			{
				//	Invoke socket handler on this address object.
				AtalkDdpInvokeHandlerBufDesc(pPortDesc,
											 pDdpAddr,
											 pSrc,
											 pDest,
											 Protocol,
											 pBuffDesc,
											 pOptHdr,
											 OptHdrLen);
	
				//	Remove the reference on the socket
				AtalkDdpDereferenceDpc(pDdpAddr);
			}

			//	Remove the reference on the node
			AtalkNodeDereference(pAtalkNode);

			//	If we had to deliver to a specific node, we are done.
			if (fSpecific)
			{
				break;
			}

			ACQUIRE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock);
			lockHeld	= TRUE;
		}
	}

	if (lockHeld)
	{
		RELEASE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock);
	}

	*Delivered = (fSpecific && fDeliver);
}




VOID
AtalkDdpInPktToNodesOnPort(
	IN	PPORT_DESCRIPTOR	pPortDesc,
	IN	PATALK_ADDR			pDest,
	IN	PATALK_ADDR			pSrc,
	IN	BYTE				Protocol,
	IN	PBYTE				pPkt			OPTIONAL,
	IN	USHORT				PktLen			OPTIONAL,
	OUT	PBOOLEAN			Delivered,
	OUT	PBOOLEAN			ShouldBeRouted
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PATALK_NODE		pAtalkNode, pNextNode;
	PDDP_ADDROBJ	pDdpAddr;
	BOOLEAN			broadcast;
	BOOLEAN			fSpecific, fDeliver, needToRef;
	ATALK_ERROR		error			= ATALK_NO_ERROR;
	BOOLEAN			lockHeld 		= FALSE;
	BOOLEAN			delivered 		= FALSE;
	BOOLEAN			shouldBeRouted  = FALSE;

	ASSERT (KeGetCurrentIrql() == DISPATCH_LEVEL);

	broadcast = (pDest->ata_Node == ATALK_BROADCAST_NODE);

	//	is a directed packet to a socket on a particular node...?
	fSpecific = (!broadcast &&
					(pDest->ata_Network != UNKNOWN_NETWORK));

	//	OPTIMIZATIONS:
	//	In most cases, this will not be true. Optimize for returning false.
	//	Also, a node closing is a rare occurence. If we run into one that is
	//	closing, we abort trying to deliver this packet to a node on our port,
	//	and instead return delivered = FALSE. DDP - unreliable, and node closing
	//	should be a transient state. We avoid the acquire/release code.
	ACQUIRE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock);
	lockHeld	= TRUE;

	pNextNode	= pPortDesc->pd_Nodes;
	needToRef	= TRUE;
	while ((pAtalkNode = pNextNode) != NULL)
	{
		fDeliver	= FALSE;
		error		= ATALK_NO_ERROR;

		//	For incoming packet, we check to see if the destination
		//	net is 0, or destination net is our node's net, or we are
		//	non-extended and our node's net is zero. i.e. is the packet
		//	destined for a node on this port. If not, route it. Continue
		//	checking all nodes though, as a single port can have nodes with
		//	different network numbers.

		if (((pAtalkNode->an_NodeAddr.atn_Network == pDest->ata_Network) 	||
			 (pDest->ata_Network == UNKNOWN_NETWORK)						||
			 (!EXT_NET(pPortDesc) &&
			  (pAtalkNode->an_NodeAddr.atn_Network == UNKNOWN_NETWORK)))	&&
			(broadcast || (pAtalkNode->an_NodeAddr.atn_Node == pDest->ata_Node)))
		{
			//	Reference node if we need to. Only happens for the first
			//	time we enter the loop. If we fail, we  abort.
			if (needToRef)
			{
				AtalkNodeRefByPtr(pAtalkNode, &error);
				if (!ATALK_SUCCESS(error))
				{
					break;
				}
			}

			fDeliver	= TRUE;

			//	Set up for next node.
			if (fSpecific)
			{
				//	Only one node on a non-extended port. So set next to NULL.
				pNextNode	= NULL;
			}
			else
			{
				//	Get next eligible node.
				pNextNode	= pAtalkNode->an_Next;
				while (pNextNode != NULL)
				{
					if (((pNextNode->an_NodeAddr.atn_Network == pDest->ata_Network) ||
						(pDest->ata_Network == UNKNOWN_NETWORK)						||
						(!EXT_NET(pPortDesc) &&
						 (pNextNode->an_NodeAddr.atn_Network == UNKNOWN_NETWORK)))	&&
						(broadcast ||
						 (pNextNode->an_NodeAddr.atn_Node == pDest->ata_Node)))
					{
						AtalkNodeRefByPtr(pNextNode, &error);
						if (!ATALK_SUCCESS(error))
						{
							pNextNode	= NULL;
						}
	
						needToRef	= FALSE;
						break;
					}

					pNextNode	= pNextNode->an_Next;
				}
			}
		}
		else
		{
			//	The packet probably could be meant to be routed.
			//	This could be set multiple times - idempotent.
			shouldBeRouted 	= TRUE;
			needToRef		= TRUE;
			pNextNode		= pAtalkNode->an_Next;
		}

		if (fDeliver)
		{
			//	Release port lock, deliver packet, and Deref the node.
			//	Find the ddp address object on this node corresponding
			//	to this address. This will get the node lock.
			if (broadcast)
				pDest->ata_Node = pAtalkNode->an_NodeAddr.atn_Node;
		
			AtalkDdpRefByAddrNode(pPortDesc,
								  pDest,
								  pAtalkNode,
								  &pDdpAddr,
								  &error);

			//	If we had changed the destination node, change it back.		
			if (broadcast)
				pDest->ata_Node = ATALK_BROADCAST_NODE;

			RELEASE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock);
			lockHeld	= FALSE;

			if (ATALK_SUCCESS(error))
			{
				//	Invoke socket handler on this address object.
				//	Use the packet pointer directly!

				AtalkDdpInvokeHandler(pPortDesc,
									  pDdpAddr,
									  pSrc,
									  pDest,
									  Protocol,
									  pPkt,
									  PktLen);
	
				//	Remove the reference on the socket
				AtalkDdpDereferenceDpc(pDdpAddr);
			}

			//	Remove the reference on the node
			AtalkNodeDereference(pAtalkNode);

			//	If we had to deliver to a specific node, we are done.
			if (fSpecific)
			{
				shouldBeRouted = FALSE;
				break;
			}

			ACQUIRE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock);
			lockHeld	= TRUE;
		}
	}

	if (lockHeld)
	{
		RELEASE_SPIN_LOCK_DPC(&pPortDesc->pd_Lock);
	}

	*Delivered = delivered;
	*ShouldBeRouted = shouldBeRouted;
}




USHORT
AtalkDdpCheckSumBuffer(
	IN	PBYTE	Buffer,
	IN	USHORT	BufLen,
	IN	USHORT	CurrentCheckSum
	)
/*++

Routine Description:

	Calculate the DDP checksum of a byte array

Arguments:


Return Value:


--*/
{
	USHORT	CheckSum = CurrentCheckSum;
	ULONG	i;

	// The following algorithm is from Inside AppleTalk, Second Edition
	// page 4-17
	for (i = 0; i < BufLen; i++)
	{
		CheckSum += Buffer[i];
		if (CheckSum & 0x8000)	// 16-bit rotate left one bit
		{
			CheckSum <<= 1;
			CheckSum ++;
		}
		else CheckSum <<= 1;
	}
	if (CheckSum == 0)
		CheckSum = 0xFFFF;

	return CheckSum;
}




USHORT
AtalkDdpCheckSumPacket(
	IN	PBYTE	pHdr,
	IN	USHORT	HdrLen,
	IN	PBYTE	pPkt,
	IN	USHORT	PktLen
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	USHORT	checksum = 0;

	//	MAX_LDDP_PKT_SIZE is 600, so we use < instead of <=

	ASSERT(HdrLen + PktLen < MAX_LDDP_PKT_SIZE);
	if ((HdrLen + PktLen) < MAX_LDDP_PKT_SIZE)
	{
		if (HdrLen > 0)
		{
			checksum = AtalkDdpCheckSumBuffer(pHdr, HdrLen, 0);
		}

		if (PktLen > 0)
		{
			checksum = AtalkDdpCheckSumBuffer(pPkt, PktLen, checksum);
		}
	}

	return checksum;
}


// Calculate the DDP checksum of the passed in buffer. The buffer is described
// by the buffer descriptor
USHORT
AtalkDdpCheckSumBufferDesc(
	IN	PBUFFER_DESC	pBuffDesc,
	IN	USHORT			Offset
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PBYTE	pBuf;
	USHORT	checksum = 0;

	while (pBuffDesc != NULL)
	{
		if (pBuffDesc->bd_Flags & BD_CHAR_BUFFER)
		{
			pBuf = pBuffDesc->bd_CharBuffer;
		}
		else
		{
			pBuf = MmGetSystemAddressForMdl(pBuffDesc->bd_OpaqueBuffer);
		}
		checksum = AtalkDdpCheckSumBuffer(pBuf, pBuffDesc->bd_Length, checksum);
		pBuffDesc = pBuffDesc->bd_Next;
	}

	return checksum;
}



//	This routine needs to verify that the socket does not already
//	exist on the node. If it doesnt it will alloc the ddp address
//	object and link it into the node and do all the required initialization.
//	The node is guaranteed to be referenced.

ATALK_ERROR
atalkDdpAllocSocketOnNode(
	IN		PPORT_DESCRIPTOR		pPortDesc,
	IN		BYTE					Socket,
	IN		PATALK_NODE				pAtalkNode,
	IN 		DDPAO_HANDLER			pSktHandler 	OPTIONAL,
	IN		PVOID					pSktCtx			OPTIONAL,
	IN		BYTE					Protocol		OPTIONAL,
	IN		PATALK_DEV_CTX			pDevCtx,
	OUT		PDDP_ADDROBJ			pDdpAddr
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ATALK_ADDR			addr;
	PDDP_ADDROBJ		pDdpAddrx;
	KIRQL				OldIrql;
	int					i, j, index;
	BOOLEAN				found	= TRUE;
	ATALK_ERROR			error 	= ATALK_NO_ERROR;

	//	See if the socket exists else, link our new socket into
	//	the node linkage. All within a critical section.

	addr.ata_Network = pAtalkNode->an_NodeAddr.atn_Network;
	addr.ata_Node = pAtalkNode->an_NodeAddr.atn_Node;
	addr.ata_Socket = Socket;

	//	Now reference the node on which this socket will reside.
	//	This will go away when the socket is closed.
	AtalkNodeReferenceByPtr(pAtalkNode, &error);
	if (!ATALK_SUCCESS(error))
	{
		TMPLOGERR();
		return error;
	}

	ACQUIRE_SPIN_LOCK(&pAtalkNode->an_Lock, &OldIrql);

	if (Socket == DYNAMIC_SOCKET)
	{
		//	Two attempts if we are at the end of the range and restart from
		//	the beginning.
		for (j = 0; (j < NUM_USER_NODES) && found; j++)
		{
			for (i = pAtalkNode->an_NextDynSkt; i <= LAST_DYNAMIC_SOCKET; i++)
			{
				addr.ata_Socket = (BYTE)i;
				index = HASH_ATALK_ADDR(&addr) % NODE_DDPAO_HASH_SIZE;

				found = atalkDdpFindAddrOnList(pAtalkNode, index, (BYTE)i, &pDdpAddrx);

				if (found)
					continue;

				Socket = (BYTE)i;
				break;
			}

			//	Now if still havent found the socket id, set NextDynSkt to
			//	beginning of the range and try again.
			if (found)
			{
				pAtalkNode->an_NextDynSkt = FIRST_DYNAMIC_SOCKET;
				continue;
			}

			//	Not found. Increment next id to be used.
			if (++(pAtalkNode->an_NextDynSkt) == 0)
			{
				//	We wrapped! Set the value to the lowest dynamic
				//	socket. Thats what it should have been initialized
				//	to.
				pAtalkNode->an_NextDynSkt = FIRST_DYNAMIC_SOCKET;
			}

			DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO,
					("atalkDdpAllocSocketOnNode: Created dynamic socket %x\n", Socket));

			//	Done.
			break;
		}

		if (found)
		{
			error = ATALK_SOCKET_NODEFULL;
		}
	}
	else
	{
		index = HASH_ATALK_ADDR(&addr) % NODE_DDPAO_HASH_SIZE;

		found = atalkDdpFindAddrOnList(pAtalkNode, index, (BYTE)Socket, &pDdpAddrx);

		if (found)
		{
			error = ATALK_SOCKET_EXISTS;
		}
	}

	if (ATALK_SUCCESS(error))
	{
		//	Initialize and thread in the structure

#if DBG
		pDdpAddr->ddpao_Signature	= DDPAO_SIGNATURE;
#endif

		pDdpAddr->ddpao_RefCount 	= 1;		//	Creation
		pDdpAddr->ddpao_DevCtx 		= pDevCtx;
		pDdpAddr->ddpao_Node 		= pAtalkNode;

		pDdpAddr->ddpao_Addr.ata_Network	= pAtalkNode->an_NodeAddr.atn_Network;
		pDdpAddr->ddpao_Addr.ata_Node  		= pAtalkNode->an_NodeAddr.atn_Node;
		pDdpAddr->ddpao_Addr.ata_Socket		= Socket;

		pDdpAddr->ddpao_Protocol 	= Protocol;
		pDdpAddr->ddpao_Handler 	= pSktHandler;
		pDdpAddr->ddpao_HandlerCtx 	= pSktCtx;

		INITIALIZE_SPIN_LOCK(&pDdpAddr->ddpao_Lock);
		InitializeListHead(&pDdpAddr->ddpao_ReadLinkage);

		//	We use 'index' to link this in.
		pDdpAddr->ddpao_Next = pAtalkNode->an_DdpAoHash[index];
		pAtalkNode->an_DdpAoHash[index] = pDdpAddr;
	}

	RELEASE_SPIN_LOCK(&pAtalkNode->an_Lock, OldIrql);

	//	If we failed, Dereference the node
	if (!ATALK_SUCCESS(error))
		AtalkNodeDereference(pAtalkNode);

	return error;
}




BOOLEAN
atalkDdpFindAddrOnList(
	IN	PATALK_NODE		pAtalkNode,
	IN	ULONG			Index,
	IN	BYTE			Socket,
	OUT	PDDP_ADDROBJ *	ppDdpAddr
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PDDP_ADDROBJ	pDdpAddr;

   	BOOLEAN		found = FALSE;

	for (pDdpAddr = pAtalkNode->an_DdpAoHash[Index];
		 pDdpAddr != NULL;
		 pDdpAddr = pDdpAddr->ddpao_Next)
	{
		if (pDdpAddr->ddpao_Addr.ata_Socket == Socket)
		{
			*ppDdpAddr = pDdpAddr;
			found = TRUE;
			break;
		}
	}

	return found;
}




VOID
AtalkDdpRefByAddr(
	IN		PPORT_DESCRIPTOR	pPortDesc,
	IN		PATALK_ADDR			pAtalkAddr,
	OUT		PDDP_ADDROBJ	*	ppDdpAddr,
	OUT		PATALK_ERROR		pErr
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ULONG				index;
	ATALK_NODEADDR		node;
	PATALK_NODE			pAtalkNode;
	PDDP_ADDROBJ		pDdpAddr;
	KIRQL				OldIrql;
	ATALK_ERROR			ErrorCode;

	node.atn_Network = pAtalkAddr->ata_Network;
	node.atn_Node	= pAtalkAddr->ata_Node;

	//	First find the node on this port given its address
	AtalkNodeReferenceByAddr(pPortDesc,
							 &node,
							 &pAtalkNode,
							 &ErrorCode);

	if (ATALK_SUCCESS(ErrorCode))
	{
		ASSERT(VALID_ATALK_NODE(pAtalkNode));

		index = HASH_ATALK_ADDR(pAtalkAddr) % NODE_DDPAO_HASH_SIZE;

		ACQUIRE_SPIN_LOCK(&pAtalkNode->an_Lock, &OldIrql);
		if(atalkDdpFindAddrOnList(pAtalkNode,
								  index,
								  pAtalkAddr->ata_Socket,
								  &pDdpAddr))
		{
			AtalkDdpReferenceByPtr(pDdpAddr, &ErrorCode);
			if (ATALK_SUCCESS(ErrorCode))
			{
				ASSERT (VALID_DDP_ADDROBJ(pDdpAddr));
				*ppDdpAddr = pDdpAddr;
			}
		}
		else
		{
			ErrorCode = ATALK_DDP_NOTFOUND;
		}
		RELEASE_SPIN_LOCK(&pAtalkNode->an_Lock, OldIrql);

		//	Remove the node reference
		ASSERT(VALID_ATALK_NODE(pAtalkNode));
		AtalkNodeDereference(pAtalkNode);
	}
	*pErr = ErrorCode;
}




VOID
AtalkDdpRefByAddrNode(
	IN		PPORT_DESCRIPTOR	pPortDesc,
	IN		PATALK_ADDR			pAtalkAddr,
	IN		PATALK_NODE			pAtalkNode,
	OUT		PDDP_ADDROBJ	*	ppDdpAddr,
	OUT		PATALK_ERROR		pErr
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ULONG			index;
	KIRQL			OldIrql;
	PDDP_ADDROBJ	pDdpAddr;

	ASSERT(VALID_ATALK_NODE(pAtalkNode));

	index = HASH_ATALK_ADDR(pAtalkAddr) % NODE_DDPAO_HASH_SIZE;

	ACQUIRE_SPIN_LOCK(&pAtalkNode->an_Lock, &OldIrql);
	if(atalkDdpFindAddrOnList(pAtalkNode,
							  index,
							  pAtalkAddr->ata_Socket,
							  &pDdpAddr))
	{
		AtalkDdpReferenceByPtr(pDdpAddr, pErr);
		if (ATALK_SUCCESS(*pErr))
		{
			ASSERT (VALID_DDP_ADDROBJ(pDdpAddr));
			*ppDdpAddr = pDdpAddr;
		}
	}
	else
	{
		*pErr = ATALK_DDP_NOTFOUND;
	}
	RELEASE_SPIN_LOCK(&pAtalkNode->an_Lock, OldIrql);
}




VOID
AtalkDdpRefNextNc(
	IN	PDDP_ADDROBJ	pDdpAddr,
	IN	PDDP_ADDROBJ *	ppDdpAddr,
	OUT	PATALK_ERROR	pErr
	)
/*++

Routine Description:

	MUST BE CALLED WITH THE NODE LOCK HELD!

Arguments:


Return Value:


--*/
{
	*pErr = ATALK_FAILURE;
	*ppDdpAddr = NULL;
	for (; pDdpAddr != NULL; pDdpAddr = pDdpAddr->ddpao_Next)
	{
		AtalkDdpReferenceByPtrDpc(pDdpAddr, pErr);
		if (ATALK_SUCCESS(*pErr))
		{
			//	Ok, this address is referenced!
			ASSERT (VALID_DDP_ADDROBJ(pDdpAddr));
			*ppDdpAddr = pDdpAddr;
			break;
		}
	}
}




VOID FASTCALL
AtalkDdpDeref(
	IN	OUT	PDDP_ADDROBJ		pDdpAddr,
	IN		BOOLEAN				AtDpc
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ATALK_ERROR		error = ATALK_NO_ERROR;
	PATALK_NODE		pNode = pDdpAddr->ddpao_Node;
	BOOLEAN			done = FALSE;
	KIRQL			OldIrql;

	ASSERT (VALID_DDP_ADDROBJ(pDdpAddr));

	if (AtDpc)
	{
		 ACQUIRE_SPIN_LOCK_DPC(&pDdpAddr->ddpao_Lock);
	}
	else
	{
		ACQUIRE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, &OldIrql);
	}

	ASSERT(pDdpAddr->ddpao_RefCount > 0);
	if (--(pDdpAddr->ddpao_RefCount) == 0)
	{
		done = TRUE;
	}

	if (AtDpc)
	{
		 RELEASE_SPIN_LOCK_DPC(&pDdpAddr->ddpao_Lock);
	}
	else
	{
		RELEASE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, OldIrql);
	}

	if (done)
	{
		PDDP_ADDROBJ *	ppDdpAddr;
		int				index;

		ASSERT((pDdpAddr->ddpao_Flags & DDPAO_CLOSING) != 0);

		if ((pDdpAddr->ddpao_Flags & DDPAO_CLOSING) == 0)
		{
			KeBugCheck(0);
		}

		//	Remove this guy from the node linkage
		if (AtDpc)
		{
			ACQUIRE_SPIN_LOCK_DPC(&pNode->an_Lock);
		}
		else
		{
			ACQUIRE_SPIN_LOCK(&pNode->an_Lock, &OldIrql);
		}
		index = HASH_ATALK_ADDR(&pDdpAddr->ddpao_Addr) % NODE_DDPAO_HASH_SIZE;
		for (ppDdpAddr = &pNode->an_DdpAoHash[index];
			 *ppDdpAddr != NULL;
			 ppDdpAddr = &((*ppDdpAddr)->ddpao_Next))
		{
			if (*ppDdpAddr == pDdpAddr)
			{
				*ppDdpAddr = pDdpAddr->ddpao_Next;
				break;
			}
		}
		if (AtDpc)
		{
			RELEASE_SPIN_LOCK_DPC(&pNode->an_Lock);
		}
		else
		{
			RELEASE_SPIN_LOCK(&pNode->an_Lock, OldIrql);
		}

		DBGPRINT(DBG_COMP_DDP, DBG_LEVEL_INFO,
				("AtalkDdpDeref: Closing ddp socket %lx\n", pDdpAddr->ddpao_Addr.ata_Socket));

		if (pDdpAddr->ddpao_EventInfo != NULL)
		{
			AtalkFreeMemory(pDdpAddr->ddpao_EventInfo);
		}

		//	Call the completion routines
		if (*pDdpAddr->ddpao_CloseComp != NULL)
		{
			(*pDdpAddr->ddpao_CloseComp)(ATALK_NO_ERROR, pDdpAddr->ddpao_CloseCtx);
		}

		//	Free the address structure
		AtalkFreeMemory(pDdpAddr);

		//	Dereference the node for this address
		AtalkNodeDereference(pNode);
	}
}




VOID
AtalkDdpNewHandlerForSocket(
	IN	PDDP_ADDROBJ			pDdpAddr,
	IN	DDPAO_HANDLER			pSktHandler,
	IN	PVOID					pSktHandlerCtx
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	KIRQL	OldIrql;

	ASSERT (VALID_DDP_ADDROBJ(pDdpAddr));

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

	pDdpAddr->ddpao_Handler = pSktHandler;
	pDdpAddr->ddpao_HandlerCtx = pSktHandlerCtx;

	RELEASE_SPIN_LOCK(&pDdpAddr->ddpao_Lock, OldIrql);
}

