/*++

Copyright (c) 1992  Microsoft Corporation

Module Name:

	asp.c

Abstract:

	This module implements the ASP protocol.

Author:

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

Revision History:
	30 Mar 1993		Initial Version

Notes:	Tab stop: 4
--*/

#define	ASP_LOCALS
#define	FILENUM		ASP
#include <atalk.h>
#pragma hdrstop

#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, AtalkInitAspInitialize)
#pragma alloc_text(PAGE, AtalkAspCreateAddress)
#pragma alloc_text(PAGE, AtalkAspBind)
#pragma alloc_text(PAGE_ASP, AtalkAspCloseAddress)
#pragma alloc_text(PAGE_ASP, AtalkAspSetStatus)
#pragma alloc_text(PAGE_ASP, AtalkAspListenControl)
#pragma alloc_text(PAGE_ASP, AtalkAspCloseConnection)
#pragma alloc_text(PAGE_ASP, AtalkAspCleanupConnection)
#pragma alloc_text(PAGE_ASP, AtalkAspReply)
#pragma alloc_text(PAGE_ASP, atalkAspPostWriteContinue)
#pragma alloc_text(PAGE_ASP, AtalkAspSendAttention)
#pragma alloc_text(PAGE_ASP, AtalkAspReferenceAddr)
#pragma alloc_text(PAGE_ASP, atalkAspReferenceConnBySrcAddr)
#pragma alloc_text(PAGE_ASP, AtalkAspDereferenceConn)
#pragma alloc_text(PAGE_ASP, atalkAspSlsXHandler)
#pragma alloc_text(PAGE_ASP, atalkAspSssXHandler)
#pragma alloc_text(PAGE_ASP, atalkAspReplyRelease)
#pragma alloc_text(PAGE_ASP, atalkAspWriteContinueResp)
#pragma alloc_text(PAGE_ASP, atalkAspSendAttentionResp)
#pragma alloc_text(PAGE_ASP, atalkAspSessionClose)
#pragma alloc_text(PAGE_ASP, atalkAspReturnResp)
#pragma alloc_text(PAGE_ASP, atalkAspRespComplete)
#pragma alloc_text(PAGE_ASP, atalkAspCloseComplete)
#endif

/*
 * The model for ASP calls in this module is as follows:
 *
 * - For create calls (CreateAddress & CreateSession), a pointer to the created
 *	 object is returned. This structure is referenced for creation.
 * - For all other calls, it expects a referenced pointer to the object.
 */


VOID
AtalkInitAspInitialize(
	VOID
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	LONG	i;

	INITIALIZE_SPIN_LOCK(&atalkAspLock);

	for (i = 0; i < NUM_ASP_CONN_LISTS; i++)
	{
		AtalkTimerInitialize(&atalkAspConnMaint[i].ascm_SMTTimer,
							 atalkAspSessionMaintenanceTimer,
							 ASP_SESSION_MAINTENANCE_TIMER - i*ASP_SESSION_TIMER_STAGGER);
		AtalkTimerScheduleEvent(&atalkAspConnMaint[i].ascm_SMTTimer);
	}
}




ATALK_ERROR
AtalkAspCreateAddress(
	OUT	PASP_ADDROBJ	*	ppAspAddr
	)
/*++

Routine Description:

 	Create an ASP address object (aka listener). This object is associated with
 	two seperate Atp sockets, one each for the Sls and the Sss. The Sls accepts
 	the tickle, getstatus and opensession requests from the client end. The
 	Sss accepts requests.

 	Currently only the server side ASP is implemented and hence the ASP address
 	object is only a listener.

Arguments:


Return Value:


--*/
{
	PASP_ADDROBJ		pAspAddr = NULL;
	ATALK_ERROR			Status;
	int					i;

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("AtalkAspCreateAddr: Entered\n"));

	do
	{
		// Allocate memory for the Asp address object
		if ((pAspAddr = AtalkAllocZeroedMemory(sizeof(ASP_ADDROBJ))) == NULL)
		{
			Status = ATALK_RESR_MEM;
			break;
		}

		// Initialize the Asp address object
#if	DBG
		pAspAddr->aspao_Signature = ASPAO_SIGNATURE;
#endif
		INITIALIZE_SPIN_LOCK(&pAspAddr->aspao_Lock);

		// Refcounts for creation, Sls & Sss sockets and request handlers
		pAspAddr->aspao_RefCount = 1 + 2 + 2;
		pAspAddr->aspao_NextSessionId = 1;
		pAspAddr->aspao_EnableNewConnections = TRUE;

		// Create an Atp Socket on the port for the Sls
		Status = AtalkAtpOpenAddress(AtalkDefaultPort,
									 0,
									 NULL,
									 ATP_DEF_MAX_SINGLE_PKT_SIZE,
									 ATP_DEF_SEND_USER_BYTES_ALL,
									 NULL,
									 TRUE,		// CACHE this address
									 &pAspAddr->aspao_pSlsAtpAddr);

		if (!ATALK_SUCCESS(Status))
		{
			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
					("AtalkAspCreateAddress: AtalkAtpOpenAddress for Sls failed %ld\n", Status));
			break;
		}

		// Set Request handler for the SLS to handle GetStatus, OpenSession and Tickle
		AtalkAtpSetReqHandler(pAspAddr->aspao_pSlsAtpAddr,
							  atalkAspSlsXHandler,
							  pAspAddr);

		// Create the Atp Socket on the port for the Sss
		Status = AtalkAtpOpenAddress(AtalkDefaultPort,
									 0,
									 NULL,
									 ATP_DEF_MAX_SINGLE_PKT_SIZE,
									 ATP_DEF_SEND_USER_BYTES_ALL,
									 NULL,
									 TRUE,		// CACHE this address
									 &pAspAddr->aspao_pSssAtpAddr);

		if (!ATALK_SUCCESS(Status))
		{
			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
					("AtalkAspCreateAddress: AtalkAtpOpenAddress for Sss failed %ld\n", Status));
			break;
		}

		// Set Request handler for the SSS to handle Cmd/Write/Close
		AtalkAtpSetReqHandler(pAspAddr->aspao_pSssAtpAddr,
							  atalkAspSssXHandler,
							  pAspAddr);
	} while (FALSE);

	if (!ATALK_SUCCESS(Status))
	{
		if (pAspAddr != NULL)
		{
			if (pAspAddr->aspao_pSlsAtpAddr != NULL)
			{
				DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
						("AtalkAspCreateAddress: Closing SLS Atp Address %lx\n",
						pAspAddr->aspao_pSlsAtpAddr));
				AtalkAtpCloseAddress(pAspAddr->aspao_pSlsAtpAddr, NULL, NULL);
			}
			if (pAspAddr->aspao_pSssAtpAddr != NULL)
			{
				DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
						("AtalkAspCreateAddress: Closing SSS Atp Address %lx\n",
						pAspAddr->aspao_pSssAtpAddr));
				AtalkAtpCloseAddress(pAspAddr->aspao_pSssAtpAddr, NULL, NULL);
			}
			AtalkFreeMemory(pAspAddr);
		}
	}
	else
	{
		*ppAspAddr = pAspAddr;
	}

	return Status;
}




ATALK_ERROR
AtalkAspCloseAddress(
	IN	PASP_ADDROBJ			pAspAddr,
	IN	GENERIC_COMPLETION		CompletionRoutine,
	IN	PVOID					CloseContext
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PASP_CONNOBJ	pAspConn;
	KIRQL			OldIrql;
	int				i;
	ATALK_ERROR		Status = ATALK_PENDING;

	ASSERT(VALID_ASPAO(pAspAddr));
	ASSERT(pAspAddr->aspao_RefCount > 1);

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("AtalkAspCloseAddr: Entered for Addr %lx\n", pAspAddr));

	ACQUIRE_SPIN_LOCK(&pAspAddr->aspao_Lock, &OldIrql);

	pAspAddr->aspao_Flags |= ASPAO_CLOSING;

	pAspAddr->aspao_CloseCompletion = CompletionRoutine;
	pAspAddr->aspao_CloseContext = CloseContext;

	RELEASE_SPIN_LOCK(&pAspAddr->aspao_Lock, OldIrql);

	// Close down the atp sockets for Sls and Sss
	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
			("AtalkAspCloseAddress: Closing SLS Atp Address %lx\n",
			pAspAddr->aspao_pSlsAtpAddr));

	AtalkAtpCloseAddress(pAspAddr->aspao_pSlsAtpAddr,
						 atalkAspCloseComplete,
						 pAspAddr);

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
			("AtalkAspCloseAddress: Closing SSS Atp Address %lx\n",
			pAspAddr->aspao_pSssAtpAddr));

	AtalkAtpCloseAddress(pAspAddr->aspao_pSssAtpAddr,
						 atalkAspCloseComplete,
						 pAspAddr);

	// Free the status buffer if any
	if (pAspAddr->aspao_pStatusBuf != NULL)
	{
		AtalkFreeMemory(pAspAddr->aspao_pStatusBuf);
	}

	// Shut down the active sessions now.
	for (i = 0; i < ASP_CONN_HASH_BUCKETS; i++)
	{
		ACQUIRE_SPIN_LOCK(&pAspAddr->aspao_Lock, &OldIrql);
		while ((pAspConn = pAspAddr->aspao_pSessions[i]) != NULL)
		{
			ACQUIRE_SPIN_LOCK_DPC(&pAspConn->aspco_Lock);
			pAspConn->aspco_Flags |= ASPCO_LOCAL_CLOSE;

			// Reference this since atalkAspSessionClose() expects it.
			pAspConn->aspco_RefCount ++;
			RELEASE_SPIN_LOCK_DPC(&pAspConn->aspco_Lock);

			RELEASE_SPIN_LOCK(&pAspAddr->aspao_Lock, OldIrql);

			atalkAspSessionClose(pAspConn);

			ACQUIRE_SPIN_LOCK(&pAspAddr->aspao_Lock, &OldIrql);
		}
		RELEASE_SPIN_LOCK(&pAspAddr->aspao_Lock, OldIrql);
	}

	ASSERT(KeGetCurrentIrql() == LOW_LEVEL);

	// Let remaining cleanup happen during the Derefernce
	AtalkAspDereferenceAddr(pAspAddr);		// Remove the creation reference

	return Status;
}




