/*++

Copyright (c) 1990-1995  Microsoft Corporation

Module Name:

    Send.c

Abstract:

    This file contains the procedures for doing a send from a protocol, bound
    to the upper interface of NdisWan, to a Wan Miniport link, bound to the
    lower interfaceof NdisWan.  The upper interface of NdisWan conforms to the
    NDIS 3.1 Miniport specification.  The lower interface of NdisWan conforms
    to the NDIS 3.1 Extentions for Wan Miniport drivers.

Author:

    Tony Bell   (TonyBe) June 06, 1995

Environment:

    Kernel Mode

Revision History:

    TonyBe      06/06/95        Created

--*/

#include "wan.h"

#ifdef DBG_SENDARRAY
UCHAR   SendArray[MAX_BYTE_DEPTH] = {0};
ULONG   __si = 0;
#endif

#define __FILE_SIG__    SEND_FILESIG

//
// Local function prototypes
//
USHORT
DoVJHeaderCompression(
    PBUNDLECB   BundleCB,
    PNDIS_PACKET    NdisPacket,
    PUCHAR      *CurrentBuffer,
    PULONG      CurrentLength,
    PULONG      PacketOffset
    );

VOID
DoCompressionEncryption(
    PBUNDLECB               BundleCB,
    PHEADER_FRAMING_INFO    FramingInfo,
    PSEND_DESC              *SendDesc
    );

VOID
FragmentAndQueue(
    PBUNDLECB               BundleCB,
    PHEADER_FRAMING_INFO    FramingInfo,
    PSEND_DESC              SendDesc,
    PLIST_ENTRY             LinkCBList,
    ULONG                   SendingLinks
    );

ULONG
GetSendingLinks(
    PBUNDLECB   BundleCB,
    INT         Class,
    PLIST_ENTRY lcbList
    );

VOID
GetNextProtocol(
    PBUNDLECB   BundleCB,
    PPROTOCOLCB *ProtocolCB,
    PULONG      SendMask
    );

VOID
BuildLinkHeader(
    PHEADER_FRAMING_INFO    FramingInfo,
    PSEND_DESC              SendDesc
    );

//
// end of local function prototypes
//
VOID
NdisWanQueueSend(
    IN  PMINIPORTCB     MiniportCB,
    IN  PNDIS_PACKET    NdisPacket
    )
{
    PNDIS_BUFFER    NdisBuffer;
    UINT            BufferCount, PacketLength;
    PETH_HEADER     EthernetHeader;
    BOOLEAN         SendOnWire = FALSE;
    BOOLEAN         CompletePacket = FALSE;
    ULONG           BufferLength;
    PUCHAR          DestAddr, SrcAddr;
    PBUNDLECB       BundleCB = NULL;
    PPROTOCOLCB     ProtocolCB = NULL;
    PCM_VCCB        CmVcCB = NULL;
    INT             Class;
    PPACKET_QUEUE   PacketQueue;

    NdisWanDbgOut(DBG_TRACE, DBG_SEND, ("NdisWanQueueSend: Enter"));

    NdisWanInterlockedInc(&glSendCount);

    NdisQueryPacket(NdisPacket,
        NULL,
        &BufferCount,
        &NdisBuffer,
        &PacketLength);

    NdisQueryBuffer(NdisBuffer,
                    &EthernetHeader,
                    &BufferLength);

    CmVcCB = PMINIPORT_RESERVED_FROM_NDIS(NdisPacket)->CmVcCB;

    if (BufferCount == 0 || BufferLength < 14) {

        //
        // Malformed packet!
        //
        CompletePacket = TRUE;

        goto QUEUE_SEND_EXIT;
    }

    //
    // Check the ndis packet flags to make sure this is not just a loopback
    // packet.  If it is we will just complete it back to the stack per 
    // winse 22881.
    //
    if (NdisGetPacketFlags(NdisPacket) & NDIS_FLAGS_LOOPBACK_ONLY) {

        //
        // Complete the packet back to the stack without sending on
        // the wire.  We might need to do loopback for these packets
        // at somepoint.
        //
        CompletePacket = TRUE;

        goto QUEUE_SEND_EXIT;
    }

    DestAddr = EthernetHeader->DestAddr;
    SrcAddr = EthernetHeader->SrcAddr;

    //
    // Is this destined for the wire or is it self directed?
    // If SendOnWire is FALSE this is a self directed packet.
    //
    ETH_COMPARE_NETWORK_ADDRESSES_EQ(DestAddr, SrcAddr, &SendOnWire);

    //
    // Do we need to do loopback?  We can check for both multicast
    // and broadcast with one check because we don't differentiate
    // between the two.
    //
    if (!SendOnWire || (DestAddr[0] & 1)) {
        NdisWanIndicateLoopbackPacket(MiniportCB, NdisPacket);
    }

    //
    // We don't want to send packets from bloodhound
    //
    if (!SendOnWire ||
        (MiniportCB == NdisWanCB.PromiscuousAdapter)) {

        CompletePacket = TRUE;

        goto QUEUE_SEND_EXIT;
    }

    //
    // We play special tricks with NBF because NBF is
    // guaranteed to have a one-to-one mapping between
    // an adapter and a bundle.  We need to do this because
    // we may need the mac address information.
    //
    if (MiniportCB->ProtocolType == PROTOCOL_NBF) {

        ProtocolCB = MiniportCB->NbfProtocolCB;

        if (ProtocolCB == NULL) {

            //
            // This should just fall through and complete successfully.
            //
            NdisWanDbgOut(DBG_TRACE, DBG_SEND,
                ("NdisWanSend: Invalid ProtocolCB %x! Miniport %p, ProtoType %x",
                ProtocolCB, MiniportCB, MiniportCB->ProtocolType));

            CompletePacket = TRUE;

            goto QUEUE_SEND_EXIT;
        }

        BundleCB = ProtocolCB->BundleCB;

        if (BundleCB == NULL) {
            //
            // This should just fall through and complete successfully.
            //
            NdisWanDbgOut(DBG_FAILURE, DBG_SEND,
                ("NdisWanSend: Invalid Bundle!"));

            NdisWanDbgOut(DBG_FAILURE, DBG_SEND,
                ("NdisWanSend: MiniportCB: 0x%p, ProtocolType: 0x%x!", MiniportCB, MiniportCB->ProtocolType));

            CompletePacket = TRUE;

            goto QUEUE_SEND_EXIT;
        }

        AcquireBundleLock(BundleCB);

        if (BundleCB->State != BUNDLE_UP) {

            //
            // This should just fall through and complete successfully.
            //
            NdisWanDbgOut(DBG_FAILURE, DBG_SEND,
                ("NdisWanSend: Invalid BundleState 0x%x", BundleCB->State));

            NdisWanDbgOut(DBG_FAILURE, DBG_SEND,
                ("NdisWanSend: MiniportCB: 0x%p, ProtocolType: 0x%x!", MiniportCB, MiniportCB->ProtocolType));

            CompletePacket = TRUE;

            ReleaseBundleLock(BundleCB);

            BundleCB = NULL;

            goto QUEUE_SEND_EXIT;
        }

        REF_BUNDLECB(BundleCB);

    } else {
        ULONG_PTR   BIndex, PIndex;

        //
        // Get the ProtocolCB from the DestAddr
        //
        GetNdisWanIndices(DestAddr, BIndex, PIndex);

        if (!IsBundleValid((NDIS_HANDLE)BIndex, 
                           TRUE,
                           &BundleCB)) {
            //
            // This should just fall through and complete successfully.
            //
            NdisWanDbgOut(DBG_FAILURE, DBG_SEND,
                ("NdisWanSend: BundleCB is not valid!, BundleHandle: 0x%x", BIndex));
            NdisWanDbgOut(DBG_FAILURE, DBG_SEND,
                ("NdisWanSend: MiniportCB: 0x%p, ProtocolType: 0x%x!", MiniportCB, MiniportCB->ProtocolType));

            CompletePacket = TRUE;

            goto QUEUE_SEND_EXIT;
        }

        AcquireBundleLock(BundleCB);

        PROTOCOLCB_FROM_PROTOCOLH(BundleCB, ProtocolCB, PIndex);
    }

    if (ProtocolCB == NULL ||
        ProtocolCB == RESERVED_PROTOCOLCB) {
        //
        // This should just fall through and complete successfully.
        //
        NdisWanDbgOut(DBG_TRACE, DBG_SEND,
            ("NdisWanSend: Invalid ProtocolCB %x! Miniport %p, ProtoType %x",
            ProtocolCB, MiniportCB, MiniportCB->ProtocolType));

        CompletePacket = TRUE;

        ReleaseBundleLock(BundleCB);

        goto QUEUE_SEND_EXIT;
    }

    if (ProtocolCB->State != PROTOCOL_ROUTED) {

        NdisWanDbgOut(DBG_FAILURE, DBG_SEND,("NdisWanSend: Problem with route!"));

        NdisWanDbgOut(DBG_FAILURE, DBG_SEND,
            ("NdisWanSend: ProtocolCB: 0x%p, State: 0x%x",
            ProtocolCB, ProtocolCB->State));

        CompletePacket = TRUE;

        ReleaseBundleLock(BundleCB);

        goto QUEUE_SEND_EXIT;
    }

    //
    // For the packet that we are inserting
    //
    REF_PROTOCOLCB(ProtocolCB);

    NdisInterlockedIncrement(&ProtocolCB->OutstandingFrames);

    //
    // Queue the packet on the ProtocolCB NdisPacketQueue
    //
    Class = (CmVcCB != NULL) ? CmVcCB->FlowClass : 0;

    NDIS_SET_PACKET_STATUS(NdisPacket, NDIS_STATUS_PENDING);

    ASSERT(Class <= MAX_MCML);

    PacketQueue = &ProtocolCB->PacketQueue[Class];

    INSERT_DBG_SEND(PacketTypeNdis,
                    MiniportCB,
                    ProtocolCB,
                    NULL,
                    NdisPacket);

    InsertTailPacketQueue(PacketQueue, NdisPacket, PacketLength);

#ifdef DBG_SENDARRAY
{
    if (Class < MAX_MCML) {
        SendArray[__si] = 'P';
    } else {
        SendArray[__si] = 'Q';
    }
    if (++__si == MAX_BYTE_DEPTH) {
        __si = 0;
    }
}
#endif

    if (PacketQueue->ByteDepth > PacketQueue->MaxByteDepth) {
        //
        // We have queue more then we should so lets flush
        // This alogrithm should be fancied up at some point
        // to use Random Early Detection!!!!!
        //
        NdisPacket =
            RemoveHeadPacketQueue(PacketQueue);

        if (NdisPacket != NULL) {
            PacketQueue->DumpedPacketCount++;
            PacketQueue->DumpedByteCount +=
                (NdisPacket->Private.TotalLength - 14);
            ReleaseBundleLock(BundleCB);
            CompleteNdisPacket(ProtocolCB->MiniportCB,
                               ProtocolCB,
                               NdisPacket);
            AcquireBundleLock(BundleCB);
        }
    }

    //
    // If we are cleared to send data then
    // try to process the protocol queues
    //
    if (!(BundleCB->Flags & PAUSE_DATA)) {
        SendPacketOnBundle(ProtocolCB->BundleCB);
    } else {
        ReleaseBundleLock(BundleCB);
    }


QUEUE_SEND_EXIT:

    if (CompletePacket) {
        NDIS_SET_PACKET_STATUS(NdisPacket, NDIS_STATUS_SUCCESS);

        if (CmVcCB != NULL) {
            NdisMCoSendComplete(NDIS_STATUS_SUCCESS,
                CmVcCB->NdisVcHandle,
                NdisPacket);

            DEREF_CMVCCB(CmVcCB);

        } else {
            NdisMSendComplete(MiniportCB->MiniportHandle,
                NdisPacket,
                NDIS_STATUS_SUCCESS);
        }

        NdisWanInterlockedInc(&glSendCompleteCount);
    }

    //
    // Deref for ref applied in IsBundleValid
    //
    DEREF_BUNDLECB(BundleCB);

    NdisWanDbgOut(DBG_TRACE, DBG_SEND, ("NdisWanQueueSend: Exit"));
}


