// Copyright (c) 1997, Microsoft Corporation, all rights reserved
//
// send.c
// RAS L2TP WAN mini-port/call-manager driver
// Send routines
//
// 01/07/97 Steve Cobb


#include "l2tpp.h"


#ifdef PSDEBUG

// List of all allocated PAYLOADSENT contexts and the lock that protects the
// list.  (for debug purposes only)
//
NDIS_SPIN_LOCK g_lockDebugPs;
LIST_ENTRY g_listDebugPs;

#endif


// Debug counts of client oddities that should not be happening.
//
ULONG g_ulSendZlbWithoutHostRoute = 0;


// Callback to add AVPs to an outgoing control message.  'PTunnel' is the
// tunnel control block.  'PVc' is the VC control block for call control
// messages or NULL for tunnel control messages.  'ulArg1', 'ulArg2', and
// 'pvArg3' are caller's arguments as passed for SendControl.  'PAvpBuffer' is
// the address of the buffer to receive the built AVPs.  '*PulAvpLength' is
// set to the length of the built AVPs.
//
typedef
VOID
(*PBUILDAVPS)(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength );


//-----------------------------------------------------------------------------
// Local prototypes (alphabetically)
//-----------------------------------------------------------------------------

USHORT
BuildAvpAch(
    IN USHORT usAttribute,
    IN BOOLEAN fMandatory,
    IN CHAR* pszValue,
    IN USHORT usValueLength,
    OUT CHAR* pAvp );

USHORT
BuildAvpAul(
    IN USHORT usAttribute,
    IN BOOLEAN fMandatory,
    IN UNALIGNED ULONG* pulValue,
    IN USHORT usValues,
    OUT CHAR* pAvp );

USHORT
BuildAvpFlag(
    IN USHORT usAttribute,
    IN BOOLEAN fMandatory,
    OUT CHAR* pAvp );

USHORT
BuildAvpUl(
    IN USHORT usAttribute,
    IN BOOLEAN fMandatory,
    IN ULONG ulValue,
    OUT CHAR* pAvp );

USHORT
BuildAvpUs(
    IN USHORT usAttribute,
    IN BOOLEAN fMandatory,
    IN USHORT usValue,
    OUT CHAR* pAvp );

USHORT
BuildAvp2UsAndAch(
    IN USHORT usAttribute,
    IN BOOLEAN fMandatory,
    IN USHORT usValue1,
    IN USHORT usValue2,
    IN CHAR* pszValue,
    IN USHORT usValueLength,
    OUT CHAR* pAvp );

VOID
BuildCdnAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength );

VOID
BuildHelloAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength );

VOID
BuildIccnAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength );

VOID
BuildIcrpAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength );

VOID
BuildIcrqAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength );

ULONG
BuildL2tpHeader(
    IN OUT CHAR* pBuffer,
    IN BOOLEAN fControl,
    IN BOOLEAN fReset,
    IN USHORT* pusTunnelId,
    IN USHORT* pusCallId,
    IN USHORT* pusNs,
    IN USHORT usNr );

VOID
BuildOccnAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength );

VOID
BuildOcrpAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength );

VOID
BuildOcrqAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength );

VOID
BuildScccnAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength );

VOID
BuildSccrpAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength );

VOID
BuildSccrqAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength );

VOID
BuildStopccnAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength );

VOID
BuildWenAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength );

VOID
CompletePayloadSent(
    IN TUNNELWORK* pWork,
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG_PTR* punpArgs );

VOID
SendControlComplete(
    IN TDIXCONTEXT* pTdix,
    IN VOID* pContext1,
    IN VOID* pContext2,
    IN CHAR* pBuffer );

VOID
SendHeaderComplete(
    IN TDIXCONTEXT* pTdix,
    IN VOID* pContext1,
    IN VOID* pContext2,
    IN CHAR* pBuffer );

VOID
SendPayloadReset(
    IN TUNNELWORK* pWork,
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG_PTR* punpArgs );

VOID
SendPayloadSeq(
    TUNNELWORK* pWork,
    TUNNELCB* pTunnel,
    VCCB* pVc,
    ULONG_PTR* punpArgs );

VOID
SendPayloadSeqComplete(
    IN TDIXCONTEXT* pTdix,
    IN VOID* pContext1,
    IN VOID* pContext2,
    IN CHAR* pBuffer );

VOID
SendPayloadUnseq(
    TUNNELWORK* pWork,
    TUNNELCB* pTunnel,
    VCCB* pVc,
    ULONG_PTR* punpArgs );

VOID
SendPayloadUnseqComplete(
    IN TDIXCONTEXT* pTdix,
    IN VOID* pContext1,
    IN VOID* pContext2,
    IN CHAR* pBuffer );

VOID
SendPayloadTimerEvent(
    IN TIMERQITEM* pItem,
    IN VOID* pContext,
    IN TIMERQEVENT event );

VOID
SendZlb(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN USHORT usNs,
    IN USHORT usNr,
    IN BOOLEAN fReset );

VOID
UpdateControlHeaderNr(
    IN CHAR* pBuffer,
    IN USHORT usNr );

VOID
UpdateHeaderLength(
    IN CHAR* pBuffer,
    IN USHORT usLength );


//-----------------------------------------------------------------------------
// Send routines
//-----------------------------------------------------------------------------

VOID
SendControl(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN USHORT usMsgType,
    IN ULONG ulBuildAvpsArg1,
    IN ULONG ulBuildAvpsArg2,
    IN PVOID pvBuildAvpsArg3,
    IN ULONG ulFlags )

    // Build and send a control message.  'PTunnel' is the tunnel control
    // block, always non-NULL.  'PVc' is the VC control block, non-NULL for
    // call connection (as opposed to tunnel connection) messages.
    // 'UsMsgType' is the message type AVP value of the message to build.
    // 'UlBuildAvpsArgX' are the arguments passed to the PBUILDAVP handler
    // associated with 'usMsgType', where the meaning depends on the specific
    // handler.  'UlFlags' is the CSF_* flag options associated with the sent
    // message context, or 0 if none.
    //
    // IMPORTANT:  Caller must hold 'pTunnel->lockT'.  If 'pVc' is non-NULL
    //             caller must also hold 'pVc->lockV'.
    //
{
    NDIS_STATUS status;
    ADAPTERCB* pAdapter;
    NDIS_BUFFER* pNdisBuffer;
    PBUILDAVPS pBuildAvpsHandler;
    TIMERQITEM* pTqiSendTimeout;
    CONTROLSENT* pCs;
    USHORT usAssignedCallId;
    ULONG ulLength;
    ULONG ulAvpLength;
    CHAR* pBuffer;

    static PBUILDAVPS apBuildAvpHandlers[ 16 ] =
    {
        BuildSccrqAvps,    // CMT_SCCRQ
        BuildSccrpAvps,    // CMT_SCCRP
        BuildScccnAvps,    // CMT_SCCCN
        BuildStopccnAvps,  // CMT_StopCCN
        NULL,              // CMT_StopCCRP (obsolete)
        BuildHelloAvps,    // CMT_Hello
        BuildOcrqAvps,     // CMT_OCRQ
        BuildOcrpAvps,     // CMT_OCRP
        BuildOccnAvps,     // CMT_OCCN
        BuildIcrqAvps,     // CMT_ICRQ
        BuildIcrpAvps,     // CMT_ICRP
        BuildIccnAvps,     // CMT_ICCN
        NULL,              // CMT_CCRQ (obsolete)
        BuildCdnAvps,      // CMT_CDN
        BuildWenAvps,      // CMT_WEN
        NULL               // CMT_SLI
    };

    TRACE( TL_V, TM_Send, ( "SendControl" ) );

    pAdapter = pTunnel->pAdapter;
    pBuffer = NULL;
    pTqiSendTimeout = NULL;
    pCs = NULL;

    do
    {
        // Get an NDIS_BUFFER to hold the control message.
        //
        pBuffer = GetBufferFromPool( &pAdapter->poolFrameBuffers );
        if (!pBuffer)
        {
            ASSERT( !"GetBfP?" );
            status = NDIS_STATUS_RESOURCES;
            break;
        }

        // Get an "unacknowledged send timeout" timer event descriptor.
        //
        pTqiSendTimeout = ALLOC_TIMERQITEM( pAdapter );
        if (!pTqiSendTimeout)
        {
            ASSERT( !"Alloc TQI?" );
            status = NDIS_STATUS_RESOURCES;
            break;
        }

        // Get a "control message sent" context.
        //
        pCs = ALLOC_CONTROLSENT( pAdapter );
        if (!pCs)
        {
            ASSERT( !"Alloc PS?" );
            status = NDIS_STATUS_RESOURCES;
            break;
        }

        status = NDIS_STATUS_SUCCESS;
    }
    while (FALSE);

    if (status != NDIS_STATUS_SUCCESS)
    {
        if (pBuffer)
        {
            FreeBufferToPool( &pAdapter->poolFrameBuffers, pBuffer, TRUE );
        }

        if (pTqiSendTimeout)
        {
            FREE_TIMERQITEM( pAdapter, pTqiSendTimeout );
        }

        // System is probably toast but try to be orderly.
        //
        ScheduleTunnelWork(
            pTunnel, NULL, FsmCloseTunnel,
            (ULONG_PTR )TRESULT_GeneralWithError,
            (ULONG_PTR )GERR_NoResources,
            0, 0, FALSE, FALSE );
        return;
    }

    // Build an L2TP control header in 'pBuffer'.  The Call-ID is 0 for tunnel
    // control messages, or peer's assigned call ID for call control messages.
    //
    usAssignedCallId = (pVc) ? pVc->usAssignedCallId : 0;
    ulLength =
        BuildL2tpHeader(
            pBuffer,
            TRUE,
            FALSE,
            &pTunnel->usAssignedTunnelId,
            &usAssignedCallId,
            &pTunnel->usNs,
            pTunnel->usNr );

    // Call the message type's "build AVPs" handler to add AVPs to the buffer
    // following the header.
    //
    ASSERT( usMsgType > 0 && usMsgType <= 16 );
    pBuildAvpsHandler = apBuildAvpHandlers[ usMsgType - 1 ];
    pBuildAvpsHandler(
        pTunnel, pVc,
        ulBuildAvpsArg1, ulBuildAvpsArg2, pvBuildAvpsArg3,
        pBuffer + ulLength, &ulAvpLength );
    ulLength += ulAvpLength;
    UpdateHeaderLength( pBuffer, (USHORT )ulLength );

    // Pare down the frame buffer to the actual length used.
    //
    pNdisBuffer = NdisBufferFromBuffer( pBuffer );
    NdisAdjustBufferLength( pNdisBuffer, (UINT )ulLength );

    // Set up the "control message sent" context with the information needed
    // to send the message and track it's progress through retransmissions.
    //
    pCs->lRef = 0;
    pCs->usNs = pTunnel->usNs;
    pCs->usMsgType = usMsgType;
    TimerQInitializeItem( pTqiSendTimeout );
    pCs->pTqiSendTimeout = pTqiSendTimeout;
    pCs->ulRetransmits = 0;
    pCs->pBuffer = pBuffer;
    pCs->ulBufferLength = ulLength;
    pCs->pTunnel = pTunnel;
    pCs->pVc = pVc;
    pCs->ulFlags = ulFlags | CSF_Pending;
    pCs->pIrp = NULL;

    // Bump the 'Next Send' counter since this message has been assigned the
    // current value.
    //
    ++pTunnel->usNs;

    // Take a reference that is removed when the context is removed from the
    // "outstanding send" list.  Take a VC and tunnel reference that is
    // removed when the context is freed.
    //
    ReferenceControlSent( pCs );
    ReferenceTunnel( pTunnel, FALSE );

    if (pCs->pVc)
    {
        ReferenceVc( pCs->pVc );
    }

    // Queue the context as "active" with transmission pending in 'Next Sent'
    // sort order, i.e. at the tail.
    //
    InsertTailList( &pTunnel->listSendsOut, &pCs->linkSendsOut );

    // See if the send window allows it to go now.
    //
    ScheduleTunnelWork(
        pTunnel, NULL, SendPending,
        0, 0, 0, 0, FALSE, FALSE );
}