ATALK_ERROR
AtalkAspBind(
	IN	PASP_ADDROBJ			pAspAddr,
	IN	PASP_BIND_PARAMS		pBindParms,
	IN	PACTREQ					pActReq
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ASSERT (VALID_ASPAO(pAspAddr));

	// Fill in our entry points into the client buffer
	pBindParms->pXportEntries->asp_AspCtxt = pAspAddr;
	pBindParms->pXportEntries->asp_SetStatus = AtalkAspSetStatus;
	pBindParms->pXportEntries->asp_CloseConn = AtalkAspCloseConnection;
	pBindParms->pXportEntries->asp_ListenControl = AtalkAspListenControl;
	pBindParms->pXportEntries->asp_Reply = AtalkAspReply;
	pBindParms->pXportEntries->asp_SendAttention = AtalkAspSendAttention;

	// Get the clients entry points
	pAspAddr->aspao_ClientEntries = pBindParms->ClientEntries;

	// Call the completion routine before returning.
	(*pActReq->ar_Completion)(ATALK_NO_ERROR, pActReq);

	return ATALK_PENDING;
}


NTSTATUS
AtalkAspSetStatus(
	IN	PASP_ADDROBJ	pAspAddr,
	IN	PUCHAR			pStatusBuf,
	IN	USHORT			StsBufSize
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	KIRQL			OldIrql;
	NTSTATUS		Status = STATUS_SUCCESS;
	PUCHAR			pOldBuf = NULL, pNewBuf = NULL;

	ASSERT(VALID_ASPAO(pAspAddr));

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("AtalkAspSetStatus: Entered for Addr %lx\n", pAspAddr));

	do
	{
		if (pStatusBuf != NULL)
		{
			// Allocate a buffer and copy the contents of the passed in
			// buffer descriptor in it. Free an existing status buffer if one exists
			if (StsBufSize >= ASP_MAX_STATUS_SIZE)
			{
				Status = STATUS_BUFFER_TOO_SMALL;
				break;
			}

			if ((pNewBuf = AtalkAllocMemory(StsBufSize)) == NULL)
			{
				Status = STATUS_INSUFFICIENT_RESOURCES;
				break;
			}
			RtlCopyMemory(pNewBuf, pStatusBuf, StsBufSize);
		}

		ACQUIRE_SPIN_LOCK(&pAspAddr->aspao_Lock, &OldIrql);

		if (pAspAddr->aspao_pStatusBuf != NULL)
		{
			ASSERT(pAspAddr->aspao_StsBufSize != 0);
			pOldBuf = pAspAddr->aspao_pStatusBuf;
		}

		pAspAddr->aspao_pStatusBuf = pNewBuf;
		pAspAddr->aspao_StsBufSize = StsBufSize;

		RELEASE_SPIN_LOCK(&pAspAddr->aspao_Lock, OldIrql);

		if (pOldBuf != NULL)
			AtalkFreeMemory(pOldBuf);
	} while (FALSE);

	return Status;
}




NTSTATUS FASTCALL
AtalkAspListenControl(
	IN	PASP_ADDROBJ	pAspAddr,
	IN	BOOLEAN			Enable
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	KIRQL		OldIrql;
	NTSTATUS	Status = STATUS_UNSUCCESSFUL;

	if (AtalkAspReferenceAddr(pAspAddr))
	{
		ACQUIRE_SPIN_LOCK(&pAspAddr->aspao_Lock, &OldIrql);
		pAspAddr->aspao_EnableNewConnections = Enable;
		RELEASE_SPIN_LOCK(&pAspAddr->aspao_Lock, OldIrql);

		AtalkAspDereferenceAddr(pAspAddr);
		Status = STATUS_SUCCESS;
	}

	return Status;
}


NTSTATUS
AtalkAspCloseConnection(
	IN	PASP_CONNOBJ			pAspConn
	)
/*++

Routine Description:

 	Shutdown a session.

Arguments:


Return Value:


--*/
{
	KIRQL		OldIrql;
	BOOLEAN		CompListen = FALSE;

	ASSERT(VALID_ASPCO(pAspConn));
	ASSERT(pAspConn->aspco_RefCount > 0);

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("AtalkAspCloseConn: Entered for Conn %lx\n", pAspConn));

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_WARN,
			("AtalkCloseConnection: Close session from %d.%d for Session %d\n",
			pAspConn->aspco_WssRemoteAddr.ata_Network,
			pAspConn->aspco_WssRemoteAddr.ata_Node,
			pAspConn->aspco_SessionId));

	AtalkAspCleanupConnection(pAspConn);

	ACQUIRE_SPIN_LOCK(&pAspConn->aspco_Lock, &OldIrql);

	pAspConn->aspco_Flags |= (ASPCO_CLOSING | ASPCO_LOCAL_CLOSE);

	RELEASE_SPIN_LOCK(&pAspConn->aspco_Lock, OldIrql);


	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("AtalkAspCloseConnection: Done for %lx (%ld)\n",
			pAspConn, pAspConn->aspco_RefCount));

	// Let remaining cleanup happen during the Derefernce
	AtalkAspDereferenceConn(pAspConn);	// Remove the creation reference

#ifdef	PROFILING
	INTERLOCKED_DECREMENT_LONG( &AtalkStatistics.stat_CurAspSessions,
								&AtalkStatsLock.SpinLock);
#endif

	return STATUS_PENDING;
}




ATALK_ERROR
AtalkAspCleanupConnection(
	IN	PASP_CONNOBJ			pAspConn
	)
/*++

Routine Description:
	Cancel all I/O on this session. Complete pending replies and write
	continues with error.

Arguments:


Return Value:


--*/
{
	PASP_REQUEST	pAspReq;
	PASP_ADDROBJ	pAspAddr;
	ATALK_ERROR		Status;
	KIRQL			OldIrql;
	USHORT			XactId;
	BYTE			UserBytes[ATP_USERBYTES_SIZE];
	ATALK_ADDR		RemoteAddr;
	BOOLEAN			CancelTickle, AlreadyCleaning;

	ASSERT(VALID_ASPCO(pAspConn));

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("AtalkAspCleanupConnection: For %lx\n", pAspConn));

	ACQUIRE_SPIN_LOCK(&pAspConn->aspco_Lock, &OldIrql);
	CancelTickle = ((pAspConn->aspco_Flags & ASPCO_TICKLING) != 0);
	if (AlreadyCleaning = ((pAspConn->aspco_Flags & ASPCO_CLEANING_UP) != 0))
		pAspConn->aspco_Flags &= ~ASPCO_TICKLING;
	pAspConn->aspco_Flags |= ASPCO_CLEANING_UP;
	RELEASE_SPIN_LOCK(&pAspConn->aspco_Lock, OldIrql);

	if (AlreadyCleaning)
		return ATALK_NO_ERROR;

	pAspAddr = pAspConn->aspco_pAspAddr;
	ASSERT(VALID_ASPAO(pAspAddr));

	// Send a session close request, if this is an active connection
	if (pAspConn->aspco_Flags & ASPCO_ACTIVE)
	{
		UserBytes[ASP_CMD_OFF] = ASP_CLOSE_SESSION;
		UserBytes[ASP_SESSIONID_OFF] = pAspConn->aspco_SessionId;
		PUTSHORT2SHORT(UserBytes + ASP_ATTN_WORD_OFF, 0);
		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
				("AtalkAspCleanupConnection: Sending close req for %lx\n",
				pAspConn));
		Status = AtalkAtpPostReq(pAspAddr->aspao_pSssAtpAddr,
								 &pAspConn->aspco_WssRemoteAddr,
								 &XactId,
								 ATP_REQ_REMOTE,		// Close session request is ALO
								 NULL,
								 0,
								 UserBytes,
								 NULL,
								 0,
								 ATP_RETRIES_FOR_ASP,
								 ATP_MAX_INTERVAL_FOR_ASP,
								 THIRTY_SEC_TIMER,
								 NULL,
								 NULL);

		if (!ATALK_SUCCESS(Status))
		{
			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
					("AtalkAspCleanupConn: AtalkAtpPostReq %ld\n", Status));
		}
	}

	// Cancel tickle packets
	if (CancelTickle)
	{
		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
				("AtalkAspCleanupConnection: Cancel tickle for %lx\n", pAspConn));
		Status = AtalkAtpCancelReq(pAspAddr->aspao_pSlsAtpAddr,
								   pAspConn->aspco_TickleXactId,
								   &pAspConn->aspco_WssRemoteAddr);
		if (!ATALK_SUCCESS(Status))
		{
			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
					("AtalkAspCleanupConn: AtalkAtpCancelReq %ld\n", Status));
		}
	}

	do
	{
		BOOLEAN		CancelReply;

		ACQUIRE_SPIN_LOCK(&pAspConn->aspco_Lock, &OldIrql);
		for (pAspReq = pAspConn->aspco_pActiveReqs;
			 pAspReq != NULL;
			 pAspReq = pAspReq->asprq_Next)
		{
			ASSERT (VALID_ASPRQ(pAspReq));

			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
					("AtalkAspCleanupConnection: Found req %lx (%lx) for %lx\n",
					pAspReq, pAspReq->asprq_Flags, pAspConn));

			CancelReply = FALSE;

			if ((pAspReq->asprq_Flags & (ASPRQ_WRTCONT | ASPRQ_WRTCONT_CANCELLED)) == ASPRQ_WRTCONT)
			{
				pAspReq->asprq_Flags |= ASPRQ_WRTCONT_CANCELLED;
				RemoteAddr = pAspConn->aspco_WssRemoteAddr;
				break;
			}
			if ((pAspReq->asprq_Flags & (ASPRQ_REPLY | ASPRQ_REPLY_CANCELLED)) == ASPRQ_REPLY)
			{
				CancelReply = TRUE;
				pAspReq->asprq_Flags |= ASPRQ_REPLY_CANCELLED;
				break;
			}
		}

		RELEASE_SPIN_LOCK(&pAspConn->aspco_Lock, OldIrql);

		if (pAspReq != NULL)
		{
			if (CancelReply)
			{
				DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
						("AtalkAspCleanupConnection: Cancel reply for %lx\n",
						pAspReq));
				AtalkAtpCancelResp(pAspReq->asprq_pAtpResp);
			}
			else
			{
				DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
						("AtalkAspCleanupConnection: Cancel wrtcont for %lx\n",
						pAspReq));
				AtalkAtpCancelReq(pAspConn->aspco_pAspAddr->aspao_pSssAtpAddr,
								  pAspReq->asprq_WCXactId,
								  &RemoteAddr);
			}
		}
		else break;
	} while (TRUE);

	return ATALK_NO_ERROR;
}