VOID
SendPacketOnBundle(
    PBUNDLECB   BundleCB
    )
/*++

Routine Name:

Routine Description:

    Called with bundle lock held but returns with lock released!!!

Arguments:

Return Values:

--*/
{
    NDIS_STATUS     Status = NDIS_STATUS_PENDING;
    ULONG           SendMask;
    PPROTOCOLCB     ProtocolCB, IOProtocolCB;
    BOOLEAN         PPPSent;

    NdisWanDbgOut(DBG_TRACE, DBG_SEND, ("SendPacketOnBundle: Enter"));

    //
    // Are we already involved in a send on this bundlecb?
    //
    if (BundleCB->Flags & IN_SEND) {

        //
        // If so flag that we should try back later
        // and get the out.
        //
        BundleCB->Flags |= TRY_SEND_AGAIN;

        ReleaseBundleLock(BundleCB);

        return;
    }

    BundleCB->Flags |= IN_SEND;

    IOProtocolCB = BundleCB->IoProtocolCB;

TryAgain:

    SendMask = BundleCB->SendMask;

    //
    // If the bundle is not up we will not send!
    //
    if (BundleCB->State != BUNDLE_UP) {
        goto TryNoMore;
    }

    do {
        BOOLEAN PacketSent = FALSE;
        BOOLEAN CouldSend;

        //
        // First try to send from the PPP send queue
        //
        do {

            CouldSend =
                SendFromPPP(BundleCB, IOProtocolCB, &PPPSent);

        } while (PPPSent);


        //
        // If we could not send a PPP frame get out
        //
        if (!CouldSend) {
            break;
        }

        //
        // This will force round-robin sends
        //
        GetNextProtocol(BundleCB, &ProtocolCB, &SendMask);

        if (ProtocolCB != NULL) {

            REF_PROTOCOLCB(ProtocolCB);

            if (BundleCB->Flags & QOS_ENABLED) {

                if (BundleCB->Flags & SEND_FRAGMENT) {
SendQosFrag:
                    //
                    // Send a single fragment from the fragment queue
                    //
                    CouldSend =
                        SendFromFragQueue(BundleCB,
                                          TRUE,
                                          &PacketSent);
                    if (CouldSend) {
                        BundleCB->Flags &= ~(SEND_FRAGMENT);
                    }
                }

                //
                // If we sent a fragment let the completion
                // handler send the next frame.
                //
                if (!PacketSent) {

                    //
                    // Now try the protocol's packet queues
                    //
                    if (SendMask != 0) {
                        INT Class;
                        INT i;

                        for (i = 0; i <= MAX_MCML; i++) {

                            CouldSend =
                                SendFromProtocol(BundleCB,
                                                 ProtocolCB,
                                                 &Class,
                                                 &SendMask,
                                                 &PacketSent);

                            if (!CouldSend) {
                                break;
                            }

                            BundleCB->Flags |= (SEND_FRAGMENT);

                            if (PacketSent) {
                                break;
                            }
                        }

                        if (!PacketSent ||
                            (PacketSent && (Class != MAX_MCML))) {

                            goto SendQosFrag;
                        }
                    }
                }

            } else {

                //
                // Now try the protocol's packet queues
                //
                if (SendMask != 0) {
                    INT Class;
                    INT i;

                    for (i = 0; i <= MAX_MCML; i++) {

                        CouldSend =
                            SendFromProtocol(BundleCB,
                                             ProtocolCB,
                                             &Class,
                                             &SendMask,
                                             &PacketSent);

                    }
                }

                SendFromFragQueue(BundleCB,
                                  FALSE,
                                  &PacketSent);
            }

            DEREF_PROTOCOLCB(ProtocolCB);
        }

    } while ((SendMask != 0) &&
             (BundleCB->State == BUNDLE_UP));

TryNoMore:

    //
    // Did someone try to do a send while we were already
    // sending on this bundle?
    //
    if (BundleCB->Flags & TRY_SEND_AGAIN) {

        //
        // If so clear the flag and try another send.
        //
        BundleCB->Flags &= ~TRY_SEND_AGAIN;

        goto TryAgain;

    }

#ifdef DBG_SENDARRAY
{
    SendArray[__si] = 'Z';
    if (++__si == MAX_BYTE_DEPTH) {
        __si = 0;
    }
}
#endif

    //
    // Clear the in send flag.
    //
    BundleCB->Flags &= ~IN_SEND;

    ReleaseBundleLock(BundleCB);

    NdisWanDbgOut(DBG_TRACE, DBG_SEND, ("SendPacketOnBundle: Exit"));
}

BOOLEAN
SendFromPPP(
    PBUNDLECB   BundleCB,
    PPROTOCOLCB ProtocolCB,
    PBOOLEAN    PacketSent
    )
{
    PLINKCB         LinkCB;
    PNDIS_PACKET    NdisPacket;
    PPACKET_QUEUE   PacketQueue;
    INT             SendingClass;
    BOOLEAN         CouldSend;
    ULONG           BytesSent;

    PacketQueue = &ProtocolCB->PacketQueue[MAX_MCML];

    CouldSend = TRUE;
    *PacketSent = FALSE;

    while (!IsPacketQueueEmpty(PacketQueue)) {
        LIST_ENTRY  LinkCBList;
        ULONG       SendingLinks;

        NdisPacket = PacketQueue->HeadQueue;

        LinkCB = 
            PPROTOCOL_RESERVED_FROM_NDIS(NdisPacket)->LinkCB;

        NdisAcquireSpinLock(&LinkCB->Lock);

        if (LinkCB->State != LINK_UP) {

            NdisReleaseSpinLock(&LinkCB->Lock);

            NdisReleaseSpinLock(&BundleCB->Lock);

            DEREF_LINKCB(LinkCB);

            NdisAcquireSpinLock(&BundleCB->Lock);

            //
            // The link has gone down since this send was
            // queued so destroy the packet
            //
            RemoveHeadPacketQueue(PacketQueue);
            FreeIoNdisPacket(NdisPacket);
            continue;
        }

        if (!LinkCB->SendWindowOpen) {
            //
            // We can not send from the I/O queue because the send
            // window for this link is closed.  We will not send
            // any data until the link has resources!
            //
            CouldSend = FALSE;

            NdisReleaseSpinLock(&LinkCB->Lock);

            break;
        }

        NdisReleaseSpinLock(&LinkCB->Lock);

        //
        // Build the linkcb send list
        //
        InitializeListHead(&LinkCBList);
        InsertHeadList(&LinkCBList, &LinkCB->SendLinkage);
        SendingLinks = 1;

        //
        // We are sending this packet so take it off of the list
        //
        RemoveHeadPacketQueue(PacketQueue);

        SendingClass = MAX_MCML;

        ASSERT(NdisPacket != NULL);
        ASSERT(ProtocolCB != NULL);

        //
        // We we get here we should have a valid NdisPacket with at least one link
        // that is accepting sends
        //

        //
        // We will get the packet into a contiguous buffer, and do framing,
        // compression and encryption.
        //
        REF_BUNDLECB(BundleCB);
        BytesSent = FramePacket(BundleCB,
                                ProtocolCB,
                                NdisPacket,
                                &LinkCBList,
                                SendingLinks,
                                SendingClass);
        *PacketSent = TRUE;
    }

    return (CouldSend);
}

BOOLEAN
SendFromProtocol(
    PBUNDLECB   BundleCB,
    PPROTOCOLCB ProtocolCB,
    PINT        RetClass,
    PULONG      SendMask,
    PBOOLEAN    PacketSent
    )
{
    ULONG           BytesSent;
    BOOLEAN         CouldSend;
    PNDIS_PACKET    NdisPacket;
    PPACKET_QUEUE   PacketQueue;
    INT             Class;
    LIST_ENTRY      LinkCBList;
    ULONG           SendingLinks;

    CouldSend = TRUE;
    *PacketSent = FALSE;
    InitializeListHead(&LinkCBList);

    ASSERT(ProtocolCB != NULL);

    do {

        Class = ProtocolCB->NextPacketClass;

        *RetClass = Class;

        *SendMask &= ~(ProtocolCB->SendMaskBit);

        PacketQueue =
            &ProtocolCB->PacketQueue[Class];

        if (IsPacketQueueEmpty(PacketQueue)) {
            break;
        }

        if (BundleCB->Flags & QOS_ENABLED) {

            if ((Class < MAX_MCML) &&
                (PacketQueue->OutstandingFrags != 0)) {
                break;
            }

        } else {

            if (BundleCB->SendingLinks == 0) {
                break;
            }
        }

        //
        // Build a list of linkcb's that can be sent over
        //

        SendingLinks =
            GetSendingLinks(BundleCB, Class, &LinkCBList);

        //
        // If there are no links/resources available
        // to send over then get out
        //
        if (SendingLinks == 0) {
            CouldSend = FALSE;
            break;
        }

        NdisPacket =
            RemoveHeadPacketQueue(PacketQueue);

        ASSERT(NdisPacket != NULL);

        *PacketSent = TRUE;

        if (!(BundleCB->Flags & QOS_ENABLED)) {
            *SendMask |= ProtocolCB->SendMaskBit;
        }

        //
        // We we get here we should have a valid NdisPacket with at least one link
        // that is accepting sends
        //
        //
        // We will get the packet into a contiguous buffer, and do framing,
        // compression and encryption.
        //
        REF_BUNDLECB(BundleCB);
        BytesSent = FramePacket(BundleCB,
                                ProtocolCB,
                                NdisPacket,
                                &LinkCBList,
                                SendingLinks,
                                Class);
#ifdef DBG_SENDARRAY
{
    if (Class < MAX_MCML) {
        SendArray[__si] = 'p';
    } else {
        SendArray[__si] = 'q';
    }
    if (++__si == MAX_BYTE_DEPTH) {
        __si = 0;
    }
}
#endif

    } while (FALSE);

    if (CouldSend) {
        ProtocolCB->NextPacketClass += 1;

        if (ProtocolCB->NextPacketClass > MAX_MCML) {
            ProtocolCB->NextPacketClass = 0;
        }
    }

    //
    // If there are any LinkCB's still on the send list
    // we have to remove the reference from them
    //
    if (!IsListEmpty(&LinkCBList)) {
        PLIST_ENTRY le;
        PLINKCB lcb;

        ReleaseBundleLock(BundleCB);

        //
        // unroll the loop so that the correct link
        // is setup for the next link to xmit
        //
        le = RemoveHeadList(&LinkCBList);
        lcb = CONTAINING_RECORD(le, LINKCB, SendLinkage);

        BundleCB->NextLinkToXmit = lcb;

        DEREF_LINKCB(lcb);

        while (!IsListEmpty(&LinkCBList)) {

            le = RemoveHeadList(&LinkCBList);
            lcb = CONTAINING_RECORD(le, LINKCB, SendLinkage);

            DEREF_LINKCB(lcb);

        }

        AcquireBundleLock(BundleCB);
    }

    return (CouldSend);
}