VOID
SendPending(
    IN TUNNELWORK* pWork,
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG_PTR* punpArgs )

    // A PTUNNELWORK routine to try to send pending messages from the
    // "outstanding send" list until the send window is full.
    //
    // This routine is called only at PASSIVE IRQL.
    //
{
    NDIS_STATUS status;
    ADAPTERCB* pAdapter;
    LIST_ENTRY* pLink;
    CONTROLSENT* pCs;
    ULONG ulFlags;

    TRACE( TL_N, TM_Send, ( "SendPending(sout=%d,sw=%d)",
        pTunnel->ulSendsOut, pTunnel->ulSendWindow ) );

    // Unpack context information then free the work item.
    //
    pAdapter = pTunnel->pAdapter;
    FREE_TUNNELWORK( pAdapter, pWork );

    NdisAcquireSpinLock( &pTunnel->lockT );
    {
        for (;;)
        {
            if (pTunnel->ulSendsOut >= pTunnel->ulSendWindow)
            {
                // The send window is closed.
                //
                break;
            }

            // Scan the "outstanding send" queue for the next send context
            // pending transmission.  Can't save our place for the next
            // iteration because the lock must be released and re-acquired
            // below to send the packet.
            //
            for (pLink = pTunnel->listSendsOut.Flink;
                 pLink != &pTunnel->listSendsOut;
                 pLink = pLink->Flink)
            {
                pCs = CONTAINING_RECORD( pLink, CONTROLSENT, linkSendsOut );
                if (pCs->ulFlags & CSF_Pending)
                {
                    break;
                }
            }

            if (pLink == &pTunnel->listSendsOut)
            {
                // There is nothing pending.
                //
                break;
            }

            // The send window is open and a pending send has been found.
            // Mark the context "not pending" and close the window by one to
            // account for the coming send.
            //
            ulFlags = pCs->ulFlags;
            pCs->ulFlags &= ~(CSF_Pending | CSF_QueryMediaSpeed);
            ++pTunnel->ulSendsOut;

            // Cancel any pending delayed acknowledge timeout, because the
            // acknowledge will piggyback on this packet.
            //
            if (pTunnel->pTqiDelayedAck)
            {
                TimerQCancelItem( pTunnel->pTimerQ, pTunnel->pTqiDelayedAck );
                pTunnel->pTqiDelayedAck = NULL;
            }

            if (pCs->ulRetransmits == 0)
            {
                LARGE_INTEGER lrgTime;

                // This is the original send so note the time sent.
                //
                NdisGetCurrentSystemTime( &lrgTime );
                pCs->llTimeSent = lrgTime.QuadPart;
            }
            else
            {
                // In the retransmission, the 'Next Send' is the same as the
                // original, but the 'Next Receive' field is updated.
                //
                UpdateControlHeaderNr( pCs->pBuffer, pTunnel->usNr );
            }

            // Take a reference that will be removed in the send completion
            // routine.
            //
            ReferenceControlSent( pCs );

            TRACE( TL_A, TM_CMsg, ( "%sSEND(%d) %s, +sout=%d, to=%d",
                ((g_ulTraceLevel <= TL_I) ? "" : "\nL2TP: "),
                pCs->ulRetransmits,
                MsgTypePszFromUs( pCs->usMsgType ),
                pTunnel->ulSendsOut,
                pTunnel->ulSendTimeoutMs ) );
            DUMPW( TL_A, TM_MDmp, pCs->pBuffer, pCs->ulBufferLength );

            NdisReleaseSpinLock( &pTunnel->lockT );

            // query media speed if necessary
            if(ulFlags & CSF_QueryMediaSpeed)
            {
                TdixGetInterfaceInfo(&pAdapter->tdix, 
                                     pTunnel->myaddress.ulIpAddress, 
                                     &pTunnel->ulMediaSpeed);
            }

            {
                FILE_OBJECT* FileObj;
                PTDIX_SEND_HANDLER SendFunc;

                // Call TDI to send the control message.
                //
                if (ReadFlags(&pTunnel->ulFlags) & TCBF_SendConnected) {

                    ASSERT(pTunnel->pRoute != NULL);

                    FileObj = 
                        CtrlObjFromUdpContext(&pTunnel->udpContext);
                    SendFunc = TdixSend;
                } else {
                    FileObj = NULL;
                    SendFunc = TdixSendDatagram;
                }

                status = SendFunc(&pAdapter->tdix,
                                  FileObj,
                                  SendControlComplete,
                                  pCs,
                                  NULL,
                                  &pTunnel->address,
                                  pCs->pBuffer,
                                  pCs->ulBufferLength,
                                  &pCs->pIrp );

                ASSERT( status == NDIS_STATUS_PENDING );
            }
            NdisAcquireSpinLock( &pTunnel->lockT );
        }
    }
    NdisReleaseSpinLock( &pTunnel->lockT );
}


VOID
SendPayload(
    IN VCCB* pVc,
    IN NDIS_PACKET* pPacket )

    // Sends payload packet 'pPacket' on VC 'pVc' eventually calling
    // NdisMCoSendComplete with the result.
    //
    // IMPORTANT: Caller must not hold any locks.
    //
{
    NDIS_STATUS status;
    TUNNELCB* pTunnel;
    ADAPTERCB* pAdapter;
    CHAR* pBuffer;

    TRACE( TL_V, TM_Send, ( "SendPayload" ) );

    pAdapter = pVc->pAdapter;
    pTunnel = pVc->pTunnel;
    status = NDIS_STATUS_SUCCESS;

    if (pTunnel)
    {
        if (ReadFlags( &pTunnel->ulFlags ) & TCBF_HostRouteAdded)
        {
            // Take a reference on the call.  For unsequenced sends, this is
            // released when the TdixSendDatagram completes.  For sequenced
            // sends, it is released when the PAYLOADSENT context is freed.
            //
            if (ReferenceCall( pVc ))
            {
                // Get an NDIS_BUFFER to hold the L2TP header that will be
                // tacked onto the front of NDISWAN's PPP-framed data packet.
                //
                pBuffer = GetBufferFromPool( &pAdapter->poolHeaderBuffers );
                if (!pBuffer)
                {
                    ASSERT( !"GetBfP?" );
                    DereferenceCall( pVc );
                    status = NDIS_STATUS_RESOURCES;
                }
            }
            else
            {
                TRACE( TL_A, TM_Send, ( "Send on inactive $%p", pVc ) );
                status = NDIS_STATUS_FAILURE;
            }
        }
        else
        {
            TRACE( TL_A, TM_Send, ( "SendPayload w/o host route?" ) );
            status = NDIS_STATUS_FAILURE;
        }
    }
    else
    {
        TRACE( TL_A, TM_Send, ( "Send $%p w/o pT?", pVc ) );
        status = NDIS_STATUS_FAILURE;
    }

    if (status != NDIS_STATUS_SUCCESS)
    {
        NDIS_SET_PACKET_STATUS( pPacket, status );
        TRACE( TL_A, TM_Send, ( "NdisMCoSendComp($%x)", status ) );
        NdisMCoSendComplete( status, pVc->NdisVcHandle, pPacket );
        TRACE( TL_N, TM_Send, ( "NdisMCoSendComp done" ) );
        return;
    }

    if (ReadFlags( &pVc->ulFlags ) & VCBF_Sequencing)
    {
         ScheduleTunnelWork(
             pTunnel, pVc, SendPayloadSeq,
             (ULONG_PTR )pPacket, (ULONG_PTR )pBuffer, 0, 0, FALSE, FALSE );
    }
    else
    {
         ScheduleTunnelWork(
             pTunnel, pVc, SendPayloadUnseq,
             (ULONG_PTR )pPacket, (ULONG_PTR )pBuffer, 0, 0, FALSE, FALSE );
    }
}