NTSTATUS FASTCALL
AtalkAspReply(
	IN	PREQUEST				pRequest,	// Pointer to request
	IN	PBYTE					pResultCode	// Pointer to the 4-byte result
	)
/*++

Routine Description:

	The response buffer is in the request itself.

Arguments:


Return Value:


--*/
{
	PASP_REQUEST	pAspReq, *ppAspReq;
	PASP_CONNOBJ	pAspConn;
	PASP_ADDROBJ	pAspAddr;
	ATALK_ERROR		error;
	KIRQL			OldIrql;
	USHORT			ReplySize;

	pAspReq = CONTAINING_RECORD(pRequest, ASP_REQUEST, asprq_Request);
	ASSERT (VALID_ASPRQ(pAspReq));

	pAspConn = pAspReq->asprq_pAspConn;
	ASSERT(VALID_ASPCO(pAspConn));

	pAspAddr = pAspConn->aspco_pAspAddr;
	ASSERT(VALID_ASPAO(pAspAddr));

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("AtalkAspReply: Entered for session %lx\n", pAspConn));
	
	ASSERT ((pAspReq->asprq_Flags & (ASPRQ_WRTCONT | ASPRQ_REPLY)) == 0);

	do
	{
		// Find and de-queue this request from the list
		ACQUIRE_SPIN_LOCK(&pAspConn->aspco_Lock, &OldIrql);
		
		for (ppAspReq = &pAspConn->aspco_pActiveReqs;
			 *ppAspReq != NULL;
			 ppAspReq = &(*ppAspReq)->asprq_Next)
		{
			if (pAspReq == *ppAspReq)
			{
				*ppAspReq = pAspReq->asprq_Next;
				pAspConn->aspco_cReqsInProcess --;
				pAspReq->asprq_Flags |= ASPRQ_REPLY;
				break;
			}
		}
		
		ASSERT(*ppAspReq == pAspReq->asprq_Next);
		
		if (pAspConn->aspco_Flags & (ASPCO_CLEANING_UP |
									 ASPCO_CLOSING |
									 ASPCO_LOCAL_CLOSE |
									 ASPCO_REMOTE_CLOSE))
		{
			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
					("AtalkAspReply: Session Closing - session %x\n", pAspConn->aspco_SessionId));
			RELEASE_SPIN_LOCK(&pAspConn->aspco_Lock, OldIrql);
			pAspReq->asprq_Flags &= ~ASPRQ_REPLY;
			pAspReq->asprq_Flags |= ASPRQ_REPLY_ABORTED;
			error = ATALK_LOCAL_CLOSE;
			break;
		}
	
		RELEASE_SPIN_LOCK(&pAspConn->aspco_Lock, OldIrql);
	
		ReplySize = (USHORT)AtalkSizeMdlChain(pAspReq->asprq_Request.rq_ReplyMdl);
	
		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
				("AtalkAspReply: Posting AtalkAtpPostResp for request %lx\n", pAspReq));
	
		error = AtalkAtpPostResp(pAspReq->asprq_pAtpResp,
								 &pAspReq->asprq_RemoteAddr,
								 pAspReq->asprq_Request.rq_ReplyMdl,
								 ReplySize,
								 pResultCode,
								 atalkAspReplyRelease,
								 pAspReq);

		if (!ATALK_SUCCESS(error))
		{
			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
					("AtalkAspReply: AtalkAtpPostResp %ld\n", error));
		}
	} while (FALSE);

	if (!ATALK_SUCCESS(error))
	{
		atalkAspReplyRelease(error, pAspReq);
	}

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("AtalkAspReply: Completing request %lx, Status %ld\n",
			pAspReq, error));

	return STATUS_PENDING;
}




LOCAL ATALK_ERROR FASTCALL
atalkAspPostWriteContinue(
	IN	PASP_REQUEST			pAspReq
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ATALK_ERROR		error;
	PASP_CONNOBJ	pAspConn;
	PAMDL			pAMdl = NULL;
	BYTE			UserBytes[ATP_USERBYTES_SIZE];
	USHORT			RespSize;

	ASSERT (VALID_ASPRQ(pAspReq));

	pAspConn = pAspReq->asprq_pAspConn;
	ASSERT(VALID_ASPCO(pAspConn));

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("atalkAspPostWriteContinue: Entered for session %lx\n", pAspConn));

	RespSize = (USHORT)AtalkSizeMdlChain(pAspReq->asprq_Request.rq_WriteMdl);
	ASSERT (RespSize <= ATP_MAX_TOTAL_RESPONSE_SIZE);

	if (RespSize > ATP_MAX_TOTAL_RESPONSE_SIZE)
		RespSize = ATP_MAX_TOTAL_RESPONSE_SIZE;

	ASSERT (!(pAspReq->asprq_Flags & (ASPRQ_WRTCONT | ASPRQ_WRTCONT_CANCELLED)));

	pAspReq->asprq_Flags |= ASPRQ_WRTCONT;

	do
	{
		// We need to build an AMdl for two bytes of response which
		// indicates how much data we are expecting !!!
		if ((pAMdl = AtalkAllocAMdl(pAspReq->asprq_WrtContRespBuf,
									ASP_WRITE_DATA_SIZE)) == NULL)
		{
			error = ATALK_RESR_MEM;
			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
					("AtalkAspWriteContinue: AtalkAllocMdl failed for 2 bytes !!\n"));
		}

		else
		{
			PBYTE	pWrtData;

			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
					("AtalkAspWriteContinue: Posting AtalkAtpPostReq for request %lx\n",
					pAspReq));

			pWrtData = AtalkGetAddressFromMdl(pAMdl);
			UserBytes[ASP_CMD_OFF] = ASP_WRITE_DATA;
			UserBytes[ASP_SESSIONID_OFF] = pAspConn->aspco_SessionId;
			PUTSHORT2SHORT(UserBytes+ASP_SEQUENCE_NUM_OFF, pAspReq->asprq_SeqNum);
			PUTSHORT2SHORT(pWrtData, RespSize);

			// Snapshot the current tick count. We use this to adjust the retry times on
			// write continue.
			pAspConn->aspco_RT.rt_New = AtalkGetCurrentTick();
			error = AtalkAtpPostReq(pAspConn->aspco_pAspAddr->aspao_pSssAtpAddr,
									&pAspConn->aspco_WssRemoteAddr,
									&pAspReq->asprq_WCXactId,
									ATP_REQ_EXACTLY_ONCE | ATP_REQ_REMOTE,
									pAMdl,
									ASP_WRITE_DATA_SIZE,
									UserBytes,
									pAspReq->asprq_Request.rq_WriteMdl,
									RespSize,
									ATP_INFINITE_RETRIES,
									pAspConn->aspco_RT.rt_Base,
									THIRTY_SEC_TIMER,
									atalkAspWriteContinueResp,
									pAspReq);
			if (!ATALK_SUCCESS(error))
			{
				DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
						("AtalkAspWriteContinue: AtalkAtpPostReq %ld\n", error));
			}
		}

		if (!ATALK_SUCCESS(error))
		{
			if (pAMdl != NULL)
				AtalkFreeAMdl(pAMdl);
		}
	} while (FALSE);

	return error;
}




NTSTATUS
AtalkAspSendAttention(
	IN	PASP_CONNOBJ			pAspConn,
	IN	USHORT					AttentionWord,
	IN	PVOID					pContext
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ATALK_ERROR		error;
	NTSTATUS		Status = STATUS_SUCCESS;
	KIRQL			OldIrql;
	PAMDL			pAMdl = NULL;
	BYTE			UserBytes[ATP_USERBYTES_SIZE];
	USHORT			XactId, RespSize = 16;		// Some small number (see comment below)

	ASSERT(VALID_ASPCO(pAspConn));

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("AtalkAspSendAttention: Entered for session %lx\n", pAspConn));

	// Reference by src addr here instead of by pointer since the former will
	// fail when the session is in one of the stages of death whereas the
	// latter will not. Also this assumes that it is called at dispatch so raise irql.
	KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
	pAspConn = atalkAspReferenceConnBySrcAddr(pAspConn->aspco_pAspAddr,
											  &pAspConn->aspco_WssRemoteAddr,
											  pAspConn->aspco_SessionId);
	KeLowerIrql(OldIrql);

	if (pAspConn == NULL)
		return STATUS_REQUEST_NOT_ACCEPTED;

	UserBytes[ASP_CMD_OFF] = ASP_ATTENTION;
	UserBytes[ASP_SESSIONID_OFF] = pAspConn->aspco_SessionId;
	PUTSHORT2SHORT(UserBytes+ASP_ATTN_WORD_OFF, AttentionWord);

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("AtalkAspSendAttention: Posting AtalkAtpPostReq for Conn %lx\n", pAspConn));

	// We need to build an AMdl for a dummy buffer to hold the response.
	// There is no real response but some brain-dead clients fry their
	// machines if we don't !!! If we cannot allocate the mdl we we go
	// ahead anyway.
	if ((pAMdl = AtalkAllocAMdl(NULL, RespSize)) == NULL)
	{
		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
				("AtalkAspSendAttention: AtalkAllocMdl failed for dummy buffer !!\n"));
		RespSize = 0;
	}
	pAspConn->aspco_AttentionContext = pContext;

	error = AtalkAtpPostReq(pAspConn->aspco_pAspAddr->aspao_pSssAtpAddr,
							&pAspConn->aspco_WssRemoteAddr,
							&XactId,
							ATP_REQ_REMOTE,		// SendAttention is ALO
							NULL,
							0,
							UserBytes,
							pAMdl,
							RespSize,
							ATP_RETRIES_FOR_ASP,
							ATP_MAX_INTERVAL_FOR_ASP,
							THIRTY_SEC_TIMER,
							atalkAspSendAttentionResp,
							pAspConn);
	if (!ATALK_SUCCESS(error))
	{
		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
				("AtalkAspSendAttention: AtalkAtpPostReq %ld\n", Status));
		Status = AtalkErrorToNtStatus(error);
		atalkAspSendAttentionResp(error,
								  pAspConn,
								  NULL,
								  pAMdl,
								  RespSize,
								  UserBytes);
	}

	return Status;
}