BOOLEAN
SendFromFragQueue(
    PBUNDLECB   BundleCB,
    BOOLEAN     SendOne,
    PBOOLEAN    FragSent
    )
{
    ULONG           i;
    BOOLEAN         CouldSend;

    CouldSend = TRUE;
    *FragSent = FALSE;

    for (i = 0; i < MAX_MCML; i++) {
        PSEND_DESC  SendDesc;
        PSEND_FRAG_INFO FragInfo;
        PLINKCB         LinkCB;

        FragInfo =
            &BundleCB->SendFragInfo[BundleCB->NextFragClass];

        BundleCB->NextFragClass += 1;

        if (BundleCB->NextFragClass == MAX_MCML) {
            BundleCB->NextFragClass = 0;
        }

        if (FragInfo->FragQueueDepth == 0) {
            continue;
        }

        SendDesc = (PSEND_DESC)FragInfo->FragQueue.Flink;

        LinkCB = SendDesc->LinkCB;

        while ((PVOID)SendDesc != (PVOID)&FragInfo->FragQueue) {
            ULONG   BytesSent;

            if (!LinkCB->SendWindowOpen) {
                //
                // We can't send on this link!
                //
                CouldSend = FALSE;
                SendDesc = (PSEND_DESC)SendDesc->Linkage.Flink;
                LinkCB = SendDesc->LinkCB;
                FragInfo->WinClosedCount++;
                continue;
            }

            CouldSend = TRUE;

            RemoveEntryList(&SendDesc->Linkage);

            FragInfo->FragQueueDepth--;

            *FragSent = TRUE;

            ASSERT((LONG)FragInfo->FragQueueDepth >= 0);

            BytesSent =
                (*LinkCB->SendHandler)(SendDesc);

#ifdef DBG_SENDARRAY
{
            SendArray[__si] = 0x40 + (UCHAR)LinkCB->hLinkHandle;
            if (++__si == MAX_BYTE_DEPTH) {
                __si = 0;
            }
}
#endif
            //
            // Update the bandwidth on demand sample array with the latest send.
            // If we need to notify someone of a bandwidth event do it.
            //
            if (BundleCB->Flags & BOND_ENABLED) {
                UpdateBandwidthOnDemand(BundleCB->SUpperBonDInfo, BytesSent);
                CheckUpperThreshold(BundleCB);
                UpdateBandwidthOnDemand(BundleCB->SLowerBonDInfo, BytesSent);
                CheckLowerThreshold(BundleCB);
            }

            SendDesc =
                (PSEND_DESC)FragInfo->FragQueue.Flink;
            LinkCB = SendDesc->LinkCB;

            //
            // If we are only supposed to send a single
            // fragment then we need to get out
            //
            if (SendOne) {
                break;
            }
        }

        //
        // If we are only supposed to send a single
        // fragment then we need to get out
        //
        if (SendOne) {
            break;
        }
    }

    return (CouldSend);
}

UINT
FramePacket(
    PBUNDLECB       BundleCB,
    PPROTOCOLCB     ProtocolCB,
    PNDIS_PACKET    NdisPacket,
    PLIST_ENTRY     LinkCBList,
    ULONG           SendingLinks,
    INT             Class
    )
{
    ULONG       Flags, BytesSent;
    ULONG       PacketOffset = 0, CurrentLength = 0;
    PUCHAR      CurrentData;
    PLINKCB     LinkCB = NULL;
    USHORT      PPPProtocolID;
    PSEND_DESC  SendDesc;
    HEADER_FRAMING_INFO FramingInfoBuffer;
    PHEADER_FRAMING_INFO FramingInfo = &FramingInfoBuffer;

    NdisWanDbgOut(DBG_TRACE, DBG_SEND, ("SendPacket: Enter"));

    ASSERT(!IsListEmpty(LinkCBList));

    Flags = BundleCB->SendFlags;

    PPPProtocolID = 
        ProtocolCB->PPPProtocolID;

    //
    // If this is a directed PPP packet then send to
    // the link indicated in the packet
    //
    if (PPPProtocolID == PPP_PROTOCOL_PRIVATE_IO) {
        Flags |= IO_PROTOCOLID;
        Flags &= ~(DO_COMPRESSION | DO_ENCRYPTION | DO_MULTILINK);
    }

    //
    // Did the last receive cause us to flush?
    //
    if ((Flags & (DO_COMPRESSION | DO_ENCRYPTION)) &&
        (BundleCB->Flags & RECV_PACKET_FLUSH)) {
        BundleCB->Flags &= ~RECV_PACKET_FLUSH;
        Flags |= DO_FLUSH;
    }

    Flags |= FIRST_FRAGMENT;

    if (Class == MAX_MCML) {

        Flags &= ~(DO_COMPRESSION | DO_ENCRYPTION | DO_MULTILINK);
    }

    //
    // Get a linkcb to send over
    //
    {
        PLIST_ENTRY  Entry;

        Entry = RemoveHeadList(LinkCBList);

        LinkCB =
            CONTAINING_RECORD(Entry, LINKCB, SendLinkage);
    }

    //
    // Get a send desc
    //
    {
        ULONG   PacketLength;

        NdisQueryPacket(NdisPacket,
                        NULL,
                        NULL,
                        NULL,
                        &PacketLength);
        SendDesc =
            NdisWanAllocateSendDesc(LinkCB, PacketLength);

        if (SendDesc == NULL) {

            ASSERT(SendDesc != NULL);

            ReleaseBundleLock(BundleCB);

            NDIS_SET_PACKET_STATUS(NdisPacket, NDIS_STATUS_RESOURCES);
            CompleteNdisPacket(ProtocolCB->MiniportCB,
                               ProtocolCB,
                               NdisPacket);
            AcquireBundleLock(BundleCB);

            goto FramePacketExit;
        }
    }
    

    BundleCB->OutstandingFrames++;

    SendDesc->ProtocolCB = ProtocolCB;
    SendDesc->OriginalPacket = NdisPacket;
    SendDesc->Class = Class;

    NdisWanDbgOut(DBG_TRACE, DBG_SEND,
        ("SendDesc: %p NdisPacket: %p", SendDesc, NdisPacket));

    //
    // Build a PPP Header in the buffer and update
    // current pointer
    //
    FramingInfo->FramingBits =
        LinkCB->LinkInfo.SendFramingBits;
    FramingInfo->Flags = Flags;
    FramingInfo->Class = Class;

    BuildLinkHeader(FramingInfo, SendDesc);

    CurrentData =
        SendDesc->StartBuffer + FramingInfo->HeaderLength;

    //
    // If we are in promiscuous mode we should indicate this
    // baby back up.
    //
    if (NdisWanCB.PromiscuousAdapter != NULL) {
        IndicatePromiscuousSendPacket(LinkCB, NdisPacket);
    }

    //
    // Copy MAC Header into buffer if needed and update
    // current pointer
    //
    if ((Flags & SAVE_MAC_ADDRESS) &&
        (PPPProtocolID == PPP_PROTOCOL_NBF)) {
        ULONG   BytesCopied;

        NdisWanCopyFromPacketToBuffer(NdisPacket,
                                      PacketOffset,
                                      12,
                                      CurrentData,
                                      &BytesCopied);

        ASSERT(BytesCopied == 12);

        CurrentData += BytesCopied;
        CurrentLength += BytesCopied;
    }

    //
    // We are beyond the mac header
    // (also skip the length/protocoltype field)
    //
    if (Flags & IO_PROTOCOLID) {
        PacketOffset = 12;
    } else {
        PacketOffset = 14;
    }

    if ((Flags & DO_VJ) &&
        PPPProtocolID == PPP_PROTOCOL_IP) {

        //
        // Do protocol header compression into buffer and
        // update current pointer.
        //
        PPPProtocolID =
            DoVJHeaderCompression(BundleCB,
                                  NdisPacket,
                                  &CurrentData,
                                  &CurrentLength,
                                  &PacketOffset);
    }

    //
    // Copy the rest of the data!
    //
    {
        ULONG   BytesCopied;
        NdisWanCopyFromPacketToBuffer(NdisPacket,
                                      PacketOffset,
                                      0xFFFFFFFF,
                                      CurrentData,
                                      &BytesCopied);

        SendDesc->DataLength =
            CurrentLength + BytesCopied;
    }

    AddPPPProtocolID(FramingInfo, PPPProtocolID);

    if (Flags & (DO_COMPRESSION | DO_ENCRYPTION)) {

        DoCompressionEncryption(BundleCB,
                                FramingInfo,
                                &SendDesc);
    }

    //
    // At this point we have our framinginfo structure initialized,
    // SendDesc->StartData pointing to the begining of the frame,
    // FramingInfo.HeaderLength is the length of the header,
    // SendDesc->DataLength is the length of the data.
    //
    if (Flags & DO_MULTILINK) {

        //
        // Fragment the data and place fragments
        // on bundles frag queue.
        //
        FragmentAndQueue(BundleCB,
                         FramingInfo,
                         SendDesc,
                         LinkCBList,
                         SendingLinks);

        BytesSent = 0;

    } else {

        //
        // This send descriptor is not to be fragmented
        // so just send it!
        //
        SendDesc->HeaderLength = FramingInfo->HeaderLength;

        InterlockedExchange(&(PMINIPORT_RESERVED_FROM_NDIS(NdisPacket)->RefCount), 1);

        BytesSent =
            (*LinkCB->SendHandler)(SendDesc);

    }

    if ((BundleCB->Flags & BOND_ENABLED) &&
        (BytesSent != 0)) {

        //
        // Update the bandwidth on demand sample array with the latest send.
        // If we need to notify someone of a bandwidth event do it.
        //
        UpdateBandwidthOnDemand(BundleCB->SUpperBonDInfo, BytesSent);
        CheckUpperThreshold(BundleCB);
        UpdateBandwidthOnDemand(BundleCB->SLowerBonDInfo, BytesSent);
        CheckLowerThreshold(BundleCB);
    }

FramePacketExit:


    NdisWanDbgOut(DBG_TRACE, DBG_SEND, ("SendPacket: Exit - BytesSent %d", BytesSent));

    return (BytesSent);
}