VOID
SendPayloadSeq(
    TUNNELWORK* pWork,
    TUNNELCB* pTunnel,
    VCCB* pVc,
    ULONG_PTR* punpArgs )

    // A PTUNNELWORK routine to handle sending a sequenced payload packet on a
    // VC.  Arg0 is the packet to send.  Arg1 is the header buffer to fill in.
    //
    // This routine is called only at PASSIVE IRQL.
    //
{
    NDIS_STATUS status;
    ADAPTERCB* pAdapter;
    PAYLOADSENT* pPs;
    TIMERQITEM* pTqiSendTimeout;
    LARGE_INTEGER lrgTime;
    ULONG ulLength;
    ULONG ulFullLength;
    NDIS_PACKET* pPacket;
    CHAR* pBuffer;
    NDIS_BUFFER* pNdisBuffer;
    USHORT usNs;

    TRACE( TL_V, TM_Send, ( "SendPayloadSeq" ) );

    // Unpack context information then free the work item.
    //
    pAdapter = pTunnel->pAdapter;
    pPacket = (NDIS_PACKET* )(punpArgs[ 0 ]);
    pBuffer = (CHAR* )(punpArgs[ 1 ]);
    FREE_TUNNELWORK( pAdapter, pWork );

    pTqiSendTimeout = NULL;
    pPs = NULL;

    do
    {
        // Get an "unacknowledged send timeout" timer event descriptor.
        //
        pTqiSendTimeout = ALLOC_TIMERQITEM( pAdapter );
        if (!pTqiSendTimeout)
        {
            ASSERT( !"Alloc TQI?" );
            status = NDIS_STATUS_RESOURCES;
            break;
        }

        // Get a "payload message sent" context.
        //
        pPs = ALLOC_PAYLOADSENT( pAdapter );
        if (!pPs)
        {
            ASSERT( !"Alloc PS?" );
            status = NDIS_STATUS_RESOURCES;
            break;
        }

        NdisAcquireSpinLock( &pVc->lockV );
        {
            // Retrieve the 'Next Send' value to assign this packet, then
            // bump the counter for the next guy.
            //
            usNs = pVc->usNs;
            ++pVc->usNs;

            // Build an L2TP payload header with Ns/Nr fields in
            // 'pBuffer'.
            //
            ulLength =
                BuildL2tpHeader(
                    pBuffer,
                    FALSE,
                    FALSE,
                    &pTunnel->usAssignedTunnelId,
                    &pVc->usAssignedCallId,
                    &usNs,
                    pVc->usNr );

            // Pare down the header buffer to the actual length used then
            // chain it onto the PPP-framed data we got from NDISWAN.
            //
            pNdisBuffer = NdisBufferFromBuffer( pBuffer );
            NdisAdjustBufferLength( pNdisBuffer, (UINT )ulLength );
            NdisChainBufferAtFront( pPacket, pNdisBuffer );
            NdisQueryPacket( pPacket, NULL, NULL, NULL, &ulFullLength );
            UpdateHeaderLength( pBuffer, (USHORT )ulFullLength );

            // Cancel any pending delayed acknowledge timeout, because the
            // acknowledge will piggyback on this packet.
            //
            if (pVc->pTqiDelayedAck)
            {
                TimerQCancelItem( pTunnel->pTimerQ, pVc->pTqiDelayedAck );
                pVc->pTqiDelayedAck = NULL;
            }

            // Fill the "payload message sent" context with the information
            // needed to track the progress of the payload's acknowledgement.
            //
            pPs->usNs = usNs;
            pPs->lRef = 0;
            TimerQInitializeItem( pTqiSendTimeout );
            pPs->pTqiSendTimeout = pTqiSendTimeout;
            pPs->pPacket = pPacket;
            pPs->pBuffer = pBuffer;

            ReferenceTunnel( pTunnel, FALSE );
            pPs->pTunnel = pTunnel;

            ReferenceVc( pVc );
            pPs->pVc = pVc;

            pPs->status = NDIS_STATUS_FAILURE;
            NdisGetCurrentSystemTime( &lrgTime );
            pPs->llTimeSent = lrgTime.QuadPart;
            pPs->pIrp = NULL;

            // Link the payload in the "outstanding" list and take a reference
            // on the context corresponding to this linkage.  Take a second
            // reference that will be removed by the send completion handler.
            // Take a third that will be removed by the timer event handler.
            //
            ReferencePayloadSent( pPs );
            InsertTailList( &pVc->listSendsOut, &pPs->linkSendsOut );
            ReferencePayloadSent( pPs );
            ReferencePayloadSent( pPs );

#ifdef PSDEBUG
            {
                extern LIST_ENTRY g_listDebugPs;
                extern NDIS_SPIN_LOCK g_lockDebugPs;

                NdisAcquireSpinLock( &g_lockDebugPs );
                {
                    InsertTailList( &g_listDebugPs, &pPs->linkDebugPs );
                }
                NdisReleaseSpinLock( &g_lockDebugPs );
            }
#endif

            TimerQScheduleItem(
                pTunnel->pTimerQ,
                pPs->pTqiSendTimeout,
                pVc->ulSendTimeoutMs,
                SendPayloadTimerEvent,
                pPs );

            TRACE( TL_A, TM_Msg,
                ( "%sSEND payload, len=%d Ns=%d Nr=%d to=%d",
                ((g_ulTraceLevel <= TL_I) ? "" : "\nL2TP: "),
                ulFullLength, pPs->usNs, pVc->usNr, pVc->ulSendTimeoutMs ) );
            DUMPW( TL_A, TM_MDmp, pPs->pBuffer, ulLength );

            ++pVc->stats.ulSentDataPacketsSeq;
            pVc->stats.ulDataBytesSent += (ulFullLength - ulLength);
            pVc->stats.ulSendWindowTotal += pVc->ulSendWindow;
        }
        NdisReleaseSpinLock( &pVc->lockV );

        status = NDIS_STATUS_SUCCESS;
    }
    while (FALSE);

    if (status != NDIS_STATUS_SUCCESS)
    {
        FreeBufferToPool( &pAdapter->poolHeaderBuffers, pBuffer, TRUE );

        if (pTqiSendTimeout)
        {
            FREE_TIMERQITEM( pAdapter, pTqiSendTimeout );
        }

        ASSERT( !pPs );

        // Complete the send, indicating the failure.
        //
        NDIS_SET_PACKET_STATUS( pPacket, status );
        TRACE( TL_A, TM_Send, ( "NdisMCoSendComp($%x)", status ) );
        NdisMCoSendComplete( status, pVc->NdisVcHandle, pPacket );
        TRACE( TL_N, TM_Send, ( "NdisMCoSendComp done" ) );
        return;
    }

    // Call TDI to send the payload message.
    //
    {
        FILE_OBJECT* FileObj;
        PTDIX_SEND_HANDLER SendFunc;

        if (ReadFlags(&pTunnel->ulFlags) & TCBF_SendConnected) {

            ASSERT(pTunnel->pRoute != NULL);

            FileObj =  PayloadObjFromUdpContext(&pTunnel->udpContext);
            SendFunc = TdixSend;
        } else {
            FileObj = NULL;
            SendFunc = TdixSendDatagram;
        }
    
        status = SendFunc(&pAdapter->tdix,
                          FileObj,
                          SendPayloadSeqComplete,
                          pPs,
                          NULL,
                          &pTunnel->address,
                          pBuffer,
                          ulFullLength,
                          &pPs->pIrp );
    }

    ASSERT( status == NDIS_STATUS_PENDING );
}


VOID
SendPayloadUnseq(
    TUNNELWORK* pWork,
    TUNNELCB* pTunnel,
    VCCB* pVc,
    ULONG_PTR* punpArgs )

    // A PTUNNELWORK routine to handle sending an unsequenced payload packet
    // on a VC.  Arg0 is the NDIS_PACKET.  Arg1 is the header buffer to fill
    // in.
    //
    // This routine is called only at PASSIVE IRQL.
    //
{
    NDIS_STATUS status;
    ADAPTERCB* pAdapter;
    ULONG ulLength;
    UINT unFullLength;
    NDIS_PACKET* pPacket;
    CHAR* pBuffer;
    NDIS_BUFFER* pNdisBuffer;

    TRACE( TL_V, TM_Send, ( "SendPayloadUnseq" ) );

    // Unpack context information then free the work item.
    //
    pAdapter = pTunnel->pAdapter;
    pPacket = (NDIS_PACKET* )(punpArgs[ 0 ]);
    pBuffer = (CHAR* )(punpArgs[ 1 ]);
    FREE_TUNNELWORK( pAdapter, pWork );

    NdisAcquireSpinLock( &pVc->lockV );
    {
        // Build an L2TP payload header without Ns/Nr fields in 'pBuffer'.
        //
        ulLength =
            BuildL2tpHeader(
                pBuffer,
                FALSE,
                FALSE,
                &pTunnel->usAssignedTunnelId,
                &pVc->usAssignedCallId,
                NULL,
                0 );

        // Pare down the header buffer to the actual length used then
        // chain it onto the PPP-framed data we got from NDISWAN.  Poke
        // the L2TP header to update the length field accounting for the
        // data.
        //
        pNdisBuffer = NdisBufferFromBuffer( pBuffer );
        NdisAdjustBufferLength( pNdisBuffer, (UINT )ulLength );
        NdisChainBufferAtFront( pPacket, pNdisBuffer );
        NdisQueryPacket( pPacket, NULL, NULL, NULL, &unFullLength );
        UpdateHeaderLength( pBuffer, (USHORT )unFullLength );

        TRACE( TL_A, TM_Msg,
             ( "%sSEND payload(%d), len=%d",
             ((g_ulTraceLevel <= TL_I) ? "" : "\nL2TP: "),
             ++pVc->usNs,
             unFullLength ) );
        DUMPW( TL_A, TM_MDmp, pBuffer, ulLength );

        ++pVc->stats.ulSentDataPacketsUnSeq;
        pVc->stats.ulDataBytesSent += ((ULONG )unFullLength - ulLength);
    }
    NdisReleaseSpinLock( &pVc->lockV );

    // Call TDI to send the payload message.
    //
    {
        FILE_OBJECT* FileObj;
        PTDIX_SEND_HANDLER SendFunc;

        NdisAcquireSpinLock(&pTunnel->lockT);

        if (pTunnel->pRoute != NULL) {
            FileObj = PayloadObjFromUdpContext(&pTunnel->udpContext);
            SendFunc = TdixSend;
        } else {
            FileObj = NULL;
            SendFunc = TdixSendDatagram;
        }

        NdisReleaseSpinLock(&pTunnel->lockT);

        status = SendFunc(&pAdapter->tdix,
                          FileObj,
                          SendPayloadUnseqComplete,
                          pVc,
                          pPacket,
                          &pTunnel->address,
                          pBuffer,
                          (ULONG )unFullLength,
                          NULL );
    }

    ASSERT( status == NDIS_STATUS_PENDING );
}


VOID
SendControlAck(
    IN TUNNELWORK* pWork,
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG_PTR* punpArgs )

    // A PTUNNELWORK routine to send a control acknowledge.
    //
    // This routine is called only at PASSIVE IRQL.
    //
{
    ADAPTERCB* pAdapter;

    TRACE( TL_N, TM_Send, ( "SendControlAck" ) );

    // Unpack context information then free the work item.
    //
    pAdapter = pTunnel->pAdapter;
    FREE_TUNNELWORK( pAdapter, pWork );

    SendZlb( pTunnel, NULL, pTunnel->usNs, pTunnel->usNr, FALSE );
}


VOID
SendPayloadAck(
    IN TUNNELWORK* pWork,
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG_PTR* punpArgs )

    // A PTUNNELWORK routine to send a payload acknowledge.
    //
    // This routine is called only at PASSIVE IRQL.
    //
    // IMPORTANT: Caller must take a call reference before calling that is
    //            removed by the send completion handler.
    //
{
    ADAPTERCB* pAdapter;

    TRACE( TL_N, TM_Send, ( "SendPayloadAck" ) );

    // Unpack context information then free the work item.
    //
    pAdapter = pTunnel->pAdapter;
    FREE_TUNNELWORK( pAdapter, pWork );

    ASSERT( pVc );
    ASSERT( pVc->usAssignedCallId > 0 );

    SendZlb( pTunnel, pVc, pVc->usNs, pVc->usNr, FALSE );
}


VOID
SendPayloadReset(
    IN TUNNELWORK* pWork,
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG_PTR* punpArgs )

    // A PTUNNELWORK routine to send a payload reset.  Arg0 is the "Next Sent"
    // value to send in the reset message.
    //
    // This routine is called only at PASSIVE IRQL.
    //
    // IMPORTANT: Caller must take a call reference before calling that is
    //            removed by the send completion handler.
    //
{
    ADAPTERCB* pAdapter;
    USHORT usNs;

    // Unpack context information then free the work item.
    //
    pAdapter = pTunnel->pAdapter;
    usNs = (USHORT )(punpArgs[ 0 ]);
    FREE_TUNNELWORK( pAdapter, pWork );

    TRACE( TL_A, TM_Send, ( "Send Reset=%d", (LONG )usNs ) );
    ASSERT( pVc );
    ASSERT( pVc->usAssignedCallId > 0 );

    SendZlb( pTunnel, pVc, usNs, pVc->usNr, TRUE );
}


VOID
ReferenceControlSent(
    IN CONTROLSENT* pCs )

    // Reference the control-sent context 'pCs'.
    //
{
    LONG lRef;

    lRef = NdisInterlockedIncrement( &pCs->lRef );
    TRACE( TL_N, TM_Ref, ( "RefCs to %d", lRef ) );
}


LONG
DereferenceControlSent(
    IN CONTROLSENT* pCs )

    // Reference the control-sent context 'pCs'.
    //
    // Returns the reference count of the dereferenced context.
    //
{
    LONG lRef;
    ADAPTERCB* pAdapter;
    NDIS_BUFFER* pNdisBuffer;

    lRef = NdisInterlockedDecrement( &pCs->lRef );
    TRACE( TL_N, TM_Ref, ( "DerefCs to %d", lRef ) );
    ASSERT( lRef >= 0 );

    if (lRef == 0)
    {
        pAdapter = pCs->pTunnel->pAdapter;

        ASSERT( pCs->linkSendsOut.Flink == &pCs->linkSendsOut );

        pNdisBuffer = NdisBufferFromBuffer( pCs->pBuffer );
        NdisAdjustBufferLength(
            pNdisBuffer, BufferSizeFromBuffer( pCs->pBuffer ) );
        FreeBufferToPool(
            &pAdapter->poolFrameBuffers, pCs->pBuffer, TRUE );

        if (pCs->pVc)
        {
            DereferenceVc( pCs->pVc );
        }

        ASSERT( pCs->pTunnel )
        DereferenceTunnel( pCs->pTunnel );

        FREE_TIMERQITEM( pAdapter, pCs->pTqiSendTimeout );
        FREE_CONTROLSENT( pAdapter, pCs );
    }

    return lRef;
}