PASP_ADDROBJ FASTCALL
AtalkAspReferenceAddr(
	IN	PASP_ADDROBJ		pAspAddr
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	KIRQL			OldIrql;
	PASP_ADDROBJ	pRefAddr;

	ASSERT(VALID_ASPAO(pAspAddr));

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("AtalkAspReferenceAddr: Addr %lx, PreCount %ld\n",
			pAspAddr, pAspAddr->aspao_RefCount));

	pRefAddr = pAspAddr;
	ACQUIRE_SPIN_LOCK(&pAspAddr->aspao_Lock, &OldIrql);

	ASSERT(pAspAddr->aspao_RefCount > 1);

	if (pAspAddr->aspao_Flags & ASPAO_CLOSING)
	{
		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
				("AtalkAspReferenceAddr: Referencing closing object %lx!!\n",
				pAspAddr));
		pRefAddr = NULL;
	}
	else pAspAddr->aspao_RefCount ++;

	RELEASE_SPIN_LOCK(&pAspAddr->aspao_Lock, OldIrql);

	return pRefAddr;
}




VOID FASTCALL
AtalkAspDereferenceAddr(
	IN	PASP_ADDROBJ		pAspAddr
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	KIRQL			OldIrql;
	BOOLEAN			Cleanup;

	ASSERT(VALID_ASPAO(pAspAddr));

	ASSERT (pAspAddr->aspao_RefCount > 0);

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("AtalkAspDereferenceAddr: Addr %lx, PreCount %ld\n",
			pAspAddr, pAspAddr->aspao_RefCount));

	ACQUIRE_SPIN_LOCK(&pAspAddr->aspao_Lock, &OldIrql);

	pAspAddr->aspao_RefCount --;

	Cleanup = FALSE;
	if (pAspAddr->aspao_RefCount == 0)
	{
		ASSERT (pAspAddr->aspao_Flags & ASPAO_CLOSING);
		Cleanup = TRUE;
	}

	RELEASE_SPIN_LOCK(&pAspAddr->aspao_Lock, OldIrql);

	// Check if this address object is history. Do all the processing needed to make this go
	// away. When all is done, clear the event to signal close is complete
	if (Cleanup)
	{
		// At this point we are sure that no active sessions exist on this
		// address.
		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_WARN,
				("AtalkAspDereferenceAddr: Cleaning up addr %lx\n", pAspAddr));

		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_WARN,
				("AtalkAspDereferenceAddr: Indicating close for %lx\n", pAspAddr));

		ASSERT(KeGetCurrentIrql() == LOW_LEVEL);

		// Call the completion routine to indicate close is successfull
		if (pAspAddr->aspao_CloseCompletion != NULL)
			(*pAspAddr->aspao_CloseCompletion)(ATALK_NO_ERROR,
											   pAspAddr->aspao_CloseContext);
		// Finally free the memory
		AtalkFreeMemory(pAspAddr);

		AtalkUnlockAspIfNecessary();
	}
}



LOCAL PASP_CONNOBJ
atalkAspReferenceConnBySrcAddr(
	IN	PASP_ADDROBJ	pAspAddr,
	IN	PATALK_ADDR		pSrcAddr,
	IN	BYTE			SessionId
	)
/*++

Routine Description:

 	ASP has the concept of 8-bit session ids which uniquely identifies a
 	session on a listener. This effectively restricts the number of sessions
 	to 255 (0 is invalid). To eliminate the restriction, the following
 	strategy is used.
 	a, Atp is modified to isolate the transaction ids on a per addr basis
 	   i.e. it monotonically increases for each <net,node,socket> combination.
 	b, We create session ids on a per <net,node> basis.

 	Given the following observed facts:
 	1, That macintoshes use the sockets starting from the top of the range.
 	2, Most network addresses have the same high byte - macintoshes tend
 	   to start from the bottom of the range.
 	3, We allocate session ids starting from 1 and most (all) clients will
 	   not have more than one session with us.

 	It does not make any sense to take either the socket, session id or the
 	high byte of the network number into account. That leaves only the low
 	byte of the network, and node id - a nice 16-bit number to hash.

Arguments:


Return Value:


--*/
{
	PASP_CONNOBJ	pAspConn, pRefConn = NULL;
	int				index;

	ASSERT(VALID_ASPAO(pAspAddr));

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("AtalkAspReferenceConnBySrcAddr: Addr %lx, Source %x.%x SessionId %d\n",
			pAspAddr, pSrcAddr->ata_Network, pSrcAddr->ata_Node, SessionId));

	index = HASH_SRCADDR(pSrcAddr);
	ACQUIRE_SPIN_LOCK_DPC(&pAspAddr->aspao_Lock);

	for (pAspConn = pAspAddr->aspao_pSessions[index];
		 pAspConn != NULL;
		 pAspConn = pAspConn->aspco_NextOverflow)
	{
		if ((pSrcAddr->ata_Network == pAspConn->aspco_WssRemoteAddr.ata_Network) &&
			(pSrcAddr->ata_Node == pAspConn->aspco_WssRemoteAddr.ata_Node) &&
			(pAspConn->aspco_SessionId == SessionId))
		{
			ACQUIRE_SPIN_LOCK_DPC(&pAspConn->aspco_Lock);
			if ((pAspConn->aspco_Flags & (ASPCO_CLOSING |
										  ASPCO_CLEANING_UP |
										  ASPCO_LOCAL_CLOSE |
										  ASPCO_REMOTE_CLOSE)) == 0)
			{
				ASSERT(pAspConn->aspco_RefCount > 0);
				pAspConn->aspco_RefCount ++;
				pRefConn = pAspConn;
			}
			RELEASE_SPIN_LOCK_DPC(&pAspConn->aspco_Lock);
			break;
		}
	}

	RELEASE_SPIN_LOCK_DPC(&pAspAddr->aspao_Lock);

	return pRefConn;
}




VOID FASTCALL
AtalkAspDereferenceConn(
	IN	PASP_CONNOBJ		pAspConn
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PASP_ADDROBJ		pAspAddr = pAspConn->aspco_pAspAddr;
	KIRQL				OldIrql;
	PASP_REQUEST		pAspReq;
	BOOLEAN				Cleanup = FALSE;

	ASSERT(VALID_ASPCO(pAspConn));

	ASSERT (pAspConn->aspco_RefCount > 0);

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("AtalkAspDereferenceConn: Conn %lx, PreCount %ld\n",
			pAspConn, pAspConn->aspco_RefCount));

	ACQUIRE_SPIN_LOCK(&pAspConn->aspco_Lock, &OldIrql);

	pAspConn->aspco_RefCount --;

	if (pAspConn->aspco_RefCount == 0)
	{
		Cleanup = TRUE;
	}

	RELEASE_SPIN_LOCK(&pAspConn->aspco_Lock, OldIrql);

	if (!Cleanup)
	{
		return;
	}

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_WARN,
			("AtalkAspDereferenceConn: Last for %lx\n", pAspConn));

	// The connection is all but dead. Perform the last rites. If its an
	// active session that we're about to shut down, send a close notification
	// to the other side. If it is a remote close, we've already responded to it.

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_WARN,
			("AtalkAspDereferenceConn: Cleaning up Conn %lx\n", pAspConn));

	ASSERT(VALID_ASPAO(pAspAddr));

	// The connection is in one of the following states:
	// a, Closed remotely - idle
	// b, Active
	//
	// In either case it is in the hash bucket so unlink it
	{
		PASP_CONNOBJ *	ppAspConn;
		int				index;

		// The connection was active. This is linked into two different
		// lists. Unlink from the hash table here and from the global
		// list later.
		ASSERT(pAspConn->aspco_pActiveReqs == NULL);
		index = HASH_SRCADDR(&pAspConn->aspco_WssRemoteAddr);

		ACQUIRE_SPIN_LOCK(&pAspAddr->aspao_Lock, &OldIrql);
		for (ppAspConn = &pAspAddr->aspao_pSessions[index];
			 *ppAspConn != NULL;
			 ppAspConn = &(*ppAspConn)->aspco_NextOverflow)
		{
			if (pAspConn == *ppAspConn)
			{
				*ppAspConn = pAspConn->aspco_NextOverflow;
				break;
			}
		}
		RELEASE_SPIN_LOCK(&pAspAddr->aspao_Lock, OldIrql);

		ASSERT (*ppAspConn == pAspConn->aspco_NextOverflow);
	}

	ACQUIRE_SPIN_LOCK(&atalkAspLock, &OldIrql);
	AtalkUnlinkDouble(pAspConn,
					  aspco_NextSession,
					  aspco_PrevSession)


	// Free any requests on the free list
	while ((pAspReq = pAspConn->aspco_pFreeReqs) != NULL)
	{
		pAspConn->aspco_pFreeReqs = pAspReq->asprq_Next;
		AtalkBPFreeBlock(pAspReq);
	}

	RELEASE_SPIN_LOCK(&atalkAspLock, OldIrql);

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_WARN,
			("AtalkAspDereferenceConn: Indicating close for Conn %lx\n", pAspConn));

	// Call the completion routine to indicate close is successful.
	(*pAspAddr->aspao_ClientEntries.clt_CloseCompletion)(STATUS_SUCCESS,
														 pAspConn->aspco_ConnContext);

	// Now Dereference the address object, before we are history
	AtalkAspDereferenceAddr(pAspAddr);

	// Finally free the memory.
	AtalkFreeMemory(pAspConn);
}