UINT
SendOnLegacyLink(
    PSEND_DESC  SendDesc
    )
{
    UINT        SendLength;
    PLINKCB     LinkCB = SendDesc->LinkCB;
    PBUNDLECB   BundleCB = LinkCB->BundleCB;
    PPROTOCOLCB ProtocolCB  = SendDesc->ProtocolCB;
    NDIS_STATUS Status = NDIS_STATUS_SUCCESS;
    PNDIS_WAN_PACKET    WanPacket = SendDesc->WanPacket;

    NdisWanDbgOut(DBG_TRACE, DBG_SEND, ("SendOnLegacyLink: LinkCB: 0x%p, SendDesc: 0x%p, WanPacket: 0x%p", LinkCB, SendDesc, WanPacket));

    LinkCB->OutstandingFrames++;
    if (LinkCB->SendWindow == LinkCB->OutstandingFrames) {
        LinkCB->SendWindowOpen = FALSE;
        BundleCB->SendingLinks--;
    }

#if 0
    if (LinkCB->Stats.FramesTransmitted == 0) {
        PUCHAR  pd;

        if (*(WanPacket->CurrentBuffer) != 0xFF) {
            pd = WanPacket->CurrentBuffer;
        } else {
            pd = (WanPacket->CurrentBuffer + 2);
        }

        if (*(pd) != 0xC0 ||
            *(pd+1) != 0x21 ||
            *(pd+2) != 0x01) {
            DbgPrint("NDISWAN: SLL-FirstFrame not LCP ConfigReq bcb %p, lcb %p\n",
                     BundleCB, LinkCB);
            DbgBreakPoint();
        }
    }
#endif

    SendLength =
    WanPacket->CurrentLength =
        SendDesc->HeaderLength + SendDesc->DataLength;

    WanPacket->ProtocolReserved1 = (PVOID)SendDesc;

    //
    // DoStats
    //
    LinkCB->Stats.FramesTransmitted++;
    BundleCB->Stats.FramesTransmitted++;
    LinkCB->Stats.BytesTransmitted += SendLength;
    BundleCB->Stats.BytesTransmitted += SendLength;

    INSERT_DBG_SEND(PacketTypeWan,
                    LinkCB->OpenCB,
                    ProtocolCB,
                    LinkCB,
                    WanPacket);

    ReleaseBundleLock(BundleCB);

    //
    // If the link is up send the packet
    //
    NdisAcquireSpinLock(&LinkCB->Lock);


    if (LinkCB->State == LINK_UP) {

        KIRQL   OldIrql;

        NdisReleaseSpinLock(&LinkCB->Lock);

        if (gbSniffLink &&
            (NdisWanCB.PromiscuousAdapter != NULL)) {

            IndicatePromiscuousSendDesc(LinkCB, SendDesc, SEND_LINK);
        }

        //
        // There is a problem in ndis right now where
        // the miniport lock is not acquired before sending
        // to the wan miniport.  This opens a window when
        // the miniport does a sendcomplete from within
        // it's send handler since sendcomplete expects
        // to be running at dpc.
        //
        KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);

        WanMiniportSend(&Status,
                        LinkCB->OpenCB->BindingHandle,
                        LinkCB->NdisLinkHandle,
                        WanPacket);

        KeLowerIrql(OldIrql);

    } else {
        NdisReleaseSpinLock(&LinkCB->Lock);
    }

    NdisWanDbgOut(DBG_TRACE, DBG_SEND, ("SendOnLegacyLink: Status: 0x%x", Status));

    //
    // If we get something other than pending back we need to
    // do the send complete.
    //
    if (Status != NDIS_STATUS_PENDING) {

        ProtoWanSendComplete(NULL,
                             WanPacket,
                             NDIS_STATUS_SUCCESS);
    }

    AcquireBundleLock(BundleCB);

    return (SendLength);
}

UINT
SendOnLink(
    PSEND_DESC  SendDesc
    )
{
    PLINKCB         LinkCB;
    PBUNDLECB       BundleCB;
    PPROTOCOLCB     ProtocolCB;
    PNDIS_PACKET    NdisPacket;
    PNDIS_BUFFER    NdisBuffer;
    UINT            SendLength;
    NDIS_STATUS     Status;


    LinkCB =
        SendDesc->LinkCB;

    ProtocolCB =
        SendDesc->ProtocolCB;

    NdisPacket =
        SendDesc->NdisPacket;

    NdisBuffer =
        SendDesc->NdisBuffer;

    BundleCB =
        LinkCB->BundleCB;


    NdisWanDbgOut(DBG_TRACE, DBG_SEND,
        ("SendOnLink: LinkCB: 0x%p, NdisPacket: 0x%p",
        LinkCB, NdisPacket));

    LinkCB->OutstandingFrames++;
    if (LinkCB->SendWindow == LinkCB->OutstandingFrames) {
        LinkCB->SendWindowOpen = FALSE;
        BundleCB->SendingLinks--;
    }

    PPROTOCOL_RESERVED_FROM_NDIS(NdisPacket)->SendDesc = SendDesc;

    INSERT_DBG_SEND(PacketTypeNdis,
                    LinkCB->OpenCB,
                    ProtocolCB,
                    LinkCB,
                    NdisPacket);

    SendLength =
        SendDesc->HeaderLength + SendDesc->DataLength;


    //
    // Fixup the bufferlength and chain at front
    //
    NdisAdjustBufferLength(NdisBuffer, SendLength);
    NdisRecalculatePacketCounts(NdisPacket);

    //
    // Do Stats
    //
    LinkCB->Stats.FramesTransmitted++;
    BundleCB->Stats.FramesTransmitted++;
    LinkCB->Stats.BytesTransmitted += SendLength;
    BundleCB->Stats.BytesTransmitted += SendLength;

    ReleaseBundleLock(BundleCB);

    //
    // If the link is up send the packet
    //
    NdisAcquireSpinLock(&LinkCB->Lock);

    LinkCB->VcRefCount++;

    if (LinkCB->State == LINK_UP) {

        NdisReleaseSpinLock(&LinkCB->Lock);

        if (gbSniffLink &&
            (NdisWanCB.PromiscuousAdapter != NULL)) {

            IndicatePromiscuousSendDesc(LinkCB, SendDesc, SEND_LINK);
        }

        NdisCoSendPackets(LinkCB->NdisLinkHandle,
                          &NdisPacket,
                          1);

    } else {

        NdisReleaseSpinLock(&LinkCB->Lock);

        ProtoCoSendComplete(NDIS_STATUS_SUCCESS,
                            LinkCB->hLinkHandle,
                            NdisPacket);
    }

    AcquireBundleLock(BundleCB);

    NdisWanDbgOut(DBG_TRACE, DBG_SEND, ("SendOnLink: Exit"));

    return (SendLength);
}

USHORT
DoVJHeaderCompression(
    PBUNDLECB   BundleCB,
    PNDIS_PACKET    NdisPacket,
    PUCHAR      *CurrentBuffer,
    PULONG      CurrentLength,
    PULONG      PacketOffset
    )
{
    UCHAR   CompType = TYPE_IP;
    PUCHAR  Header = *CurrentBuffer;
    ULONG   CopyLength;
    ULONG   HeaderLength;
    ULONG   PreCompHeaderLen = 0, PostCompHeaderLen = 0;
    NDIS_STATUS Status = NDIS_STATUS_SUCCESS;

    ASSERT(BundleCB->FramingInfo.SendFramingBits &
        (SLIP_VJ_COMPRESSION | PPP_FRAMING));

    NdisQueryPacket(NdisPacket,
        NULL,
        NULL,
        NULL,
        &CopyLength);

    CopyLength -= *PacketOffset;

    if (CopyLength > PROTOCOL_HEADER_LENGTH) {
        CopyLength = PROTOCOL_HEADER_LENGTH;
    }

    NdisWanCopyFromPacketToBuffer(NdisPacket,
        *PacketOffset,
        CopyLength,
        Header,
        &HeaderLength);

    ASSERT(CopyLength == HeaderLength);

    *PacketOffset += HeaderLength;

    //
    // Are we compressing TCP/IP headers?  There is a nasty
    // hack in VJs implementation for attempting to detect
    // interactive TCP/IP sessions.  That is, telnet, login,
    // klogin, eklogin, and ftp sessions.  If detected,
    // the traffic gets put on a higher TypeOfService (TOS).  We do
    // no such hack for RAS.  Also, connection ID compression
    // is negotiated, but we always don't compress it.
    //
    CompType =
        sl_compress_tcp(&Header,
        &HeaderLength,
        &PreCompHeaderLen,
        &PostCompHeaderLen,
        BundleCB->VJCompress,
        0);

    if (BundleCB->FramingInfo.SendFramingBits & SLIP_FRAMING) {
        Header[0] |= CompType;
    }

#if DBG
    if (CompType == TYPE_COMPRESSED_TCP) {
        NdisWanDbgOut(DBG_TRACE, DBG_SEND_VJ,("svj b %d a %d",PreCompHeaderLen, PostCompHeaderLen));
    }
#endif

    BundleCB->Stats.BytesTransmittedUncompressed +=
        PreCompHeaderLen;

    BundleCB->Stats.BytesTransmittedCompressed +=
        PostCompHeaderLen;

    if (CompType == TYPE_COMPRESSED_TCP) {
        PNDIS_BUFFER    MyBuffer;

        //
        // Source/Dest overlap so must use RtlMoveMemory
        //
        RtlMoveMemory(*CurrentBuffer, Header, HeaderLength);

        *CurrentBuffer += HeaderLength;
        *CurrentLength += HeaderLength;

        return (PPP_PROTOCOL_COMPRESSED_TCP);
    }

    *CurrentBuffer += HeaderLength;
    *CurrentLength += HeaderLength;

    switch (CompType) {
        case TYPE_IP:
            return (PPP_PROTOCOL_IP);
        case TYPE_UNCOMPRESSED_TCP:
            return (PPP_PROTOCOL_UNCOMPRESSED_TCP);
        default:
            DbgBreakPoint();
    }

    return (PPP_PROTOCOL_IP);
}