VOID
ReferencePayloadSent(
    IN PAYLOADSENT* pPs )

    // Reference the payload-sent context 'pPs'.
    //
{
    LONG lRef;

    lRef = NdisInterlockedIncrement( &pPs->lRef );
    TRACE( TL_N, TM_Ref, ( "RefPs to %d", lRef ) );
}


LONG
DereferencePayloadSent(
    IN PAYLOADSENT* pPs )

    // Reference the payload-sent context 'pPs'.
    //
    // Returns the reference count of the dereferenced context.
    //
{
    LONG lRef;
    ADAPTERCB* pAdapter;

    lRef = NdisInterlockedDecrement( &pPs->lRef );
    TRACE( TL_N, TM_Ref, ( "DerefPs to %d", lRef ) );
    ASSERT( lRef >= 0 );

    if (lRef == 0)
    {
        ASSERT( pPs->linkSendsOut.Flink == &pPs->linkSendsOut );

        // The actual work is scheduled because it calls outside the driver
        // and we don't want any lock restrictions on this routine.
        //
        ScheduleTunnelWork(
            pPs->pTunnel, pPs->pVc, CompletePayloadSent,
            (ULONG_PTR )pPs, 0, 0, 0, FALSE, FALSE );
    }

    return lRef;
}


//-----------------------------------------------------------------------------
// Send utility routines (alphabetically)
//-----------------------------------------------------------------------------

USHORT
BuildAvpAch(
    IN USHORT usAttribute,
    IN BOOLEAN fMandatory,
    IN CHAR* pszValue,
    IN USHORT usValueLength,
    OUT CHAR* pAvp )

    // Builds a byte-array-valued AVP in caller's buffer 'pAvp' with attribute
    // field value 'usAttribute' and value the first 'usValueLength' bytes of
    // array 'pszlValue'.  'FMandatory' indicates the M-bit should be set in
    // the AVP.
    //
    // Returns the length of the built AVP.
    //
{
    UNALIGNED USHORT* pusCur;
    UNALIGNED USHORT* pusBits;
    USHORT usLength;

    pusCur = (UNALIGNED USHORT* )pAvp;
    pusBits = pusCur;
    ++pusCur;

    // Set Vendor ID to "IETF-defined".
    //
    *pusCur = 0;
    ++pusCur;

    // Set Attribute field.
    //
    *pusCur = htons( usAttribute );
    ++pusCur;

    // Set Value field.
    //
    if (usValueLength)
    {
        NdisMoveMemory( (CHAR* )pusCur, pszValue, (ULONG )usValueLength );
        ((CHAR* )pusCur) += usValueLength;
    }

    // Now, go back and set bits/length field.
    //
    usLength = (USHORT )(((CHAR* )pusCur) - pAvp);
    *pusBits = usLength;
    if (fMandatory)
    {
        *pusBits |= ABM_M;
    }
    *pusBits = htons( *pusBits );

    return usLength;
}


USHORT
BuildAvpAul(
    IN USHORT usAttribute,
    IN BOOLEAN fMandatory,
    IN UNALIGNED ULONG* pulValue,
    IN USHORT usValues,
    OUT CHAR* pAvp )

    // Builds a ULONG-array-valued AVP in caller's buffer 'pAvp' with
    // attribute field value 'usAttribute' and value the first 'usValues'
    // ULONGS of array 'pszlValue'.  'FMandatory' indicates the M-bit should
    // be set in the AVP.
    //
    // Returns the length of the built AVP.
    //
{
    UNALIGNED USHORT* pusCur;
    UNALIGNED USHORT* pusBits;
    USHORT usLength;
    USHORT i;

    pusCur = (UNALIGNED USHORT* )pAvp;
    pusBits = pusCur;
    ++pusCur;

    // Set Vendor ID to "IETF-defined".
    //
    *pusCur = 0;
    ++pusCur;

    // Set Attribute field.
    //
    *pusCur = htons( usAttribute );
    ++pusCur;

    // Set Value field.
    //
    for (i = 0; i < usValues; ++i)
    {
        *((UNALIGNED ULONG* )pusCur) = pulValue[ i ];
        *((UNALIGNED ULONG* )pusCur) = htonl( *((UNALIGNED ULONG* )pusCur) );
        pusCur += 2;
    }

    // Now, go back and set bits/length field.
    //
    usLength = (USHORT )(((CHAR* )pusCur) - pAvp);
    *pusBits = usLength;
    if (fMandatory)
    {
        *pusBits |= ABM_M;
    }
    *pusBits = htons( *pusBits );

    return usLength;
}


USHORT
BuildAvpFlag(
    IN USHORT usAttribute,
    IN BOOLEAN fMandatory,
    OUT CHAR* pAvp )

    // Builds an empty (no data) flag AVP in caller's buffer 'pAvp' with
    // attribute field value 'usAttribute'.  'FMandatory' indicates the M-bit
    // should be set in the AVP.
    //
    // Returns the length of the built AVP.
    //
{
    UNALIGNED USHORT* pusCur;
    UNALIGNED USHORT* pusBits;
    USHORT usLength;

    pusCur = (UNALIGNED USHORT* )pAvp;
    pusBits = pusCur;
    ++pusCur;

    // Set Vendor ID to "IETF-defined".
    //
    *pusCur = 0;
    ++pusCur;

    // Set Attribute field.
    //
    *pusCur = htons( usAttribute );
    ++pusCur;

    // Now, go back and set bits/length field.
    //
    usLength = (USHORT )(((CHAR* )pusCur) - pAvp);
    *pusBits = usLength;
    if (fMandatory)
    {
        *pusBits |= ABM_M;
    }
    *pusBits = htons( *pusBits );

    return usLength;
}


USHORT
BuildAvpUl(
    IN USHORT usAttribute,
    IN BOOLEAN fMandatory,
    IN ULONG ulValue,
    OUT CHAR* pAvp )

    // Builds a ULONG-valued AVP in caller's buffer 'pAvp' with attribute
    // field value 'usAttribute' and value 'ulValue'.  'FMandatory' indicates
    // the M-bit should be set in the AVP.
    //
    // Returns the length of the built AVP.
    //
{
    UNALIGNED USHORT* pusCur;
    UNALIGNED USHORT* pusBits;
    USHORT usLength;

    pusCur = (UNALIGNED USHORT* )pAvp;
    pusBits = pusCur;
    ++pusCur;

    // Set Vendor ID to "IETF-defined".
    //
    *pusCur = 0;
    ++pusCur;

    // Set Attribute field.
    //
    *pusCur = htons( usAttribute );
    ++pusCur;

    // Set Value field.
    //
    *((UNALIGNED ULONG* )pusCur) = htonl( ulValue );
    pusCur += 2;

    // Now, go back and set bits/length field.
    //
    usLength = (USHORT )(((CHAR* )pusCur) - pAvp);
    *pusBits = usLength;
    if (fMandatory)
    {
        *pusBits |= ABM_M;
    }
    *pusBits = htons( *pusBits );

    return usLength;
}


USHORT
BuildAvpUs(
    IN USHORT usAttribute,
    IN BOOLEAN fMandatory,
    IN USHORT usValue,
    OUT CHAR* pAvp )

    // Builds a USHORT-valued AVP in caller's buffer 'pAvp' with attribute
    // field value 'usAttribute' and value 'usValue'.  'FMandatory' indicates
    // the M-bit should be set in the AVP.
    //
    // Returns the length of the built AVP.
    //
{
    UNALIGNED USHORT* pusCur;
    UNALIGNED USHORT* pusBits;
    USHORT usLength;

    pusCur = (UNALIGNED USHORT* )pAvp;
    pusBits = pusCur;
    ++pusCur;

    // Set Vendor ID to "IETF-defined".
    //
    *pusCur = 0;
    ++pusCur;

    // Set Attribute field.
    //
    *pusCur = htons( usAttribute );
    ++pusCur;

    // Set Value field.
    //
    *pusCur = htons( usValue );
    ++pusCur;

    // Now, go back and set bits/length field.
    //
    usLength = (USHORT )(((CHAR* )pusCur) - pAvp);
    *pusBits = usLength;
    if (fMandatory)
    {
        *pusBits |= ABM_M;
    }
    *pusBits = htons( *pusBits );

    return usLength;
}


USHORT
BuildAvp2UsAndAch(
    IN USHORT usAttribute,
    IN BOOLEAN fMandatory,
    IN USHORT usValue1,
    IN USHORT usValue2,
    IN CHAR* pszValue,
    IN USHORT usValueLength,
    OUT CHAR* pAvp )

    // Builds an AVP consisting of 'usValue1' and 'usValue2' followed by
    // message 'pszValue' of length 'usValueLength' bytes in caller's buffer
    // 'pAvp' with attribute field value 'usAttribute'.  'FMandatory'
    // indicates the M-bit should be set in the AVP.
    //
    // Returns the length of the built AVP.
    //
{
    UNALIGNED USHORT* pusCur;
    UNALIGNED USHORT* pusBits;
    USHORT usLength;

    pusCur = (UNALIGNED USHORT* )pAvp;
    pusBits = pusCur;
    ++pusCur;

    // Set Vendor ID to "IETF-defined".
    //
    *pusCur = 0;
    ++pusCur;

    // Set Attribute field.
    //
    *pusCur = htons( usAttribute );
    ++pusCur;

    // Set first USHORT value field.
    //
    *pusCur = htons( usValue1 );
    ++pusCur;

    // Set second USHORT value field.
    //
    *pusCur = htons( usValue2 );
    ++pusCur;

    // Set message value field.
    //
    if (usValueLength)
    {
        NdisMoveMemory( (CHAR* )pusCur, pszValue, (ULONG )usValueLength );
        ((CHAR*)pusCur) += usValueLength;
    }

    // Now, go back and set bits/length field.
    //
    usLength = (USHORT )(((CHAR* )pusCur) - pAvp);
    *pusBits = usLength;
    if (fMandatory)
    {
        *pusBits |= ABM_M;
    }
    *pusBits = htons( *pusBits );

    return usLength;
}


VOID
BuildCdnAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength )

    // PBUILDAVPS handler to add AVPs to an outgoing CallDisconnNotify control
    // message.  'PTunnel' and 'pVc' are the tunnel/VC control blocks.
    // 'ulArg1' and 'ulArg2' are the result and error codes to be returned.
    // 'pvArg3' is ignored.  'PAvpBuffer' is the address of the buffer to
    // receive the built AVPs.  '*PulAvpLength' is set to the length of the
    // built AVPs.
    //
{
    CHAR* pCurAvp;
    ULONG ulAvpLength;
    USHORT usResult;
    USHORT usError;

    TRACE( TL_V, TM_Send, ( "BuildCdnAvps" ) );

    usResult = (USHORT )ulArg1;
    usError = (USHORT )ulArg2;

    pCurAvp = pAvpBuffer;

    pCurAvp += BuildAvpUs(
        ATTR_MsgType, TRUE, CMT_CDN, pCurAvp );

    pCurAvp += BuildAvp2UsAndAch(
        ATTR_Result, TRUE, usResult, usError, NULL, 0, pCurAvp );

    pCurAvp += BuildAvpUs(
        ATTR_AssignedCallId, TRUE, pVc->usCallId, pCurAvp );

    *pulAvpLength = (ULONG )(pCurAvp - pAvpBuffer);
}


