/*++

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


//----------------------------------------------------------------------------