LOCAL VOID
atalkAspSlsXHandler(
	IN	ATALK_ERROR			ErrorCode,
	IN	PASP_ADDROBJ		pAspAddr,		// Listener (our context)
	IN	PATP_RESP			pAtpResp,		// Atp Response context
	IN	PATALK_ADDR			pSrcAddr,		// Address of requestor
	IN	USHORT				PktLen,
	IN	PBYTE				pPkt,
	IN	PBYTE				pUserBytes
	)
/*++

Routine Description:

 	Handler for incoming requests on the Sls. It handles session opens, tickles
 	and get status on the session.

Arguments:


Return Value:


--*/
{
	PASP_CONNOBJ		pAspConn;
	ATALK_ERROR			Status;
	PASP_POSTSTAT_CTX	pStsCtx;
	int					index;
	USHORT				StsBufSize;
	BYTE				AspCmd, SessionId, StartId;

	if (!ATALK_SUCCESS(ErrorCode))
	{
		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
				("atalkAspSlsXHandler: Error %ld\n", ErrorCode));

		// Take away the reference on the Sls now that the atp address is closing
		if (ErrorCode == ATALK_ATP_CLOSING)
			AtalkAspDereferenceAddr(pAspAddr);
		return;
	}

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("atalkAspSlsXHandler: Entered for Function %x from %x.%x\n",
			pUserBytes[ASP_CMD_OFF], pSrcAddr->ata_Network, pSrcAddr->ata_Node));

	switch (AspCmd = pUserBytes[ASP_CMD_OFF])
	{
	  case ASP_OPEN_SESSION:
		// Is the version number ok ?
		if ((pUserBytes[ASP_VERSION_OFF] != ASP_VERSION[0]) ||
			(pUserBytes[ASP_VERSION_OFF+1] != ASP_VERSION[1]))
		{
			atalkAspReturnResp( pAtpResp,
								pSrcAddr,
								0,					// SSS
								0,					// SessionId
								ASP_BAD_VERSION);	// ErrorCode
			break;
		}

		// Create a connection object corres. to this listen and then notify
		// the client that it needs to handle a new session
		// Allocate memory for a connection object
		if ((pAspConn = AtalkAllocZeroedMemory(sizeof(ASP_CONNOBJ))) != NULL)
		{
#if	DBG
			pAspConn->aspco_Signature = ASPCO_SIGNATURE;
#endif
			INITIALIZE_SPIN_LOCK(&pAspConn->aspco_Lock);
			pAspConn->aspco_RefCount = 1;				// Creation reference
			pAspConn->aspco_pAspAddr = pAspAddr;		// Owning address object
			AtalkInitializeRT(&pAspConn->aspco_RT,
							  ATP_INITIAL_INTERVAL_FOR_ASP,
							  ATP_MIN_INTERVAL_FOR_ASP,
							  ATP_MAX_INTERVAL_FOR_ASP);
		}

		ACQUIRE_SPIN_LOCK_DPC(&pAspAddr->aspao_Lock);

		if (pAspConn != NULL)
		{
			PASP_CONNOBJ	pTmp;

			// Find a session id that we can use for this session. We use
			// the next assignable id, if that is not in use. Otherwise we
			// use the next id not in use by that session. In most cases
			// we have only one session from any client.
			index = HASH_SRCADDR(pSrcAddr);

			// If we do not find any, we use this
			SessionId = StartId = pAspAddr->aspao_NextSessionId++;
			ASSERT (SessionId != 0);
			if (pAspAddr->aspao_NextSessionId == 0)
				pAspAddr->aspao_NextSessionId = 1;

			for (pTmp = pAspAddr->aspao_pSessions[index];
				 pTmp != NULL;
				 NOTHING)
			{
				if ((pTmp->aspco_WssRemoteAddr.ata_Node == pSrcAddr->ata_Node) &&
					(pTmp->aspco_WssRemoteAddr.ata_Network == pSrcAddr->ata_Network))
				{
					if (pTmp->aspco_SessionId == SessionId)
					{
						SessionId ++;
						pTmp = pAspAddr->aspao_pSessions[index];
						continue;
					}
				}
				pTmp = pTmp->aspco_NextOverflow;
			}

			// if there are 255 sessions already from this address, then
			// we can't have any more, sorry !!!
			if (SessionId != (StartId - 1))
			{
				// Link it into the hash table
				pAspAddr->aspao_RefCount ++;
				pAspConn->aspco_SessionId = SessionId;
				pAspConn->aspco_cReqsInProcess = 0;
				pAspConn->aspco_WssRemoteAddr.ata_Address = pSrcAddr->ata_Address;
				pAspConn->aspco_WssRemoteAddr.ata_Socket = pUserBytes[ASP_WSS_OFF];
				pAspConn->aspco_LastContactTime = AtalkGetCurrentTick();
				pAspConn->aspco_NextExpectedSeqNum = 0;
				pAspConn->aspco_Flags |= (ASPCO_ACTIVE | ASPCO_TICKLING);

				// The session should be linked *after* all of the above
				// are initialized
				pAspConn->aspco_NextOverflow = pAspAddr->aspao_pSessions[index];
				pAspAddr->aspao_pSessions[index] = pAspConn;
#ifdef	PROFILING
				INTERLOCKED_INCREMENT_LONG_DPC(&AtalkStatistics.stat_CurAspSessions,
											   &AtalkStatsLock.SpinLock);

				INTERLOCKED_INCREMENT_LONG_DPC(&AtalkStatistics.stat_TotalAspSessions,
											   &AtalkStatsLock.SpinLock);
#endif
			}
			else
			{
				AtalkFreeMemory(pAspConn);
				pAspConn = NULL;
			}
		}

		RELEASE_SPIN_LOCK_DPC(&pAspAddr->aspao_Lock);

		if (pAspConn != NULL)
		{
			BYTE	Socket;
			BYTE	UserBytes[ATP_USERBYTES_SIZE];

			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
					("atalkAspSlsXHandler: Opening session from %d.%d for Session %d\n",
					pSrcAddr->ata_Network, pSrcAddr->ata_Node, SessionId));


			// Call the open completion routine and get a context. This is needed
			// before we do anything else. Once we send out an open success it'll
			// be too late.
			pAspConn->aspco_ConnContext =
				(*pAspAddr->aspao_ClientEntries.clt_SessionNotify)(pAspConn);

			if (pAspConn->aspco_ConnContext != NULL)
			{
				// Now link the session into the global list
				ACQUIRE_SPIN_LOCK_DPC(&atalkAspLock);
				AtalkLinkDoubleAtHead(atalkAspConnMaint[SessionId & (NUM_ASP_CONN_LISTS-1)].ascm_ConnList,
									  pAspConn,
									  aspco_NextSession,
									  aspco_PrevSession)
				RELEASE_SPIN_LOCK_DPC(&atalkAspLock);
	
				// Send an open session response - XO
				Socket = pAspAddr->aspao_pSssAtpAddr->atpao_DdpAddr->ddpao_Addr.ata_Socket;
				atalkAspReturnResp( pAtpResp,
									pSrcAddr,
									Socket,
									pAspConn->aspco_SessionId,
									0);				// Success
	
				// Send a tickle out every ASP_TICKLE_INTERVAL seconds
				UserBytes[ASP_CMD_OFF] = ASP_TICKLE;
				UserBytes[ASP_SESSIONID_OFF] = pAspConn->aspco_SessionId;
				PUTSHORT2SHORT(UserBytes + ASP_ERRORCODE_OFF, 0);
				Status = AtalkAtpPostReq(pAspAddr->aspao_pSlsAtpAddr,
										&pAspConn->aspco_WssRemoteAddr,
										&pAspConn->aspco_TickleXactId,
										ATP_REQ_REMOTE,		// Tickle packets are ALO
										NULL,
										0,
										UserBytes,
										NULL,
										0,
										ATP_INFINITE_RETRIES,
										ASP_TICKLE_INTERVAL,
										THIRTY_SEC_TIMER,
										NULL,
										NULL);
				if (!ATALK_SUCCESS(Status))
				{
					pAspConn->aspco_Flags &= ~ASPCO_TICKLING;
					DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
							("atalkAspSlsXHandler: AtalkAtpPostReq %ld\n", Status));
				}
			}
			else
			{
				PASP_CONNOBJ *	ppAspConn;

				// Unlink it from the hash table
				ACQUIRE_SPIN_LOCK_DPC(&pAspAddr->aspao_Lock);

				for (ppAspConn = &pAspAddr->aspao_pSessions[index];
					 *ppAspConn != NULL;
					 ppAspConn = &(*ppAspConn)->aspco_NextOverflow)
				{
					if (*ppAspConn == pAspConn)
					{
						*ppAspConn = pAspConn->aspco_NextOverflow;
						break;
					}
				}

				ASSERT (*ppAspConn == pAspConn->aspco_NextOverflow);

				RELEASE_SPIN_LOCK_DPC(&pAspAddr->aspao_Lock);

				AtalkFreeMemory(pAspConn);
				pAspConn = NULL;
#ifdef	PROFILING
				INTERLOCKED_DECREMENT_LONG_DPC(&AtalkStatistics.stat_CurAspSessions,
											   &AtalkStatsLock.SpinLock);

				INTERLOCKED_DECREMENT_LONG_DPC(&AtalkStatistics.stat_TotalAspSessions,
											   &AtalkStatsLock.SpinLock);
#endif
			}
		}

		// If we are set to disable listens or could not allocate memory, drop it
		if (pAspConn == NULL)
		{
			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
					("atalkAspSlsXHandler: No conn objects available\n"));

			atalkAspReturnResp( pAtpResp,
								pSrcAddr,
								0,
								0,
								ASP_SERVER_BUSY);
		}
		break;

	  case ASP_GET_STATUS:
		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
				("atalkAspSssXHandler: Received GetStat from %x.%x\n",
				pSrcAddr->ata_Network, pSrcAddr->ata_Node));
		// Create an Mdl to describe the status buffer and post a response
		// to the GetStatus request
		StsBufSize = 0;
        pStsCtx = NULL;
		ACQUIRE_SPIN_LOCK_DPC(&pAspAddr->aspao_Lock);
		if (pAspAddr->aspao_pStatusBuf != NULL)
		{
			pStsCtx = (PASP_POSTSTAT_CTX)AtalkAllocMemory(sizeof(ASP_POSTSTAT_CTX) +
														  pAspAddr->aspao_StsBufSize);
			if (pStsCtx != NULL)
			{
				pStsCtx->aps_pAMdl = AtalkAllocAMdl((PBYTE)pStsCtx + sizeof(ASP_POSTSTAT_CTX),
													pAspAddr->aspao_StsBufSize);
				if (pStsCtx->aps_pAMdl != NULL)
				{
					pStsCtx->aps_pAtpResp = pAtpResp;
					StsBufSize = pAspAddr->aspao_StsBufSize;
					RtlCopyMemory((PBYTE)pStsCtx + sizeof(ASP_POSTSTAT_CTX),
								  pAspAddr->aspao_pStatusBuf,
								  StsBufSize);
				}
				else AtalkFreeMemory(pStsCtx);
			}
		}
		RELEASE_SPIN_LOCK_DPC(&pAspAddr->aspao_Lock);

		Status = AtalkAtpPostResp(pAtpResp,
								  pSrcAddr,
								  (pStsCtx != NULL) ?
									pStsCtx->aps_pAMdl : NULL,
								  StsBufSize,
								  NULL,
								  atalkAspRespComplete,
								  pStsCtx);
		if (!ATALK_SUCCESS(Status))
		{
			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
					("atalkAspSlsXHandler: AtalkAtpPostResp %ld\n", Status));
			atalkAspRespComplete(Status, pStsCtx);
		}
		break;

	  case ASP_TICKLE:
		SessionId = pUserBytes[ASP_SESSIONID_OFF];
		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
				("atalkAspSssXHandler: Received tickle from %x.%x Session %d\n",
				pSrcAddr->ata_Network, pSrcAddr->ata_Node, SessionId));

		if ((pAspConn = atalkAspReferenceConnBySrcAddr(pAspAddr, pSrcAddr, SessionId)) != NULL)
		{
			ACQUIRE_SPIN_LOCK_DPC(&pAspConn->aspco_Lock);
			pAspConn->aspco_LastContactTime = AtalkGetCurrentTick();
			RELEASE_SPIN_LOCK_DPC(&pAspConn->aspco_Lock);
			AtalkAspDereferenceConn(pAspConn);
		}
		else
		{
			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
					("atalkAspSssXHandler: Conn not found for addr %d.%d Session %d\n",
					pSrcAddr->ata_Network, pSrcAddr->ata_Node, SessionId));
		}
		// Fall through to the default case

	  default:
		// Cancel this response since we never respond to it and we want this to go away
		AtalkAtpCancelResp(pAtpResp);

	  	if (AspCmd != ASP_TICKLE)
		{
			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
					("atalkAspSlsXHandler: Invalid command\n"));
		}
		break;
	}
}