VOID
BuildHelloAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength )

    // PBUILDAVPS handler to add AVPs to an outgoing Hello control message.
    // 'PTunnel' is the tunnel control block.  'PVc', 'ulArgX' and 'pvArg3' are ignored.
    // 'PAvpBuffer' is the address of the buffer to receive the built AVPs.
    // '*PulAvpLength' is set to the length of the built AVPs.
    //
{
    CHAR* pCurAvp;
    ULONG ulAvpLength;
    ADAPTERCB* pAdapter;

    TRACE( TL_V, TM_Send, ( "BuildHelloAvps" ) );

    pAdapter = pTunnel->pAdapter;
    pCurAvp = pAvpBuffer;

    pCurAvp += BuildAvpUs(
        ATTR_MsgType, TRUE, CMT_Hello, pCurAvp );

    *pulAvpLength = (ULONG )(pCurAvp - pAvpBuffer);
}


VOID
BuildIccnAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength )

    // PBUILDAVPS handler to add AVPs to an outgoing Incoming-Call-Connected
    // control message.  'PTunnel' and 'pVc' are the tunnel/VC control blocks.
    // 'UlArgX' and 'pvArg3' are ignored.  'PAvpBuffer' is the address of the buffer to
    // receive the built AVPs.  '*PulAvpLength' is set to the length of the
    // built AVPs.
    //
{
    CHAR* pCurAvp;
    ULONG ulAvpLength;
    ADAPTERCB* pAdapter;
    BOOLEAN fSequencing;

    pAdapter = pTunnel->pAdapter;

    TRACE( TL_V, TM_Send, ( "BuildIccnAvps" ) );

    pCurAvp = pAvpBuffer;

    pCurAvp += BuildAvpUs(
        ATTR_MsgType, TRUE, CMT_ICCN, pCurAvp );

    // For now, we don't support WAN link relays, so this is the estimated
    // speed of the LAN relay.  This could be totally wrong if, for instance,
    // the tunnel is itself tunneled over a PPP link.
    //
    pCurAvp += BuildAvpUl(
        ATTR_TxConnectSpeed, TRUE, pVc->ulConnectBps, pCurAvp );

    pCurAvp += BuildAvpUl(
        ATTR_FramingType, TRUE, FBM_Sync, pCurAvp );

    fSequencing = !!(ReadFlags( &pVc->ulFlags ) & VCBF_Sequencing);
    if (fSequencing)
    {
        USHORT usRWindow;

        usRWindow = pAdapter->usPayloadReceiveWindow;
        if (!usRWindow)
        {
            usRWindow = L2TP_DefaultReceiveWindow;
        }

        pCurAvp += BuildAvpUs(
            ATTR_RWindowSize, TRUE, usRWindow, pCurAvp );
    }

#if 0
    // Use the LNS default PPD even when we're LAC, for now.
    //
    pCurAvp += BuildAvpUs(
        ATTR_PacketProcDelay, TRUE, L2TP_LnsDefaultPpd, pCurAvp );
#endif

    pCurAvp += BuildAvpUs(
        ATTR_ProxyAuthType, FALSE, PAT_None, pCurAvp );

    if (fSequencing)
    {
        pCurAvp += BuildAvpFlag(
            ATTR_SequencingRequired, TRUE, pCurAvp );
    }

    *pulAvpLength = (ULONG )(pCurAvp - pAvpBuffer);
}


VOID
BuildIcrpAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength )

    // PBUILDAVPS handler to add AVPs to an outgoing Incoming-Call-Reply
    // control message.  'PTunnel' and 'pVc' are the tunnel/VC control blocks.
    // 'UlArgX' and 'pvArg3' are ignored.  'PAvpBuffer' is the address of the buffer to
    // receive the built AVPs.  '*PulAvpLength' is set to the length of the
    // built AVPs.
    //
{
    CHAR* pCurAvp;
    ULONG ulAvpLength;
    ADAPTERCB* pAdapter;

    pAdapter = pTunnel->pAdapter;

    TRACE( TL_V, TM_Send, ( "BuildIcrpAvps" ) );

    pCurAvp = pAvpBuffer;

    pCurAvp += BuildAvpUs(
        ATTR_MsgType, TRUE, CMT_ICRP, pCurAvp );

    pCurAvp += BuildAvpUs(
        ATTR_AssignedCallId, TRUE, pVc->usCallId, pCurAvp );

    if (ReadFlags( &pVc->ulFlags ) & VCBF_Sequencing)
    {
        USHORT usRWindow;

        usRWindow = pAdapter->usPayloadReceiveWindow;
        if (!usRWindow)
            usRWindow = L2TP_DefaultReceiveWindow;

        pCurAvp += BuildAvpUs(
            ATTR_RWindowSize, TRUE, usRWindow, pCurAvp );
    }

#if 0
    pCurAvp += BuildAvpUs(
        ATTR_PacketProcDelay, TRUE, L2TP_LnsDefaultPpd, pCurAvp );
#endif

    *pulAvpLength = (ULONG )(pCurAvp - pAvpBuffer);
}


VOID
BuildIcrqAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength )

    // PBUILDAVPS handler to add AVPs to an outgoing Incoming-Call-Request
    // control message.  'PTunnel' and 'pVc' are the tunnel/VC control block.
    // 'UlArgX' and 'pvArg3' are ignored.  'PAvpBuffer' is the address of the buffer to
    // receive the built AVPs.  '*PulAvpLength' is set to the length of the
    // built AVPs.
    //
{
    CHAR* pCurAvp;
    ULONG ulAvpLength;
    ADAPTERCB* pAdapter;

    pAdapter = pTunnel->pAdapter;

    TRACE( TL_V, TM_Send, ( "BuildIcrqAvps" ) );

    pCurAvp = pAvpBuffer;

    pCurAvp += BuildAvpUs(
        ATTR_MsgType, TRUE, CMT_ICRQ, pCurAvp );

    pCurAvp += BuildAvpUs(
        ATTR_AssignedCallId, TRUE, pVc->usCallId, pCurAvp );

    pCurAvp += BuildAvpUl(
        ATTR_CallSerialNumber, TRUE,
        pVc->pLcParams->ulCallSerialNumber, pCurAvp );

    {
        ULONG ulBearerType;

        ulBearerType = 0;
        if (pVc->pTcParams->ulMediaMode & LINEMEDIAMODE_DATAMODEM)
        {
            ulBearerType |= BBM_Analog;
        }

        if (pVc->pTcParams->ulMediaMode & LINEMEDIAMODE_DIGITALDATA)
        {
            ulBearerType |= BBM_Digital;
        }

        pCurAvp += BuildAvpUl(
            ATTR_BearerType, TRUE, ulBearerType, pCurAvp );
    }

    if (pVc->pLcParams->ulPhysicalChannelId != 0xFFFFFFFF)
    {
        pCurAvp += BuildAvpUl(
            ATTR_PhysicalChannelId, FALSE,
            pVc->pLcParams->ulPhysicalChannelId, pCurAvp );
    }

    *pulAvpLength = (ULONG )(pCurAvp - pAvpBuffer);
}


ULONG
BuildL2tpHeader(
    IN OUT CHAR* pBuffer,
    IN BOOLEAN fControl,
    IN BOOLEAN fReset,
    IN USHORT* pusTunnelId,
    IN USHORT* pusCallId,
    IN USHORT* pusNs,
    IN USHORT usNr )

    // Fill in caller's 'pBuffer' with an L2TP header matching caller's
    // arguments.  'FControl' indicates to build a control header, otherwise a
    // payload header is built.  'fReset' indicates to build a reset rather
    // than a simple acknowledge.  Arguments that are not to appear in the
    // header are NULL.  Note that 'usNr' is not a pointer because it's
    // appearance in the header is tied to the appearance of 'pusNs'.
    //
    // Returns the total length of the header.
    //
{
    UNALIGNED USHORT* pusBits;
    UNALIGNED USHORT* pusLength;
    UNALIGNED USHORT* pusCur;
    ULONG ulLength;

    pusCur = (UNALIGNED USHORT* )pBuffer;
    pusBits = pusCur;
    ++pusCur;

    pusLength = pusCur;
    ++pusCur;

    // Initialize header bit mask with the version, and set the length bit
    // since the Length field is always sent.
    //
    *pusBits = HBM_L | VER_L2tp;
    if (fControl)
    {
        ASSERT( pusTunnelId && pusCallId && pusNs && !fReset );
        *pusBits |= HBM_T;
    }
    else if (fReset)
    {
        ASSERT( pusTunnelId && pusCallId && pusNs );
        *pusBits |= HBM_R;
    }

    if (pusTunnelId)
    {
        // Tunnel-ID field present.  Draft-05 removes the 'I' bit that used to
        // indicate the Tunnel-ID is present.  It is now assumed to be always
        // present.
        //
        *pusCur = htons( *pusTunnelId );
        ++pusCur;
    }

    if (pusCallId)
    {
        // Call-ID field present.  Draft-05 removes the 'C' bit that used to
        // indicate the Tunnel-ID is present.  It is now assumed to be always
        // present.
        //
        *pusCur = htons( *pusCallId );
        ++pusCur;
    }

    if (pusNs)
    {
        // Ns and Nr fields are present.
        //
        *pusBits |= HBM_F;
        *pusCur = htons( *pusNs );
        ++pusCur;
        *pusCur = htons( usNr );
        ++pusCur;
    }

    // Fill in the header and length fields with the accumulated
    // values.
    //
    *pusBits = htons( *pusBits );
    *pusLength = (USHORT )(((CHAR* )pusCur) - pBuffer);
    ulLength = (ULONG )*pusLength;
    *pusLength = htons( *pusLength );

    return ulLength;
}


VOID
BuildOccnAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength )

    // PBUILDAVPS handler to add AVPs to an outgoing Outgoing-Call-Connected
    // control message.  'PTunnel' and 'pVc' are the tunnel/VC control blocks.
    // 'UlArgX' and 'pvArg3' are ignored.  'PAvpBuffer' is the address of the buffer to
    // receive the built AVPs.  '*PulAvpLength' is set to the length of the
    // built AVPs.
    //
{
    CHAR* pCurAvp;
    ULONG ulAvpLength;
    ADAPTERCB* pAdapter;
    BOOLEAN fSequencing;

    pAdapter = pTunnel->pAdapter;

    TRACE( TL_V, TM_Send, ( "BuildOccnAvps" ) );

    pCurAvp = pAvpBuffer;

    pCurAvp += BuildAvpUs(
        ATTR_MsgType, TRUE, CMT_OCCN, pCurAvp );

    pCurAvp += BuildAvpUl(
        ATTR_TxConnectSpeed, TRUE, pVc->ulConnectBps, pCurAvp );

    pCurAvp += BuildAvpUl(
        ATTR_FramingType, TRUE, FBM_Sync, pCurAvp );

    fSequencing = !!(ReadFlags( &pVc->ulFlags ) & VCBF_Sequencing);
    if (fSequencing)
    {
        USHORT usRWindow;

        usRWindow = pAdapter->usPayloadReceiveWindow;
        if (!usRWindow)
        {
            usRWindow = L2TP_DefaultReceiveWindow;
        }

        pCurAvp += BuildAvpUs(
            ATTR_RWindowSize, TRUE, usRWindow, pCurAvp );
    }

#if 0
    // Use the LNS default PPD even when we're LAC, for now.
    //
    pCurAvp += BuildAvpUs(
        ATTR_PacketProcDelay, TRUE, L2TP_LnsDefaultPpd, pCurAvp );
#endif

    if (fSequencing)
    {
        pCurAvp += BuildAvpFlag(
            ATTR_SequencingRequired, TRUE, pCurAvp );
    }

    *pulAvpLength = (ULONG )(pCurAvp - pAvpBuffer);
}