VOID
DoCompressionEncryption(
    PBUNDLECB               BundleCB,
    PHEADER_FRAMING_INFO    FramingInfo,
    PSEND_DESC              *SendDesc
    )
{
    ULONG   Flags = FramingInfo->Flags;
    PSEND_DESC  SendDesc1 = *SendDesc;
    PLINKCB LinkCB = SendDesc1->LinkCB;
    PUCHAR  DataBuffer, DataBuffer1;
    ULONG   DataLength;
    union {
        USHORT  uShort;
        UCHAR   uChar[2];
    }CoherencyCounter;

    NdisWanDbgOut(DBG_TRACE, DBG_SEND, ("DoCompressionEncryption: Enter"));

    //
    // If we are compressing/encrypting, the ProtocolID
    // is part of the compressed data so fix the pointer
    // and the length;
    //
    FramingInfo->HeaderLength -=
        FramingInfo->ProtocolID.Length;

    SendDesc1->DataLength += FramingInfo->ProtocolID.Length;

    DataBuffer = DataBuffer1 =
        SendDesc1->StartBuffer + FramingInfo->HeaderLength;

    DataLength =
        SendDesc1->DataLength;

    //
    // Get the coherency counter
    //
    CoherencyCounter.uShort = BundleCB->SCoherencyCounter;
    CoherencyCounter.uChar[1] &= 0x0F;

    //
    // Bump the coherency count
    //
    BundleCB->SCoherencyCounter++;

    if (Flags & DO_COMPRESSION) {
        PSEND_DESC  SendDesc2;
        PUCHAR  DataBuffer2;

        //
        // We need to get the max size here to protect
        // against expansion of the data
        //
        SendDesc2 =
            NdisWanAllocateSendDesc(LinkCB, glLargeDataBufferSize);

        if (SendDesc2 == NULL) {
            //
            // Just don't compress!
            //
            BundleCB->SCoherencyCounter--;
            return;
        }

        DataBuffer2 =
            SendDesc2->StartBuffer + FramingInfo->HeaderLength;

        BundleCB->Stats.BytesTransmittedUncompressed += DataLength;

        if (Flags & DO_FLUSH ||
            Flags & DO_HISTORY_LESS) {
            //
            // Init the compression history table and tree
            //
            initsendcontext(BundleCB->SendCompressContext);
        }

        //
        // We are doing the copy to get things into a contiguous buffer before
        // compression occurs
        //
        CoherencyCounter.uChar[1] |=
            compress(DataBuffer1,
                     DataBuffer2,
                     &DataLength,
                     BundleCB->SendCompressContext);

        if (CoherencyCounter.uChar[1] & PACKET_FLUSHED) {

            NdisWanFreeSendDesc(SendDesc2);

            //
            // If encryption is enabled this will force a
            // reinit of the table
            //
            Flags |= DO_FLUSH;

        } else {
            //
            // We compressed the packet so now the data is in
            // the CopyBuffer. We need to copy the PPP header
            // from DataBuffer to CopyBuffer.  The header
            // includes everything except for the protocolid field.
            //
            NdisMoveMemory(SendDesc2->StartBuffer,
                           SendDesc1->StartBuffer,
                           FramingInfo->HeaderLength);

            FramingInfo->ProtocolID.Length = 0;

            UpdateFramingInfo(FramingInfo, SendDesc2->StartBuffer);

            SendDesc2->DataLength = DataLength;
            SendDesc2->ProtocolCB = SendDesc1->ProtocolCB;
            SendDesc2->OriginalPacket = SendDesc1->OriginalPacket;
            SendDesc2->Class = SendDesc1->Class;
            NdisWanFreeSendDesc(SendDesc1);

            *SendDesc = SendDesc2;
            DataBuffer = DataBuffer2;
        }

        BundleCB->Stats.BytesTransmittedCompressed += DataLength;
    }

    //
    // If encryption is enabled encrypt the data in the
    // buffer.  Encryption is done inplace so additional
    // buffers are not needed.
    //
    // Do data encryption
    //
    if (Flags & DO_ENCRYPTION) {
        PUCHAR  SessionKey = BundleCB->SendCryptoInfo.SessionKey;
        ULONG   SessionKeyLength = BundleCB->SendCryptoInfo.SessionKeyLength;
        PVOID   SendRC4Key = BundleCB->SendCryptoInfo.RC4Key;

        //
        // We may need to reinit the rc4 table
        //
        if ((Flags & DO_FLUSH) &&
            !(Flags & DO_HISTORY_LESS)) {
            rc4_key(SendRC4Key, SessionKeyLength, SessionKey);
        }

        //
        // Mark this as being encrypted
        //
        CoherencyCounter.uChar[1] |= PACKET_ENCRYPTED;

        //
        // If we are in history-less mode we will
        // change the RC4 session key for every
        // packet, otherwise every 256 frames
        // change the RC4 session key
        //
        if ((Flags & DO_HISTORY_LESS) ||
            (BundleCB->SCoherencyCounter & 0xFF) == 0) {

            if (Flags & DO_LEGACY_ENCRYPTION) {
                //
                // Simple munge for legacy encryption
                //
                SessionKey[3] += 1;
                SessionKey[4] += 3;
                SessionKey[5] += 13;
                SessionKey[6] += 57;
                SessionKey[7] += 19;

            } else {

                //
                // Use SHA to get new sessionkey
                //
                GetNewKeyFromSHA(&BundleCB->SendCryptoInfo);

            }

            //
            // We use rc4 to scramble and recover a new key
            //

            //
            // Re-initialize the rc4 receive table to the
            // intermediate value
            //
            rc4_key(SendRC4Key, SessionKeyLength, SessionKey);

            //
            // Scramble the existing session key
            //
            rc4(SendRC4Key, SessionKeyLength, SessionKey);

            if (Flags & DO_40_ENCRYPTION) {

                //
                // If this is 40 bit encryption we need to fix
                // the first 3 bytes of the key.
                //
                SessionKey[0] = 0xD1;
                SessionKey[1] = 0x26;
                SessionKey[2] = 0x9E;

            } else if (Flags & DO_56_ENCRYPTION) {

                //
                // If this is 56 bit encryption we need to fix
                // the first byte of the key.
                //
                SessionKey[0] = 0xD1;
            }

            NdisWanDbgOut(DBG_TRACE, DBG_CCP,
                ("RC4 Send encryption KeyLength %d", BundleCB->SendCryptoInfo.SessionKeyLength));
            NdisWanDbgOut(DBG_TRACE, DBG_CCP,
                ("RC4 Send encryption Key %.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x",
                BundleCB->SendCryptoInfo.SessionKey[0],
                BundleCB->SendCryptoInfo.SessionKey[1],
                BundleCB->SendCryptoInfo.SessionKey[2],
                BundleCB->SendCryptoInfo.SessionKey[3],
                BundleCB->SendCryptoInfo.SessionKey[4],
                BundleCB->SendCryptoInfo.SessionKey[5],
                BundleCB->SendCryptoInfo.SessionKey[6],
                BundleCB->SendCryptoInfo.SessionKey[7],
                BundleCB->SendCryptoInfo.SessionKey[8],
                BundleCB->SendCryptoInfo.SessionKey[9],
                BundleCB->SendCryptoInfo.SessionKey[10],
                BundleCB->SendCryptoInfo.SessionKey[11],
                BundleCB->SendCryptoInfo.SessionKey[12],
                BundleCB->SendCryptoInfo.SessionKey[13],
                BundleCB->SendCryptoInfo.SessionKey[14],
                BundleCB->SendCryptoInfo.SessionKey[15]));

            //
            // Re-initialize the rc4 receive table to the
            // scrambled session key
            //
            rc4_key(SendRC4Key, SessionKeyLength, SessionKey);
        }

        //
        // Encrypt the data
        //
        rc4(SendRC4Key, DataLength, DataBuffer);
    }


    //
    // Did the last receive cause us to flush?
    //
    if (Flags & (DO_FLUSH | DO_HISTORY_LESS)) {
        CoherencyCounter.uChar[1] |= PACKET_FLUSHED;
    }

    //
    // Add the coherency bytes to the frame
    //
    AddCompressionInfo(FramingInfo, CoherencyCounter.uShort);

    ASSERT(((CoherencyCounter.uShort + 1) & 0x0FFF) ==
        (BundleCB->SCoherencyCounter & 0x0FFF));

    NdisWanDbgOut(DBG_TRACE, DBG_SEND, ("DoCompressionEncryption: Exit"));
}

