mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1086 lines
35 KiB
1086 lines
35 KiB
/*++
|
|
|
|
Copyright (c) 2000-2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
Connect.c
|
|
|
|
Abstract:
|
|
|
|
This module implements Connect handling routines
|
|
for the PGM Transport
|
|
|
|
Author:
|
|
|
|
Mohammad Shabbir Alam (MAlam) 3-30-2000
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
#include "precomp.h"
|
|
#include <tcpinfo.h> // for AO_OPTION_xxx, TCPSocketOption
|
|
|
|
|
|
//******************* Pageable Routine Declarations ****************
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, PgmCreateConnection)
|
|
#endif
|
|
//******************* Pageable Routine Declarations ****************
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
PgmCreateConnection(
|
|
IN tPGM_DEVICE *pPgmDevice,
|
|
IN PIRP pIrp,
|
|
IN PIO_STACK_LOCATION pIrpSp,
|
|
IN PFILE_FULL_EA_INFORMATION TargetEA
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to create a connection context for the client
|
|
At this time, we do not knwo which address which connection will
|
|
be associated with, nor do we know whether it will be a sender
|
|
or a receiver.
|
|
|
|
Arguments:
|
|
|
|
IN pPgmDevice -- Pgm's Device object context
|
|
IN pIrp -- Client's request Irp
|
|
IN pIrpSp -- current request's stack pointer
|
|
IN TargetEA -- contains the client's Connect context
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Final status of the set event operation
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
CONNECTION_CONTEXT ConnectionContext;
|
|
tCOMMON_SESSION_CONTEXT *pSession = NULL;
|
|
|
|
PAGED_CODE();
|
|
|
|
if (TargetEA->EaValueLength < sizeof(CONNECTION_CONTEXT))
|
|
{
|
|
PgmLog (PGM_LOG_ERROR, DBG_CONNECT, "PgmCreateConnection",
|
|
"(BufferLength=%d < Min=%d)\n", TargetEA->EaValueLength, sizeof(CONNECTION_CONTEXT));
|
|
return (STATUS_INVALID_ADDRESS_COMPONENT);
|
|
}
|
|
|
|
if (!(pSession = PgmAllocMem (sizeof(tCOMMON_SESSION_CONTEXT), PGM_TAG('0'))))
|
|
{
|
|
PgmLog (PGM_LOG_ERROR, DBG_CONNECT, "PgmCreateConnection",
|
|
"STATUS_INSUFFICIENT_RESOURCES, Requested <%d> bytes\n", sizeof(tCOMMON_SESSION_CONTEXT));
|
|
return (STATUS_INSUFFICIENT_RESOURCES);
|
|
}
|
|
PgmZeroMemory (pSession, sizeof (tCOMMON_SESSION_CONTEXT));
|
|
|
|
InitializeListHead (&pSession->Linkage);
|
|
PgmInitLock (pSession, SESSION_LOCK);
|
|
pSession->Verify = PGM_VERIFY_SESSION_UNASSOCIATED;
|
|
PGM_REFERENCE_SESSION_UNASSOCIATED (pSession, REF_SESSION_CREATE, TRUE);
|
|
pSession->Process = (PEPROCESS) PsGetCurrentProcess();
|
|
|
|
// the connection context value is stored in the string just after the
|
|
// name "connectionContext", and it is most likely unaligned, so just
|
|
// copy it out.( 4 bytes of copying ).
|
|
PgmCopyMemory (&pSession->ClientSessionContext,
|
|
(CONNECTION_CONTEXT) &TargetEA->EaName[TargetEA->EaNameLength+1],
|
|
sizeof (CONNECTION_CONTEXT));
|
|
|
|
PgmLog (PGM_LOG_INFORM_STATUS, DBG_CONNECT, "PgmCreateConnection",
|
|
"pSession=<%x>, ConnectionContext=<%x>\n",
|
|
pSession, * ((PVOID UNALIGNED *) &TargetEA->EaName[TargetEA->EaNameLength+1]));
|
|
|
|
// link on to list of open connections for this device so that we
|
|
// know how many open connections there are at any time (if we need to know)
|
|
// This linkage is only in place until the client does an associate, then
|
|
// the connection is unlinked from here and linked to the client ConnectHead.
|
|
//
|
|
PgmInterlockedInsertTailList (&PgmDynamicConfig.ConnectionsCreated, &pSession->Linkage,&PgmDynamicConfig);
|
|
|
|
pIrpSp->FileObject->FsContext = pSession;
|
|
pIrpSp->FileObject->FsContext2 = (PVOID) TDI_CONNECTION_FILE;
|
|
|
|
return (STATUS_SUCCESS);
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
VOID
|
|
PgmCleanupSession(
|
|
IN tCOMMON_SESSION_CONTEXT *pSession,
|
|
IN PVOID Unused1,
|
|
IN PVOID Unused2
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the cleanup operation for a session
|
|
handle once the RefCount goes to 0. It is called after a
|
|
cleanup has been requested on this handle
|
|
This routine has to be called at Passive Irql since we will
|
|
need to do some file/section handle manipulation here.
|
|
|
|
Arguments:
|
|
|
|
IN pSession -- the session object to be cleaned up
|
|
|
|
Return Value:
|
|
|
|
NONE
|
|
|
|
--*/
|
|
{
|
|
PIRP pIrpCleanup;
|
|
PGMLockHandle OldIrq;
|
|
|
|
PgmLog (PGM_LOG_INFORM_STATUS, DBG_CONNECT, "PgmCleanupSession",
|
|
"Cleanup Session=<%x>\n", pSession);
|
|
|
|
if ((pSession->SessionFlags & PGM_SESSION_FLAG_SENDER) &&
|
|
(pSession->pSender->SendDataBufferMapping))
|
|
{
|
|
PgmUnmapAndCloseDataFile (pSession);
|
|
pSession->pSender->SendDataBufferMapping = NULL;
|
|
}
|
|
|
|
PgmLock (pSession, OldIrq);
|
|
pSession->Process = NULL; // To remember that we have cleanedup
|
|
|
|
if (pIrpCleanup = pSession->pIrpCleanup)
|
|
{
|
|
pSession->pIrpCleanup = NULL;
|
|
PgmUnlock (pSession, OldIrq);
|
|
PgmIoComplete (pIrpCleanup, STATUS_SUCCESS, 0);
|
|
}
|
|
else
|
|
{
|
|
PgmUnlock (pSession, OldIrq);
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
VOID
|
|
PgmDereferenceSessionCommon(
|
|
IN tCOMMON_SESSION_CONTEXT *pSession,
|
|
IN ULONG SessionType,
|
|
IN ULONG RefContext
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called as a result of a dereference on a session object
|
|
|
|
Arguments:
|
|
|
|
IN pSession -- the Session object
|
|
IN SessionType -- basically, whether it is PGM_VERIFY_SESSION_SEND
|
|
or PGM_VERIFY_SESSION_RECEIVE
|
|
IN RefContext -- the context for which this session object was
|
|
referenced earlier
|
|
|
|
Return Value:
|
|
|
|
NONE
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
PGMLockHandle OldIrq;
|
|
PIRP pIrpCleanup;
|
|
LIST_ENTRY *pEntry;
|
|
tNAK_FORWARD_DATA *pNak;
|
|
|
|
PgmLock (pSession, OldIrq);
|
|
|
|
ASSERT (PGM_VERIFY_HANDLE2 (pSession, SessionType, PGM_VERIFY_SESSION_DOWN));
|
|
ASSERT (pSession->RefCount); // Check for too many derefs
|
|
ASSERT (pSession->ReferenceContexts[RefContext]--);
|
|
|
|
if (--pSession->RefCount)
|
|
{
|
|
PgmUnlock (pSession, OldIrq);
|
|
return;
|
|
}
|
|
|
|
PgmLog (PGM_LOG_INFORM_STATUS, DBG_CONNECT, "PgmDereferenceSessionCommon",
|
|
"pSession=<%x> Derefenced out, pIrpCleanup=<%x>\n", pSession, pSession->pIrpCleanup);
|
|
|
|
ASSERT (!pSession->pAssociatedAddress);
|
|
|
|
pIrpCleanup = pSession->pIrpCleanup;
|
|
//
|
|
// Sonce we may have a lot of memory buffered up, we need
|
|
// to free it at non-Dpc
|
|
//
|
|
PgmUnlock (pSession, OldIrq);
|
|
if (pSession->pReceiver)
|
|
{
|
|
CleanupPendingNaks (pSession, (PVOID) FALSE, NULL);
|
|
}
|
|
|
|
//
|
|
// Remove from the global list
|
|
//
|
|
PgmLock (&PgmDynamicConfig, OldIrq);
|
|
RemoveEntryList (&pSession->Linkage);
|
|
PgmUnlock (&PgmDynamicConfig, OldIrq);
|
|
|
|
//
|
|
// If we are currently at Dpc, we will have to close the handles
|
|
// on a Delayed Worker thread!
|
|
//
|
|
if (PgmGetCurrentIrql())
|
|
{
|
|
status = PgmQueueForDelayedExecution (PgmCleanupSession, pSession, NULL, NULL, FALSE);
|
|
if (!NT_SUCCESS (status))
|
|
{
|
|
//
|
|
// Apparently we ran out of Resources!
|
|
// Complete the cleanup Irp for now and hope that the Close
|
|
// can complete the rest of the cleanup
|
|
//
|
|
PgmLog (PGM_LOG_ERROR, DBG_CONNECT, "PgmDereferenceSessionCommon",
|
|
"OUT_OF_RSRC, cannot queue Worker request\n", pSession);
|
|
|
|
if (pIrpCleanup)
|
|
{
|
|
pSession->pIrpCleanup = NULL;
|
|
PgmIoComplete (pIrpCleanup, STATUS_SUCCESS, 0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PgmCleanupSession (pSession, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
PgmCleanupConnection(
|
|
IN tCOMMON_SESSION_CONTEXT *pSession,
|
|
IN PIRP pIrp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called as a result of a close on the client's
|
|
session handle. If we are a sender, our main job here is to
|
|
send the FIN immediately, otherwise if we are a receiver, we
|
|
need to remove ourselves from the timer list
|
|
|
|
Arguments:
|
|
|
|
IN pSession -- the Session object
|
|
IN pIrp -- Client's request Irp
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Final status of the request (STATUS_PENDING)
|
|
|
|
--*/
|
|
{
|
|
tCLIENT_SEND_REQUEST *pSendContext;
|
|
PGMLockHandle OldIrq, OldIrq1;
|
|
|
|
PgmLock (&PgmDynamicConfig, OldIrq);
|
|
PgmLock (pSession, OldIrq1);
|
|
|
|
ASSERT (PGM_VERIFY_HANDLE3 (pSession, PGM_VERIFY_SESSION_UNASSOCIATED,
|
|
PGM_VERIFY_SESSION_SEND,
|
|
PGM_VERIFY_SESSION_RECEIVE));
|
|
pSession->Verify = PGM_VERIFY_SESSION_DOWN;
|
|
|
|
//
|
|
// If Connection is currently associated, we must let the Disassociate handle
|
|
// Removing the connection from the list
|
|
//
|
|
if (pSession->pAssociatedAddress)
|
|
{
|
|
PgmLog (PGM_LOG_INFORM_STATUS, DBG_CONNECT, "PgmCleanupConnection",
|
|
"WARNING: pSession=<%x : %x> was not disassociated from pAddress=<%x : %x> earlier\n",
|
|
pSession, pSession->Verify,
|
|
pSession->pAssociatedAddress, pSession->pAssociatedAddress->Verify);
|
|
|
|
ASSERT (0);
|
|
|
|
PgmUnlock (pSession, OldIrq1);
|
|
PgmUnlock (&PgmDynamicConfig, OldIrq);
|
|
|
|
PgmDisassociateAddress (pIrp, IoGetCurrentIrpStackLocation(pIrp));
|
|
|
|
PgmLock (&PgmDynamicConfig, OldIrq);
|
|
PgmLock (pSession, OldIrq1);
|
|
}
|
|
|
|
ASSERT (!pSession->pAssociatedAddress);
|
|
|
|
//
|
|
// Remove connection from either ConnectionsCreated list
|
|
// or ConnectionsDisassociated list
|
|
//
|
|
RemoveEntryList (&pSession->Linkage);
|
|
InsertTailList (&PgmDynamicConfig.CleanedUpConnections, &pSession->Linkage);
|
|
|
|
PgmLog (PGM_LOG_INFORM_ALL_FUNCS, DBG_CONNECT, "PgmCleanupConnection",
|
|
"pSession=<%x>\n", pSession);
|
|
|
|
pSession->pIrpCleanup = pIrp;
|
|
if (pSession->SessionFlags & PGM_SESSION_ON_TIMER)
|
|
{
|
|
pSession->SessionFlags |= PGM_SESSION_TERMINATED_ABORT;
|
|
}
|
|
|
|
PgmUnlock (pSession, OldIrq1);
|
|
PgmUnlock (&PgmDynamicConfig, OldIrq);
|
|
|
|
PGM_DEREFERENCE_SESSION_UNASSOCIATED (pSession, REF_SESSION_CREATE);
|
|
|
|
return (STATUS_PENDING);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
PgmCloseConnection(
|
|
IN PIRP pIrp,
|
|
IN PIO_STACK_LOCATION pIrpSp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is the final dispatch operation to be performed
|
|
after the cleanup, which should result in the session object
|
|
being completely destroyed -- our RefCount must have already
|
|
been set to 0 when we completed the Cleanup request.
|
|
|
|
Arguments:
|
|
|
|
IN pIrp -- Client's request Irp
|
|
IN pIrpSp -- current request's stack pointer
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Final status of the operation (STATUS_SUCCESS)
|
|
|
|
--*/
|
|
{
|
|
tCOMMON_SESSION_CONTEXT *pSession = (tCOMMON_SESSION_CONTEXT *) pIrpSp->FileObject->FsContext;
|
|
|
|
PgmLog (PGM_LOG_INFORM_ALL_FUNCS, DBG_CONNECT, "PgmCloseConnection",
|
|
"pSession=<%x>\n", pSession);
|
|
|
|
ASSERT (!pSession->pIrpCleanup);
|
|
|
|
if (pSession->Process)
|
|
{
|
|
PgmLog (PGM_LOG_INFORM_STATUS, DBG_CONNECT, "PgmCloseConnection",
|
|
"WARNING! pSession=<%x>, Not yet cleaned up -- Calling cleanup again ...\n", pSession);
|
|
|
|
PgmCleanupSession (pSession, NULL, NULL);
|
|
}
|
|
|
|
pIrpSp->FileObject->FsContext = NULL;
|
|
|
|
if (pSession->FECOptions)
|
|
{
|
|
DestroyFECContext (&pSession->FECContext);
|
|
}
|
|
|
|
if (pSession->pSender)
|
|
{
|
|
ExDeleteResourceLite (&pSession->pSender->Resource); // Delete the resource
|
|
|
|
if (pSession->pSender->DataFileName.Buffer)
|
|
{
|
|
PgmFreeMem (pSession->pSender->DataFileName.Buffer);
|
|
}
|
|
|
|
if (pSession->pSender->pProActiveParityContext)
|
|
{
|
|
PgmFreeMem (pSession->pSender->pProActiveParityContext);
|
|
}
|
|
|
|
if (pSession->pSender->MaxPayloadSize)
|
|
{
|
|
ExDeleteNPagedLookasideList (&pSession->pSender->SenderBufferLookaside);
|
|
ExDeleteNPagedLookasideList (&pSession->pSender->SendContextLookaside);
|
|
}
|
|
PgmFreeMem (pSession->pSender);
|
|
}
|
|
else if (pSession->pReceiver)
|
|
{
|
|
if (pSession->pFECBuffer)
|
|
{
|
|
PgmFreeMem (pSession->pFECBuffer);
|
|
}
|
|
|
|
if (pSession->FECGroupSize)
|
|
{
|
|
ExDeleteNPagedLookasideList (&pSession->pReceiver->NonParityContextLookaside);
|
|
|
|
if (pSession->FECOptions)
|
|
{
|
|
ExDeleteNPagedLookasideList (&pSession->pReceiver->ParityContextLookaside);
|
|
}
|
|
}
|
|
|
|
PgmFreeMem (pSession->pReceiver);
|
|
}
|
|
|
|
PgmFreeMem (pSession);
|
|
|
|
//
|
|
// The final Dereference will complete the Irp!
|
|
//
|
|
return (STATUS_SUCCESS);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
NTSTATUS
|
|
PgmConnect(
|
|
IN tPGM_DEVICE *pPgmDevice,
|
|
IN PIRP pIrp,
|
|
IN PIO_STACK_LOCATION pIrpSp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to setup a connection, but since we are
|
|
doing PGM, there are no packets to be sent out. What we will
|
|
do here is to create the file + section map for buffering the
|
|
data packets, and also finalize the settings based on the default
|
|
+ user -specified settings
|
|
|
|
Arguments:
|
|
|
|
IN pPgmDevice -- Pgm's Device object context
|
|
IN pIrp -- Client's request Irp
|
|
IN pIrpSp -- current request's stack pointer
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Final status of the connect operation
|
|
|
|
--*/
|
|
{
|
|
tIPADDRESS IpAddress, OutIfAddress;
|
|
LIST_ENTRY *pEntry;
|
|
LIST_ENTRY *pEntry2;
|
|
tLOCAL_INTERFACE *pLocalInterface = NULL;
|
|
tADDRESS_ON_INTERFACE *pLocalAddress = NULL;
|
|
USHORT Port;
|
|
PGMLockHandle OldIrq;
|
|
NTSTATUS status;
|
|
ULONGLONG WindowAdvanceInMSecs;
|
|
tADDRESS_CONTEXT *pAddress = NULL;
|
|
tSEND_SESSION *pSend = (tSEND_SESSION *) pIrpSp->FileObject->FsContext;
|
|
PTDI_REQUEST_KERNEL pRequestKernel = (PTDI_REQUEST_KERNEL) &pIrpSp->Parameters;
|
|
ULONG Length, MCastTtl;
|
|
|
|
//
|
|
// Verify Minimum Buffer length!
|
|
//
|
|
if (!GetIpAddress (pRequestKernel->RequestConnectionInformation->RemoteAddress,
|
|
pRequestKernel->RequestConnectionInformation->RemoteAddressLength,
|
|
&IpAddress,
|
|
&Port))
|
|
{
|
|
PgmLog (PGM_LOG_ERROR, DBG_CONNECT, "PgmConnect",
|
|
"pSend=<%x>, Invalid Dest address!\n", pSend);
|
|
return (STATUS_INVALID_ADDRESS_COMPONENT);
|
|
}
|
|
|
|
PgmLock (&PgmDynamicConfig, OldIrq);
|
|
//
|
|
// Now, verify that the Connection handle is valid + associated!
|
|
//
|
|
if ((!PGM_VERIFY_HANDLE (pSend, PGM_VERIFY_SESSION_SEND)) ||
|
|
(!(pAddress = pSend->pAssociatedAddress)) ||
|
|
(!PGM_VERIFY_HANDLE (pAddress, PGM_VERIFY_ADDRESS)) ||
|
|
(pAddress->Flags & PGM_ADDRESS_FLAG_INVALID_OUT_IF))
|
|
{
|
|
PgmUnlock (&PgmDynamicConfig, OldIrq);
|
|
PgmLog (PGM_LOG_ERROR, DBG_CONNECT, "PgmConnect",
|
|
"BAD Handle(s), pSend=<%x>, pAddress=<%x>\n", pSend, pAddress);
|
|
|
|
return (STATUS_INVALID_HANDLE);
|
|
}
|
|
|
|
PGM_REFERENCE_ADDRESS (pAddress, REF_ADDRESS_CONNECT, FALSE);
|
|
|
|
//
|
|
// If an outgoing interface has not yet been specified by the
|
|
// client, select one ourselves
|
|
//
|
|
if (!pAddress->SenderMCastOutIf)
|
|
{
|
|
status = STATUS_ADDRESS_NOT_ASSOCIATED;
|
|
OutIfAddress = 0;
|
|
|
|
pEntry = &PgmDynamicConfig.LocalInterfacesList;
|
|
while ((pEntry = pEntry->Flink) != &PgmDynamicConfig.LocalInterfacesList)
|
|
{
|
|
pLocalInterface = CONTAINING_RECORD (pEntry, tLOCAL_INTERFACE, Linkage);
|
|
pEntry2 = &pLocalInterface->Addresses;
|
|
while ((pEntry2 = pEntry2->Flink) != &pLocalInterface->Addresses)
|
|
{
|
|
pLocalAddress = CONTAINING_RECORD (pEntry2, tADDRESS_ON_INTERFACE, Linkage);
|
|
OutIfAddress = htonl (pLocalAddress->IpAddress);
|
|
|
|
PgmUnlock (&PgmDynamicConfig, OldIrq);
|
|
status = SetSenderMCastOutIf (pAddress, OutIfAddress);
|
|
PgmLock (&PgmDynamicConfig, OldIrq);
|
|
|
|
break;
|
|
}
|
|
|
|
if (OutIfAddress)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS (status))
|
|
{
|
|
PgmUnlock (&PgmDynamicConfig, OldIrq);
|
|
PGM_DEREFERENCE_ADDRESS (pAddress, REF_ADDRESS_CONNECT);
|
|
|
|
PgmLog (PGM_LOG_ERROR, DBG_CONNECT, "PgmConnect",
|
|
"Could not bind sender to <%x>!\n", OutIfAddress);
|
|
|
|
return (STATUS_UNSUCCESSFUL);
|
|
}
|
|
}
|
|
|
|
//
|
|
// So, we found a valid address to send to
|
|
//
|
|
pSend->pSender->DestMCastIpAddress = ntohl (IpAddress);
|
|
pSend->pSender->DestMCastPort = pAddress->SenderMCastPort = ntohs (Port);
|
|
pSend->pSender->SenderMCastOutIf = pAddress->SenderMCastOutIf;
|
|
|
|
//
|
|
// Set the FEC Info (if applicable)
|
|
//
|
|
pSend->FECBlockSize = pAddress->FECBlockSize;
|
|
pSend->FECGroupSize = pAddress->FECGroupSize;
|
|
|
|
if (pAddress->FECOptions)
|
|
{
|
|
Length = sizeof(tBUILD_PARITY_CONTEXT) + pSend->FECGroupSize*sizeof(PUCHAR);
|
|
if (!(pSend->pSender->pProActiveParityContext = (tBUILD_PARITY_CONTEXT *) PgmAllocMem (Length,PGM_TAG('0'))) ||
|
|
(!NT_SUCCESS (status = CreateFECContext (&pSend->FECContext, pSend->FECGroupSize, pSend->FECBlockSize, FALSE))))
|
|
{
|
|
PgmUnlock (&PgmDynamicConfig, OldIrq);
|
|
PGM_DEREFERENCE_ADDRESS (pAddress, REF_ADDRESS_CONNECT);
|
|
|
|
PgmLog (PGM_LOG_ERROR, DBG_CONNECT, "PgmConnect",
|
|
"CreateFECContext returned <%x>, pSend=<%x>, Dest IpAddress=<%x>, Port=<%x>\n",
|
|
status, pSend, IpAddress, Port);
|
|
|
|
return (STATUS_INSUFFICIENT_RESOURCES);
|
|
}
|
|
|
|
pSend->FECOptions = pAddress->FECOptions;
|
|
pSend->FECProActivePackets = pAddress->FECProActivePackets;
|
|
pSend->pSender->SpmOptions |= PGM_OPTION_FLAG_PARITY_PRM;
|
|
pSend->pSender->LastVariableTGPacketSequenceNumber = -1;
|
|
|
|
ASSERT (!(pSend->FECProActivePackets || pSend->FECProActivePackets) ||
|
|
((pSend->FECGroupSize && pSend->FECBlockSize) &&
|
|
(pSend->FECGroupSize < pSend->FECBlockSize)));
|
|
|
|
//
|
|
// Now determine the MaxPayloadsize and buffer size
|
|
// We will also need to adjust the buffer size to avoid alignment issues
|
|
//
|
|
Length = sizeof (tPACKET_OPTIONS) + pAddress->OutIfMTU + sizeof (tPOST_PACKET_FEC_CONTEXT);
|
|
pSend->pSender->PacketBufferSize = (Length + sizeof (PVOID) - 1) & ~(sizeof (PVOID) - 1);
|
|
pSend->pSender->MaxPayloadSize = pAddress->OutIfMTU - (PGM_MAX_FEC_DATA_HEADER_LENGTH + sizeof (USHORT));
|
|
}
|
|
else
|
|
{
|
|
Length = sizeof (tPACKET_OPTIONS) + pAddress->OutIfMTU;
|
|
pSend->pSender->PacketBufferSize = (Length + sizeof (PVOID) - 1) & ~(sizeof (PVOID) - 1);
|
|
pSend->pSender->MaxPayloadSize = pAddress->OutIfMTU - PGM_MAX_DATA_HEADER_LENGTH;
|
|
}
|
|
ASSERT (pSend->pSender->MaxPayloadSize);
|
|
|
|
pSend->TSIPort = PgmDynamicConfig.SourcePort++; // Set the Global Src Port + Global Src Id
|
|
if (pSend->TSIPort == PgmDynamicConfig.SourcePort)
|
|
{
|
|
//
|
|
// We don't want the local port and remote port to be the same
|
|
// (especially for handling TSI settings on loopback case),
|
|
// so pick a different port
|
|
//
|
|
pSend->TSIPort = PgmDynamicConfig.SourcePort++;
|
|
}
|
|
|
|
//
|
|
// Now, initialize the Sender info
|
|
//
|
|
InitializeListHead (&pSend->pSender->PendingSends);
|
|
InitializeListHead (&pSend->pSender->PendingPacketizedSends);
|
|
InitializeListHead (&pSend->pSender->CompletedSendsInWindow);
|
|
InitializeListHead (&pSend->pSender->PendingRDataRequests);
|
|
InitializeListHead (&pSend->pSender->HandledRDataRequests);
|
|
|
|
MCastTtl = pAddress->MCastPacketTtl;
|
|
PgmUnlock (&PgmDynamicConfig, OldIrq);
|
|
|
|
KeInitializeEvent (&pSend->pSender->SendEvent, SynchronizationEvent, FALSE);
|
|
|
|
//
|
|
// Now, set the MCast TTL
|
|
//
|
|
status = PgmSetTcpInfo (pAddress->FileHandle,
|
|
AO_OPTION_MCASTTTL,
|
|
&MCastTtl,
|
|
sizeof (ULONG));
|
|
if (NT_SUCCESS (status))
|
|
{
|
|
//
|
|
// Set the MCast TTL for the RouterAlert handle also
|
|
//
|
|
status = PgmSetTcpInfo (pAddress->RAlertFileHandle,
|
|
AO_OPTION_MCASTTTL,
|
|
&MCastTtl,
|
|
sizeof (ULONG));
|
|
}
|
|
|
|
if (NT_SUCCESS (status))
|
|
{
|
|
status = PgmCreateDataFileAndMapSection (pSend);
|
|
if (!NT_SUCCESS (status))
|
|
{
|
|
PgmLog (PGM_LOG_ERROR, DBG_CONNECT, "PgmConnect",
|
|
"PgmCreateDataFileAndMapSection returned <%x>, pSend=<%x>, Dest IpAddress=<%x>, Port=<%x>\n",
|
|
status, pSend, IpAddress, Port);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PgmLog (PGM_LOG_ERROR, DBG_ADDRESS, "PgmConnect",
|
|
"AO_OPTION_MCASTTTL returned <%x>\n", status);
|
|
}
|
|
|
|
if (!NT_SUCCESS (status))
|
|
{
|
|
pSend->pSender->MaxPayloadSize = 0;
|
|
PGM_DEREFERENCE_ADDRESS (pAddress, REF_ADDRESS_CONNECT);
|
|
|
|
return (status);
|
|
}
|
|
|
|
PgmLock (&PgmDynamicConfig, OldIrq);
|
|
|
|
//
|
|
// Set the appropriate data parameters for the timeout routine
|
|
// If the Send rate is quite high, we may need to send more than
|
|
// 1 packet per timeout, but typically it should be low enough to
|
|
// require several timeouts
|
|
// Rate of Kb/Sec == Rate of Bytes/MilliSecs
|
|
//
|
|
ASSERT (pAddress->RateKbitsPerSec &&
|
|
(BASIC_TIMER_GRANULARITY_IN_MSECS > BITS_PER_BYTE));
|
|
if (((pAddress->RateKbitsPerSec * BASIC_TIMER_GRANULARITY_IN_MSECS) / BITS_PER_BYTE) >=
|
|
pSend->pSender->PacketBufferSize)
|
|
{
|
|
//
|
|
// We have a high Send rate, so we need to increment our window every timeout
|
|
// So, Bytes to be sent in (x) Millisecs = Rate of Bytes/Millisecs * (x)
|
|
//
|
|
pSend->pSender->SendTimeoutCount = 1;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We will set our Send timeout count based for a slow timer
|
|
// -- enough for pAddress->OutIfMTU
|
|
// So, Number of Timeouts of x millisecs before we can send pAddress->OutIfMTU:
|
|
// = Rate of Bytes/Millisecs * (x)
|
|
//
|
|
pSend->pSender->SendTimeoutCount = (pAddress->OutIfMTU +(pAddress->RateKbitsPerSec/BITS_PER_BYTE-1)) /
|
|
((pAddress->RateKbitsPerSec * BASIC_TIMER_GRANULARITY_IN_MSECS)/BITS_PER_BYTE);
|
|
if (!pSend->pSender->SendTimeoutCount)
|
|
{
|
|
ASSERT (0);
|
|
pSend->pSender->SendTimeoutCount = 1;
|
|
}
|
|
}
|
|
pSend->pSender->IncrementBytesOnSendTimeout = (ULONG) (pAddress->RateKbitsPerSec *
|
|
pSend->pSender->SendTimeoutCount *
|
|
BASIC_TIMER_GRANULARITY_IN_MSECS) /
|
|
BITS_PER_BYTE;
|
|
|
|
//
|
|
// Now, set the values for the next timeout!
|
|
//
|
|
pSend->pSender->CurrentTimeoutCount = pSend->pSender->SendTimeoutCount;
|
|
pSend->pSender->CurrentBytesSendable = pSend->pSender->IncrementBytesOnSendTimeout;
|
|
|
|
//
|
|
// Set the SPM timeouts
|
|
//
|
|
pSend->pSender->CurrentSPMTimeout = 0;
|
|
pSend->pSender->AmbientSPMTimeout = AMBIENT_SPM_TIMEOUT_IN_MSECS / BASIC_TIMER_GRANULARITY_IN_MSECS;
|
|
pSend->pSender->InitialHeartbeatSPMTimeout = INITIAL_HEARTBEAT_SPM_TIMEOUT_IN_MSECS / BASIC_TIMER_GRANULARITY_IN_MSECS;
|
|
pSend->pSender->MaxHeartbeatSPMTimeout = MAX_HEARTBEAT_SPM_TIMEOUT_IN_MSECS / BASIC_TIMER_GRANULARITY_IN_MSECS;
|
|
pSend->pSender->HeartbeatSPMTimeout = pSend->pSender->InitialHeartbeatSPMTimeout;
|
|
|
|
//
|
|
// Set the Increment window settings
|
|
//
|
|
// TimerTickCount, LastWindowAdvanceTime and LastTrailingEdgeTime should be 0
|
|
WindowAdvanceInMSecs = (((ULONGLONG)pAddress->WindowSizeInMSecs) * pAddress->WindowAdvancePercentage)/100;
|
|
pSend->pSender->WindowSizeTime = pAddress->WindowSizeInMSecs / BASIC_TIMER_GRANULARITY_IN_MSECS;
|
|
pSend->pSender->WindowAdvanceDeltaTime = WindowAdvanceInMSecs / BASIC_TIMER_GRANULARITY_IN_MSECS;
|
|
pSend->pSender->NextWindowAdvanceTime = pSend->pSender->WindowSizeTime + pSend->pSender->WindowAdvanceDeltaTime;
|
|
|
|
// Set the RData linger time!
|
|
pSend->pSender->RDataLingerTime = RDATA_LINGER_TIME_MSECS / BASIC_TIMER_GRANULARITY_IN_MSECS;
|
|
|
|
//
|
|
// Set the late Joiner settings
|
|
//
|
|
if (pAddress->LateJoinerPercentage)
|
|
{
|
|
pSend->pSender->LateJoinSequenceNumbers = (SEQ_TYPE) ((pSend->pSender->MaxPacketsInBuffer *
|
|
pAddress->LateJoinerPercentage) /
|
|
(2 * 100));
|
|
|
|
pSend->pSender->DataOptions |= PGM_OPTION_FLAG_JOIN;
|
|
pSend->pSender->DataOptionsLength += PGM_PACKET_OPT_JOIN_LENGTH;
|
|
|
|
pSend->pSender->SpmOptions |= PGM_OPTION_FLAG_JOIN;
|
|
}
|
|
|
|
// The timer will be started when the first send comes down
|
|
pSend->SessionFlags |= (PGM_SESSION_FLAG_FIRST_PACKET | PGM_SESSION_FLAG_SENDER);
|
|
|
|
#if 0
|
|
pSend->pSender->LeadingWindowOffset = pSend->pSender->TrailingWindowOffset =
|
|
(pSend->pSender->MaxDataFileSize/(pSend->pSender->PacketBufferSize*2))*pSend->pSender->PacketBufferSize;
|
|
#endif
|
|
pSend->pSender->LeadingWindowOffset = pSend->pSender->TrailingWindowOffset = 0;
|
|
|
|
PgmUnlock (&PgmDynamicConfig, OldIrq);
|
|
PGM_DEREFERENCE_ADDRESS (pAddress, REF_ADDRESS_CONNECT);
|
|
|
|
ExInitializeNPagedLookasideList (&pSend->pSender->SenderBufferLookaside,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
pAddress->OutIfMTU + sizeof (tPOST_PACKET_FEC_CONTEXT),
|
|
PGM_TAG('2'),
|
|
SENDER_BUFFER_LOOKASIDE_DEPTH);
|
|
|
|
ExInitializeNPagedLookasideList (&pSend->pSender->SendContextLookaside,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
sizeof (tCLIENT_SEND_REQUEST),
|
|
PGM_TAG('2'),
|
|
SEND_CONTEXT_LOOKASIDE_DEPTH);
|
|
|
|
PgmLog (PGM_LOG_INFORM_STATUS, DBG_CONNECT, "PgmConnect",
|
|
"DestIP=<%x>, Rate=<%d>, WinBytes=<%d>, WinMS=<%d>, SendTC=<%d>, IncBytes=<%d>, CurrentBS=<%d>\n",
|
|
(ULONG) IpAddress, (ULONG) pAddress->RateKbitsPerSec,
|
|
(ULONG) pAddress->WindowSizeInBytes, (ULONG) pAddress->WindowSizeInMSecs,
|
|
(ULONG) pSend->pSender->SendTimeoutCount, (ULONG) pSend->pSender->IncrementBytesOnSendTimeout,
|
|
(ULONG) pSend->pSender->CurrentBytesSendable);
|
|
|
|
return (STATUS_SUCCESS);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
VOID
|
|
PgmCancelDisconnectIrp(
|
|
IN PDEVICE_OBJECT DeviceContext,
|
|
IN PIRP pIrp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine handles the cancelling of a Disconnect Irp. It must
|
|
release the cancel spin lock before returning re: IoCancelIrp().
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
PIO_STACK_LOCATION pIrpSp = IoGetCurrentIrpStackLocation (pIrp);
|
|
tCOMMON_SESSION_CONTEXT *pSession = (tCOMMON_SESSION_CONTEXT *) pIrpSp->FileObject->FsContext;
|
|
PGMLockHandle OldIrq;
|
|
PIRP pIrpToComplete;
|
|
|
|
if (!PGM_VERIFY_HANDLE2 (pSession, PGM_VERIFY_SESSION_SEND, PGM_VERIFY_SESSION_RECEIVE))
|
|
{
|
|
IoReleaseCancelSpinLock (pIrp->CancelIrql);
|
|
|
|
PgmLog (PGM_LOG_ERROR, (DBG_SEND | DBG_ADDRESS | DBG_CONNECT), "PgmCancelDisconnectIrp",
|
|
"pIrp=<%x> pSession=<%x>, pAddress=<%x>\n", pIrp, pSession, pSession->pAssociatedAddress);
|
|
return;
|
|
}
|
|
|
|
PgmLock (pSession, OldIrq);
|
|
|
|
ASSERT (pIrp == pSession->pIrpDisconnect);
|
|
if ((pIrpToComplete = pSession->pIrpDisconnect) &&
|
|
(pIrpToComplete == pIrp))
|
|
{
|
|
pSession->pIrpDisconnect = NULL;
|
|
}
|
|
else
|
|
{
|
|
pIrpToComplete = NULL;
|
|
}
|
|
|
|
if (pSession->pSender)
|
|
{
|
|
pSession->pSender->DisconnectTimeInTicks = pSession->pSender->TimerTickCount;
|
|
}
|
|
|
|
//
|
|
// If we have reached here, then the Irp must already
|
|
// be in the process of being completed!
|
|
//
|
|
PgmUnlock (pSession, OldIrq);
|
|
IoReleaseCancelSpinLock (pIrp->CancelIrql);
|
|
|
|
PgmLog (PGM_LOG_INFORM_ALL_FUNCS, (DBG_SEND | DBG_ADDRESS | DBG_CONNECT), "PgmCancelDisconnectIrp",
|
|
"pIrp=<%x> was CANCELled, pIrpTpComplete=<%x>\n", pIrp, pIrpToComplete);
|
|
|
|
if (pIrpToComplete)
|
|
{
|
|
pIrp->IoStatus.Information = 0;
|
|
pIrp->IoStatus.Status = STATUS_CANCELLED;
|
|
IoCompleteRequest (pIrp, IO_NETWORK_INCREMENT);
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
PgmDisconnect(
|
|
IN tPGM_DEVICE *pPgmDevice,
|
|
IN PIRP pIrp,
|
|
IN PIO_STACK_LOCATION pIrpSp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by the client to disconnect a currently-active
|
|
session
|
|
|
|
Arguments:
|
|
|
|
IN pPgmDevice -- Pgm's Device object context
|
|
IN pIrp -- Client's request Irp
|
|
IN pIrpSp -- current request's stack pointer
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Final status of the disconnect operation
|
|
|
|
--*/
|
|
{
|
|
LIST_ENTRY PendingIrpsList;
|
|
PGMLockHandle OldIrq1, OldIrq2, OldIrq3;
|
|
PIRP pIrpReceive;
|
|
NTSTATUS Status;
|
|
LARGE_INTEGER TimeoutInMSecs;
|
|
LARGE_INTEGER *pTimeoutInMSecs;
|
|
tADDRESS_CONTEXT *pAddress = NULL;
|
|
tCOMMON_SESSION_CONTEXT *pSession = (tCOMMON_SESSION_CONTEXT *) pIrpSp->FileObject->FsContext;
|
|
PTDI_REQUEST_KERNEL_DISCONNECT pDisconnectRequest = (PTDI_REQUEST_KERNEL_CONNECT) &(pIrpSp->Parameters);
|
|
|
|
PgmLock (&PgmDynamicConfig, OldIrq1);
|
|
//
|
|
// Now, verify that the Connection handle is valid + associated!
|
|
//
|
|
if ((!PGM_VERIFY_HANDLE2 (pSession, PGM_VERIFY_SESSION_SEND, PGM_VERIFY_SESSION_RECEIVE)) ||
|
|
(!(pAddress = pSession->pAssociatedAddress)) ||
|
|
(!PGM_VERIFY_HANDLE (pAddress, PGM_VERIFY_ADDRESS)))
|
|
{
|
|
PgmLog (PGM_LOG_ERROR, DBG_CONNECT, "PgmDisconnect",
|
|
"BAD Handle(s), pSession=<%x>, pAddress=<%x>\n", pSession, pAddress);
|
|
|
|
PgmUnlock (&PgmDynamicConfig, OldIrq1);
|
|
return (STATUS_INVALID_HANDLE);
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
InitializeListHead (&PendingIrpsList);
|
|
TimeoutInMSecs.QuadPart = 0;
|
|
|
|
IoAcquireCancelSpinLock (&OldIrq2);
|
|
PgmLock (pSession, OldIrq3);
|
|
|
|
if (pSession->pReceiver)
|
|
{
|
|
//
|
|
// If we have any receive Irps pending, cancel them
|
|
//
|
|
RemovePendingIrps (pSession, &PendingIrpsList);
|
|
}
|
|
else if (pSession->pSender)
|
|
{
|
|
//
|
|
// See if there is an abortive or graceful disconnect, and
|
|
// also if there is a timeout specified.
|
|
//
|
|
if ((pDisconnectRequest->RequestFlags & TDI_DISCONNECT_ABORT) ||
|
|
(pSession->SessionFlags & PGM_SESSION_FLAG_FIRST_PACKET)) // No packets sent yet!
|
|
{
|
|
pSession->pSender->DisconnectTimeInTicks = pSession->pSender->TimerTickCount;
|
|
}
|
|
else if (NT_SUCCESS (PgmCheckSetCancelRoutine (pIrp, PgmCancelDisconnectIrp, TRUE)))
|
|
{
|
|
if ((pTimeoutInMSecs = pDisconnectRequest->RequestSpecific) &&
|
|
((pTimeoutInMSecs->LowPart != -1) || (pTimeoutInMSecs->HighPart != -1))) // Check Infinite
|
|
{
|
|
//
|
|
// NT relative timeouts are negative. Negate first to get a
|
|
// positive value to pass to the transport.
|
|
//
|
|
TimeoutInMSecs.QuadPart = -((*pTimeoutInMSecs).QuadPart);
|
|
TimeoutInMSecs = PgmConvert100nsToMilliseconds (TimeoutInMSecs);
|
|
|
|
pSession->pSender->DisconnectTimeInTicks = pSession->pSender->TimerTickCount +
|
|
TimeoutInMSecs.QuadPart /
|
|
BASIC_TIMER_GRANULARITY_IN_MSECS;
|
|
}
|
|
|
|
pSession->pIrpDisconnect = pIrp;
|
|
Status = STATUS_PENDING;
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_CANCELLED;
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS (Status))
|
|
{
|
|
pSession->SessionFlags |= PGM_SESSION_CLIENT_DISCONNECTED;
|
|
}
|
|
|
|
PgmLog (PGM_LOG_INFORM_STATUS, DBG_CONNECT, "PgmDisconnect",
|
|
"pIrp=<%x>, pSession=<%x>, pAddress=<%x>, Timeout=<%x : %x>, %s\n",
|
|
pIrp, pSession, pAddress, TimeoutInMSecs.QuadPart,
|
|
(pDisconnectRequest->RequestFlags & TDI_DISCONNECT_ABORT ? "ABORTive" : "GRACEful"));
|
|
|
|
PgmUnlock (pSession, OldIrq3);
|
|
IoReleaseCancelSpinLock (OldIrq2);
|
|
PgmUnlock (&PgmDynamicConfig, OldIrq1);
|
|
|
|
while (!IsListEmpty (&PendingIrpsList))
|
|
{
|
|
pIrpReceive = CONTAINING_RECORD (PendingIrpsList.Flink, IRP, Tail.Overlay.ListEntry);
|
|
RemoveEntryList (&pIrpReceive->Tail.Overlay.ListEntry);
|
|
|
|
PgmCancelCancelRoutine (pIrpReceive);
|
|
pIrpReceive->IoStatus.Status = STATUS_CANCELLED;
|
|
IoCompleteRequest (pIrpReceive, IO_NETWORK_INCREMENT);
|
|
}
|
|
|
|
return (Status);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
PgmSetRcvBufferLength(
|
|
IN PIRP pIrp,
|
|
IN PIO_STACK_LOCATION pIrpSp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by the client by the client to set the receive buffer length
|
|
Currently, we do not utilize this option meaningfully.
|
|
|
|
Arguments:
|
|
|
|
IN pIrp -- Client's request Irp
|
|
IN pIrpSp -- current request's stack pointer
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Final status of the operation
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
tRECEIVE_SESSION *pReceive = (tRECEIVE_SESSION *) pIrpSp->FileObject->FsContext;
|
|
tPGM_MCAST_REQUEST *pInputBuffer = (tPGM_MCAST_REQUEST *) pIrp->AssociatedIrp.SystemBuffer;
|
|
|
|
PAGED_CODE();
|
|
|
|
if (pIrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof (tPGM_MCAST_REQUEST))
|
|
{
|
|
PgmLog (PGM_LOG_ERROR, DBG_CONNECT, "PgmSetRcvBufferLength",
|
|
"Invalid BufferLength, <%d> < <%d>\n",
|
|
pIrpSp->Parameters.DeviceIoControl.InputBufferLength, sizeof (tPGM_MCAST_REQUEST));
|
|
return (STATUS_INVALID_PARAMETER);
|
|
}
|
|
|
|
if (!PGM_VERIFY_HANDLE (pReceive, PGM_VERIFY_SESSION_RECEIVE))
|
|
{
|
|
PgmLog (PGM_LOG_ERROR, DBG_CONNECT, "PgmSetRcvBufferLength",
|
|
"Invalid Handle <%x>\n", pReceive);
|
|
return (STATUS_INVALID_HANDLE);
|
|
}
|
|
|
|
pReceive->pReceiver->RcvBufferLength = pInputBuffer->RcvBufferLength;
|
|
|
|
PgmLog (PGM_LOG_INFORM_STATUS, DBG_CONNECT, "PgmSetRcvBufferLength",
|
|
"RcvBufferLength=<%d>\n", pReceive->pReceiver->RcvBufferLength);
|
|
|
|
//
|
|
// ISSUE: What else should we do here ?
|
|
//
|
|
|
|
return (STATUS_SUCCESS);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|