LOCAL VOID
atalkAspSssXHandler(
	IN	ATALK_ERROR			ErrorCode,
	IN	PASP_ADDROBJ		pAspAddr,		// Listener (our context)
	IN	PATP_RESP			pAtpResp,		// Atp Response context
	IN	PATALK_ADDR			pSrcAddr,		// Address of requestor
	IN	USHORT				PktLen,
	IN	PBYTE				pPkt,
	IN	PBYTE				pUserBytes
	)
/*++

Routine Description:

 	Handler for incoming requests on the Sss. It handles incoming requests, close
 	and write continue.

Arguments:


Return Value:


--*/
{
	PASP_CONNOBJ	pAspConn;		// Session which will handle this request
	PASP_REQUEST	pAspReq;		// The request that will be satisfied
	ATALK_ERROR		Status;
	USHORT			SequenceNum;	// From the incoming packet
	BYTE			SessionId;		// -- ditto --
	BYTE			RequestType;	// -- ditto --
	BOOLEAN			CancelResp = FALSE,
	                CancelTickle;

	do
	{
		if (!ATALK_SUCCESS(ErrorCode))
		{
			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
					("atalkAspSssXHandler: Error %ld\n", ErrorCode));
			// Take away the reference on the Sls now that the atp address is closing
			if (ErrorCode == ATALK_ATP_CLOSING)
				AtalkAspDereferenceAddr(pAspAddr);
			break;
		}
	
		// Get the session id out of the packet and reference the session that this
		// request is targeted to.
		SessionId = pUserBytes[ASP_SESSIONID_OFF];
		RequestType = pUserBytes[ASP_CMD_OFF];
		GETSHORT2SHORT(&SequenceNum, pUserBytes+ASP_SEQUENCE_NUM_OFF);
	
		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
				("atalkAspSssXHandler: Entered for Request %x from %x.%x\n",
				RequestType, pSrcAddr->ata_Network, pSrcAddr->ata_Node));
	
		// The reference for this connection is passed down to the request
		// for the ASP_CMD & ASP_WRITE case.
		pAspConn = atalkAspReferenceConnBySrcAddr(pAspAddr, pSrcAddr, SessionId);
		if (pAspConn == NULL)
		{
			CancelResp = TRUE;
			break;
		}
	
		ASSERT (pAspConn->aspco_pAspAddr == pAspAddr);
	
		ACQUIRE_SPIN_LOCK_DPC(&pAspConn->aspco_Lock);
		pAspConn->aspco_LastContactTime = AtalkGetCurrentTick();
	
		switch (RequestType)
		{
		  case ASP_CMD:
		  case ASP_WRITE:
			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
					("atalkAspSssXHandler: %s\n",
					(RequestType == ASP_CMD) ? "Command" : "Write"));
			// Create a request for this and notify the client to handle this
			// Validate the incoming sequence number. Reject if out of sequence
			if (SequenceNum == pAspConn->aspco_NextExpectedSeqNum)
			{
				// We now have a request to be handled.
				// The reference to the connection above will be passed on
				// to the request. This will get de-referenced when the
				// request is replied to. See if we have a free request to pick up
				// Allocate a request structure if not and link it in the listener object
				if ((pAspReq = pAspConn->aspco_pFreeReqs) != NULL)
					 pAspConn->aspco_pFreeReqs = pAspReq->asprq_Next;
				else pAspReq = AtalkBPAllocBlock(BLKID_ASPREQ);
	
				if (pAspReq != NULL)
				{
					pAspConn->aspco_NextExpectedSeqNum ++;
#if	DBG
					pAspReq->asprq_Signature = ASPRQ_SIGNATURE;
#endif
					pAspReq->asprq_pAtpResp = pAtpResp;
					pAspReq->asprq_pAspConn = pAspConn;
					pAspReq->asprq_ReqType = RequestType;
					pAspReq->asprq_SeqNum = SequenceNum;
					pAspReq->asprq_RemoteAddr = *pSrcAddr;
					pAspReq->asprq_Flags = 0;
					pAspReq->asprq_Request.rq_WriteMdl = NULL;
					pAspReq->asprq_Request.rq_RequestSize = PktLen;
					pAspReq->asprq_Next = pAspConn->aspco_pActiveReqs;
					pAspConn->aspco_cReqsInProcess ++;
					pAspConn->aspco_pActiveReqs = pAspReq;
				}
			}
			else
			{
				pAspReq = NULL;
				DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
						("atalkAspSssXHandler: Sequence mismatch exp %x, act %x\n",
						pAspConn->aspco_NextExpectedSeqNum, SequenceNum));
			}
	
			RELEASE_SPIN_LOCK_DPC(&pAspConn->aspco_Lock);
	
			// If we do not have an request to handle this, cancel the
			// response. Otherwise the client will keep retrying and atp
			// will not tell us since it already has.
			if (pAspReq == NULL)
			{
				AtalkAspDereferenceConn(pAspConn);
				DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
						("atalkAspSssXHandler: Dropping request for session %d from %x.%x\n",
						SessionId, pSrcAddr->ata_Network, pSrcAddr->ata_Node));
				CancelResp = TRUE;
				break;
			}
	
			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
					("atalkAspSssXHandler: Indicating Request %lx\n", pAspReq));
	
			if (RequestType == ASP_WRITE)
			{
				pAspReq->asprq_Request.rq_WriteMdl =
						(*pAspAddr->aspao_ClientEntries.clt_GetWriteBuffer)(pPkt, PktLen);
				if (pAspReq->asprq_Request.rq_WriteMdl != NULL)
				{
					// Save the request
					RtlCopyMemory(pAspReq->asprq_ReqBuf, pPkt, PktLen);
					pAspReq->asprq_Request.rq_RequestBuf = pAspReq->asprq_ReqBuf;
	
					atalkAspPostWriteContinue(pAspReq);
				}
			}
	
			if (pAspReq->asprq_Request.rq_WriteMdl == NULL)	// TRUE for CMD as well
			{
				pAspReq->asprq_Request.rq_RequestBuf = pPkt;
				// Notify the client that it has a request to handle
				(*pAspAddr->aspao_ClientEntries.clt_RequestNotify)(STATUS_SUCCESS,
																   pAspConn->aspco_ConnContext,
																   &pAspReq->asprq_Request);
			}
			break;
	
		  case ASP_CLOSE_SESSION:
			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_WARN,
					("atalkAspSssXHandler: Close request from %d.%d for Session %d\n",
					pSrcAddr->ata_Network, pSrcAddr->ata_Node, SessionId));
	
#ifdef	PROFILING
			INTERLOCKED_INCREMENT_LONG_DPC(&AtalkStatistics.stat_AspSessionsClosed,
										   &AtalkStatsLock.SpinLock);