VOID
FragmentAndQueue(
    PBUNDLECB               BundleCB,
    PHEADER_FRAMING_INFO    FramingInfo,
    PSEND_DESC              SendDesc,
    PLIST_ENTRY             LinkCBList,
    ULONG                   SendingLinks
    )
{
    ULONG       DataLeft;
    ULONG       FragmentsSent;
    ULONG       FragmentsLeft;
    ULONG       Flags;
    PLINKCB     LinkCB;
    PUCHAR      DataBuffer;
    ULONG       DataLength;
    PPROTOCOLCB ProtocolCB;
    PNDIS_PACKET    NdisPacket;
    PSEND_FRAG_INFO FragInfo;
    INT         Class;
#if DBG
    ULONG   MaxFragments;
#endif

    Class = SendDesc->Class;

    ASSERT(Class >= 0 && Class < MAX_MCML);

    FragInfo =
        &BundleCB->SendFragInfo[Class];

    DataBuffer =
        SendDesc->StartBuffer + FramingInfo->HeaderLength;

    DataLeft =
    DataLength = SendDesc->DataLength;

    FragmentsSent = 0;
    Flags = FramingInfo->Flags;
    LinkCB = SendDesc->LinkCB;
    ProtocolCB = SendDesc->ProtocolCB;
    NdisPacket = SendDesc->OriginalPacket;

    if (BundleCB->Flags & QOS_ENABLED) {

        FragmentsLeft = 
            (DataLength/FragInfo->MaxFragSize == 0) ?
            1 : DataLength/FragInfo->MaxFragSize;

        if (DataLength > FragInfo->MaxFragSize * FragmentsLeft) {
            FragmentsLeft += 1;
        }

        if (FragmentsLeft > BundleCB->SendResources) {
            FragmentsLeft = BundleCB->SendResources;
        }

    } else {

        FragmentsLeft = SendingLinks;
    }

#if DBG
    MaxFragments = FragmentsLeft;
#endif

    //
    // For all fragments we loop fixing up the multilink header
    // if multilink is on, fixing up pointers in the wanpacket,
    // and queuing the wanpackets for further processing.
    //
    while (DataLeft) {
        ULONG   FragDataLength;

        if (!(Flags & FIRST_FRAGMENT)) {
            PLIST_ENTRY  Entry;

            //
            // We had more than one fragment, get the next
            // link to send over and a wanpacket from the
            // link.
            //
            //
            // Get a linkcb to send over
            //
            if (IsListEmpty(LinkCBList)) {
                ULONG   Count;

                Count = 
                    GetSendingLinks(BundleCB, Class, LinkCBList);

                if (Count == 0) {
                    //
                    //
                    //
                    DbgPrint("NDISWAN: FragmentAndQueue LinkCBCount %d\n", Count);
                    continue;
                }
            }

            Entry = RemoveHeadList(LinkCBList);

            LinkCB =
                CONTAINING_RECORD(Entry, LINKCB, SendLinkage);

            SendDesc =
                NdisWanAllocateSendDesc(LinkCB, DataLeft + 6);

            if (SendDesc == NULL) {
                //
                // 
                //
                InsertTailList(LinkCBList, &LinkCB->SendLinkage);

                DbgPrint("NDISWAN: FragmentAndQueue SendDesc == NULL! LinkCB: 0x%p\n", LinkCB);
                continue;
            }

            SendDesc->ProtocolCB = ProtocolCB;
            SendDesc->OriginalPacket = NdisPacket;
            SendDesc->Class = Class;

            //
            // Get new framing information and build a new
            // header for the new link.
            //
            FramingInfo->FramingBits = 
                LinkCB->LinkInfo.SendFramingBits;
            FramingInfo->Flags = Flags;

            BuildLinkHeader(FramingInfo, SendDesc);
        }

        if (FragmentsLeft > 1) {

            //
            // Calculate the length of this fragment
            //
            FragDataLength = (DataLength * LinkCB->SBandwidth / 100);

            if (BundleCB->Flags & QOS_ENABLED) {

                FragDataLength = (FragDataLength > FragInfo->MaxFragSize) ?
                    FragInfo->MaxFragSize : FragDataLength;

            } else {

                FragDataLength = (FragDataLength < FragInfo->MinFragSize) ?
                    FragInfo->MinFragSize : FragDataLength;
            }

            if ((FragDataLength > DataLeft) ||
                ((LONG)DataLeft - FragDataLength < FragInfo->MinFragSize)) {
                //
                // This will leave a fragment of less than min frag size
                // so send all of the data
                //
                FragDataLength = DataLeft;
                FragmentsLeft = 1;
            }

        } else {
            //
            // We either have one fragment left or this link has
            // more than 85 percent of the bundle so send what
            // data is left
            //
            FragDataLength = DataLeft;
            FragmentsLeft = 1;
        }

        if (!(Flags & FIRST_FRAGMENT)) {
            //
            // Copy the data to the new buffer from the old buffer.
            //
            NdisMoveMemory(SendDesc->StartBuffer + FramingInfo->HeaderLength,
                           DataBuffer,
                           FragDataLength);
        }

        //
        // Update the data pointer and the length left to send
        //
        DataBuffer += FragDataLength;
        DataLeft -= FragDataLength;

        {
            UCHAR   MultilinkFlags = 0;

            //
            // Multlink is on so create flags for this
            // fragment.
            //
            if (Flags & FIRST_FRAGMENT) {
                MultilinkFlags = MULTILINK_BEGIN_FRAME;
                Flags &= ~FIRST_FRAGMENT;
            }

            if (FragmentsLeft == 1) {
                MultilinkFlags |= MULTILINK_END_FRAME;
            }

            //
            // Add the multilink header information and
            // take care of the sequence number.
            //
            AddMultilinkInfo(FramingInfo,
                             MultilinkFlags,
                             FragInfo->SeqNumber,
                             BundleCB->SendSeqMask);

            NdisWanDbgOut(DBG_INFO, DBG_MULTILINK_SEND, ("sf %x %x %d",
                FragInfo->SeqNumber, MultilinkFlags, FragDataLength));

            FragInfo->SeqNumber++;
        }

        //
        // Setup the SEND_DESC
        //
        SendDesc->HeaderLength = FramingInfo->HeaderLength;
        SendDesc->DataLength = FragDataLength;
        SendDesc->Flags |= SEND_DESC_FRAG;

        //
        // Queue for further processing.
        //
        InsertTailList(&FragInfo->FragQueue, &SendDesc->Linkage);

        FragInfo->FragQueueDepth++;

        FragmentsSent++;
        FragmentsLeft--;

    }   // end of the fragment loop

    ASSERT(FragmentsLeft == 0);

    InterlockedExchangeAdd(&ProtocolCB->PacketQueue[Class].OutstandingFrags, (LONG)FragmentsSent);

#ifdef DBG_SENDARRAY
{
    SendArray[__si] = '0' + (UCHAR)FragmentsSent;
    if (++__si == MAX_BYTE_DEPTH) {
        __si = 0;
    }
}
#endif

    //
    // Get the mac reserved structure from the ndispacket.  This
    // is where we will keep the reference count on the packet.
    //
    ASSERT(((LONG)FragmentsSent > 0) && (FragmentsSent <= MaxFragments));

    InterlockedExchange(&(PMINIPORT_RESERVED_FROM_NDIS(SendDesc->OriginalPacket)->RefCount), FragmentsSent);

    NdisWanDbgOut(DBG_TRACE, DBG_SEND, ("NdisPacket: 0x%p RefCount %d", SendDesc->OriginalPacket, FragmentsSent));
}

ULONG
GetSendingLinks(
    PBUNDLECB   BundleCB,
    INT         Class,
    PLIST_ENTRY lcbList
    )
{
    ULONG   SendingLinks;
    PLINKCB LinkCB, StartLinkCB, LastLinkCB;

    StartLinkCB = LinkCB = LastLinkCB =
        BundleCB->NextLinkToXmit;

    SendingLinks = 0;

    //
    // If this is a fragmented send...
    // If QOS is enabled we just need some send resources
    // If QOS is not enabled we need sending links
    // If this is a non-fragmented send...
    // We need sending links
    //

    if (LinkCB != NULL) {

        if (Class == MAX_MCML) {

            do {

                NdisDprAcquireSpinLock(&LinkCB->Lock);

                if ((LinkCB->State == LINK_UP) &&
                    LinkCB->LinkActive && 
                    LinkCB->SendWindowOpen) {

                    InsertTailList(lcbList, &LinkCB->SendLinkage);

                    REF_LINKCB(LinkCB);

                    SendingLinks += 1;
                    LastLinkCB = LinkCB;
                }

                NdisDprReleaseSpinLock(&LinkCB->Lock);

                LinkCB = (PLINKCB)LinkCB->Linkage.Flink;

                if ((PVOID)LinkCB == (PVOID)&BundleCB->LinkCBList) {
                    LinkCB = (PLINKCB)BundleCB->LinkCBList.Flink;
                }

            } while (LinkCB != StartLinkCB);

        } else {
            if (BundleCB->Flags& QOS_ENABLED) {

                do {

                    NdisDprAcquireSpinLock(&LinkCB->Lock);

                    if ((LinkCB->State == LINK_UP) &&
                        LinkCB->LinkActive && 
                        (LinkCB->SendResources != 0)) {
                        InsertTailList(lcbList, &LinkCB->SendLinkage);

                        REF_LINKCB(LinkCB);

                        SendingLinks += 1;
                        LastLinkCB = LinkCB;
                    }

                    NdisDprReleaseSpinLock(&LinkCB->Lock);

                    LinkCB = (PLINKCB)LinkCB->Linkage.Flink;

                    if ((PVOID)LinkCB == (PVOID)&BundleCB->LinkCBList) {
                        LinkCB = (PLINKCB)BundleCB->LinkCBList.Flink;
                    }

                } while (LinkCB != StartLinkCB);

            } else {

                do {

                    NdisDprAcquireSpinLock(&LinkCB->Lock);

                    if ((LinkCB->State == LINK_UP) &&
                        LinkCB->LinkActive && 
                        LinkCB->SendWindowOpen) {
                        InsertTailList(lcbList, &LinkCB->SendLinkage);

                        REF_LINKCB(LinkCB);

                        SendingLinks += 1;
                        LastLinkCB = LinkCB;
                    }

                    NdisDprReleaseSpinLock(&LinkCB->Lock);

                    LinkCB = (PLINKCB)LinkCB->Linkage.Flink;

                    if ((PVOID)LinkCB == (PVOID)&BundleCB->LinkCBList) {
                        LinkCB = (PLINKCB)BundleCB->LinkCBList.Flink;
                    }

                } while (LinkCB != StartLinkCB);
            }
        }

        BundleCB->NextLinkToXmit =
            (LastLinkCB->Linkage.Flink == &BundleCB->LinkCBList) ?
            (PLINKCB)BundleCB->LinkCBList.Flink : 
            (PLINKCB)LastLinkCB->Linkage.Flink;
    }

#ifdef DBG_SENDARRAY
{
    if (SendingLinks == 0) {
        SendArray[__si] = 'g';
    } else {
        SendArray[__si] = 'G';
    }
    if (++__si == MAX_BYTE_DEPTH) {
        __si = 0;
    }
}
#endif

    return (SendingLinks);
}

VOID
GetNextProtocol(
    PBUNDLECB   BundleCB,
    PPROTOCOLCB *ProtocolCB,
    PULONG      SendMask
    )
{
    PLIST_ENTRY     ppcblist;
    PPROTOCOLCB     ppcb;
    ULONG           mask;
    ULONG           i;
    BOOLEAN         Found;

    *ProtocolCB = NULL;
    mask = *SendMask;
    *SendMask = 0;

    ppcb = BundleCB->NextProtocol;

    if (ppcb == NULL) {
        return;
    }

    //
    // There is a window where we could have set the initial
    // send mask and had a protocol removed without clearing
    // it's send bit.  If we 'and' the temp mask with the 
    // bundle's mask we should clear out any bits that are
    // left dangling.
    //
    mask &= BundleCB->SendMask;

    //
    // Starting with the next flagged protocol
    // see if it can send.  If not clear its
    // sendbit from the mask and go to the next.
    // If none can send mask will be 0 and
    // protocol will be NULL.  We know that there
    // are only ulnumberofroutes in table so only
    // look for that many.
    //

    i = BundleCB->ulNumberOfRoutes;
    Found = FALSE;

    do {

        if (ppcb->State == PROTOCOL_ROUTED) {
            *ProtocolCB = ppcb;
            Found = TRUE;
        } else {
            mask &= ~ppcb->SendMaskBit;
        }

        ppcb = (PPROTOCOLCB)ppcb->Linkage.Flink;

        if ((PVOID)ppcb == (PVOID)&BundleCB->ProtocolCBList) {

            ppcb = (PPROTOCOLCB)BundleCB->ProtocolCBList.Flink;
        }

        if (Found) {
            BundleCB->NextProtocol = ppcb;
            break;
        }

    } while ( --i );

    if (*ProtocolCB != NULL) {
        *SendMask = mask;
    }
}