VOID
BuildOcrpAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength )

    // PBUILDAVPS handler to add AVPs to an outgoing Outgoing-Call-Reply
    // control message.  'PTunnel' and 'pVc' are the tunnel/VC control blocks.
    // 'UlArgX' and 'pvArg3' are ignored.  'PAvpBuffer' is the address of the buffer to
    // receive the built AVPs.  '*PulAvpLength' is set to the length of the
    // built AVPs.
    //
{
    CHAR* pCurAvp;
    ULONG ulAvpLength;

    TRACE( TL_V, TM_Send, ( "BuildOcrpAvps" ) );

    pCurAvp = pAvpBuffer;

    pCurAvp += BuildAvpUs(
        ATTR_MsgType, TRUE, CMT_OCRP, pCurAvp );

    pCurAvp += BuildAvpUs(
        ATTR_AssignedCallId, TRUE, pVc->usCallId, pCurAvp );

    ASSERT( pVc->pLcParams );
    if (pVc->pLcParams->ulPhysicalChannelId != 0xFFFFFFFF)
    {
        pCurAvp += BuildAvpUl(
            ATTR_PhysicalChannelId, FALSE,
            pVc->pLcParams->ulPhysicalChannelId, pCurAvp );
    }

    *pulAvpLength = (ULONG )(pCurAvp - pAvpBuffer);
}


VOID
BuildOcrqAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength )

    // PBUILDAVPS handler to add AVPs to an outgoing Outgoing-Call-Request
    // control message.  'PTunnel' and 'pVc' are the tunnel/VC control block.
    // 'UlArgX' are ignored.  'PAvpBuffer' is the address of the buffer to
    // receive the built AVPs.  '*PulAvpLength' is set to the length of the
    // built AVPs.
    //
{
    CHAR* pCurAvp;
    ULONG ulAvpLength;
    ADAPTERCB* pAdapter;

    pAdapter = pTunnel->pAdapter;

    TRACE( TL_V, TM_Send, ( "BuildOcrqAvps" ) );

    pCurAvp = pAvpBuffer;

    pCurAvp += BuildAvpUs(
        ATTR_MsgType, TRUE, CMT_OCRQ, pCurAvp );

    pCurAvp += BuildAvpUs(
        ATTR_AssignedCallId, TRUE, pVc->usCallId, pCurAvp );

    pCurAvp += BuildAvpUl(
        ATTR_CallSerialNumber, TRUE,
        pVc->pLcParams->ulCallSerialNumber, pCurAvp );

    {
        ULONG ulBps;

        ulBps = pVc->pTcParams->ulMinRate;
        if (ulBps == 0)
        {
            ulBps = 1;
        }
        else if (ulBps > 0x7FFFFFFF)
        {
            ulBps = 0x7FFFFFFF;
        }

        pCurAvp += BuildAvpUl(
            ATTR_MinimumBps, TRUE, ulBps, pCurAvp );

        ulBps = pVc->pTcParams->ulMaxRate;
        if (ulBps == 0)
        {
            ulBps = 1;
        }
        else if (ulBps > 0x7FFFFFFF)
        {
            ulBps = 0x7FFFFFFF;
        }

        pCurAvp += BuildAvpUl(
            ATTR_MaximumBps, TRUE, ulBps, pCurAvp );
    }

    {
        ULONG ulBearerType;

        ulBearerType = 0;
        if (pVc->pTcParams->ulMediaMode & LINEMEDIAMODE_DATAMODEM)
        {
            ulBearerType |= BBM_Analog;
        }

        if (pVc->pTcParams->ulMediaMode & LINEMEDIAMODE_DIGITALDATA)
        {
            ulBearerType |= BBM_Digital;
        }

        pCurAvp += BuildAvpUl(
            ATTR_BearerType, TRUE, ulBearerType, pCurAvp );
    }

    pCurAvp += BuildAvpUl(
        ATTR_FramingType, TRUE, FBM_Sync, pCurAvp );

    if (ReadFlags( &pVc->ulFlags ) & VCBF_Sequencing)
    {
        ASSERT( pAdapter->usPayloadReceiveWindow );
        pCurAvp += BuildAvpUs(
            ATTR_RWindowSize, TRUE,
            pAdapter->usPayloadReceiveWindow, pCurAvp );
    }

#if 0
    pCurAvp += BuildAvpUs(
        ATTR_PacketProcDelay, TRUE, L2TP_LnsDefaultPpd, pCurAvp );
#endif

    *pulAvpLength = (ULONG )(pCurAvp - pAvpBuffer);
}


VOID
BuildScccnAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength )

    // PBUILDAVPS handler to add AVPs to an outgoing Start-Cc-Connected
    // control message.  'PTunnel' is the tunnel control block.  'PVc' is
    // ignored.  'UlArg1' is the true if a challenge response is to be sent,
    // false otherwise.  'UlArg2' and 'pvArg3' are ignored.  'PAvpBuffer' is
    // the address of the buffer to receive the built AVPs.  '*PulAvpLength'
    // is set to the length of the built AVPs.
    //
{
    CHAR* pCurAvp;
    ULONG ulAvpLength;

    TRACE( TL_V, TM_Send, ( "BuildScccnAvps" ) );

    pCurAvp = pAvpBuffer;

    pCurAvp += BuildAvpUs(
        ATTR_MsgType, TRUE, CMT_SCCCN, pCurAvp );

    if (ulArg1)
    {
        pCurAvp += BuildAvpAch(
            ATTR_ChallengeResponse, TRUE,
            pTunnel->achResponseToSend, sizeof(pTunnel->achResponseToSend),
            pCurAvp );
    }

    *pulAvpLength = (ULONG )(pCurAvp - pAvpBuffer);
}


VOID
BuildSccrpAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength )

    // PBUILDAVPS handler to add AVPs to an outgoing Start-Cc-Reply control
    // message.  'PTunnel' is the tunnel control block.  'PVc' is ignored.
    // 'UlArg1' is true if a challenge response is to be sent, false
    // otherwise.  'UlArg2' and 'pvArg3' are ignored.  'PAvpBuffer' is the
    // address of the buffer to receive the built AVPs.  '*PulAvpLength' is
    // set to the length of the built AVPs.
    //
{
    CHAR* pCurAvp;
    ULONG ulAvpLength;
    ADAPTERCB* pAdapter;

    TRACE( TL_N, TM_Send, ( "BuildSccrpAvps" ) );

    pAdapter = pTunnel->pAdapter;

    pCurAvp = pAvpBuffer;

    pCurAvp += BuildAvpUs(
        ATTR_MsgType, TRUE, CMT_SCCRP, pCurAvp );

    pCurAvp += BuildAvpUs(
        ATTR_ProtocolVersion, TRUE, L2TP_ProtocolVersion, pCurAvp );

    pCurAvp += BuildAvpUl(
        ATTR_FramingCaps, TRUE, pAdapter->ulFramingCaps, pCurAvp );

    pCurAvp += BuildAvpUl(
        ATTR_BearerCaps, TRUE, pAdapter->ulBearerCaps, pCurAvp );

    pCurAvp += BuildAvpUs(
        ATTR_FirmwareRevision, FALSE, L2TP_FirmwareRevision, pCurAvp );

    ASSERT( pAdapter->pszHostName );
    pCurAvp += BuildAvpAch(
        ATTR_HostName, TRUE,
        pAdapter->pszHostName,
        (USHORT )strlen( pAdapter->pszHostName ),
        pCurAvp );

    pCurAvp += BuildAvpAch(
        ATTR_VendorName, FALSE,
        L2TP_VendorName, (USHORT )strlen( L2TP_VendorName ), pCurAvp );

    pCurAvp += BuildAvpUs(
        ATTR_AssignedTunnelId, TRUE, pTunnel->usTunnelId, pCurAvp );

    if (pAdapter->usControlReceiveWindow)
    {
        pCurAvp += BuildAvpUs(
            ATTR_RWindowSize, TRUE,
            pAdapter->usControlReceiveWindow, pCurAvp );
    }

    if (pAdapter->pszPassword)
    {
        pCurAvp += BuildAvpAch(
            ATTR_Challenge, TRUE,
            pTunnel->achChallengeToSend,
            sizeof(pTunnel->achChallengeToSend),
            pCurAvp );
    }

    if (ulArg1)
    {
        pCurAvp += BuildAvpAch(
            ATTR_ChallengeResponse, TRUE,
            pTunnel->achResponseToSend,
            sizeof(pTunnel->achResponseToSend),
            pCurAvp );
    }

    *pulAvpLength = (ULONG )(pCurAvp - pAvpBuffer);
}


VOID
BuildSccrqAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength )

    // PBUILDAVPS handler to add AVPs to an outgoing Start-Cc-Request control
    // message.  'PTunnel' is the tunnel control block.  'PVc', 'ulArgX' and 'pvArg3'
    // are ignored.  'PAvpBuffer' is the address of the buffer to receive the
    // built AVPs.  '*PulAvpLength' is set to the length of the built AVPs.
    //
{
    CHAR* pCurAvp;
    ULONG ulAvpLength;
    ADAPTERCB* pAdapter;

    TRACE( TL_V, TM_Send, ( "BuildSccrqAvps" ) );

    pAdapter = pTunnel->pAdapter;
    pCurAvp = pAvpBuffer;

    pCurAvp += BuildAvpUs(
        ATTR_MsgType, TRUE, CMT_SCCRQ, pCurAvp );

    pCurAvp += BuildAvpUs(
        ATTR_ProtocolVersion, TRUE, L2TP_ProtocolVersion, pCurAvp );

    pCurAvp += BuildAvpUl(
        ATTR_FramingCaps, TRUE, pAdapter->ulFramingCaps, pCurAvp );

    pCurAvp += BuildAvpUl(
        ATTR_BearerCaps, TRUE, pAdapter->ulBearerCaps, pCurAvp );

    pCurAvp += BuildAvpUs(
        ATTR_FirmwareRevision, FALSE, L2TP_FirmwareRevision, pCurAvp );

    if (pAdapter->pszHostName)
    {
        pCurAvp += BuildAvpAch(
            ATTR_HostName, TRUE,
            pAdapter->pszHostName,
            (USHORT )strlen( pAdapter->pszHostName ),
            pCurAvp );
    }

    pCurAvp += BuildAvpAch(
        ATTR_VendorName, FALSE,
        L2TP_VendorName, (USHORT )strlen( L2TP_VendorName ), pCurAvp );

    pCurAvp += BuildAvpUs(
        ATTR_AssignedTunnelId, TRUE, pTunnel->usTunnelId, pCurAvp );

    if (pAdapter->usControlReceiveWindow)
    {
        pCurAvp += BuildAvpUs(
            ATTR_RWindowSize, TRUE, pAdapter->usControlReceiveWindow, pCurAvp );
    }

    if (pAdapter->pszPassword)
    {
        pCurAvp += BuildAvpAch(
            ATTR_Challenge, TRUE,
            pTunnel->achChallengeToSend,
            sizeof(pTunnel->achChallengeToSend),
            pCurAvp );
    }

    *pulAvpLength = (ULONG )(pCurAvp - pAvpBuffer);
}


VOID
BuildStopccnAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength )

    // PBUILDAVPS handler to add AVPs to an outgoing Stop-Cc-Notify control
    // message.  'PTunnel' is the tunnel control block.  'PVc' is ignored.
    // 'ulArg1' and 'ulArg2' are the result and error codes to be sent.
    // 'pvArg3' is ignored.  'PAvpBuffer' is the address of the buffer to
    // receive the built AVPs.  '*PulAvpLength' is set to the length of the
    // built AVPs.
    //
{
    CHAR* pCurAvp;
    ULONG ulAvpLength;
    USHORT usResult;
    USHORT usError;

    TRACE( TL_V, TM_Send, ( "BuildStopCcReqAvps" ) );

    usResult = (USHORT )ulArg1;
    usError = (USHORT )ulArg2;

    pCurAvp = pAvpBuffer;

    pCurAvp += BuildAvpUs(
        ATTR_MsgType, TRUE, CMT_StopCCN, pCurAvp );

    pCurAvp += BuildAvpUs(
        ATTR_AssignedTunnelId, TRUE, pTunnel->usTunnelId, pCurAvp );

    pCurAvp += BuildAvp2UsAndAch(
        ATTR_Result, TRUE, usResult, usError, NULL, 0, pCurAvp );

    *pulAvpLength = (ULONG )(pCurAvp - pAvpBuffer);
}