#endif
			CancelTickle = ((pAspConn->aspco_Flags &ASPCO_TICKLING) != 0);
			pAspConn->aspco_Flags &= ~(ASPCO_ACTIVE | ASPCO_TICKLING);
			pAspConn->aspco_Flags |= ASPCO_REMOTE_CLOSE;
			RELEASE_SPIN_LOCK_DPC(&pAspConn->aspco_Lock);
	
			// Send a CloseSession reply and close the session
			Status = AtalkAtpPostResp(pAtpResp,
									  pSrcAddr,
									  NULL,
									  0,
									  NULL,
									  AtalkAtpGenericRespComplete,
									  pAtpResp);
			if (!ATALK_SUCCESS(Status))
			{
				AtalkAtpGenericRespComplete(Status, pAtpResp);
				DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
						("atalkAspSssXHandler: AtalkAtpPostResp failed %ld\n", Status));
			}
	
			// Cancel the tickle requests for this session
			if (CancelTickle)
			{
				Status = AtalkAtpCancelReq(pAspAddr->aspao_pSlsAtpAddr,
										   pAspConn->aspco_TickleXactId,
										   &pAspConn->aspco_WssRemoteAddr);
		
				if (!ATALK_SUCCESS(Status))
				{
					DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
							("atalkAspSssXHandler: AtalkAtpCancelReq %ld\n", Status));
				}
			}
	
			// Shut down this session, well almost ... Note that we have a reference
			// to this connection which will be Dereferenced by atalkAspSessionClose.
			atalkAspSessionClose(pAspConn);
			break;
	
		  default:
			RELEASE_SPIN_LOCK_DPC(&pAspConn->aspco_Lock);
			CancelResp = TRUE;
			AtalkAspDereferenceConn(pAspConn);
			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
					("atalkAspSssXHandler: Invalid command %d\n", RequestType));
			break;
		}
	} while (FALSE);

	if (CancelResp)
	{
		Status = AtalkAtpCancelResp(pAtpResp);
		if (!ATALK_SUCCESS(Status))
		{
			DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_ERR,
					("atalkAspSssXHandler: AtalkAspCancelResp %ld\n", Status));
		}
	}
}




LOCAL VOID FASTCALL
atalkAspReplyRelease(
	IN	ATALK_ERROR		ErrorCode,
	IN	PASP_REQUEST	pAspReq
	)
/*++

Routine Description:

 	Handler for incoming release for reply

Arguments:


Return Value:


--*/
{
	PASP_CONNOBJ	pAspConn;
	PASP_ADDROBJ	pAspAddr;
	KIRQL			OldIrql;
	NTSTATUS		Status = STATUS_SUCCESS;

	ASSERT (VALID_ASPRQ(pAspReq));
	pAspConn = pAspReq->asprq_pAspConn;
	ASSERT (VALID_ASPCO(pAspConn));

	pAspAddr = pAspConn->aspco_pAspAddr;
	ASSERT (VALID_ASPAO(pAspAddr));

	ASSERT ((pAspReq->asprq_Flags & ASPRQ_REPLY) || !ATALK_SUCCESS(ErrorCode));

	if (!NT_SUCCESS(ErrorCode))
	{
		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_WARN,
				("atalkAspReplyRelease: Failure %ld\n", ErrorCode));
		Status = AtalkErrorToNtStatus(ErrorCode);
	}

	// We complete here
	(*pAspAddr->aspao_ClientEntries.clt_ReplyCompletion)(Status,
														 pAspConn->aspco_ConnContext,
														 pAspReq->asprq_Request.rq_ReplyMdl);

	// Based on whether a reply was actually sent or not, either deref the response
	// or cancel it.
	if (pAspReq->asprq_Flags & ASPRQ_REPLY)
	{
		AtalkAtpRespDereference(pAspReq->asprq_pAtpResp);
	}
	else
	{
		AtalkAtpCancelResp(pAspReq->asprq_pAtpResp);
	}

	// Free this as we are done with this request now
	ACQUIRE_SPIN_LOCK(&pAspConn->aspco_Lock, &OldIrql);

	if (pAspConn->aspco_pFreeReqs == NULL)
	{
		pAspReq->asprq_Next = NULL;
		pAspConn->aspco_pFreeReqs = pAspReq;
	}
	else AtalkBPFreeBlock(pAspReq);

	RELEASE_SPIN_LOCK(&pAspConn->aspco_Lock, OldIrql);

	// We are done with this request.
	AtalkAspDereferenceConn(pAspConn);
}




LOCAL VOID
atalkAspWriteContinueResp(
	IN	ATALK_ERROR		ErrorCode,
	IN	PASP_REQUEST	pAspReq,
	IN	PAMDL			pReqAMdl,
	IN	PAMDL			pRespAMdl,
	IN	USHORT			RespSize,
	IN	PBYTE			RespUserBytes
	)
/*++

Routine Description:

 	Handler for incoming write continue response.

Arguments:


Return Value:


--*/
{
	PASP_CONNOBJ	pAspConn;
	PASP_ADDROBJ	pAspAddr;
	NTSTATUS		Status;

	ASSERT (VALID_ASPRQ(pAspReq));

	ASSERT(pAspReq->asprq_Flags & ASPRQ_WRTCONT);

	pAspConn = pAspReq->asprq_pAspConn;
	ASSERT(VALID_ASPCO(pAspConn));

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("atalkAspWriteContinueResp: Entered for request %lx\n", pAspReq));

	pAspAddr = pAspConn->aspco_pAspAddr;
	ASSERT(VALID_ASPAO(pAspAddr));

	pAspReq->asprq_Flags &= ~ASPRQ_WRTCONT;

	if (ATALK_SUCCESS(ErrorCode))
	{
		pAspConn->aspco_RT.rt_New = AtalkGetCurrentTick() - pAspConn->aspco_RT.rt_New;
	
		// Estimate the retry interval for next time.
		AtalkCalculateNewRT(&pAspConn->aspco_RT);
		Status = STATUS_SUCCESS;
	}
	else
	{
		PASP_REQUEST *	ppAspReq;

		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
				("atalkAspWriteContinueResp: Failure %ld\n", ErrorCode));

		// In case the writecontinue returned an error, this request needs to go away
		// since there will never be a call to AtalkAspReply(). Also deref the conn.
		ACQUIRE_SPIN_LOCK_DPC(&pAspConn->aspco_Lock);
	
		for (ppAspReq = &pAspConn->aspco_pActiveReqs;
			 *ppAspReq != NULL;
			 ppAspReq = &(*ppAspReq)->asprq_Next)
		{
			if (pAspReq == *ppAspReq)
			{
				*ppAspReq = pAspReq->asprq_Next;
				pAspConn->aspco_cReqsInProcess --;
				break;
			}
		}

		ASSERT (*ppAspReq == pAspReq->asprq_Next);
	
		RELEASE_SPIN_LOCK_DPC(&pAspConn->aspco_Lock);

		AtalkAspDereferenceConn(pAspConn);

		// Cancel the response for the original request that caused wrtcont to be posted
		AtalkAtpCancelResp(pAspReq->asprq_pAtpResp);

		Status = AtalkErrorToNtStatus(ErrorCode);
	}

#ifdef	PROFILING
	{
		KIRQL	OldIrql;

		ACQUIRE_SPIN_LOCK(&AtalkStatsLock, &OldIrql);

		AtalkStatistics.stat_LastAspRTT = (ULONG)(pAspConn->aspco_RT.rt_Base);
		if ((ULONG)(pAspConn->aspco_RT.rt_Base) > AtalkStatistics.stat_MaxAspRTT)
			AtalkStatistics.stat_MaxAspRTT = (ULONG)(pAspConn->aspco_RT.rt_Base);

		RELEASE_SPIN_LOCK(&AtalkStatsLock, OldIrql);
	}
#endif
	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("atalkAspWriteContinueResp: Indicating request %lx\n", pAspReq));

	// Notify the client that it has a request to handle
	(*pAspAddr->aspao_ClientEntries.clt_RequestNotify)(Status,
													   pAspConn->aspco_ConnContext,
													   &pAspReq->asprq_Request);

	ASSERT (pReqAMdl != NULL);
	ASSERT (AtalkGetAddressFromMdl(pReqAMdl) == pAspReq->asprq_WrtContRespBuf);
	ASSERT (AtalkSizeMdlChain(pReqAMdl) == ASP_WRITE_DATA_SIZE);

	AtalkFreeAMdl(pReqAMdl);

	if (!ATALK_SUCCESS(ErrorCode))
	{
		// Free this request as well
		AtalkBPFreeBlock(pAspReq);
	}
}




LOCAL VOID
atalkAspSendAttentionResp(
	IN	ATALK_ERROR		ErrorCode,
	IN	PVOID			pContext,
	IN	PAMDL			pReqAMdl,
	IN	PAMDL			pRespAMdl,
	IN	USHORT			RespSize,
	IN	PBYTE			RespUserBytes
	)
/*++

Routine Description:

 	Handler for incoming write continue response.

Arguments:


Return Value:


--*/
{
	PBYTE			pBuf;
	PASP_CONNOBJ	pAspConn = (PASP_CONNOBJ)pContext;

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("atalkAspSendAttentionResp: Entered for conn %lx\n", pAspConn));

	if (!ATALK_SUCCESS(ErrorCode))
	{
		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
				("atalkAspSendAttentionResp: Failure %ld\n", ErrorCode));
	}

	if (pRespAMdl != NULL)
	{
		pBuf = AtalkGetAddressFromMdl(pRespAMdl);
		ASSERT (pBuf != NULL);
		AtalkFreeMemory(pBuf);
		AtalkFreeAMdl(pRespAMdl);
	}

	// Call the completion routine
	(*pAspConn->aspco_pAspAddr->aspao_ClientEntries.clt_AttnCompletion)(pAspConn->aspco_AttentionContext);

	pAspConn->aspco_AttentionContext = NULL;

	// Finally Dereference the connection
	AtalkAspDereferenceConn(pAspConn);
}