NDIS_STATUS
BuildIoPacket(
    IN  PLINKCB             LinkCB,
    IN  PBUNDLECB           BundleCB,
    IN  PNDISWAN_IO_PACKET  pWanIoPacket,
    IN  BOOLEAN             SendImmediate
    )
/*++

Routine Name:

Routine Description:

Arguments:

Return Values:

--*/
{
    NDIS_STATUS Status = NDIS_STATUS_RESOURCES;
    ULONG   Stage = 0;
    ULONG   DataLength;
    PUCHAR  DataBuffer, pSrcAddr, pDestAddr;
    PNDIS_PACKET    NdisPacket;
    PNDIS_BUFFER    NdisBuffer;
    PPROTOCOLCB     IoProtocolCB;
    PSEND_DESC      SendDesc;
    UCHAR   SendHeader[] = {' ', 'S', 'E', 'N', 'D', 0xFF};

    NdisWanDbgOut(DBG_TRACE, DBG_SEND, ("BuildIoPacket: Enter!"));
    //
    // Some time in the future this should be redone so that
    // there is a pool of packets and buffers attached to a
    // BundleCB.  This pool could be grown and shrunk as needed
    // but some minimum number would live for the lifetime of
    // the BundleCB.

    //
    // Allocate needed resources
    //
    {
        ULONG   SizeNeeded;

        //
        // Need max of 18 bytes; 4 bytes for ppp/llc header and
        // 14 for MAC address
        //
        SizeNeeded = 18;

        //
        // The header will either be given to us or
        // it will be added by us (ethernet mac header)
        //
        SizeNeeded += (pWanIoPacket->usHeaderSize > 0) ?
            pWanIoPacket->usHeaderSize : MAC_HEADER_LENGTH;

        //
        // Amount of data we need to send
        //
        SizeNeeded += pWanIoPacket->usPacketSize;

        Status = 
            AllocateIoNdisPacket(SizeNeeded,
                                 &NdisPacket,
                                 &NdisBuffer, 
                                 &DataBuffer);

        if (Status != NDIS_STATUS_SUCCESS) {

            NdisWanDbgOut(DBG_FAILURE, DBG_SEND, 
                          ("BuildIoPacket: Error Allocating IoNdisPacket!"));

            DEREF_LINKCB(LinkCB);

            return (NDIS_STATUS_RESOURCES);
        }
    }

    PPROTOCOL_RESERVED_FROM_NDIS(NdisPacket)->LinkCB = LinkCB;

    //
    // We only support ethernet headers right now so the supplied header
    // either has to be ethernet or none at all!
    //
    pDestAddr = &DataBuffer[0];
    pSrcAddr = &DataBuffer[6];

    //
    // If no header build a header
    //
    if (pWanIoPacket->usHeaderSize == 0) {

        //
        // Header will look like " S XXYYYY" where
        // XX is the ProtocolCB index and YYYY is the
        // BundleCB index.  Both the Src and Dst addresses
        // look the same.
        //
        NdisMoveMemory(pDestAddr,
                       SendHeader,
                       sizeof(SendHeader));

        NdisMoveMemory(pSrcAddr,
                       SendHeader,
                       sizeof(SendHeader));

        //
        // Fill the BundleCB Index for the Src and Dest Address
        //
        pDestAddr[5] = pSrcAddr[5] = 
            (UCHAR)LinkCB->hLinkHandle;

        DataLength = 12;

    } else {
        //
        // Header supplied so go ahead and move it.
        //
        NdisMoveMemory(pDestAddr,
                       pWanIoPacket->PacketData,
                       pWanIoPacket->usHeaderSize);

        DataLength = pWanIoPacket->usHeaderSize;
    }

    //
    // Copy the data to the buffer
    //
    NdisMoveMemory(&DataBuffer[12],
                   &pWanIoPacket->PacketData[pWanIoPacket->usHeaderSize],
                   pWanIoPacket->usPacketSize);

    DataLength += pWanIoPacket->usPacketSize;

    //
    // Adjust buffer length and chain buffer to ndis packet
    //
    NdisAdjustBufferLength(NdisBuffer, DataLength);
    NdisRecalculatePacketCounts(NdisPacket);

    //
    // Queue the packet on the bundlecb
    //
    IoProtocolCB = BundleCB->IoProtocolCB;

    ASSERT(IoProtocolCB != NULL);

    if (SendImmediate) {
        InsertHeadPacketQueue(&IoProtocolCB->PacketQueue[MAX_MCML],
                              NdisPacket, DataLength);
    } else {
        InsertTailPacketQueue(&IoProtocolCB->PacketQueue[MAX_MCML],
                              NdisPacket, DataLength);
    }

    InterlockedIncrement(&IoProtocolCB->OutstandingFrames);

    //
    // Try to send
    //
    // Called with lock held and returns with
    // lock released
    //
    SendPacketOnBundle(BundleCB);

    AcquireBundleLock(BundleCB);

    NdisWanDbgOut(DBG_TRACE, DBG_SEND, ("BuildIoPacket: Exit\n"));

    return (NDIS_STATUS_SUCCESS);
}

VOID
BuildLinkHeader(
    PHEADER_FRAMING_INFO    FramingInfo,
    PSEND_DESC              SendDesc
    )
/*++

Routine Name:

Routine Description:

Arguments:

Return Values:

--*/
{
    ULONG   LinkFraming = FramingInfo->FramingBits;
    ULONG   Flags = FramingInfo->Flags;
    PUCHAR  CurrentPointer = SendDesc->StartBuffer;

    FramingInfo->HeaderLength =
        FramingInfo->AddressControl.Length =
        FramingInfo->Multilink.Length =
        FramingInfo->Compression.Length =
        FramingInfo->ProtocolID.Length = 0;

    if (LinkFraming & PPP_FRAMING) {

        if (!(LinkFraming & PPP_COMPRESS_ADDRESS_CONTROL)) {
            //
            // If there is no address/control compression
            // we need a pointer and a length
            //

            if (LinkFraming & LLC_ENCAPSULATION) {
                FramingInfo->AddressControl.Pointer = CurrentPointer;
                *CurrentPointer++ = 0xFE;
                *CurrentPointer++ = 0xFE;
                *CurrentPointer++ = 0x03;
                *CurrentPointer++ = 0xCF;
                FramingInfo->AddressControl.Length = 4;
                FramingInfo->HeaderLength += FramingInfo->AddressControl.Length;

            } else {
                FramingInfo->AddressControl.Pointer = CurrentPointer;
                *CurrentPointer++ = 0xFF;
                *CurrentPointer++ = 0x03;
                FramingInfo->AddressControl.Length = 2;
                FramingInfo->HeaderLength += FramingInfo->AddressControl.Length;
            }
        }

        if (!(Flags & IO_PROTOCOLID)) {

            //
            // If this is not from our private I/O interface we will
            // build the rest of the header.
            //
            if ((Flags & DO_MULTILINK) && (LinkFraming & PPP_MULTILINK_FRAMING)) {

                //
                // We are doing multilink so we need a pointer
                // and a length
                //
                FramingInfo->Multilink.Pointer = CurrentPointer;

                if (!(LinkFraming & PPP_COMPRESS_PROTOCOL_FIELD)) {
                    //
                    // No protocol compression
                    //
                    *CurrentPointer++ = 0x00;
                    FramingInfo->Multilink.Length++;
                }

                *CurrentPointer++ = 0x3D;
                FramingInfo->Multilink.Length++;

                if (!(LinkFraming & PPP_SHORT_SEQUENCE_HDR_FORMAT)) {
                    //
                    // We are using long sequence number
                    //
                    FramingInfo->Multilink.Length += 2;
                    CurrentPointer += 2;

                }

                FramingInfo->Multilink.Length += 2;
                CurrentPointer += 2;

                FramingInfo->HeaderLength += FramingInfo->Multilink.Length;

            }

            if (Flags & FIRST_FRAGMENT) {

                if (Flags & (DO_COMPRESSION | DO_ENCRYPTION)) {
                    //
                    // We are doing compression/encryption so we need
                    // a pointer and a length
                    //
                    FramingInfo->Compression.Pointer = CurrentPointer;

                    //
                    // It appears that legacy ras (< NT 4.0) requires that
                    // the PPP protocol field in a compressed packet not
                    // be compressed, ie has to have the leading 0x00
                    //
                    if (!(LinkFraming & PPP_COMPRESS_PROTOCOL_FIELD)) {
                        //
                        // No protocol compression
                        //
                        *CurrentPointer++ = 0x00;
                        FramingInfo->Compression.Length++;
                    }

                    *CurrentPointer++ = 0xFD;
                    FramingInfo->Compression.Length++;

                    //
                    // Add coherency bytes
                    //
                    FramingInfo->Compression.Length += 2;
                    CurrentPointer += 2;

                    FramingInfo->HeaderLength += FramingInfo->Compression.Length;
                }


                FramingInfo->ProtocolID.Pointer = CurrentPointer;

                if (!(LinkFraming & PPP_COMPRESS_PROTOCOL_FIELD) ||
                    (Flags & (DO_COMPRESSION | DO_ENCRYPTION))) {
                    FramingInfo->ProtocolID.Length++;
                    CurrentPointer++;
                }

                FramingInfo->ProtocolID.Length++;
                FramingInfo->HeaderLength += FramingInfo->ProtocolID.Length;
                CurrentPointer++;
            }
        }


    } else if ((LinkFraming & RAS_FRAMING)) {
        //
        // If this is old ras framing:
        //
        // Alter the framing so that 0xFF 0x03 is not added
        // and that the first byte is 0xFD not 0x00 0xFD
        //
        // So basically, a RAS compression looks like
        // <0xFD> <2 BYTE COHERENCY> <NBF DATA FIELD>
        //
        // Whereas uncompressed looks like
        // <NBF DATA FIELD> which always starts with 0xF0
        //
        // If this is ppp framing:
        //
        // A compressed frame will look like (before address/control
        // - multilink is added)
        // <0x00> <0xFD> <2 Byte Coherency> <Compressed Data>
        //
        if (Flags & (DO_COMPRESSION | DO_ENCRYPTION)) {
            FramingInfo->Compression.Pointer = CurrentPointer;

            *CurrentPointer++ = 0xFD;
            FramingInfo->Compression.Length++;

            //
            // Coherency bytes
            //
            FramingInfo->Compression.Length += 2;
            CurrentPointer += 2;

            FramingInfo->HeaderLength += FramingInfo->Compression.Length;
        }
    }
}