VOID
BuildWenAvps(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG ulArg1,
    IN ULONG ulArg2,
    IN PVOID pvArg3,
    IN OUT CHAR* pAvpBuffer,
    OUT ULONG* pulAvpLength )

    // PBUILDAVPS handler to add AVPs to an outgoing Wan-Error-Notify control
    // message.  'PTunnel' and 'pVc' are the tunnel/VC control block.
    // 'pvArg3' is the address of an array of 6 error ULONGs, i.e. CRC,
    // framing, hardware overrun, buffer overrun, timeouts, and alignment
    // errors that this routine FREE_NONPAGEDs after use. 'ulArgX' are ignored.  
    // 'PAvpBuffer' is the address of the buffer to
    // receive the built AVPs.  '*PulAvpLength' is set to the length of the
    // built AVPs.
    //
{
    CHAR* pCurAvp;
    ULONG ulAvpLength;
    ADAPTERCB* pAdapter;
    UNALIGNED ULONG* pul;

    pAdapter = pTunnel->pAdapter;
    pul = (UNALIGNED ULONG* )pvArg3;

    TRACE( TL_V, TM_Send, ( "BuildWenAvps" ) );

    pCurAvp = pAvpBuffer;

    pCurAvp += BuildAvpUs(
        ATTR_MsgType, TRUE, CMT_WEN, pCurAvp );

    pCurAvp += BuildAvpAul(
        ATTR_CallErrors, TRUE, pul, 6, pCurAvp );
    FREE_NONPAGED( pul );

    *pulAvpLength = (ULONG )(pCurAvp - pAvpBuffer);
}


VOID
CompletePayloadSent(
    IN TUNNELWORK* pWork,
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN ULONG_PTR* punpArgs )

    // A PTUNNELWORK routine to complete a "sent payload".  Arg0 is the
    // PAYLOADSENT context which has already been de-queued from the
    // "outstanding send" list.
    //
    // This routine is called only at PASSIVE IRQL.
    //
{
    NDIS_STATUS status;
    ADAPTERCB* pAdapter;
    PAYLOADSENT* pPs;
    NDIS_BUFFER* pNdisBuffer;

    // Unpack context information then free the work item.
    //
    pAdapter = pTunnel->pAdapter;
    pPs = (PAYLOADSENT* )(punpArgs[ 0 ]);
    FREE_TUNNELWORK( pAdapter, pWork );

    TRACE( TL_N, TM_Send, ( "CompletePayloadSent(Ns=%d)", (UINT )pPs->usNs ) );

    // Undo the adjustments made before the send so the owner of each
    // component resource gets back what they originally provided for clean-up
    // and recycling.
    //
    NdisUnchainBufferAtFront( pPs->pPacket, &pNdisBuffer );
    NdisAdjustBufferLength(
        pNdisBuffer, BufferSizeFromBuffer( pPs->pBuffer ) );
    FreeBufferToPool( &pAdapter->poolHeaderBuffers, pPs->pBuffer, TRUE );

    // Notify sending driver of the result.
    //
    NDIS_SET_PACKET_STATUS( pPs->pPacket, pPs->status );
    TRACE( TL_N, TM_Send, ("NdisMCoSendComp(s=$%x)", pPs->status ) );
    NdisMCoSendComplete( pPs->status, pPs->pVc->NdisVcHandle, pPs->pPacket );
    TRACE( TL_N, TM_Send, ("NdisMCoSendComp done" ) );

    DereferenceCall( pVc );
    DereferenceTunnel( pPs->pTunnel );
    DereferenceVc( pPs->pVc );

#ifdef PSDEBUG
    {
        extern LIST_ENTRY g_listDebugPs;
        extern NDIS_SPIN_LOCK g_lockDebugPs;

        NdisAcquireSpinLock( &g_lockDebugPs );
        {
            RemoveEntryList( &pPs->linkDebugPs );
            InitializeListHead( &pPs->linkDebugPs );
        }
        NdisReleaseSpinLock( &g_lockDebugPs );
    }
#endif

    FREE_TIMERQITEM( pAdapter, pPs->pTqiSendTimeout );
    FREE_PAYLOADSENT( pAdapter, pPs );
}


VOID
SendControlComplete(
    IN TDIXCONTEXT* pTdix,
    IN VOID* pContext1,
    IN VOID* pContext2,
    IN CHAR* pBuffer )

    // PTDIXSENDCOMPLETE handler for sends that send only a single buffer from
    // the 'ADAPTERCB.poolFrameBuffers' pool.
    //
{
    CONTROLSENT* pCs;
    ULONG ulSendTimeoutMs;

    TRACE( TL_V, TM_Send, ( "SendControlComp" ) );

    pCs = (CONTROLSENT* )pContext1;
    pCs->pIrp = NULL;

    // "Instant expire" the timer if the message is longer queued as an
    // outstanding send, i.e. it's been cancelled or terminated.  This is the
    // easiest way to clean up quickly yet reliably in this odd case.
    // Accessing the link and the send timeout without locks held is
    // technically not allowed, but the consequence of a misread is just a
    // very slight additional delay.  This is judged preferable to adding the
    // cost of taking and releasing a spinlock to every send.
    //
    if (pCs->linkSendsOut.Flink == &pCs->linkSendsOut)
    {
        ulSendTimeoutMs = 0;
        TRACE( TL_A, TM_Send,
            ( "Instant expire pCs=$%p pT=%p", pCs, pCs->pTunnel ) );
    }
    else
    {
        ulSendTimeoutMs = pCs->pTunnel->ulSendTimeoutMs;
    }

    // Schedule a retransmit of the packet, should it go unacknowledged.  This
    // occurs here rather than in SendPending to remove any chance of having
    // the same MDL chain outstanding in two separate calls to the IP stack.
    //
    // Note: The logical code commented out below can be omitted for
    // efficiency because the ReferenceControlSent for this scheduled timer
    // and the DereferenceControlSent for this completed send cancel each
    // other out.
    //
    // ReferenceControlSent( pCs );
    // DereferenceControlSent( pCs );
    //
    ASSERT( pCs->pTqiSendTimeout );
    TimerQScheduleItem(
        pCs->pTunnel->pTimerQ,
        pCs->pTqiSendTimeout,
        ulSendTimeoutMs,
        SendControlTimerEvent,
        pCs );
}


VOID
SendControlTimerEvent(
    IN TIMERQITEM* pItem,
    IN VOID* pContext,
    IN TIMERQEVENT event )

    // PTIMERQEVENT handler set to expire when it's time to give up on
    // receiving an acknowledge to the sent control packet indicated by
    // 'pContext'.
    //
{
    NDIS_STATUS status;
    ADAPTERCB* pAdapter;
    TUNNELCB* pTunnel;
    CONTROLSENT* pCs;

    TRACE( TL_N, TM_Send,
        ( "SendControlTimerEvent(%s)", TimerQPszFromEvent( event ) ) );

    // Unpack context information.  The timer item is owned by the "control
    // sent" context and freed indirectly by dereferencing below.
    //
    pCs = (CONTROLSENT* )pContext;
    pTunnel = pCs->pTunnel;
    pAdapter = pTunnel->pAdapter;

    if (event == TE_Expire)
    {
        // Timer expired, meaning it's time to give up on ever receiving an
        // acknowledge to the sent packet.  Per the draft/RFC, adjustments to
        // the send window and send timeouts are necessary.
        //
        NdisAcquireSpinLock( &pTunnel->lockT );
        do
        {
            if (pCs->linkSendsOut.Flink == &pCs->linkSendsOut)
            {
                // The context is not on the out queue, so it must have been
                // cancelled or terminated while the expire handling was being
                // set up.  Do nothing.
                //
                TRACE( TL_I, TM_Send,
                    ( "T%d: Timeout aborted", (ULONG )pTunnel->usTunnelId ) );
                break;
            }

            AdjustTimeoutsAndSendWindowAtTimeout(
                pAdapter->ulMaxSendTimeoutMs,
                pTunnel->lDeviationMs,
                &pTunnel->ulSendTimeoutMs,
                &pTunnel->ulRoundTripMs,
                &pTunnel->ulSendWindow,
                &pTunnel->ulAcksSinceSendTimeout );

            --pTunnel->ulSendsOut;
            ++pCs->ulRetransmits;

            TRACE( TL_I, TM_Send,
                ( "T%d: TIMEOUT(%d) -sout=%d +retry=%d rtt=%d ato=%d sw=%d",
                (ULONG )pTunnel->usTunnelId, (ULONG )pCs->usNs,
                pTunnel->ulSendsOut, pCs->ulRetransmits,
                pTunnel->ulRoundTripMs, pTunnel->ulSendTimeoutMs,
                pTunnel->ulSendWindow ) );

            // Retransmit the packet, or close the tunnel if retries are
            // exhausted.
            //
            if (pCs->ulRetransmits > pAdapter->ulMaxRetransmits)
            {
                // Retries are exhausted.  Give up and close the tunnel.  No
                // point in trying to be graceful since peer is not
                // responding.
                //
                SetFlags( &pTunnel->ulFlags, TCBF_PeerNotResponding );

                RemoveEntryList( &pCs->linkSendsOut );
                InitializeListHead( &pCs->linkSendsOut );
                DereferenceControlSent( pCs );

                ScheduleTunnelWork(
                    pTunnel, NULL, CloseTunnel,
                    0, 0, 0, 0, FALSE, FALSE );
            }
            else
            {
                // Retries remaining.  Mark the packet as pending
                // retransmission, then see if the send window allows the
                // retransmit to go now.
                //
                pCs->ulFlags |= CSF_Pending;
                ScheduleTunnelWork(
                    pTunnel, NULL, SendPending,
                    0, 0, 0, 0, FALSE, FALSE );
            }
        }
        while (FALSE);
        NdisReleaseSpinLock( &pTunnel->lockT );
    }

    // Remove the reference covering the scheduled timer.
    //
    DereferenceControlSent( pCs );
}


VOID
SendHeaderComplete(
    IN TDIXCONTEXT* pTdix,
    IN VOID* pContext1,
    IN VOID* pContext2,
    IN CHAR* pBuffer )

    // PTDIXSENDCOMPLETE handler for sends that send only a single buffer from
    // the 'ADAPTERCB.poolHeaderBuffers' pool.
    //
{
    ADAPTERCB* pAdapter;
    VCCB* pVc;
    NDIS_BUFFER* pNdisBuffer;

    TRACE( TL_V, TM_Send, ( "SendHeaderComp" ) );

    pAdapter = (ADAPTERCB* )pContext1;
    pVc = (VCCB* )pContext2;

    // Undo the adjustments made before the send the buffer is ready for
    // re-use.
    //
    pNdisBuffer = NdisBufferFromBuffer( pBuffer );
    NdisAdjustBufferLength( pNdisBuffer, BufferSizeFromBuffer( pBuffer ) );
    FreeBufferToPool( &pAdapter->poolHeaderBuffers, pBuffer, TRUE );

    if (pVc)
    {
        DereferenceCall( pVc );
    }
}