LOCAL LONG FASTCALL
atalkAspSessionMaintenanceTimer(
	IN	PTIMERLIST	pTimer,
	IN	BOOLEAN		TimerShuttingDown
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PASP_CONNOBJ	pAspConn, pAspConnNext;
    PASP_CONN_MAINT	pAspCM;
	BOOLEAN			Close = FALSE;
	LONG			CurrentTick =  AtalkGetCurrentTick();
#ifdef	PROFILING
	TIME			TimeS, TimeE, TimeD;

	TimeS = KeQueryPerformanceCounter(NULL);
#endif

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("atalkAspSessionMaintenanceTimer: Entered\n"));

	if (TimerShuttingDown)
		return ATALK_TIMER_NO_REQUEUE;

	pAspCM = CONTAINING_RECORD(pTimer, ASP_CONN_MAINT, ascm_SMTTimer);

	ACQUIRE_SPIN_LOCK_DPC(&atalkAspLock);

	// Walk the list of sessions on the global list and shut down
	// sessions that have not tickle'd for a while
	for (pAspConn = pAspCM->ascm_ConnList; pAspConn != NULL; pAspConn = pAspConnNext)
	{
		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
				("atalkAspSessionMaintenanceTimer: Checking out session %d from %x.%x\n",
				pAspConn->aspco_SessionId,
				pAspConn->aspco_WssRemoteAddr.ata_Network,
				pAspConn->aspco_WssRemoteAddr.ata_Node));

		pAspConnNext = pAspConn->aspco_NextSession;

		Close = FALSE;
		ASSERT (VALID_ASPCO(pAspConn));

		ACQUIRE_SPIN_LOCK_DPC(&pAspConn->aspco_Lock);

		if ((pAspConn->aspco_Flags & ASPCO_ACTIVE)	&&
			((CurrentTick - pAspConn->aspco_LastContactTime) > ASP_MAX_SESSION_IDLE_TIME))
		{
			pAspConn->aspco_Flags |= (ASPCO_REMOTE_CLOSE | ASPCO_DROPPED);
			pAspConn->aspco_Flags &= ~ASPCO_ACTIVE;
			pAspConn->aspco_RefCount ++;	// Since atalkAspSessionClose Derefs it
			Close = TRUE;
		}

		RELEASE_SPIN_LOCK_DPC(&pAspConn->aspco_Lock);

		if (Close)
		{
			PASP_ADDROBJ	pAspAddr;
			ATALK_ERROR		Status;

#ifdef	PROFILING
			INTERLOCKED_INCREMENT_LONG_DPC(&AtalkStatistics.stat_AspSessionsDropped,
										   &AtalkStatsLock.SpinLock);
#endif
			pAspAddr = pAspConn->aspco_pAspAddr;
			ASSERT (VALID_ASPAO(pAspAddr));

			DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
					("atalkAspSessionMaintenanceTimer: Shutting down session %d from %x.%x\n",
					pAspConn->aspco_SessionId,
					pAspConn->aspco_WssRemoteAddr.ata_Network,
					pAspConn->aspco_WssRemoteAddr.ata_Node));

			RELEASE_SPIN_LOCK_DPC(&atalkAspLock);

			// This session is being punted. Cancel tickles on this and notify the
			// server that this session is history.
			Status = AtalkAtpCancelReq(pAspAddr->aspao_pSlsAtpAddr,
									   pAspConn->aspco_TickleXactId,
									   &pAspConn->aspco_WssRemoteAddr);
			if (!ATALK_SUCCESS(Status))
			{
				DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
						("atalkAspSessionMaintenanceTimer: AtalkAtpCancelReq %ld\n", Status));
			}

			// Shut down this session, well almost ...
			atalkAspSessionClose(pAspConn);

			ACQUIRE_SPIN_LOCK_DPC(&atalkAspLock);
			pAspConnNext = pAspCM->ascm_ConnList;
		}
	}

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

	INTERLOCKED_ADD_LARGE_INTGR_DPC(&AtalkStatistics.stat_AspSmtProcessTime,
									TimeD,
									&AtalkStatsLock.SpinLock);

	INTERLOCKED_INCREMENT_LONG_DPC( &AtalkStatistics.stat_AspSmtCount,
									&AtalkStatsLock.SpinLock);
#endif

	RELEASE_SPIN_LOCK_DPC(&atalkAspLock);

	// Reschedule ourselves
	return ATALK_TIMER_REQUEUE;
}




LOCAL VOID
atalkAspSessionClose(
	IN	PASP_CONNOBJ	pAspConn
	)
/*++

Routine Description:

	This should be called with a reference to the connection which is Dereferenced
	here.

Arguments:


Return Value:


--*/
{
	PASP_REQUEST	pAspReq, pAspReqNext;
	PASP_ADDROBJ	pAspAddr = pAspConn->aspco_pAspAddr;
	REQUEST			Request;
	KIRQL			OldIrql;
	NTSTATUS		Status = STATUS_REMOTE_DISCONNECT;

	ACQUIRE_SPIN_LOCK(&pAspConn->aspco_Lock, &OldIrql);
   	pAspConn->aspco_Flags &= ~ASPCO_ACTIVE;

	// Cancel any Write-continues pending. Do not bother cancelling
	// replies as they will time out anyway. Also atalkAspReplyRelease()
	// will attempt to acquire the connection lock and we're already
	// holding it
	for (pAspReq = pAspConn->aspco_pActiveReqs;
		 pAspReq != NULL;
		 pAspReq = pAspReqNext)
	{
	    pAspReqNext = pAspReq->asprq_Next;
		if ((pAspReq->asprq_Flags & (ASPRQ_WRTCONT | ASPRQ_WRTCONT_CANCELLED)) == ASPRQ_WRTCONT)
		{
			pAspReq->asprq_Flags |= ASPRQ_WRTCONT_CANCELLED;

			RELEASE_SPIN_LOCK(&pAspConn->aspco_Lock, OldIrql);

			AtalkAtpCancelReq(pAspAddr->aspao_pSssAtpAddr,
							  pAspReq->asprq_WCXactId,
							  &pAspConn->aspco_WssRemoteAddr);

			ACQUIRE_SPIN_LOCK(&pAspConn->aspco_Lock, &OldIrql);
			pAspReqNext = pAspConn->aspco_pActiveReqs;
		}
	}

	RELEASE_SPIN_LOCK(&pAspConn->aspco_Lock, OldIrql);

	if (pAspConn->aspco_Flags & ASPCO_DROPPED)
	{
		Status = STATUS_LOCAL_DISCONNECT;
	}

	// Indicate a request with an error to indicate that the session closed remotely.
	// Pass the remote address of the client so that the server can log an event if
	// the session did not shutdown gracefully.

	Request.rq_RequestSize = (LONG)(pAspConn->aspco_WssRemoteAddr.ata_Address);
	Request.rq_RequestBuf = NULL;
	Request.rq_WriteMdl = NULL;
	(*pAspAddr->aspao_ClientEntries.clt_RequestNotify)(Status,
													   pAspConn->aspco_ConnContext,
													   &Request);

	// Finally Dereference the session
	AtalkAspDereferenceConn(pAspConn);
}


LOCAL VOID
atalkAspReturnResp(
	IN	PATP_RESP		pAtpResp,
	IN	PATALK_ADDR		pDstAddr,
	IN	BYTE			Byte0,
	IN	BYTE			Byte1,
	IN	USHORT			Word2
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	BYTE		UserBytes[ATP_USERBYTES_SIZE];
	ATALK_ERROR	Status;

	UserBytes[0] = Byte0;
	UserBytes[1] = Byte1;
	PUTSHORT2SHORT(UserBytes+2, Word2);

	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("atalkAspReturnResp: For Resp %lx\n", pAtpResp));

	Status = AtalkAtpPostResp(pAtpResp,
							  pDstAddr,
							  NULL,
							  0,
							  UserBytes,
							  AtalkAtpGenericRespComplete,
							  pAtpResp);
	if (!ATALK_SUCCESS(Status))
	{
		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
				("atalkAspReturnResp: AtalkAtpPostResp failed %ld\n", Status));
		AtalkAtpGenericRespComplete(Status, pAtpResp);
	}
}




LOCAL VOID FASTCALL
atalkAspRespComplete(
	IN	ATALK_ERROR				ErrorCode,
	IN	PASP_POSTSTAT_CTX		pStsCtx
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_INFO,
			("atalkAspRespComplete: Entered pStsCtx %lx\n", pStsCtx));

	if (!ATALK_SUCCESS(ErrorCode))
	{
		DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
				("atalkAspRespComplete: Failed %ld, pStsCtx %lx\n", ErrorCode, pStsCtx));
	}

	if (pStsCtx != NULL)
	{
		AtalkFreeAMdl(pStsCtx->aps_pAMdl);
		AtalkAtpRespDereferenceDpc(pStsCtx->aps_pAtpResp);
		AtalkFreeMemory(pStsCtx);
	}
}




LOCAL VOID
atalkAspCloseComplete(
	IN	ATALK_ERROR		Status,
	IN	PASP_ADDROBJ	pAspAddr
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	DBGPRINT(DBG_COMP_ASP, DBG_LEVEL_ERR,
			("atalkAspCloseComplete: AtalkAtpCloseAddr returned %ld\n",  Status));
	AtalkAspDereferenceAddr(pAspAddr);
}


#if	DBG

VOID
AtalkAspDumpSessions(
	VOID
)
{
	PASP_CONNOBJ	pAspConn;
	KIRQL			OldIrql;
	LONG			i;

	DBGPRINT(DBG_COMP_DUMP, DBG_LEVEL_FATAL, ("ASP SESSION LIST:\n"));

	ACQUIRE_SPIN_LOCK(&atalkAspLock, &OldIrql);

	for (i = 0; i < NUM_ASP_CONN_LISTS; i++)
	{
		for (pAspConn = atalkAspConnMaint[i].ascm_ConnList;
			 pAspConn != NULL;
			 pAspConn = pAspConn->aspco_NextSession)
		{
			DBGPRINT(DBG_COMP_DUMP, DBG_LEVEL_FATAL,
					("\tRemote Addr %4d.%3d.%2d SessionId %2d Flags %4x RefCount %ld\n",
					pAspConn->aspco_WssRemoteAddr.ata_Network,
					pAspConn->aspco_WssRemoteAddr.ata_Node,
					pAspConn->aspco_WssRemoteAddr.ata_Socket,
					pAspConn->aspco_SessionId,
					pAspConn->aspco_Flags,
					pAspConn->aspco_RefCount));
		}
	}
	RELEASE_SPIN_LOCK(&atalkAspLock, OldIrql);
}

#endif