VOID
IndicatePromiscuousSendPacket(
    PLINKCB         LinkCB,
    PNDIS_PACKET    NdisPacket
    )
{
    PNDIS_BUFFER    NdisBuffer;
    PNDIS_PACKET    LocalNdisPacket;
    NDIS_STATUS     Status;
    PRECV_DESC      RecvDesc;
    PBUNDLECB       BundleCB = LinkCB->BundleCB;
    KIRQL           OldIrql;
    PMINIPORTCB     Adapter;
    ULONG           PacketLength;

    NdisAcquireSpinLock(&NdisWanCB.Lock);
    Adapter = NdisWanCB.PromiscuousAdapter;
    NdisReleaseSpinLock(&NdisWanCB.Lock);

    if (Adapter == NULL) {
        return;
    }

    NdisQueryPacket(NdisPacket, 
                    NULL, 
                    NULL, 
                    NULL, 
                    &PacketLength);

    RecvDesc =
        NdisWanAllocateRecvDesc(PacketLength);

    if (RecvDesc == NULL) {
        return;
    }

    //
    // Get an ndis packet
    //
    LocalNdisPacket =
        RecvDesc->NdisPacket;

    NdisWanCopyFromPacketToBuffer(NdisPacket,
        0,
        0xFFFFFFFF,
        RecvDesc->StartBuffer,
        &RecvDesc->CurrentLength);

    PPROTOCOL_RESERVED_FROM_NDIS(LocalNdisPacket)->RecvDesc = RecvDesc;

    //
    // Attach the buffers
    //
    NdisAdjustBufferLength(RecvDesc->NdisBuffer,
                           RecvDesc->CurrentLength);

    NdisRecalculatePacketCounts(LocalNdisPacket);

    ReleaseBundleLock(BundleCB);

    KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);

    NDIS_SET_PACKET_STATUS(LocalNdisPacket, NDIS_STATUS_RESOURCES);

    INSERT_DBG_RECV(PacketTypeNdis, 
                    Adapter, 
                    NULL, 
                    LinkCB, 
                    LocalNdisPacket);

    //
    // Indicate the packet
    // This assumes that bloodhound is always a legacy transport
    //
    NdisMIndicateReceivePacket(Adapter->MiniportHandle,
                               &LocalNdisPacket,
                               1);

    KeLowerIrql(OldIrql);

    Status = NDIS_GET_PACKET_STATUS(LocalNdisPacket);

    ASSERT(Status == NDIS_STATUS_RESOURCES);

    REMOVE_DBG_RECV(PacketTypeNdis, Adapter, LocalNdisPacket);

    {
        PNDIS_BUFFER    NdisBuffer;

        NdisWanFreeRecvDesc(RecvDesc);
    }

    AcquireBundleLock(BundleCB);
}

VOID
IndicatePromiscuousSendDesc(
    PLINKCB     LinkCB,
    PSEND_DESC  SendDesc,
    SEND_TYPE   SendType
    )
{
    UCHAR   Header1[] = {' ', 'W', 'A', 'N', 'S', 0xFF, ' ', 'W', 'A', 'N', 'S', 0xFF};
    PUCHAR  HeaderBuffer, DataBuffer;
    ULONG   HeaderLength, DataLength;
    PNDIS_BUFFER    NdisBuffer;
    PNDIS_PACKET    NdisPacket;
    NDIS_STATUS     Status;
    PBUNDLECB   BundleCB = LinkCB->BundleCB;
    PRECV_DESC  RecvDesc;
    KIRQL       OldIrql;
    PMINIPORTCB     Adapter;

    AcquireBundleLock(BundleCB);

    NdisAcquireSpinLock(&NdisWanCB.Lock);
    Adapter = NdisWanCB.PromiscuousAdapter;
    NdisReleaseSpinLock(&NdisWanCB.Lock);

    if (Adapter == NULL) {
        ReleaseBundleLock(BundleCB);
        return;
    }

    DataLength = 
        SendDesc->HeaderLength + SendDesc->DataLength;

    RecvDesc = 
        NdisWanAllocateRecvDesc(DataLength + MAC_HEADER_LENGTH);

    if (RecvDesc == NULL) {
        ReleaseBundleLock(BundleCB);
        return;
    }

    HeaderBuffer = RecvDesc->StartBuffer;
    HeaderLength = 0;

    switch (SendType) {
        case SEND_LINK:
            NdisMoveMemory(HeaderBuffer, Header1, sizeof(Header1));
            HeaderBuffer[5] =
                HeaderBuffer[11] = (UCHAR)LinkCB->hLinkHandle;

            HeaderBuffer[12] = (UCHAR)(DataLength >> 8);
            HeaderBuffer[13] = (UCHAR)DataLength;
            HeaderLength = MAC_HEADER_LENGTH;
            break;

        case SEND_BUNDLE_PPP:
        case SEND_BUNDLE_DATA:
            break;


    }

    DataBuffer = HeaderBuffer + HeaderLength;

    NdisMoveMemory(DataBuffer,
                   SendDesc->StartBuffer,
                   DataLength);

    RecvDesc->CurrentBuffer = HeaderBuffer;
    RecvDesc->CurrentLength = HeaderLength + DataLength;
    if (RecvDesc->CurrentLength > 1514) {
        RecvDesc->CurrentLength = 1514;
    }

    //
    // Get an ndis packet
    //
    NdisPacket = 
        RecvDesc->NdisPacket;

    PPROTOCOL_RESERVED_FROM_NDIS(NdisPacket)->RecvDesc = RecvDesc;

    //
    // Attach the buffers
    //
    NdisAdjustBufferLength(RecvDesc->NdisBuffer,
                           RecvDesc->CurrentLength);

    NdisRecalculatePacketCounts(NdisPacket);

    ReleaseBundleLock(BundleCB);

    KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);

    NDIS_SET_PACKET_STATUS(NdisPacket, NDIS_STATUS_RESOURCES);

    INSERT_DBG_RECV(PacketTypeNdis, Adapter, NULL, LinkCB, NdisPacket);

    //
    // Indicate the packet
    // This assumes that bloodhound is always a legacy transport
    //
    NdisMIndicateReceivePacket(Adapter->MiniportHandle,
                               &NdisPacket,
                               1);

    KeLowerIrql(OldIrql);

    Status = NDIS_GET_PACKET_STATUS(NdisPacket);

    ASSERT(Status == NDIS_STATUS_RESOURCES);

    REMOVE_DBG_RECV(PacketTypeNdis, Adapter, NdisPacket);

    {
        PNDIS_BUFFER    NdisBuffer;

        NdisWanFreeRecvDesc(RecvDesc);
    }
}

VOID
CompleteNdisPacket(
    PMINIPORTCB     MiniportCB,
    PPROTOCOLCB     ProtocolCB,
    PNDIS_PACKET    NdisPacket
    )
{
    PBUNDLECB   BundleCB = ProtocolCB->BundleCB;
    PCM_VCCB    CmVcCB;

    InterlockedDecrement(&ProtocolCB->OutstandingFrames);

    if (ProtocolCB->ProtocolType == PROTOCOL_PRIVATE_IO) {
        //
        // If this is a packet that we created we need to free
        // the resources
        //
        FreeIoNdisPacket(NdisPacket);
        return;
    }

    AcquireBundleLock(BundleCB);

    DEREF_PROTOCOLCB(ProtocolCB);

    ReleaseBundleLock(BundleCB);

    REMOVE_DBG_SEND(PacketTypeNdis, MiniportCB, NdisPacket);

    CmVcCB =
        PMINIPORT_RESERVED_FROM_NDIS(NdisPacket)->CmVcCB;

    if (CmVcCB != NULL) {
        NdisMCoSendComplete(NDIS_STATUS_SUCCESS,
                            CmVcCB->NdisVcHandle,
                            NdisPacket);

        DEREF_CMVCCB(CmVcCB);

    } else {

        NdisMSendComplete(MiniportCB->MiniportHandle,
                          NdisPacket,
                          NDIS_STATUS_SUCCESS);
    }

    //
    // Increment global count
    //
    NdisWanInterlockedInc(&glSendCompleteCount);
}

#if DBG
VOID
InsertDbgPacket(
    PDBG_PKT_CONTEXT   DbgContext
    )
{
    PDBG_PACKET DbgPacket, temp;
    PBUNDLECB   BundleCB = DbgContext->BundleCB;
    PPROTOCOLCB ProtocolCB = DbgContext->ProtocolCB;
    PLINKCB     LinkCB = DbgContext->LinkCB;

    DbgPacket =
        NdisAllocateFromNPagedLookasideList(&DbgPacketDescList);

    if (DbgPacket == NULL) {
        return;
    }

    DbgPacket->Packet = DbgContext->Packet;
    DbgPacket->PacketType = DbgContext->PacketType;
    DbgPacket->BundleCB = BundleCB;
    if (BundleCB) {
        DbgPacket->BundleState = BundleCB->State;
        DbgPacket->BundleFlags = BundleCB->Flags;
    }

    DbgPacket->ProtocolCB = ProtocolCB;
    if (ProtocolCB) {
        DbgPacket->ProtocolState = ProtocolCB->State;
    }

    DbgPacket->LinkCB = LinkCB;
    if (LinkCB) {
        DbgPacket->LinkState = LinkCB->State;
    }

    DbgPacket->SendCount = glSendCount;

    NdisAcquireSpinLock(DbgContext->ListLock);

    temp = (PDBG_PACKET)DbgContext->ListHead->Flink;

    while ((PVOID)temp != (PVOID)DbgContext->ListHead) {
        if (temp->Packet == DbgPacket->Packet) {
            DbgPrint("NDISWAN: Packet on list twice l %x desc %x pkt %x\n",
                     DbgContext->ListHead, DbgPacket, DbgPacket->Packet);
            DbgBreakPoint();
        }
        temp = (PDBG_PACKET)temp->Linkage.Flink;
    }

    InsertTailList(DbgContext->ListHead, &DbgPacket->Linkage);

    NdisReleaseSpinLock(DbgContext->ListLock);
}

BOOLEAN
RemoveDbgPacket(
    PDBG_PKT_CONTEXT DbgContext
    )
{
    PDBG_PACKET DbgPacket = NULL;
    BOOLEAN     Found = FALSE;

    NdisAcquireSpinLock(DbgContext->ListLock);

    if (!IsListEmpty(DbgContext->ListHead)) {
        for (DbgPacket = (PDBG_PACKET)DbgContext->ListHead->Flink;
            (PVOID)DbgPacket != (PVOID)DbgContext->ListHead;
            DbgPacket = (PDBG_PACKET)DbgPacket->Linkage.Flink) {

            if (DbgPacket->Packet == DbgContext->Packet) {
                RemoveEntryList(&DbgPacket->Linkage);
                NdisFreeToNPagedLookasideList(&DbgPacketDescList,
                    DbgPacket);
                Found = TRUE;
                break;
            }
        }
    }

    ASSERT(Found == TRUE);

    NdisReleaseSpinLock(DbgContext->ListLock);

    return (Found);
}

#endif