VOID
SendPayloadSeqComplete(
    IN TDIXCONTEXT* pTdix,
    IN VOID* pContext1,
    IN VOID* pContext2,
    IN CHAR* pBuffer )

    // PTDIXSENDCOMPLETE handler for sequenced payloads.
    //
{
    PAYLOADSENT* pPs;

    TRACE( TL_V, TM_Send, ( "SendPayloadSeqComp" ) );

    pPs = (PAYLOADSENT* )pContext1;
    pPs->pIrp = NULL;
    DereferencePayloadSent( pPs );
}


VOID
SendPayloadUnseqComplete(
    IN TDIXCONTEXT* pTdix,
    IN VOID* pContext1,
    IN VOID* pContext2,
    IN CHAR* pBuffer )

    // PTDIXSENDCOMPLETE handler for unsequenced payloads.
    //
{
    ADAPTERCB* pAdapter;
    VCCB* pVc;
    NDIS_PACKET* pPacket;
    NDIS_BUFFER* pNdisBuffer;

    TRACE( TL_V, TM_Send, ( "SendPayloadUnseqComp" ) );

    pVc = (VCCB* )pContext1;
    pPacket = (NDIS_PACKET* )pContext2;
    pAdapter = pVc->pAdapter;

    // Undo the adjustments made before the send so the owner of each
    // component resource gets back what they originally provided for clean-up
    // and recycling.
    //
    NdisUnchainBufferAtFront( pPacket, &pNdisBuffer );
    NdisAdjustBufferLength( pNdisBuffer, BufferSizeFromBuffer( pBuffer ) );
    FreeBufferToPool( &pAdapter->poolHeaderBuffers, pBuffer, TRUE );

    // Notify sending driver of the result.  Without sequencing, just trying
    // to send it is enough to claim success.
    //
    NDIS_SET_PACKET_STATUS( pPacket, NDIS_STATUS_SUCCESS );
    TRACE( TL_N, TM_Send, ("NdisMCoSendComp($%x)", NDIS_STATUS_SUCCESS ) );
    NdisMCoSendComplete( NDIS_STATUS_SUCCESS, pVc->NdisVcHandle, pPacket );
    TRACE( TL_N, TM_Send, ("NdisMCoSendComp done" ) );

    DereferenceCall( pVc );
}


VOID
SendPayloadTimerEvent(
    IN TIMERQITEM* pItem,
    IN VOID* pContext,
    IN TIMERQEVENT event )

    // PTIMERQEVENT handler set to expire when it's time to give up on
    // receiving an acknowledge to the sent payload packet indicated in the
    // PAYLOADSENT* 'pContext'.
    //
{
    PAYLOADSENT* pPs;
    ADAPTERCB* pAdapter;
    TUNNELCB* pTunnel;
    VCCB* pVc;

    TRACE( TL_N, TM_Send,
        ( "SendPayloadTimerEvent(%s)", TimerQPszFromEvent( event ) ) );

    // Unpack context information.  The timer item is owned by the "payload
    // sent" context and freed indirectly by the de-referencing of that
    // context below.
    //
    pPs = (PAYLOADSENT* )pContext;
    pVc = pPs->pVc;
    pTunnel = pPs->pTunnel;
    pAdapter = pVc->pAdapter;

    if (event == TE_Expire)
    {
        LONG lOldSendWindow;
        LONG lSwChange;
        BOOLEAN fCallActive;
        LINKSTATUSINFO info;

        // Timer expired, meaning it's time to give up on ever receiving an
        // acknowledge to the sent packet.
        //
        NdisAcquireSpinLock( &pVc->lockV );
        do
        {
            if (pPs->linkSendsOut.Flink == &pPs->linkSendsOut)
            {
                // The context is not on the "outstanding send" list, so it
                // must have been cancelled or terminated while the expire
                // handling was being set up.  Do nothing.
                //
                TRACE( TL_I, TM_Send,
                    ( "C%d: Timeout aborted", (ULONG )pVc->usCallId ) );
                fCallActive = FALSE;
                break;
            }

            // This packet was not acknowledged.
            //
            pPs->status = NDIS_STATUS_FAILURE;

            // Remove the context from the "outstanding send" list.  The
            // corresponding dereference occurs below.
            //
            RemoveEntryList( &pPs->linkSendsOut );
            InitializeListHead( &pPs->linkSendsOut );

            // The rest has to do with call related fields so get a reference
            // now.  This is removed by the "reset" send completion.
            //
            fCallActive = ReferenceCall( pVc );
            if (fCallActive)
            {
                // Per the draft/RFC, adjustments to the send window and send
                // timeouts are necessary when a send times out.
                //
                lOldSendWindow = (LONG )pVc->ulSendWindow;
                AdjustTimeoutsAndSendWindowAtTimeout(
                    pAdapter->ulMaxSendTimeoutMs,
                    pVc->lDeviationMs,
                    &pVc->ulSendTimeoutMs,
                    &pVc->ulRoundTripMs,
                    &pVc->ulSendWindow,
                    &pVc->ulAcksSinceSendTimeout );
                lSwChange = ((LONG )pVc->ulSendWindow) - lOldSendWindow;

                TRACE( TL_I, TM_Send,
                    ( "C%d: TIMEOUT(%d) new rtt=%d ato=%d sw=%d(%+d)",
                    (ULONG )pVc->usCallId, (ULONG )pPs->usNs,
                    pVc->ulRoundTripMs, pVc->ulSendTimeoutMs,
                    pVc->ulSendWindow, lSwChange ) );

                if (lSwChange != 0)
                {
                    // The send window changed, i.e. it closed some because of
                    // the timeout.  Update the statistics accordingly.
                    //
                    ++pVc->stats.ulSendWindowChanges;

                    if (pVc->ulSendWindow > pVc->stats.ulMaxSendWindow)
                    {
                        pVc->stats.ulMaxSendWindow = pVc->ulSendWindow;
                    }
                    else if (pVc->ulSendWindow < pVc->stats.ulMinSendWindow)
                    {
                        pVc->stats.ulMinSendWindow = pVc->ulSendWindow;
                    }

                    // Need to release the lock before indicating the link
                    // status change outside our driver, so make a "safe" copy
                    // of the link status information.
                    //
                    TransferLinkStatusInfo( pVc, &info );
                }

                // Send a zero length payload with the R-bit set to reset the
                // peer's Nr to the packet after this one.  The call reference
                // will be removed when the send completes.
                //
                ScheduleTunnelWork(
                    pTunnel, pVc, SendPayloadReset,
                    (ULONG_PTR )(pPs->usNs + 1), 0, 0, 0, FALSE, FALSE );

                ++pVc->stats.ulSentResets;
                ++pVc->stats.ulSentPacketsTimedOut;
            }

            // Remove the reference for linkage in the "outstanding send"
            // list.
            //
            DereferencePayloadSent( pPs );

        }
        while (FALSE);
        NdisReleaseSpinLock( &pVc->lockV );

        if (fCallActive && lSwChange != 0)
        {
            // Inform NDISWAN of the new send window since it's the component
            // that actually does the throttling.
            //
            IndicateLinkStatus( pVc, &info );
        }
    }

    // Remove the reference covering the scheduled timer event.
    //
    DereferencePayloadSent( pPs );
}


VOID
SendZlb(
    IN TUNNELCB* pTunnel,
    IN VCCB* pVc,
    IN USHORT usNs,
    IN USHORT usNr,
    IN BOOLEAN fReset )

    // Send a data-less packet with sequence 'usNs' and 'usNr' on 'pTunnel'.
    // 'PVc' is the associated VC, or NULL if none.  When 'pVc' is provided,
    // 'fReset' may be set to indicate a payload reset is to be built,
    // otherwise a simple acknowledge is built.
    //
    // This routine is called only at PASSIVE IRQL.
    //
    // IMPORTANT: Caller must take a call reference before calling that is
    //            removed by the send completion handler.
    //
{
    NDIS_STATUS status;
    ADAPTERCB* pAdapter;
    CHAR* pBuffer;
    ULONG ulLength;
    USHORT usAssignedCallId;
    BOOLEAN fControl;
    NDIS_BUFFER* pNdisBuffer;

    pAdapter = pTunnel->pAdapter;

    usAssignedCallId = (pVc) ? pVc->usAssignedCallId : 0;
    fControl = (usAssignedCallId == 0);
    ASSERT( !(fReset && fControl) );

    if (!fControl && !(ReadFlags( &pTunnel->ulFlags ) & TCBF_HostRouteAdded))
    {
        TRACE( TL_A, TM_Send, ( "SendZlb w/o host route?" ) );
        ++g_ulSendZlbWithoutHostRoute;
        if (pVc)
        {
            DereferenceCall( pVc );
        }
        return;
    }

    // Get an NDIS_BUFFER to hold the L2TP header.
    //
    pBuffer = GetBufferFromPool( &pAdapter->poolHeaderBuffers );
    if (!pBuffer)
    {
        ASSERT( "GetBfP?" );
        if (pVc)
        {
            DereferenceCall( pVc );
        }
        return;
    }

    // Fill in 'pBuffer' with the L2TP header.
    //
    ulLength =
        BuildL2tpHeader(
            pBuffer,
            fControl,
            fReset,
            &pTunnel->usAssignedTunnelId,
            &usAssignedCallId,
            &usNs,
            usNr );

    // Pare down the buffer to the actual length used.
    //
    pNdisBuffer = NdisBufferFromBuffer( pBuffer );
    NdisAdjustBufferLength( pNdisBuffer, (UINT )ulLength );

    // Call TDI to send the bare L2TP header.
    //
    TRACE( TL_A, TM_Msg,
        ( "%sSEND ZLB(Nr=%d) CID=%d R=%d",
        (g_ulTraceLevel <= TL_I) ? "" : "\nL2TP: ",
        (ULONG )usNr, (ULONG )usAssignedCallId, (ULONG )fReset ) );
    DUMPW( TL_A, TM_MDmp, pBuffer, ulLength );

    {
        PTDIX_SEND_HANDLER SendFunc;
        FILE_OBJECT* FileObj;

        if (ReadFlags(&pTunnel->ulFlags) & TCBF_SendConnected) {

            ASSERT(pTunnel->pRoute != NULL);

            SendFunc = TdixSend;

            if (fControl)
            {
                FileObj = 
                    CtrlObjFromUdpContext(&pTunnel->udpContext);
            }
            else
            {
                FileObj = 
                    PayloadObjFromUdpContext(&pTunnel->udpContext);
            }

        } else {
            FileObj = NULL;
            SendFunc = TdixSendDatagram;
        }

        status = 
            SendFunc(
                &pAdapter->tdix,
                FileObj,
                SendHeaderComplete,
                pAdapter,
                pVc,
                &pTunnel->address.ulIpAddress,
                pBuffer,
                ulLength,
                NULL );
    }

    ASSERT( status == NDIS_STATUS_PENDING );
}


VOID
UpdateControlHeaderNr(
    IN CHAR* pBuffer,
    IN USHORT usNr )

    // Updates the 'Next Receive' field of control message buffer 'pBuffer'
    // with the value 'usNr'.
    //
{
    USHORT* pusNr;

    // Fortunately, the control header up to 'Next Receive' is fixed so a
    // simple offset calculation can be used.
    //
    pusNr = ((USHORT* )pBuffer) + 5;
    *pusNr = htons( usNr );
}


VOID
UpdateHeaderLength(
    IN CHAR* pBuffer,
    IN USHORT usLength )

    // Updates the 'Length' field of the L2TP message buffer 'pBuffer' to the
    // value 'usLength'.
    //
{
    USHORT* pusLength;

    // Fortunately, the control header up to 'Length' is fixed so a simple
    // offset calculation can be used.
    //
    pusLength = ((USHORT* )pBuffer) + 1;
    *pusLength = htons( usLength );
}