/*++

Copyright (c) 1991  Microsoft Corporation
Copyright (c) 1991  Nokia Data Systems

Module Name:

    advanc.c

Abstract:

    The module includes advanced testing thread procedures
    for link level protocols:

        DlcServer()
            - generic DLC server module, used against all
              DLC link level clients.

        LinkFunctionalityTest()
            - tests session setup and data transfer

        LinkConnectStressTest()
            - stress tests the link connection, small data transfer,
              and disconnection

        StressLinkDataTransfer()
            - ultimate stress test for data transfer over link

Author:

    Antti Saarenheimo (o-anttis) 10-Oct-1991

Revision History:

--*/

#include "dlctest.h"
#include <stdlib.h>
#include <dlcdebug.h>

//
// what's a foobar header? Originally, DLC (AcsLan) was skipping 3 bytes out
// of the start of a transmit buffer for most transmit commands. It was doing
// this because the IBM LAN Tech. Ref. kind of insinuates that this is what
// DLC should do. However it isn't. So to easily change 3 to 0 (and back again?)
// use FOOBAR_HEADER_LENGTH
//

#define FOOBAR_HEADER_LENGTH    0

UCHAR
TranslateLanHeader(
    IN UINT AddressTranslationMode,
    IN PUCHAR pSrcLanHeader,
    OUT PUCHAR pDestLanHeader
    );

enum _LLC_FRAME_XLATE_MODES {
    LLC_SEND_802_5_TO_802_3,
    LLC_SEND_802_5_TO_DIX,
    LLC_SEND_802_5_TO_802_5,
    LLC_SEND_DIX_TO_DIX,
    LLC_SEND_802_3_TO_802_3,
    LLC_SEND_802_3_TO_DIX,
    LLC_SEND_802_3_TO_802_5,
    LLC_SEND_UNMODIFIED
};

UCHAR FunctionalAddress1[4] = {0, 0, 1, 0};
static UCHAR FunctionalAddress2[4] = {0, 0x80, 0, 0};
static UCHAR FunctionalAddress3[4] = {0, 0x40, 0, 0};

static BOOLEAN TraceFlagSet = FALSE;

//static UCHAR    DebugTable[256];

#define MAX_ALTERNATE_PARMS 4

static PVOID AlternateParms[MAX_ALTERNATE_PARMS];

UCHAR BroadcastHeader[16] = {0, 0x40, 0xc0, 0, 0, 0, 0, 0, 0x80, 0, 0, 0, 0, 0, 0xe2, 0x70};
static UCHAR TestBroadcastHeader[16] = {0, 0x40, 0xc0, 0, 0, 0, 0, 0, 0x80, 0, 0, 0, 0, 0, 0xe2, 0x70};
static UCHAR LanHeader[16] = {0, 0x40, 0xc0, 0, 0, 1, 0, 0, 0x80, 0, 0, 0, 0, 0, 0xe2, 0x70};
//static UCHAR TestLanHeader[16] =
//    {0, 0x40, 0x10, 0, 0x5a, 0x7a, 0xa3, 0xb7, 0, 0, 0, 0, 0, 0, 0xc2, 0x70};
static UCHAR DixLanHeader[12];

static HANDLE hStartSignal;
BOOLEAN EventCheckDisabled = TRUE;

#ifdef OS2_EMU_DLC

static LLC_DLC_MODIFY_PARMS FirstModifyParms = {
    0,
    0,
    10,             // response timer (uchT1)
    1,              // aknowledgment timer (uchT2)
    10,             // inactivity timer (uchTi)
    1,              // max transmits without ack (uchMaxOut)
    1,              // max receives without ack (uchMaxIn)
    1,              // dynamic window increment value (uchMaxOutIncr)
    5,              // N2 value (retries) (uchMaxRetryCnt)
    0,              // (remote sap value for open link station)
    -1,             // (max i-field if DlcOpenSap or DlcOpenStation)
    1,              // token ring access priority (uchAccessPriority)
    {0, 0, 0, 0},
    0,
    (PUCHAR)NULL
};

static LLC_DLC_MODIFY_PARMS DlcModifyParms1 = {
    0,
    0,
    10,             // response timer (uchT1)
    1,              // aknowledgment timer (uchT2)
    10,             // inactivity timer (uchTi)
    1,              // max transmits without ack (uchMaxOut)
    1,              // max receives without ack (uchMaxIn)
    1,              // dynamic window increment value (uchMaxOutIncr)
    5,              // N2 value (retries) (uchMaxRetryCnt)
    0,              // (remote sap value for open link station)
    -1,             // (max i-field if DlcOpenSap or DlcOpenStation)
    1,              // token ring access priority (uchAccessPriority)
    {0, 0, 0, 0},
    0,
    (PUCHAR)NULL
};

static LLC_DLC_MODIFY_PARMS DlcModifyParms2 = {
    0,
    0,
    10,             // response timer (uchT1)
    5,              // aknowledgment timer (uchT2)
    10,             // inactivity timer (uchTi)
    127,            // max transmits without ack (uchMaxOut)
    127,            // max receives without ack (uchMaxIn)
    255,            // dynamic window increment value (uchMaxOutIncr)
    255,            // N2 value (retries) (uchMaxRetryCnt)
    0,              // (remote sap value for open link station)
    -1,             // (max i-field if DlcOpenSap or DlcOpenStation)
    1,              // token ring access priority (uchAccessPriority)
    {0, 0, 0, 0},
    0,
    (PUCHAR)NULL
};

static LLC_DLC_MODIFY_PARMS DlcModifyParms3 = {
    0,
    0,
    10,             // response timer (uchT1)
    1,              // aknowledgment timer (uchT2)
    10,             // inactivity timer (uchTi)
    10,             // max transmits without ack (uchMaxOut)
    5,              // max receives without ack (uchMaxIn)
    13,             // dynamic window increment value (uchMaxOutIncr)
    5,              // N2 value (retries) (uchMaxRetryCnt)
    0,              // (remote sap value for open link station)
    -1,             // (max i-field if DlcOpenSap or DlcOpenStation)
    1,              // token ring access priority (uchAccessPriority)
    {0, 0, 0, 0},
    0,
    (PUCHAR)NULL
};

static LLC_DLC_MODIFY_PARMS WrongDlcModifyParms = {
    0,
    0,
    11,             // response timer (uchT1)
    1,              // aknowledgment timer (uchT2)
    10,             // inactivity timer (uchTi)
    10,             // max transmits without ack (uchMaxOut)
    5,              // max receives without ack (uchMaxIn)
    13,             // dynamic window increment value (uchMaxOutIncr)
    3,              // N2 value (retries) (uchMaxRetryCnt)
    0,              // (remote sap value for open link station)
    -1,             // (max i-field if DlcOpenSap or DlcOpenStation)
    4,              // token ring access priority (uchAccessPriority)
    {0, 0, 0, 0},
    0,
    (PUCHAR)NULL
};

#else

static LLC_DLC_MODIFY_PARMS DlcModifyParms1 = {
    0,
    0,
    2,              // response timer (uchT1)
    1,              // aknowledgment timer (uchT2)
    5,              // inactivity timer (uchTi)
    1,              // max transmits without ack (uchMaxOut)
    1,              // max receives without ack (uchMaxIn)
    1,              // dynamic window increment value (uchMaxOutIncr)
    5,              // N2 value (retries) (uchMaxRetryCnt)
    0,              // (remote sap value for open link station)
    -1,             // (max i-field if DlcOpenSap or DlcOpenStation)
    1,              // token ring access priority (uchAccessPriority)
    {0, 0, 0, 0},
    0,
    (PUCHAR)NULL
};

static LLC_DLC_MODIFY_PARMS DlcModifyParms2 = {
    0,
    0,
    5,              // response timer (uchT1)
    4,              // aknowledgment timer (uchT2)
    10,             // inactivity timer (uchTi)
    127,            // max transmits without ack (uchMaxOut)
    127,            // max receives without ack (uchMaxIn)
    255,            // dynamic window increment value (uchMaxOutIncr)
    255,            // N2 value (retries) (uchMaxRetryCnt)
    0,              // (remote sap value for open link station)
    -1,             // (max i-field if DlcOpenSap or DlcOpenStation)
    1,              // token ring access priority (uchAccessPriority)
    {0, 0, 0, 0},
    0,
    (PUCHAR)NULL
};

static LLC_DLC_MODIFY_PARMS DlcModifyParms3 = {
    0,
    0,
    4,              // response timer (uchT1)
    1,              // aknowledgment timer (uchT2)
    7,              // inactivity timer (uchTi)
    10,             // max transmits without ack (uchMaxOut)
    5,              // max receives without ack (uchMaxIn)
    13,             // dynamic window increment value (uchMaxOutIncr)
    5,              // N2 value (retries) (uchMaxRetryCnt)
    0,              // (remote sap value for open link station)
    -1,             // (max i-field if DlcOpenSap or DlcOpenStation)
    1,              // token ring access priority (uchAccessPriority)
    {0, 0, 0, 0},
    0,
    (PUCHAR)NULL
};

static LLC_DLC_MODIFY_PARMS WrongDlcModifyParms = {
    0,
    0,
    11,             // response timer (uchT1)
    1,              // aknowledgment timer (uchT2)
    10,             // inactivity timer (uchTi)
    10,             // max transmits without ack (uchMaxOut)
    5,              // max receives without ack (uchMaxIn)
    13,             // dynamic window increment value (uchMaxOutIncr)
    3,              // N2 value (retries) (uchMaxRetryCnt)
    0,              // (remote sap value for open link station)
    -1,             // (max i-field if DlcOpenSap or DlcOpenStation)
    4,              // token ring access priority (uchAccessPriority)
    {0, 0, 0, 0},
    0,
    (PUCHAR)NULL
};


static LLC_DLC_MODIFY_PARMS FirstModifyParms = {
    0,
    0,
    2,              // response timer (uchT1)
    1,              // aknowledgment timer (uchT2)
    5,              // inactivity timer (uchTi)
    1,              // max transmits without ack (uchMaxOut)
    1,              // max receives without ack (uchMaxIn)
    1,              // dynamic window increment value (uchMaxOutIncr)
    40,             // N2 value (retries) (uchMaxRetryCnt)
    0,              // (remote sap value for open link station)
    -1,             // (max i-field if DlcOpenSap or DlcOpenStation)
    1,              // token ring access priority (uchAccessPriority)
    {0, 0, 0, 0},
    0,
    (PUCHAR)NULL
};

#endif

static LLC_DLC_MODIFY_PARMS DlcModifyQuickDeathParms = {
    0,
    0,
    1,              // response timer (uchT1)
    1,              // aknowledgment timer (uchT2)
    5,              // inactivity timer (uchTi)
    1,              // max transmits without ack (uchMaxOut)
    1,              // max receives without ack (uchMaxIn)
    1,              // dynamic window increment value (uchMaxOutIncr)
    1,              // N2 value (retries) (uchMaxRetryCnt)
    0,              // (remote sap value for open link station)
    -1,             // (max i-field if DlcOpenSap or DlcOpenStation)
    1,              // token ring access priority (uchAccessPriority)
    {0, 0, 0, 0},
    0,
    (PUCHAR)NULL
};

UINT cSrvFramesTransmitted = 0;
UINT cSrvFramesReceived = 0;
UINT cSrvFramesReleased = 0;
UINT cSrvFramesSecondaryXmits = 0;

extern HANDLE hServerReady;
extern BOOL VerboseMode;

ULONG TestLinkStation(DLC_TEST_THREAD_PARMS *pThreadParms);

ULONG TestLinkStation(DLC_TEST_THREAD_PARMS *pThreadParms) {

    PDLC_TEST_THREAD_PARMS pParms;
    UINT CloseCounter;
    UINT i = 0;

    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);

    pThreadParms->pCloseCounter = &CloseCounter;

    //
    // We use these parameters to stress the extream parameter values
    // of link station
    //

    AlternateParms[0] = &DlcModifyParms1;
    AlternateParms[1] = &DlcModifyParms2;
    AlternateParms[2] = &DlcModifyParms3;
    AlternateParms[3] = NULL;   // uses defaults

    //
    // We use the same buffer pool are for all functions
    //

    pThreadParms->pPool = ALLOC(DEFAULT_BUFFER_SIZE);

    //
    // We may execute the functionality test from the main thread
    //

    pParms = ALLOC(sizeof(DLC_TEST_THREAD_PARMS));
    *pParms = *pThreadParms;
    LinkFunctionalityTest(pParms);

    //
    // Start the link station connection stress threads
    //

    CloseCounter = 0;
    pParms = ALLOC(sizeof(DLC_TEST_THREAD_PARMS));
    *pParms = *pThreadParms;

    puts("LinkConnectStressTest");
    LinkConnectStressTest(pParms);

    //
    // Start the link station data transfer stress threads,
    // Test at first the very fast data transfer (with compare)
    // 25 * 48kB = 1.2 MB
    //

    pParms = ALLOC(sizeof(DLC_TEST_THREAD_PARMS));
    *pParms = *pThreadParms;
    pParms->ResetCommand = 0;
//    pParms->LoopCount = 5;
    pParms->LoopCount = 25;
    puts("LinkDataTransferTest");
    LinkDataTransferTest(pParms);

    //
    // Test the close commands when the link transmit is active
    //

    pThreadParms->AllocatedStations = 1;

    puts("LinkDataTransferTest & closing with DIR_INITIALIZE");
    pParms->LoopCount = 2;
    pParms = ALLOC(sizeof(DLC_TEST_THREAD_PARMS));
    *pParms = *pThreadParms;
    pParms->ResetCommand = LLC_DIR_INITIALIZE;
    LinkDataTransferTest(pParms);

    //
    // HERE WE USE AN INCORRECT RESET FUNCTION (DirOpenAdapter),
    // but has been proved very use to test several different bugs
    // in driver.  We leave this here to prevent those damn bugs to
    // come back.
    //

    puts("LinkDataTransferTest & closing with DIR_OPEN_ADAPTER (I know it's wrong)");
    pParms = ALLOC(sizeof(DLC_TEST_THREAD_PARMS));
    *pParms = *pThreadParms;
    pParms->ResetCommand = LLC_DIR_OPEN_ADAPTER;
    LinkDataTransferTest(pParms);

    puts("LinkDataTransferTest & closing with DLC_RESET");
    pParms = ALLOC(sizeof(DLC_TEST_THREAD_PARMS));
    *pParms = *pThreadParms;
    pParms->ResetCommand = LLC_DLC_RESET;

    puts("LinkDataTransferTest & closing with DIR_CLOSE_ADAPTER");
    pParms = ALLOC(sizeof(DLC_TEST_THREAD_PARMS));
    *pParms = *pThreadParms;
    pParms->ResetCommand = LLC_DIR_CLOSE_ADAPTER;
    LinkDataTransferTest(pParms);

    EnterCriticalSection(&CloseSection);
    (*pThreadParms->pCloseCounter)--;
    LeaveCriticalSection(&CloseSection);

    //
    // ThreadParms is not malloc'd
    //

//    FREE(pThreadParms);

    puts("Test completed successfully.");
    exit(0);
    return 0;
}

/*
    LLC_DIRECT_TRANSMIT             = 0x0000,       // transmit
    LLC_DIRECT_MAC                  = 0x0002,       // receive
    LLC_I_FRAME                     = 0x0004,       // receive & transmit
    LLC_UI_FRAME                    = 0x0006,       // receive & transmit
    LLC_XID_COMMAND_POLL            = 0x0008,       // receive & transmit
    LLC_XID_COMMAND_NOT_POLL        = 0x000A,       // receive & transmit
    LLC_XID_RESPONSE_FINAL          = 0x000C,       // receive & transmit
    LLC_XID_RESPONSE_NOT_FINAL      = 0x000E,       // receive & transmit
    LLC_TEST_RESPONSE_FINAL         = 0x0010,       // receive & transmit
    LLC_TEST_RESPONSE_NOT_FINAL     = 0x0012,       // receive & transmit
    LLC_DIRECT_8022                 = 0x0014,       // receive (direct station)
    LLC_TEST_COMMAND_POLL           = 0x0016,       // transmit
    LLC_DIRECT_ETHERNET_TYPE        = 0x0018,       // receive (direct station)
*/

static USHORT ResponseTable[(LLC_DIRECT_ETHERNET_TYPE + 2) / 2] = {
    0,  // LLC_DIRECT_TRANSMIT             = 0x0000,       // transmit
    0,  // LLC_DIRECT_MAC                  = 0x0002,       // receive
    0,  // LLC_I_FRAME                     = 0x0004,       // receive & transmit
    LLC_UI_FRAME,
    LLC_XID_RESPONSE_FINAL,
    LLC_XID_RESPONSE_NOT_FINAL,
    0,  // LLC_XID_RESPONSE_FINAL          = 0x000C,       // receive & transmit
    0,  // LLC_XID_RESPONSE_NOT_FINAL      = 0x000E,       // receive & transmit
    0,  // LLC_TEST_RESPONSE_FINAL         = 0x0010,       // receive & transmit
    0,  // LLC_TEST_RESPONSE_NOT_FINAL     = 0x0012,       // receive & transmit
    0,  // LLC_DIRECT_8022                 = 0x0014,       // receive (direct station)
    0,  // LLC_TEST_COMMAND_POLL           = 0x0016,       // transmit
    -1  // LLC_DIRECT_ETHERNET_TYPE        = 0x0018,       // receive (direct station)
};

ULONG
DlcServer(
    IN PDLC_TEST_THREAD_PARMS pParms
    );

UINT
FreeReceiveBuffers(
    UCHAR AdapterNumber,
    PLLC_BUFFER pFirstBuffer,
    PLLC_BUFFER *ppNextFrame,
    PUINT pcLeft
    )

/*++

    This routine frees all chained frames received by Dlc test server.
    This procedure makes only one BufferFree command for
    a very large number of received frames.

--*/

{
    PLLC_BUFFER pBuffer;
    PLLC_BUFFER pFrame;
    UINT FrameCount = 1;
    UINT Status;

    //
    // Create buffer chain of all 'TEST_COMMAND_READ' packets
    // in the buffer chain.
    //

    pBuffer = pFirstBuffer;
    for (pFrame = pFirstBuffer->Contiguous.pNextFrame;
         pFrame != NULL;
         pFrame = pFrame->Contiguous.pNextFrame
         ) {

        PDLC_TEST_PACKET pDlcPacket;

        pDlcPacket = (PDLC_TEST_PACKET)((PUCHAR)pFrame
                   + pFrame->Next.cbUserData
                   + pFrame->Next.offUserData
                   );

        if ((pDlcPacket->PacketId != DLC_TEST_ID)
        || (pDlcPacket->Command != TEST_COMMAND_READ)) {
            break;
        }

        FrameCount++;

        while (pBuffer->pNext != NULL) {
            pBuffer = pBuffer->pNext;
        }
        pBuffer->pNext = pFrame;
        pBuffer = pFrame;
    }

    Status = BufferFree(AdapterNumber,
                        pFirstBuffer,
                        pcLeft
                        );
    DBG_ASSERT(Status);
    *ppNextFrame = pFrame;
    return FrameCount;
}

ULONG
DlcServer(
    IN PDLC_TEST_THREAD_PARMS pParms
    )

/*++

    Generic DLC server module, used against all
    DLC client test programs.

--*/

{
    UINT Status;
    UINT OpenError;
    UINT MaxFrameLength;
    USHORT DlcStatus;
    PLLC_BUFFER pNextFrame;
    PLLC_BUFFER pFirstBuffer;
    PDLC_TEST_PACKET pDlcPacket;
    LLC_TEST_CCB_POOL TransmitCcbPool;
    PLLC_TRANSMIT2_COMMAND pTransmitBuffer;
    UCHAR SapStation;
    USHORT LastSapStationId;
    PLLC_READ_PARMS pReadParms;
    PLLC_CCB pCcb;
    PLLC_CCB pReadCcb;
    PLLC_CCB pNextCcb;
    UCHAR LinksAvail;
    BOOLEAN EndTest;
    PLLC_BUFFER SavedLinkBuffers[255];
    PLLC_BUFFER pCurrentBuffer;
    UCHAR MaxStations;
    UINT iStation;
    UINT SapOptions;
    UCHAR StationsLeft;
    UINT cGroupSaps;
    UINT i;
    static UCHAR GroupSaps[3] = { GROUP_SAP1, GROUP_SAP2, GROUP_SAP3 };
    PVOID pPool;
    LLC_DLC_MODIFY_PARMS DlcModifyParms;
    USHORT GroupSapHost;
    UINT cLeft;
    UINT PendingCcbCount = 0;
    UINT cTransmitted;
    UINT FrameCount;
    static PVOID hPool = NULL;

    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);

    memset(SavedLinkBuffers, 0, sizeof(SavedLinkBuffers));
    pTransmitBuffer = (PLLC_TRANSMIT2_COMMAND)
                      ALLOC(sizeof(LLC_TRANSMIT2_COMMAND)
                            + sizeof(LLC_TRANSMIT_DESCRIPTOR) * MAX_XMIT_BUFFERS
                            );

    //
    // Initialize the Transmit ccb pool
    //

    CcbPoolInitialize(&TransmitCcbPool,
                     pParms->AdapterNumber,
                     LLC_TRANSMIT_FRAMES,
                     TEST_TRANSMIT_COMPLETION_FLAG
                     );
    Status = LlcDirOpenAdapter(pParms->AdapterNumber,
                               NULL,
                               hPool,
                               &OpenError,
                               NULL,
                               &MaxFrameLength,
                               NULL
                               );
    DBG_ASSERT(Status);

    if (hPool == NULL) {
        Status = BufferCreate(pParms->AdapterNumber,
                              pPool = ALLOC(SERVER_BUFFER_SIZE),
                              SERVER_BUFFER_SIZE,
                              min(0xfff0, SERVER_BUFFER_SIZE),
                              SERVER_BUFFER_SIZE / 2,
                              &pParms->hPool
                              );
        DBG_ASSERT(Status);
    }

    Status = DirSetFunctionalAddress(pParms->AdapterNumber,
                                     pParms->FunctionalAddress
                                     );
    DBG_ASSERT(Status);

    Status = DirSetGroupAddress(pParms->AdapterNumber, pParms->GroupAddress);
    if (Status != STATUS_SUCCESS) {
        puts("Group address is not supported by NDIS driver!");
    }

    //
    // Open the group saps (not really necessary in NT DLC)
    //

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

        if (VerboseMode) {
            printf("Server: opening group SAP %02x\n", GroupSaps[i]);
        }

        Status = LlcDlcOpenSap((UINT)pParms->AdapterNumber,
                               MaxFrameLength,
                               (UCHAR)(GroupSaps[i] & (UCHAR)0xfe),
                               LLC_GROUP_SAP | LLC_XID_HANDLING_IN_DLC,
                               0,
                               0,
                               NULL,
                               0,
                               NULL,
                               &LastSapStationId,
                               &LinksAvail
                               );
        DBG_ASSERT(Status);
    }

    MaxStations = (UCHAR)((256 * pParms->ThreadCount) / pParms->SapCount);
    GroupSapHost = (USHORT)(pParms->FirstServerSap + 2) << 8;

    memset(&DlcModifyParms, 0, sizeof(DlcModifyParms));
    DlcModifyParms.pGroupList = GroupSaps;

    StationsLeft = 255;

    for (SapStation = (UCHAR)(pParms->FirstServerSap + (pParms->AdapterNumber >> 4) * 2);
         SapStation < (UCHAR)(pParms->FirstServerSap + pParms->SapCount * 2);
         SapStation += (2 * pParms->ThreadCount)) {

        //
        // This first SAP is NOT MEMBER of groups saps and it handles
        // the XID frames by itself (they are handled as any XID commands).
        // ALL OTHER SAPS ARE MEMBERS OF GROUPS SAPS AND THEY LET LINK LAYER
        // TO HANDLE XID FRAMES!
        //

        if (SapStation == pParms->FirstServerSap) {
            cGroupSaps = 0;
            SapOptions = LLC_INDIVIDUAL_SAP | LLC_XID_HANDLING_IN_APPLICATION;
        } else {
            cGroupSaps = sizeof(GroupSaps);
            SapOptions = LLC_INDIVIDUAL_SAP | LLC_MEMBER_OF_GROUP_SAP | LLC_XID_HANDLING_IN_DLC;
        }

        //
        // We have only 255 link stations! The last sap must take
        // all stations ids, that are left.
        //

        if ((SapStation + (pParms->ThreadCount * 2)) >= (UCHAR)(pParms->FirstServerSap + pParms->SapCount * 2)) {
            MaxStations = StationsLeft;
        } else {
            StationsLeft -= MaxStations;
        }

        if (VerboseMode) {
            printf("Server: opening individual SAP %02x\n", SapStation);
        }

        Status = LlcDlcOpenSap(pParms->AdapterNumber,
                               MaxFrameLength,
                               SapStation,
                               SapOptions,
                               MaxStations,
                               cGroupSaps,
                               GroupSaps,
                               TEST_DLC_STATUS_FLAG,
                               NULL,
                               &LastSapStationId,
                               &LinksAvail
                               );
        DBG_ASSERT(Status);

        //
        // Setup immediately a pending receive
        //

        pCcb = ReceiveInit(pParms->AdapterNumber,
                           TEST_COMMAND_COMPLETION_FLAG,
                           LastSapStationId,
                           0,
                           TEST_RECEIVE_FLAG,
                           LLC_NOT_CONTIGUOUS_DATA,
                           LLC_RCV_CHAIN_FRAMES_ON_LINK
//                           LLC_RCV_READ_INDIVIDUAL_FRAMES
                           );
        Status = AcsLan(pCcb, NULL);

        //
        // We setup all group saps using DlcModify (actually we
        // first reset the group saps and then set them again)
        //

        DlcModifyParms.cGroupCount = 0;
        Status = DlcModify((UINT)pParms->AdapterNumber,
                           (USHORT)((USHORT)SapStation << 8),
                           &DlcModifyParms
                           );
        DBG_ASSERT(Status);

        DlcModifyParms.cGroupCount = sizeof(GroupSaps);
        Status = DlcModify(pParms->AdapterNumber,
                           (USHORT)((USHORT)SapStation << 8),
                           &DlcModifyParms
                           );
        DBG_ASSERT(Status);
    } // for

    //
    // And finally we setup direct station to read frames
    // from a specific dix station.
    //

    Status = LlcDirOpenDirect(pParms->AdapterNumber, 0, TEST_DIX_TYPE);

    //
    // Dix stations doesn't work on token-ring
    //

    if (Status == STATUS_SUCCESS) {
        pCcb = ReceiveInit(pParms->AdapterNumber,
                           TEST_COMMAND_COMPLETION_FLAG,
                           0,
                           0,
                           TEST_RECEIVE_FLAG,
                           LLC_NOT_CONTIGUOUS_DATA,
                           LLC_RCV_CHAIN_FRAMES_ON_LINK
                           );
        Status = AcsLan(pCcb, NULL);
        DBG_ASSERT(pCcb->uchDlcStatus);
    }

    pReadCcb = ReadInit(pParms->AdapterNumber,
                        0,
                        LLC_OPTION_READ_ALL,
                        LLC_READ_ALL_EVENTS
                        );
    pReadParms = &pReadCcb->u.pParameterTable->Read;

    SetEvent(hServerReady);

    EndTest = FALSE;

    while (EndTest == FALSE) {
        Status = AcsLan(pReadCcb, NULL);

        if (pReadCcb->uchDlcStatus == LLC_STATUS_ADAPTER_CLOSED) {
            break;
        }
        Status = WaitForSingleObject(pReadCcb->hCompletionEvent, INFINITE);

        if (Status != STATUS_SUCCESS) {
            break;
        }

        switch (pReadParms->uchEvent) {
        case LLC_EVENT_TRANSMIT_COMPLETION:

            if (pReadParms->ulNotificationFlag != TEST_TRANSMIT_COMPLETION_FLAG) {
                DlcDebugBreak();
            }

            for (pCcb = pReadParms->Type.Event.pCcbCompletionList;
                 pCcb != NULL && pReadParms->Type.Event.usCcbCount != 0;
                 pCcb = pNextCcb) {

                pNextCcb = pCcb->pNext;
                if (pCcb->uchDlcStatus == LLC_STATUS_LINK_PROTOCOL_ERROR
                || pCcb->uchDlcStatus == LLC_STATUS_INVALID_STATION_ID) {
                    PRINTF("T: LLC_STATUS_LINK_PROTOCOL_ERROR\n");
                } else {
                    DBG_ASSERT(pCcb->uchDlcStatus);
                }
                CcbPoolFree(&TransmitCcbPool, pCcb);
            }
            break;

        case LLC_EVENT_COMMAND_COMPLETION:

            if (pReadParms->ulNotificationFlag != TEST_COMMAND_COMPLETION_FLAG) {
                DlcDebugBreak();
            }

            for (pCcb = pReadParms->Type.Event.pCcbCompletionList;
                 pCcb != NULL && pReadParms->Type.Event.usCcbCount != 0;
                 pCcb = pNextCcb) {

                pNextCcb = pCcb->pNext;

                //
                // Restore the receive command, if we have run out
                // of the buffers.  The receive command should
                // expand the buffer pool, if it is necessary.
                //

                if ((pCcb->uchDlcCommand == LLC_RECEIVE)
                && (pCcb->u.pParameterTable->Receive.ulReceiveFlag != 0)) {

                    printf("cSrvFramesTransmitted: %x\n",cSrvFramesTransmitted);
                    printf("cSrvFramesReceived: %x\n", cSrvFramesReceived);
                    printf("cSrvFramesReleased: %x\n", cSrvFramesReleased);
                    printf("cSrvFramesSecondaryXmits: %x\n", cSrvFramesSecondaryXmits);

                    if (pCcb->uchDlcStatus != LLC_STATUS_LOST_DATA_NO_BUFFERS) {
                        printf("Invalid Receive ret code: %xh\n", pCcb->uchDlcStatus);
                    }

                    Status = AcsLan(pCcb, NULL);
                    DBG_ASSERT(pCcb->uchDlcStatus);
                } else {
                    if ((pCcb->uchDlcCommand == LLC_DLC_CLOSE_STATION)
                    && (pReadParms->Type.Event.pFirstBuffer != NULL)) {
                        cSrvFramesReleased += FreeAllFrames(pParms->AdapterNumber,
                                                            pReadParms->Type.Event.pFirstBuffer,
                                                            &cLeft
                                                            );
                    }

                    if ((pCcb->uchDlcStatus == LLC_STATUS_LINK_PROTOCOL_ERROR)
                    || (pCcb->uchDlcStatus == LLC_STATUS_INVALID_STATION_ID)) {
                        PRINTF("C: LLC_STATUS_LINK_PROTOCOL_ERROR\n");
                    } else {
                        DBG_ASSERT(pCcb->uchDlcStatus);
                    }
                    PendingCcbCount--;
                    FreeCcb(pCcb);
                }
            }
            break;

        case LLC_EVENT_STATUS_CHANGE:

            if (pReadParms->ulNotificationFlag != TEST_DLC_STATUS_FLAG) {
                DlcDebugBreak();
            }

            DlcStatus = pReadParms->Type.Status.usDlcStatusCode;
            if (DlcStatus & LLC_INDICATE_LINK_LOST) {
                pCcb = LlcCloseInit(pParms->AdapterNumber,
                                    TEST_COMMAND_COMPLETION_FLAG,
                                    LLC_DLC_CLOSE_STATION,
                                    pReadParms->Type.Status.usStationId
                                    );
                Status = AcsLan(pCcb, NULL);
                PendingCcbCount++;
                DBG_ASSERT(pCcb->uchDlcStatus);
                PRINTF("SERVER: LLC_INDICATE_LINK_LOST (%u pending extra CCBs)\n", PendingCcbCount);
            }

            if (DlcStatus & LLC_INDICATE_DM_DISC_RECEIVED) {
                pCcb = LlcCloseInit(pParms->AdapterNumber,
                                    TEST_COMMAND_COMPLETION_FLAG,
                                    LLC_DLC_CLOSE_STATION,
                                    pReadParms->Type.Status.usStationId
                                    );
                Status = AcsLan(pCcb, NULL);
                PendingCcbCount++;
                PRINTF("SERVER: LLC_INDICATE_DM_DISC_RECEIVED (%x)", pReadParms->Type.Status.usStationId);
                PRINTF("; %u pending extra CCBs\n", PendingCcbCount);
            }

            if (DlcStatus & LLC_INDICATE_FRMR_RECEIVED) {
                PRINTF("SERVER: LLC_INDICATE_FRMR_RECEIVED\n");
            }

            if (DlcStatus & LLC_INDICATE_FRMR_SENT) {
                PRINTF("SERVER: LLC_INDICATE_FRMR_SENT\n");
            }

            if (DlcStatus & LLC_INDICATE_CONNECT_REQUEST) {
                pCcb = DlcConnectStationInit(pParms->AdapterNumber,
                                             TEST_COMMAND_COMPLETION_FLAG,
                                             pReadParms->Type.Status.usStationId,
                                             NULL
                                             );
                Status = AcsLan(pCcb, NULL);
                PendingCcbCount++;

                //
                // The link may have been disconnected by the remote node.
                //

                if (pCcb->uchDlcStatus != LLC_STATUS_INVALID_STATION_ID) {
                    DBG_ASSERT(pCcb->uchDlcStatus);
                }

                PRINTF("SERVER: LLC_INDICATE_CONNECT_REQUEST (%u pending extra CCBs)\n", PendingCcbCount);
            }

            if (DlcStatus & LLC_INDICATE_RESET) {

                //
                // We must reconnect after a reset, Reset happens
                // when the remote side has closed the adapter
                // while a link was in a checkpointing state (when
                // it cannot send DISC) and then immediately reconnects
                // the same link station.
                //
                // DLC.CLOSE.STATION and DLC.RESET wait always the DLC
                // protocol to complete the disconnect and eventually
                // to send DISC, but DIR.CLOSE.ADAPTER and DIR.INITIALIZE
                // shuts down immediately the network traffic (any the
                // pending NDIS packets are waited)
                //

                pCcb = DlcConnectStationInit(pParms->AdapterNumber,
                                             TEST_COMMAND_COMPLETION_FLAG,
                                             pReadParms->Type.Status.usStationId,
                                             NULL
                                             );
                Status = AcsLan(pCcb, NULL);
                PendingCcbCount++;

                PRINTF("SERVER: LLC_INDICATE_RESET (%u pending extra CCBs)\n", PendingCcbCount);
            }

            if (DlcStatus & LLC_INDICATE_REMOTE_BUSY) {
                PRINTF("SERVER: LLC_INDICATE_REMOTE_BUSY\n");
            }

            if (DlcStatus & LLC_INDICATE_REMOTE_READY) {
                PRINTF("SERVER: LLC_INDICATE_REMOTE_READY\n");
            }

            if (DlcStatus & LLC_INDICATE_TI_TIMER_EXPIRED) {
                PRINTF("SERVER: LLC_INDICATE_TI_TIMER_EXPIRED\n");
            }

            if (DlcStatus & LLC_INDICATE_DLC_COUNTER_OVERFLOW) {
                PRINTF("SERVER: LLC_INDICATE_DLC_COUNTER_OVERFLOW\n");
                ReportDlcStatistics(pParms->AdapterNumber,
                                    pReadParms->Type.Status.usStationId
                                    );
            }

            if (DlcStatus & LLC_INDICATE_ACCESS_PRTY_LOWERED) {
                PRINTF("SERVER: LLC_INDICATE_ACCESS_PRTY_LOWERED\n");
            }

            if (DlcStatus & LLC_INDICATE_LOCAL_STATION_BUSY) {

                //
                // Let's hope there is now enough buffers to
                // receive the data. Clear the out of buffer busy state.
                //

                PRINTF("SERVER: LLC_INDICATE_LOCAL_STATION_BUSY\n");
                DlcFlowControl(pParms->AdapterNumber,
                               pReadParms->Type.Status.usStationId,
                               LLC_RESET_LOCAL_BUSY_BUFFER
                               );
            }
            break;

        case LLC_EVENT_RECEIVE_DATA:

            if (pReadParms->ulNotificationFlag != TEST_RECEIVE_FLAG) {
                DlcDebugBreak();
            }

            //
            // We will echo back all connect requests sent to any SAP
            // from DLC test stations.
            //

            for (pFirstBuffer = pReadParms->Type.Event.pReceivedFrame;
                 pFirstBuffer != NULL;
                 pFirstBuffer = pNextFrame) {

                cSrvFramesReceived++;

                pNextFrame = pFirstBuffer->Contiguous.pNextFrame;
                pDlcPacket = (PDLC_TEST_PACKET)
                           ((PUCHAR)pFirstBuffer
                           + pFirstBuffer->Next.cbUserData
                           + pFirstBuffer->Next.offUserData
                           );

                if (((pFirstBuffer->NotContiguous.usStationId & 0xff) == 0)
                && (pDlcPacket->PacketId == DLC_TEST_ID)) {

                    //
                    // Only the first sap number responds to queries sent
                    // to group saps.  And only the first virtual adapters
                    // responds to any connectionless frames!
                    //

                    if (((pParms->AdapterNumber & 0xf0) != 0)
                    || ((pFirstBuffer->NotContiguous.cbDlcHeader == 3)
                    && (pFirstBuffer->NotContiguous.auchDlcHeader[0] & 0x01)
                    && pFirstBuffer->NotContiguous.usStationId != LastSapStationId)) {

                        //
                        // We just free the received buffers
                        //

                        Status = BufferFree(pParms->AdapterNumber,
                                            pFirstBuffer,
                                            &cLeft
                                            );
                        cSrvFramesReleased++;
                        continue;
                    }

                    switch (pDlcPacket->Command) {
                    case TEST_COMMAND_ECHO_BACK:

                        pFirstBuffer->Contiguous.pNextFrame = NULL;
                        pTransmitBuffer->usStationId = pFirstBuffer->NotContiguous.usStationId;
                        pTransmitBuffer->usFrameType = ResponseTable[pFirstBuffer->NotContiguous.uchMsgType / 2];

                        if (pTransmitBuffer->usFrameType == 0) {
                            Status = BufferFree(pParms->AdapterNumber,
                                                pFirstBuffer,
                                                &cLeft
                                                );
                            cSrvFramesReleased++;
                            break;
                        } else if (pTransmitBuffer->usFrameType == (USHORT)-1) {

                            //
                            // Send to the same ethernet type as used
                            // in the received dlc header.
                            // (note small endian byte order)
                            //

                            pTransmitBuffer->usFrameType =
                                (USHORT)((((USHORT)pFirstBuffer->NotContiguous.auchDlcHeader[0]) << 8)
                                    + pFirstBuffer->NotContiguous.auchDlcHeader[1]);

                            //
                            // Modify the lan header to a network address.
                            // We use ethernet header format for dix frames
                            //

                            memcpy(&pFirstBuffer->NotContiguous.auchLanHeader[0],
                                   &pFirstBuffer->NotContiguous.auchLanHeader[6],
                                   6
                                   );

                            //
                            //  Reset the possible broadcast bit,
                            //

                            pFirstBuffer->NotContiguous.auchLanHeader[0] &= (UCHAR)0x7f;
                        } else {

                            //
                            // Modify the lan header to a network address.
                            // We expect to have here always a token-ring
                            // header.
                            //

                            memcpy(&pFirstBuffer->NotContiguous.auchLanHeader[2],
                                   &pFirstBuffer->NotContiguous.auchLanHeader[8],
                                   6
                                   );

                            //
                            // Reset the possible broadcast bit,
                            // Reverse the direction bit of the source routing
                            // info,  if the source routing is enabled.
                            // Reset the broadcast indicators, we response
                            // always with the directed frames
                            //

                            pFirstBuffer->NotContiguous.auchLanHeader[2] &= 0x7f;
                            pFirstBuffer->NotContiguous.auchLanHeader[15] ^= 0x80;
                            pFirstBuffer->NotContiguous.auchLanHeader[14] &= 0x1f;
                        }

                        pTransmitBuffer->uchRemoteSap = pFirstBuffer->NotContiguous.auchDlcHeader[1] & (UCHAR)0xfe;
                        pTransmitBuffer->uchXmitReadOption = LLC_CHAIN_XMIT_COMMANDS_ON_SAP;
                        pTransmitBuffer->aXmitBuffer[0].eSegmentType = LLC_FIRST_DATA_SEGMENT;
                        pTransmitBuffer->aXmitBuffer[0].boolFreeBuffer = TRUE;
                        pTransmitBuffer->aXmitBuffer[0].cbBuffer = pFirstBuffer->NotContiguous.cbLanHeader;
                        pTransmitBuffer->aXmitBuffer[0].pBuffer = pFirstBuffer->NotContiguous.auchLanHeader;
                        Status = TransmitBuffers(pFirstBuffer,
                                                 pTransmitBuffer,
                                                 &TransmitCcbPool,
                                                 1,
                                                 1,
                                                 TRUE,
                                                 FALSE,
                                                 &cTransmitted
                                                 );
                        if (cTransmitted == 0) {
                            DebugBreak();
                        }
                        cSrvFramesTransmitted += cTransmitted;

                        //
                        // We must free the buffers (the transmitted
                        // data was released)
                        //

                        if ((Status != LLC_STATUS_PENDING) && (Status != LLC_STATUS_SUCCESS)) {

                            UINT cReleased;

                            cReleased = FreeAllFrames(pParms->AdapterNumber,
                                                      pFirstBuffer,
                                                      &cLeft
                                                      );
                            cSrvFramesReleased += cReleased;
                            cSrvFramesTransmitted -= cReleased;
                        }
                        break;

                    case TEST_COMMAND_EXIT:
                        EndTest = TRUE;
                        break;

                    case TEST_COMMAND_READ:
                    default:

                        //
                        // We just free the received buffers
                        //

                        FrameCount = FreeReceiveBuffers(pParms->AdapterNumber,
                                                        pFirstBuffer,
                                                        &pNextFrame,
                                                        &cLeft
                                                        );
                        cSrvFramesReleased += FrameCount;
                        cSrvFramesReceived += (FrameCount - 1);
                        break;
                    }
                } else if ((pFirstBuffer->NotContiguous.usStationId & 0xff00) != 0) {

                    //
                    // We got an invalid data packet over link
                    //

                    if (pDlcPacket->PacketId != DLC_TEST_ID) {
                        printf("Invalid Data: READ CCB=%x\n", pReadCcb);
                        DlcDebugBreak();
                    }

                    switch (pDlcPacket->Command) {
                    case TEST_COMMAND_ECHO_BACK:
                    case TEST_COMMAND_DELAYED_ECHO:
                        pFirstBuffer->Contiguous.pNextFrame = NULL;
                        iStation = (pFirstBuffer->NotContiguous.usStationId & 0xff) - 1;
                        if (SavedLinkBuffers[iStation] != NULL) {

                            for (pCurrentBuffer = SavedLinkBuffers[iStation];
                                 pCurrentBuffer->Contiguous.pNextFrame != NULL;
                                 pCurrentBuffer = pCurrentBuffer->Contiguous.pNextFrame) {

                                     //
                                     // NOTHING (?)
                                     //

                                 }

                            pCurrentBuffer->Contiguous.pNextFrame = pFirstBuffer;
                            pFirstBuffer = SavedLinkBuffers[iStation];
                        } else {
                            SavedLinkBuffers[iStation] = pFirstBuffer;
                        }

                        if (pDlcPacket->Command == TEST_COMMAND_ECHO_BACK) {

                            //
                            // Small performance trick, send multiple
                            // frames with the same command, if sequential
                            // packets are from the same link station and
                            // they have the same type.
                            //

                            if ((pNextFrame != NULL)
                            && (pNextFrame->NotContiguous.usStationId == pFirstBuffer->NotContiguous.usStationId)) {

                                pDlcPacket = (PDLC_TEST_PACKET)((PUCHAR)pNextFrame
                                    + pNextFrame->Next.cbUserData
                                    + pNextFrame->Next.offUserData
                                    );

                                if (pDlcPacket->Command == TEST_COMMAND_ECHO_BACK) {

                                    //
                                    // Send the packet in the next time
                                    //

                                    cSrvFramesSecondaryXmits++;
                                    continue;
                                }
                            }
                            pTransmitBuffer->usStationId = pFirstBuffer->NotContiguous.usStationId;
                            pTransmitBuffer->usFrameType = LLC_I_FRAME;
                            Status = TransmitBuffers(SavedLinkBuffers[iStation],
                                                     pTransmitBuffer,
                                                     &TransmitCcbPool,
                                                     1,
                                                     0,
                                                     TRUE,
                                                     TRUE,
                                                     &cTransmitted
                                                     );
                            if (cTransmitted == 0) {
                                DebugBreak();
                            }
                            cSrvFramesTransmitted += cTransmitted;

                            //
                            // We must free the buffers (the transmitted
                            // data was released)
                            //

                            if ((Status != LLC_STATUS_PENDING) && (Status != LLC_STATUS_SUCCESS)) {

                                UINT cReleased;

                                cReleased = FreeAllFrames(pParms->AdapterNumber,
                                                          SavedLinkBuffers[iStation],
                                                          &cLeft
                                                          );
                                cSrvFramesReleased += cReleased;
                                cSrvFramesTransmitted -= cReleased;
                            }
                            SavedLinkBuffers[iStation] = NULL;
                        }
                        break;

                    case TEST_COMMAND_READ:
                    default:

                        //
                        // We just free the received buffers
                        //

                        FrameCount = FreeReceiveBuffers(pParms->AdapterNumber,
                                                        pFirstBuffer,
                                                        &pNextFrame,
                                                        &cLeft
                                                        );
                        cSrvFramesReleased += FrameCount;
                        cSrvFramesReceived += (FrameCount - 1);
                        break;
                    }
                } else {
                    Status = BufferFree(pParms->AdapterNumber,
                                        pFirstBuffer,
                                        &cLeft
                                        );
                    cSrvFramesReleased++;
                    DBG_ASSERT(Status);
                }
            } // end_for

//if ((pReadParms->Type.Event.pReceivedFrame->NotContiguous.usStationId & 0xff00) != 0) {
//    PRINTF("%u ", pReadParms->Type.Event.pReceivedFrame->NotContiguous.cBuffersLeft);
//}
            break;
        }
    }

    FREE(pTransmitBuffer);
    FREE(pPool);
    CcbPoolDelete(&TransmitCcbPool);
    FreeCcb(pReadCcb);
    ExitThread(STATUS_SUCCESS);
}


UINT
LinkFunctionalityTest(
    IN PDLC_TEST_THREAD_PARMS pParms
    )

/*++

    This tests the basic functionality of link station:
    - connection setup
    - data send and receive
    - local busy state and its opening
    - out of local receive buffers
    - the closing of data link connection

--*/

{
    UINT Status;
    UINT OpenError;
    UINT MaxFrameLength;
    USHORT SapStationId;
    USHORT SapStationId2;
    USHORT LinkStationId;
    PVOID hPool;
    PLLC_BUFFER pFirstBuffer;
    PLLC_TRANSMIT2_COMMAND pTransmitBuffer;
    DLC_TEST_PACKET TestPacket;
    PLLC_CCB pCcb;
    PLLC_CCB pReadCcb;
    PLLC_CCB pLinkReadCcb;
    LLC_TEST_CCB_POOL TransmitCcbPool;
    UCHAR LinksAvail;
    PLLC_READ_PARMS pReadParms;
    PLLC_BUFFER pBuffer1;
    PLLC_BUFFER pBuffer2;
    UCHAR SendBuffer[sizeof(TestPacket) + FOOBAR_HEADER_LENGTH];
    HANDLE hEvent;
    PVOID pPool;
    LLC_TRANSMIT2_VAR_PARMS(2) TransmitParms;
    UCHAR NodeAddress[6];
    UCHAR DestinationSap = 2;
    PLLC_CCB aCcb[10];
    UINT i;
    UINT cLeft;
    BOOLEAN GlobalSapIsWorking = TRUE;   // doesn't work with ibmtok
    PUCHAR pSourceRoutingInfo;

    puts("****** Link Functionality Test ******");

    hEvent = CreateEvent(NULL, FALSE, TRUE, NULL);
    pTransmitBuffer = (PLLC_TRANSMIT2_COMMAND)
        ALLOC(sizeof(LLC_TRANSMIT2_COMMAND)
              + sizeof(LLC_TRANSMIT_DESCRIPTOR) * MAX_XMIT_BUFFERS
              );

    //
    // Initialize the Transmit ccb pool
    //

    CcbPoolInitialize(&TransmitCcbPool,
                      pParms->AdapterNumber,
                      LLC_TRANSMIT_FRAMES,
                      TEST_TRANSMIT_COMPLETION_FLAG
                      );
    Status = LlcDirOpenAdapter(pParms->AdapterNumber + 16,
                               NULL,
                               NULL,
                               &OpenError,
                               NULL,
                               &MaxFrameLength,
                               NULL
                               );
    if (Status && Status != LLC_STATUS_ADAPTER_OPEN) {
        DBG_ASSERT(Status);
    }
    printf("MaxFrameLength: %u\n", MaxFrameLength);

    Status = BufferCreate(pParms->AdapterNumber + 16,
                          pPool = ALLOC(SMALL_BUFFER_POOL_SIZE),
                          SMALL_BUFFER_POOL_SIZE,
                          min(0xfff0, SMALL_BUFFER_POOL_SIZE),
                          0x2000,
                          &hPool
                          );
    if (Status && Status != LLC_STATUS_DUPLICATE_COMMAND) {
        DBG_ASSERT(Status);
    }

    Status = LlcDirOpenAdapter(pParms->AdapterNumber,
                               NULL,
                               hPool,
                               &OpenError,
                               NULL,
                               &MaxFrameLength,
                               NULL
                               );
    if (Status && Status != LLC_STATUS_ADAPTER_OPEN) {
        DBG_ASSERT(Status);
    }

    //
    // Now we may close the actual owner of the buffer pool!
    //

    pCcb = LlcCloseInit((UCHAR)(pParms->AdapterNumber + 16),
                        0,
                        LLC_DIR_CLOSE_ADAPTER,
                        0
                        );
    Status = AcsLan(pCcb, NULL);
    DBG_ASSERT(pCcb->uchDlcStatus);

    Status = LlcDlcOpenSap(pParms->AdapterNumber,
                           MaxFrameLength,
                           pParms->FirstClientSap,
                           LLC_INDIVIDUAL_SAP | LLC_XID_HANDLING_IN_DLC,
                           pParms->AllocatedStations,
                           0,
                           NULL,
                           TEST_DLC_STATUS_FLAG,
                           NULL,
                           &SapStationId,
                           &LinksAvail
                           );
    DBG_ASSERT(Status);

    Status = LlcDlcOpenSap(pParms->AdapterNumber,
                           MaxFrameLength,
                           (UCHAR)(pParms->FirstClientSap + (UCHAR)2),
                           LLC_INDIVIDUAL_SAP | LLC_XID_HANDLING_IN_APPLICATION,
                           pParms->AllocatedStations,
                           0,
                           NULL,
                           TEST_DLC_STATUS_FLAG,
                           NULL,
                           &SapStationId2,
                           &LinksAvail
                           );
    DBG_ASSERT(Status);

    ReportDirStatus(pParms->AdapterNumber);

    //
    // Test DlcReallocate
    //

    ReportDlcReallocate(pParms->AdapterNumber,
                        SapStationId2,
                        LLC_INCREASE_LINK_STATIONS,
                        0,
                        FALSE
                        );
    ReportDlcReallocate(pParms->AdapterNumber,
                        SapStationId2,
                        LLC_DECREASE_LINK_STATIONS,
                        (UCHAR)(pParms->AllocatedStations - 1),
                        FALSE
                        );
    ReportDlcReallocate(pParms->AdapterNumber,
                        SapStationId2,
                        LLC_DECREASE_LINK_STATIONS,
                        (UCHAR)2,
                        FALSE
                        );
    ReportDlcReallocate(pParms->AdapterNumber,
                        SapStationId2,
                        LLC_INCREASE_LINK_STATIONS,
                        (UCHAR)pParms->AllocatedStations,
                        FALSE
                        );

    //
    // Setup immediately a pending receive
    //

    pCcb = ReceiveInit(pParms->AdapterNumber,
                       TEST_COMMAND_COMPLETION_FLAG,
                       SapStationId,
                       0,
                       TEST_RECEIVE_FLAG,
                       LLC_NOT_CONTIGUOUS_DATA,
                       LLC_RCV_READ_INDIVIDUAL_FRAMES
                       );
    Status = AcsLan(pCcb, NULL);

    //
    // Setup immediately a pending receive
    //

    pCcb = ReceiveInit(pParms->AdapterNumber,
                       TEST_COMMAND_COMPLETION_FLAG,
                       SapStationId2,
                       0,
                       TEST_RECEIVE_FLAG,
                       LLC_NOT_CONTIGUOUS_DATA,
                       LLC_RCV_READ_INDIVIDUAL_FRAMES
                       );
    Status = AcsLan(pCcb, NULL);

    //
    // **** TEST FUNCTIONAL AND BROADCAST ADDRESSES ****
    //

    TestPacket.PacketId = DLC_TEST_ID;
    TestPacket.Command = TEST_COMMAND_READ;
    TestPacket.RepeatCount = 1;
    memcpy(&SendBuffer[FOOBAR_HEADER_LENGTH], &TestPacket, sizeof(TestPacket));

    //
    // read the broadcast responses to figure out the real destination address
    //

    pReadCcb = ReadInit(pParms->AdapterNumber,
                        0,
                        LLC_OPTION_READ_ALL,
                        LLC_READ_ALL_EVENTS     // THIS DOESN't MEAN ANYTHING ANY MORE
                        );
    pReadParms = &pReadCcb->u.pParameterTable->Read;


#ifdef OS2_EMU_DLC
    for (i = 0; i < 4; i++) {
#else
    for (i = 0; i < 5; i++) {
#endif
        aCcb[i] = TransmitInit(pParms->AdapterNumber,
                               LLC_TRANSMIT_UI_FRAME,
                               TEST_TRANSMIT_COMPLETION_FLAG,
                               SapStationId,
                               (UCHAR)(SapStationId >> 8),
                               LLC_CHAIN_XMIT_COMMANDS_ON_SAP
                               );
        aCcb[i]->u.pParameterTable->Transmit.pBuffer1 = TestBroadcastHeader;
        aCcb[i]->u.pParameterTable->Transmit.cbBuffer1 = sizeof(TestBroadcastHeader);
        aCcb[i]->u.pParameterTable->Transmit.pBuffer2 = SendBuffer;
        aCcb[i]->u.pParameterTable->Transmit.cbBuffer2 = sizeof(SendBuffer);
        //aCcb[i]->u.pParameterTable->Transmit.cbBuffer2 = SIZEOF_TEST_PACKET;
        aCcb[i]->hCompletionEvent = hEvent;

        switch (i) {
        case 0:

            //
            // NDIS emulator needs the first frame to synchronize again
            // with the client (I don't really know what happens,
            // but the first frame is lost, when the ndis emulator
            // is reconnected),
            //

            memcpy(&TestBroadcastHeader[4], FunctionalAddress1, 4);
            break;

        case 1:
            TestPacket.Command = TEST_COMMAND_ECHO_BACK;
            memcpy(&SendBuffer[FOOBAR_HEADER_LENGTH], &TestPacket, sizeof(TestPacket));
            memcpy(&TestBroadcastHeader[4], FunctionalAddress1, 4);
            break;

        case 2:
            memcpy(&TestBroadcastHeader[4], FunctionalAddress2, 4);
            break;

        case 3:
            memcpy(&TestBroadcastHeader[4], FunctionalAddress3, 4);
            break;

        case 4:
            memcpy(&TestBroadcastHeader[4], pParms->GroupAddress, 4);
            break;
        }
        Status = AcsLan(aCcb[i], NULL);
        DBG_ASSERT(aCcb[i]->uchDlcStatus);
        Status = WaitForSingleObject(aCcb[i]->hCompletionEvent, 10000);
        DBG_ASSERT(Status);
        DBG_ASSERT(aCcb[i]->uchDlcStatus);
        aCcb[i]->hCompletionEvent = NULL;
    }
    Status = ReadClientEvent(pReadCcb,
                             NULL,
                             LLC_EVENT_RECEIVE_DATA,
                             2,
                             5000L,
                             NULL
                             );
    DBG_ASSERT(Status);

    pFirstBuffer = pReadCcb->u.pParameterTable->Read.Type.Event.pReceivedFrame;

    memcpy(NodeAddress, &pFirstBuffer->NotContiguous.auchLanHeader[8], 6);
    memcpy(LanHeader, pFirstBuffer->NotContiguous.auchLanHeader, 32);
    memcpy(&LanHeader[2], &pFirstBuffer->NotContiguous.auchLanHeader[8], 6);
    pSourceRoutingInfo = ((LanHeader[8] & 0x80) ? &LanHeader[14] : NULL);
    NodeAddress[0] &= (UCHAR)0x7f;
    LanHeader[2] &= (UCHAR)0x7f;

    //
    // Toggle the direction bit and reset the source routing info bit
    // in the destination address (leave the source address as it, because
    // it will be updated by DLC)
    //

    LanHeader[15] ^= (UCHAR)0x80;
    DestinationSap = (UCHAR)(pFirstBuffer->NotContiguous.auchDlcHeader[1] & 0xfe);

    Status = ReadClientEvent(pReadCcb,
                             NULL,
                             LLC_EVENT_RECEIVE_DATA,
                             1,
                             1000L,
                             NULL
                             );
    if (Status != STATUS_SUCCESS) {
        puts("ERROR!  GROUP ADDRESS DOESN'T WORK!!!!");
    }
    Status = ReadClientEvent(pReadCcb,
                             NULL,
                             LLC_EVENT_RECEIVE_DATA,
                             1,
                             100L,
                             NULL
                             );
//    if (Status == STATUS_SUCCESS)
//        DBG_ASSERT(-1);

    Status = ReadClientEvent(pReadCcb,
                             &TransmitCcbPool,
                             LLC_EVENT_TRANSMIT_COMPLETION,
//                             5,
                             3,
                             1000L,
                             NULL
                             );
    DBG_ASSERT(Status);

    //
    // **** TESTING: ****
    //      - GROUP SAPs
    //      - Global SAP
    //      - null sap
    //      - Xid commands (xid handling in data link & in application)
    //      - test command
    //

    pCcb = TransmitInit(pParms->AdapterNumber,
                        LLC_TRANSMIT_UI_FRAME,
                        TEST_TRANSMIT_COMPLETION_FLAG,
                        SapStationId,
                        (UCHAR)(SapStationId >> 8),
                        LLC_CHAIN_XMIT_COMMANDS_ON_SAP
                        );
    pCcb->u.pParameterTable->Transmit.pBuffer1 = LanHeader;
//    pCcb->u.pParameterTable->Transmit.pBuffer1 = TestLanHeader;
    pCcb->u.pParameterTable->Transmit.cbBuffer1 = sizeof(LanHeader);
    pCcb->u.pParameterTable->Transmit.pBuffer2 = SendBuffer;
    pCcb->u.pParameterTable->Transmit.cbBuffer2 = sizeof(SendBuffer);
    //pCcb->u.pParameterTable->Transmit.cbBuffer2 = SIZEOF_TEST_PACKET;
    pCcb->hCompletionEvent = hEvent;
    pCcb->ulCompletionFlag = 0;

    pCcb->u.pParameterTable->Transmit.uchRemoteSap = 0xff;
    Status = DlcTestTransact(pCcb, pReadCcb, LLC_UI_FRAME);
    if (Status != STATUS_SUCCESS) {
        GlobalSapIsWorking = FALSE;
        puts("ERROR!!!!, IBMTOK ADAPTER ATE A PACKET SENT TO THE GLOBAL SAP!!!!");
    }
    pCcb->u.pParameterTable->Transmit.uchRemoteSap = GROUP_SAP1;
    Status = DlcTestTransact(pCcb, pReadCcb, LLC_UI_FRAME);
    if (Status != STATUS_SUCCESS) {
        puts("HELP!!!!, IBMTOK ADAPTER ATE A PACKET SENT TO A GROUP SAP!!!!");
    } else {
        pCcb->u.pParameterTable->Transmit.uchRemoteSap = GROUP_SAP3;
        Status = DlcTestTransact(pCcb, pReadCcb, LLC_UI_FRAME);
        DBG_ASSERT(Status);
    }

    //
    // Test TEST and XID commands agains the null sap
    // !!!! XID HANDLING IN DLC !!!!
    //

    pCcb->u.pParameterTable->Transmit.cbBuffer2 = 3;
    pCcb->u.pParameterTable->Transmit.uchRemoteSap = 0;
    pCcb->uchDlcCommand = LLC_TRANSMIT_TEST_CMD;
    Status = DlcTestTransact(pCcb, pReadCcb, LLC_TEST_RESPONSE_FINAL);
    if (Status != STATUS_SUCCESS) {
        puts("HELP!!!!, IBMTOK ADAPTER ATE A PACKET SENT TO A GROUP SAP!!!!");
    }
    pCcb->uchDlcCommand = LLC_TRANSMIT_XID_CMD;
    Status = DlcTestTransact(pCcb, pReadCcb, LLC_XID_RESPONSE_FINAL);
    DBG_ASSERT(Status);

    //
    // Test TEST the test frame with a big buffer, send any stuff
    // from the stack (exclude the max lan header (32))
    //

    pCcb->u.pParameterTable->Transmit.pBuffer2 = ALLOC(MaxFrameLength);
    if (pCcb->u.pParameterTable->Transmit.pBuffer2 == NULL) {
        DBG_ASSERT(-1);
    }
    pCcb->u.pParameterTable->Transmit.cbBuffer2 = (USHORT)(3 + MaxFrameLength - 32);
    pCcb->u.pParameterTable->Transmit.uchRemoteSap = 0;
    pCcb->uchDlcCommand = LLC_TRANSMIT_TEST_CMD;
    Status = DlcTestTransact(pCcb, pReadCcb, LLC_TEST_RESPONSE_FINAL);
    DBG_ASSERT(Status);
    FREE(pCcb->u.pParameterTable->Transmit.pBuffer2);

    pCcb->u.pParameterTable->Transmit.cbBuffer2 = 3;    // sizeof dlc header
    pCcb->u.pParameterTable->Transmit.pBuffer2 = SendBuffer;
    pCcb->u.pParameterTable->Transmit.uchRemoteSap = GROUP_SAP3;
    pCcb->uchDlcCommand = LLC_TRANSMIT_TEST_CMD;
    Status = DlcTestTransact(pCcb, pReadCcb, LLC_TEST_RESPONSE_FINAL);
    if (Status != STATUS_SUCCESS) {
        puts("HELP!!!!, IBMTOK ADAPTER ATE A LLC TEST FRAME SENT TO A GROUP SAP!!!!");
    } else {
        pCcb->uchDlcCommand = LLC_TRANSMIT_XID_CMD;
        Status = DlcTestTransact(pCcb, pReadCcb, LLC_XID_RESPONSE_FINAL);
        DBG_ASSERT(Status);
    }

    //
    // !!!! XID HANDLING IN DLC !!!!
    //

    if (GlobalSapIsWorking) {
        pCcb->u.pParameterTable->Transmit.uchRemoteSap = 0xff;
        pCcb->uchDlcCommand = LLC_TRANSMIT_TEST_CMD;
        Status = DlcTestTransact(pCcb, pReadCcb, LLC_TEST_RESPONSE_FINAL);
        DBG_ASSERT(Status);
        pCcb->uchDlcCommand = LLC_TRANSMIT_XID_CMD;
        Status = DlcTestTransact(pCcb, pReadCcb, LLC_XID_RESPONSE_FINAL);
        DBG_ASSERT(Status);
    }

/*
    pCcb->uchDlcCommand = LLC_TRANSMIT_XID_CMD;
    Status = DlcTestTransact(pCcb, pReadCcb, LLC_XID_RESPONSE_FINAL);
    DBG_ASSERT(Status);
*/

    //
    // The first remote sap station should handle the XID stations by itself,
    // The first server sap in the other side does the same thing =>
    // we can use XID's to exchange data.
    //

    pCcb->u.pParameterTable->Transmit.uchRemoteSap = DestinationSap;
    pCcb->uchDlcCommand = LLC_TRANSMIT_TEST_CMD;
    Status = DlcTestTransact(pCcb, pReadCcb, LLC_TEST_RESPONSE_FINAL);
    if (Status != STATUS_SUCCESS) {
        puts("HELP!!!!, IBMTOK ADAPTER ATE A LLC TEST FRAME!!!!");
    }

    //
    //  This will fail, because remote sap will not answer to 802.2 xids
    //

//    pCcb->uchDlcCommand = LLC_TRANSMIT_XID_CMD;
//    Status = DlcTestTransact(pCcb, pReadCcb, LLC_XID_RESPONSE_FINAL);
//    if (Status == STATUS_SUCCESS)
//        DBG_ASSERT(-1);

    //
    // Send now the packet header (=> server echoes it back)
    // *** XID CHANGE BEWTEEN APPLICATIONS ***
    //

    pCcb->u.pParameterTable->Transmit.usStationId = SapStationId2;
    pCcb->u.pParameterTable->Transmit.cbBuffer2 = sizeof(SendBuffer);
    //pCcb->u.pParameterTable->Transmit.cbBuffer2 = SIZEOF_TEST_PACKET;
    pCcb->uchDlcCommand = LLC_TRANSMIT_XID_CMD;
    Status = DlcTestTransact(pCcb, pReadCcb, LLC_XID_RESPONSE_FINAL);
    DBG_ASSERT(Status);

    //
    // Read all extra connectionless frames (XID/TEST duplicates or
    // broadcasts from the net.
    //
    //
    // Here we test the send and receive on a dix station
    //

    Status = LlcDirOpenDirect(pParms->AdapterNumber, 0, TEST_DIX_TYPE);

    //
    // Dix stations doesn't work on token-ring
    //

    if (Status == STATUS_SUCCESS) {
        pCcb = ReceiveInit(pParms->AdapterNumber,
                           TEST_COMMAND_COMPLETION_FLAG,
                           0,
                           0,
                           TEST_RECEIVE_FLAG,
                           LLC_NOT_CONTIGUOUS_DATA,
                           LLC_RCV_READ_INDIVIDUAL_FRAMES
                           );
        Status = AcsLan(pCcb, NULL);
        DBG_ASSERT(Status);

        //
        // DIX interface always uses Ethernet LAN headers and Ethernet
        // format.
        //

        TranslateLanHeader(LLC_SEND_802_5_TO_802_3, LanHeader, DixLanHeader);
        memset(&TransmitParms, 0, sizeof(TransmitParms));
        TransmitParms.usStationId = 0;
        TransmitParms.usFrameType = TEST_DIX_TYPE;
        TransmitParms.uchRemoteSap = 0;                // ignored
        TransmitParms.uchXmitReadOption = LLC_CHAIN_XMIT_COMMANDS_ON_SAP;
        TransmitParms.cXmitBufferCount = 2;
        TransmitParms.XmitBuffer[0].pBuffer =  DixLanHeader;
        TransmitParms.XmitBuffer[0].cbBuffer = sizeof(DixLanHeader);
        TransmitParms.XmitBuffer[0].eSegmentType = LLC_FIRST_DATA_SEGMENT;
        TransmitParms.XmitBuffer[1].pBuffer =  &TestPacket;
        TransmitParms.XmitBuffer[1].cbBuffer = sizeof(TestPacket);
        TransmitParms.XmitBuffer[1].eSegmentType = LLC_NEXT_DATA_SEGMENT;
        pCcb = AllocCcb(pParms->AdapterNumber, LLC_TRANSMIT_FRAMES, 0);
        pCcb->u.pParameterTable = (PVOID)&TransmitParms;
        Status = DlcTestTransact(pCcb, pReadCcb, LLC_DIRECT_ETHERNET_TYPE);
        DBG_ASSERT(Status);
        FreeCcb(pCcb);
    }

    //
    // Open a link station,
    // we have the destination station node address and sap number
    // in the received frame.
    //

    Status = DlcOpenStation(pParms->AdapterNumber,
                            SapStationId,
                            DestinationSap,
                            NodeAddress,
                            NULL,
                            &LinkStationId
                            );
    DBG_ASSERT(Status);

    pCcb = DlcConnectStationInit(pParms->AdapterNumber,
                                 TEST_COMMAND_COMPLETION_FLAG,
                                 LinkStationId,
                                 pSourceRoutingInfo
                                 );

    //
    //  Connect to the remote node
    //  BUG-BUG-BUG-BUG-BUG-: THIS DOESN'T WORK WITH SOURCE ROUTING INFO!!!
    //

    Status = AcsLan(pCcb, NULL);
    DBG_ASSERT(pCcb->uchDlcStatus);
    Status = ReadClientEvent(pReadCcb,
                             &TransmitCcbPool,
                             LLC_EVENT_COMMAND_COMPLETION,
                             1,
                             20000L,
                             NULL
                             );
    DBG_ASSERT(Status);

    //
    // Setup receive
    //

    pCcb = ReceiveInit(pParms->AdapterNumber,
                       TEST_COMMAND_COMPLETION_FLAG,
                       LinkStationId,
                       0,
                       TEST_RECEIVE_FLAG,
                       LLC_NOT_CONTIGUOUS_DATA,
                       LLC_RCV_CHAIN_FRAMES_ON_LINK
                       );
    Status = AcsLan(pCcb, NULL);
    DBG_ASSERT(Status);

    //
    //  We read the events only on this link station
    //

    pLinkReadCcb = ReadInit(pParms->AdapterNumber,
                            LinkStationId,
                            LLC_OPTION_READ_STATION,
                            0
                            );
    Status = BufferGet(pParms->AdapterNumber, 1, 0x100, &pBuffer1, &cLeft);
    DBG_ASSERT(Status);
    Status = BufferGet(pParms->AdapterNumber, 15, 0x100, &pBuffer2, &cLeft);
    DBG_ASSERT(Status);
    SetBuffers(pBuffer1, TEST_COMMAND_ECHO_BACK, 1, '0', '9');

    //
    // Send data and receive it back
    //

    pTransmitBuffer->usStationId = LinkStationId;
    pTransmitBuffer->usFrameType = LLC_I_FRAME;
    pTransmitBuffer->uchXmitReadOption = LLC_CHAIN_XMIT_COMMANDS_ON_LINK;
    TransmitBuffers(pBuffer1,                   // DLC buffer list
                    pTransmitBuffer,            //
                    &TransmitCcbPool,           // pool for transmit CCBs
                    1,                          // repeat count
                    0,                          // current index in buffer table
                    FALSE,
                    FALSE,
                    NULL
                    );
    Status = ReadClientEvent(pLinkReadCcb,
                             &TransmitCcbPool,
                             LLC_EVENT_TRANSMIT_COMPLETION + LLC_EVENT_RECEIVE_DATA,
                             2,
                             5000L,
                             NULL
                             );
    DBG_ASSERT(Status);

    //
    // Set local station busy
    //

    DlcFlowControl(pParms->AdapterNumber, LinkStationId, LLC_SET_LOCAL_BUSY_USER);

    //
    // Send a lot of data with an echo request
    //

    TransmitBuffers(pBuffer1,                   // DLC buffer list
                    pTransmitBuffer,            //
                    &TransmitCcbPool,           // pool for transmit CCBs
                    50,                         // repeat count
                    0,                          // current index in buffer table
                    FALSE,
                    FALSE,
                    NULL
                    );
    Status = ReadClientEvent(pLinkReadCcb,
                             &TransmitCcbPool,
                             LLC_EVENT_RECEIVE_DATA,
                             1,
                             500L,
                             NULL
                             );
    if (Status == STATUS_SUCCESS) {
        DBG_ASSERT(-1);
    }

    //
    // reset local busy
    //

    DlcFlowControl(pParms->AdapterNumber, LinkStationId, LLC_RESET_LOCAL_BUSY_USER);

    //
    // Sleep a second to wait receive buffers to overflow
    //

    Sleep(1000L);

    //
    // Check the out of buffers problem
    //

    Status = ReadClientEvent(pLinkReadCcb,
                             &TransmitCcbPool,
                             LLC_EVENT_STATUS_CHANGE,
                             1,
                             5000L,
                             NULL
                             );
    DBG_ASSERT(Status);

    Status = BufferFree(pParms->AdapterNumber, pBuffer2, &cLeft);
    DBG_ASSERT(Status);

    //
    // reset local out of buffer busy and receive the data
    //

    DlcFlowControl(pParms->AdapterNumber, LinkStationId, LLC_RESET_LOCAL_BUSY_BUFFER);

    Status = ReadClientEvent(pLinkReadCcb,
                             &TransmitCcbPool,
                             LLC_EVENT_RECEIVE_DATA,
                             50,
                             5000L,
                             NULL
                             );
    DBG_ASSERT(Status);

    ReportDlcStatistics(pParms->AdapterNumber, SapStationId);
    ReportDlcStatistics(pParms->AdapterNumber, LinkStationId);

    //
    // Send a lot of data and close the station when all
    // this data is pending.
    //

    SetBuffers(pBuffer1, TEST_COMMAND_READ, 1, '0', '9');
    TransmitBuffers(pBuffer1,                   // DLC buffer list
                    pTransmitBuffer,
                    &TransmitCcbPool,           // pool for transmit CCBs
                    20,                         // repeat count
                    0,                          // current index in buffer table
                    FALSE,
                    FALSE,
                    NULL
                    );

    //
    // Close station
    //

    pCcb = LlcCloseInit(pParms->AdapterNumber,
                        TEST_COMMAND_COMPLETION_FLAG,
                        LLC_DLC_CLOSE_STATION,
                        LinkStationId
                        );
    Status = AcsLan(pCcb, NULL);
    DBG_ASSERT(pCcb->uchDlcStatus);

    //
    // Read the close command and the receive command linked to it
    //

    Status = ReadClientEvent(pReadCcb,
                             &TransmitCcbPool,
                             LLC_EVENT_COMMAND_COMPLETION,
                             1,
                             10000L,
                             NULL
                             );
    DBG_ASSERT(Status);

    //
    // TEST LINK TIMEOUT (By sending a too large frame => disconnects
    // the link station, when we cannot send the frame (its discarded
    // every time as too big).
    //

    Status = DlcOpenStation(pParms->AdapterNumber,
                            SapStationId,
                            DestinationSap,
                            NodeAddress,
                            NULL,
                            &LinkStationId
                            );
    DBG_ASSERT(Status);

    pCcb = DlcConnectStationInit(pParms->AdapterNumber,
                                 TEST_COMMAND_COMPLETION_FLAG,
                                 LinkStationId,
                                 pSourceRoutingInfo
                                 );
    Status = AcsLan(pCcb, NULL);
    DBG_ASSERT(Status);
    DBG_ASSERT(pCcb->uchDlcStatus);

    Status = ReadClientEvent(pReadCcb,
                             &TransmitCcbPool,
                             LLC_EVENT_COMMAND_COMPLETION,
                             1,
                             5000L,
                             NULL
                             );
    DBG_ASSERT(Status);

    //
    //  Set parameters, that makes the linkstation die quickly
    //

    Status = DlcModify((UINT)pParms->AdapterNumber,
                       LinkStationId,
                       &DlcModifyQuickDeathParms
                       );
    DBG_ASSERT(Status);

    pCcb = TransmitInit(pParms->AdapterNumber,
                        LLC_TRANSMIT_I_FRAME,
                        0,
                        LinkStationId,
                        0,
                        LLC_CHAIN_XMIT_COMMANDS_ON_LINK
                        );
    pCcb->u.pParameterTable->Transmit.pBuffer1 = ALLOC(20000);
    pCcb->u.pParameterTable->Transmit.cbBuffer1 = 20000;
    pCcb->hCompletionEvent = hEvent;

    Status = AcsLan(pCcb, NULL);
    if (pCcb->uchDlcStatus == LLC_STATUS_PENDING) {
        Status = WaitForSingleObject(hEvent, 10000);
    }
    if (pCcb->uchDlcStatus == STATUS_SUCCESS) {
        puts("ERROR: the sending of too large frame succeeded!");
        DBG_ASSERT(-1);
    }
    FREE(pCcb->u.pParameterTable->Transmit.pBuffer1);
    FreeCcb(pCcb);

/*
*******************************************************************************
*    Change in the code: the link always discards too long frames
*    before they are queued => too long frames cannot disconnect
*    the link any more.
*
*    //
*    //  There must be an indication of the lost link station
*    //
*    Status =
*        ReadClientEvent(
*            pReadCcb,
*            &TransmitCcbPool,
*            LLC_EVENT_STATUS_CHANGE,
*            1,
*            5000L,
*            NULL
*            );
*    //
*    //  The lost link station event returns an error code!
*    //
*    if (Status != -1)
*    {
*        DBG_ASSERT(-1);
*    }
*
*    // close sap
*    pCcb =
*        LlcCloseInit(
*            pParms->AdapterNumber,
*            TEST_COMMAND_COMPLETION_FLAG,
*            LLC_DLC_CLOSE_SAP,
*            SapStationId
*            );
*    Status = AcsLan(pCcb, NULL);
*    DBG_ASSERT(pCcb->uchDlcStatus);
*******************************************************************************
*/

    pCcb = LlcCloseInit(pParms->AdapterNumber, 0, LLC_DIR_CLOSE_ADAPTER, 0);
    Status = AcsLan(pCcb, NULL);
    DBG_ASSERT(pCcb->uchDlcStatus);

    //
    // Free all uncompleted commands on this adapter
    //

    while (pCcb != NULL) {

        PLLC_CCB pNextCcb;

        pNextCcb = pCcb->pNext;
        FreeCcb(pCcb);
        pCcb = pNextCcb;
    }

    FREE(pTransmitBuffer);
    CcbPoolDelete(&TransmitCcbPool);
    FreeCcb(pReadCcb);
    FreeCcb(pLinkReadCcb);
    FREE(pParms);
    FREE(pPool);

#ifdef OS2_EMU_DLC
    LlcTraceDumpAndReset(-1, 1, NULL);
#endif

    return STATUS_SUCCESS;
}

UINT
LinkConnectStressTest(
    IN PDLC_TEST_THREAD_PARMS pParms
    )

/*++

    This procedure stress tests the connection,
    small data transfer, and disconnection of very
    many link stations.

--*/

{
    UINT Status;
    UINT OpenError;
    UINT MaxFrameLength;
    PVOID hPool;
    LLC_TEST_CCB_POOL TransmitCcbPool;
    PLLC_TRANSMIT2_COMMAND pTransmitBuffer;
    USHORT aLinkStationIds[255];
    USHORT aSapStationIds[128];
    UINT iSap;
    UINT MaxSapStations;
    UINT iLink;
    UINT MaxLinks;
    UINT i;
    DLC_TEST_PACKET TestPacket;
    PLLC_CCB pCcb;
    PLLC_CCB pReadCcb;
    PLLC_CCB pConnectCcb;
    PLLC_CCB pReceiveCcb;
    UCHAR LinksAvail;
    UINT LoopCounter;
    UINT TotalBuffers;
    UCHAR MaxStations;
    UCHAR SapStation;
    UCHAR RemoteSap;
    PLLC_BUFFER pFirstBuffer;
    PLLC_BUFFER pBuffer1;
    PLLC_BUFFER pBuffer;
    PLLC_BUFFER* ppBuffer;
    UINT OpenLinks;
    UINT AllocatedLinks;
    UCHAR SendBuffer[sizeof(TestPacket) + FOOBAR_HEADER_LENGTH];
    UCHAR FirstAvailRemoteSap;
    UCHAR LanHeader[32];
    UINT LanHeaderLength;
    UINT cLeft;
    BOOLEAN FirstTime;
    PUCHAR pSourceRoutingInfo;

    puts("****** LinkConnectStressTest ******");

    pTransmitBuffer = (PLLC_TRANSMIT2_COMMAND)
        ALLOC(sizeof(LLC_TRANSMIT2_COMMAND)
              + sizeof(LLC_TRANSMIT_DESCRIPTOR) * MAX_XMIT_BUFFERS
              );

    //
    // Initialize the Transmit ccb pool
    //

    CcbPoolInitialize(&TransmitCcbPool,
                      pParms->AdapterNumber,
                      LLC_TRANSMIT_FRAMES,
                      TEST_TRANSMIT_COMPLETION_FLAG
                      );
    Status = LlcDirOpenAdapter(pParms->AdapterNumber,
                               NULL,
                               NULL,
                               &OpenError,
                               NULL,
                               &MaxFrameLength,
                               NULL
                               );
    DBG_ASSERT(Status);

    Status = BufferCreate(pParms->AdapterNumber,
                          pParms->pPool,
                          DEFAULT_BUFFER_SIZE,
                          min(0xfff0, DEFAULT_BUFFER_SIZE),
                          0x1000,
                          &hPool
                          );
    DBG_ASSERT(Status);

    Status = LlcDlcOpenSap(pParms->AdapterNumber,
                           MaxFrameLength,
                           pParms->FirstServerSap,
                           LLC_INDIVIDUAL_SAP,
                           0,                      // the stations are allocated by DlcRealloc
                           0,
                           NULL,
                           TEST_DLC_STATUS_FLAG,
                           NULL,
                           &aSapStationIds[0],
                           &LinksAvail
                           );
    DBG_ASSERT(Status);

    //
    // We will use that strange mode of receive command to
    // receive one frame without read command.
    //

    pReceiveCcb = ReceiveInit(pParms->AdapterNumber,
                              0,
                              aSapStationIds[0],
                              0,
                              0,
                              LLC_NOT_CONTIGUOUS_DATA,
                              0
                              );
    Status = AcsLan(pReceiveCcb, NULL);

    //
    // search first a the network address of the DLC server and
    // its all available sap stations
    // (query command: returns number of saps, etc.)
    //

    //
    // Send a broadcast to figure out the destination
    //

    pCcb = TransmitInit(pParms->AdapterNumber,
                        LLC_TRANSMIT_UI_FRAME,
                        TEST_TRANSMIT_COMPLETION_FLAG,
                        aSapStationIds[0],
                        (UCHAR)(aSapStationIds[0] >> 8),
                        LLC_CHAIN_XMIT_COMMANDS_ON_SAP
                        );
    memcpy(&BroadcastHeader[4], FunctionalAddress1, 4);
    pCcb->u.pParameterTable->Transmit.pBuffer1 = BroadcastHeader;
    pCcb->u.pParameterTable->Transmit.cbBuffer1 = sizeof(BroadcastHeader);
    pCcb->u.pParameterTable->Transmit.pBuffer2 = SendBuffer;
    pCcb->u.pParameterTable->Transmit.cbBuffer2 = sizeof(SendBuffer);
    TestPacket.PacketId = DLC_TEST_ID;
    TestPacket.Command = TEST_COMMAND_ECHO_BACK;
    TestPacket.RepeatCount = 1;
    memcpy(&SendBuffer[FOOBAR_HEADER_LENGTH], &TestPacket, sizeof(TestPacket));

    Status = AcsLan(pCcb, NULL);
    DBG_ASSERT(pCcb->uchDlcStatus);

    Status = WaitForSingleObject(pReceiveCcb->hCompletionEvent, 5000L);
    DBG_ASSERT(Status);
    DBG_ASSERT(pReceiveCcb->uchDlcStatus);

    //
    // BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG
    //     pFirstBuffer has already been returned to driver,
    //     the driver can reuse it in any moment and overwrite
    //     the data!!!!
    // BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG
    //

    pFirstBuffer = pReceiveCcb->u.pParameterTable->Receive.pFirstBuffer;
    LanHeaderLength = pFirstBuffer->NotContiguous.cbLanHeader;
    memcpy(LanHeader,
           pFirstBuffer->NotContiguous.auchLanHeader,
           pFirstBuffer->NotContiguous.cbLanHeader
           );
    memcpy(&LanHeader[2], &LanHeader[8], 6);

    //
    // Toggle the direction bit and reset the source routing info bit
    // in the destination address (leave the source address as it, because
    // it will be updated by DLC)
    //

    LanHeader[2] &= (UCHAR)0x7f;
    LanHeader[15] ^= (UCHAR)0x80;

    pSourceRoutingInfo = ((LanHeader[8] & 0x80) ? &LanHeader[14] : NULL);

    FreeCcb(pReceiveCcb);

/*
*******************************************************************************
*We transfer data only over links, don't need receive for a sap station.
*
*    //
*    //  Setup immediately a pending receive, link stations
*    //  data will be read from link stations and sap station
*    //  data from sap stations by default
*    //
*    pCcb =
*        ReceiveInit(
*            pParms->AdapterNumber,
*            TEST_COMMAND_COMPLETION_FLAG,
*            aSapStationIds[0],
*            0,
*            TEST_RECEIVE_FLAG,
*            LLC_NOT_CONTIGUOUS_DATA,
*            LLC_RCV_CHAIN_FRAMES_ON_SAP
*            );
*    Status = AcsLan(pCcb, NULL);
*
*******************************************************************************
*/
    pReadCcb = ReadInit(pParms->AdapterNumber,
                        0,
                        LLC_OPTION_READ_ALL,
                        LLC_READ_ALL_EVENTS
                        );
    Status = ReadClientEvent(pReadCcb,
                             &TransmitCcbPool,
                             LLC_EVENT_TRANSMIT_COMPLETION,
                             1,
                             5000L,
                             NULL
                             );
    DBG_ASSERT(Status);

/*
*******************************************************************************
*    Status =
*        ReadClientEvent(
*            pReadCcb,
*            &TransmitCcbPool,
*            LLC_EVENT_RECEIVE_DATA,
*            1,
*            5000L
*            );
*    DBG_ASSERT(Status);
*******************************************************************************
*/

    iSap = 1;
    MaxStations = (UCHAR)(256 / pParms->SapCount);

    for (SapStation = (UCHAR)(pParms->FirstServerSap + 2);
         SapStation < (UCHAR)(pParms->FirstClientSap + pParms->SapCount * 2);
         SapStation += 2) {

        Status = LlcDlcOpenSap(pParms->AdapterNumber,
                               MaxFrameLength,
                               SapStation,
                               LLC_INDIVIDUAL_SAP,
                               0,       // MaxStations,
                               0,
                               NULL,
                               TEST_DLC_STATUS_FLAG,
                               NULL,
                               &aSapStationIds[iSap],
                               &LinksAvail
                               );
        DBG_ASSERT(Status);

        iSap++;
    }
    MaxSapStations = iSap;

    //
    // prepare a transmit buffer
    //

    Status = BufferGet(pParms->AdapterNumber, 1, 0x100, &pBuffer1, &cLeft);
    DBG_ASSERT(Status);

    //
    //  Allocate all buffers left in the buffer pool
    //

    ppBuffer = &pBuffer;
    TotalBuffers = 0;
    while (BufferGet(pParms->AdapterNumber, 1, 0x100, ppBuffer, &cLeft) == STATUS_SUCCESS) {
        TotalBuffers++;
        ppBuffer = &(*ppBuffer)->Next.pNextBuffer;
    }
    PRINTF("Total buffer available: %u\n", TotalBuffers);
    Status = BufferFree(pParms->AdapterNumber, pBuffer, &cLeft);
    DBG_ASSERT(Status);

    //
    // We must be able to allocate the same number of buffer as before
    // the test. Otherwise we have lost buffers.
    //

    Status = BufferGet(pParms->AdapterNumber, TotalBuffers, 0x100, &pBuffer, &cLeft);
    DBG_ASSERT(Status);
    Status = BufferFree(pParms->AdapterNumber, pBuffer, &cLeft);
    DBG_ASSERT(Status);

    pBuffer1->Next.cbBuffer = 0x80;
    pTransmitBuffer->usFrameType = LLC_I_FRAME;
    pTransmitBuffer->uchXmitReadOption = LLC_CHAIN_XMIT_COMMANDS_ON_SAP;

    pConnectCcb = DlcConnectStationInit(pParms->AdapterNumber,
                                        0,
                                        0,
                                        pSourceRoutingInfo
                                        );

    for (LoopCounter = 0; LoopCounter < pParms->LoopCount; LoopCounter++) {

        //
        // Reset all linkstation counts
        //

        for (iSap = 0; iSap < pParms->SapCount; iSap++) {

             Status = ReportDlcReallocate(pParms->AdapterNumber,
                                          aSapStationIds[iSap],
                                          LLC_DECREASE_LINK_STATIONS,
                                          127,
                                          TRUE
                                          );
        }

        //
        // Setup a link sessions to all possible remote stations
        // this loop try to create a maximum number of cross
        // connections between two machines.
        // There may be several other threads creating link stations
        // parallelly. (Parallelly Jones I presume?)
        //

        FirstTime = TRUE;
        FirstAvailRemoteSap = pParms->FirstServerSap;
        iLink = 0;
        for (iSap = 0; iSap < pParms->SapCount; iSap++) {
            MaxLinks = iLink + MaxStations;
            AllocatedLinks = OpenLinks = 0;
            for (RemoteSap = FirstAvailRemoteSap;
                 RemoteSap <= (UCHAR)(pParms->FirstServerSap + pParms->SapCount * 2);
                 RemoteSap += 2) {

                //
                // We increase the allocated link stations one by one
                //

                if (OpenLinks >= AllocatedLinks) {
                    Status = ReportDlcReallocate(pParms->AdapterNumber,
                                                 aSapStationIds[iSap],
                                                 LLC_INCREASE_LINK_STATIONS,
                                                 1,
                                                 TRUE
                                                 );
                    DBG_ASSERT(Status);
                    AllocatedLinks++;
                }

                //
                // Open a link station,
                // we have the destination station node address and sap number
                // in the received frame.
                //

                if (VerboseMode) {
                    printf("Opening link station %d: SSAP = %04x DSAP = %04x\n",
                           iSap,
                           aSapStationIds[iSap],
                           RemoteSap
                           );
                }

                Status = DlcOpenStation(pParms->AdapterNumber,
                                        aSapStationIds[iSap],
                                        RemoteSap,
                                        &LanHeader[2],
                                        NULL,
                                        &aLinkStationIds[iLink]
                                        );
                DBG_ASSERT(Status);

                if (Status == STATUS_SUCCESS) {
//*
                    //
                    //  The first link connection must have a high retry
                    //  count, because server cannot response too fast
                    //  when its processing close command completions
                    //

                    if (iLink == 0) {
                        Status = DlcModify(pParms->AdapterNumber,
                                           aLinkStationIds[iLink],
                                           &FirstModifyParms
                                           );
                    }

                    //
                    // Here we will change DLC parameters, when the
                    // station is not yet active
                    //

                    else if (AlternateParms[iLink % MAX_ALTERNATE_PARMS] != NULL) {
                        Status = DlcModify(pParms->AdapterNumber,
                                           aLinkStationIds[iLink],
                                           AlternateParms[iLink % MAX_ALTERNATE_PARMS]
                                           );
                    }
//*/
                    //
                    // Connect to the remote node
                    //

                    pConnectCcb->u.pParameterTable->DlcConnectStation.usStationId = aLinkStationIds[iLink];
                    AcsLan(pConnectCcb, NULL);

                    //
                    // The trace printing in the server side may take a
                    // very long time, before it completes
                    //

                    if (FirstTime == TRUE) {
                        FirstTime = FALSE;
                        Status = WaitForSingleObject(pConnectCcb->hCompletionEvent, 20000L);
                    } else {
                        Status = WaitForSingleObject(pConnectCcb->hCompletionEvent, 5000L);
                    }
                    DBG_ASSERT(Status);
                    if (pConnectCcb->uchDlcStatus == LLC_STATUS_CONNECT_FAILED) {

                        //
                        // The other side has no link stations allocated
                        // for this sap. Try the next one.
                        //

                        pCcb = LlcCloseInit(pParms->AdapterNumber,
                                            TEST_COMMAND_COMPLETION_FLAG,
                                            LLC_DLC_CLOSE_STATION,
                                            aLinkStationIds[iLink]
                                            );
                        Status = AcsLan(pCcb, NULL);
                        DBG_ASSERT(pCcb->uchDlcStatus);

                        Status = ReadClientEvent(pReadCcb,
                                                 &TransmitCcbPool,
                                                 LLC_EVENT_COMMAND_COMPLETION,
                                                 1,
                                                 5000L,
                                                 NULL
                                                 );
                        DBG_ASSERT(Status);
                        FirstAvailRemoteSap += 2;
                    } else {
                        DBG_ASSERT(pConnectCcb->uchDlcStatus);

                        pCcb = ReceiveInit(pParms->AdapterNumber,
                                           TEST_COMMAND_COMPLETION_FLAG,
                                           aLinkStationIds[iLink],
                                           0,
                                           TEST_RECEIVE_FLAG,
                                           LLC_NOT_CONTIGUOUS_DATA,
                                           LLC_RCV_CHAIN_FRAMES_ON_SAP
                                           );
                        Status = AcsLan(pCcb, NULL);
                        DBG_ASSERT(pCcb->uchDlcStatus);

                        iLink++;
                        OpenLinks++;
                    }
                    if ((iSap != (UINT)(pParms->SapCount - 1)
                    && OpenLinks == (UINT)(256 / pParms->SapCount))
                    || iLink == 255) {
                        break;
                    }
                }
            }
            if (iLink == 255) {
                break;
            }
        }
        MaxLinks = iLink;

        if (iLink != 255) {
            printf(" Couldn't open all link stations (only %u/255)!", iLink);
            DBG_ASSERT(-1);
        }

        SetBuffers(pBuffer1, TEST_COMMAND_ECHO_BACK, 1, '0', '9');
        for (i = 0; i < 2; i++) {

            puts("Sending data to all link stations.");

            for (iLink = 0; iLink < MaxLinks; iLink++) {
                pTransmitBuffer->usStationId = aLinkStationIds[iLink];
                TransmitBuffers(pBuffer1,                   // DLC buffer list
                                pTransmitBuffer,            //
                                &TransmitCcbPool,           // pool for transmit CCBs
                                DEFAULT_REPEAT_COUNT,       // repeat count
                                0,                          // cur index in buf table
                                FALSE,
                                FALSE,
                                NULL
                                );
            }
            Status = ReadClientEvent(pReadCcb,
                                     &TransmitCcbPool,
                                     LLC_EVENT_RECEIVE_DATA | LLC_EVENT_TRANSMIT_COMPLETION,
                                     MaxLinks * (DEFAULT_REPEAT_COUNT + 1),
                                     20000L,
                                     NULL
                                     );
            DBG_ASSERT(Status);
        }

        SetBuffers(pBuffer1, TEST_COMMAND_READ, 1, 'a', 'z');
        for (iLink = 0; iLink < MaxLinks; iLink++) {
            pTransmitBuffer->usStationId = aLinkStationIds[iLink];
            TransmitBuffers(pBuffer1,                   // DLC buffer list
                            pTransmitBuffer,            //
                            &TransmitCcbPool,           // pool for transmit CCBs
                            1,                          // repeat count
                            0,                          // current index in buffer table
                            FALSE,
                            FALSE,
                            NULL
                            );
            if (LoopCounter < pParms->LoopCount - 1) {
                pCcb = LlcCloseInit(pParms->AdapterNumber,
                                    TEST_COMMAND_COMPLETION_FLAG,
                                    LLC_DLC_CLOSE_STATION,
                                    aLinkStationIds[iLink]
                                    );
                Status = AcsLan(pCcb, NULL);
                DBG_ASSERT(pCcb->uchDlcStatus);
            }
        }
        if (LoopCounter < pParms->LoopCount - 1) {

            //
            // XMIT, CLOSE and RECEIVE for each link:
            //

            puts("Reading DLC.CLOSE.STATIONs & xmit command completions.");

            Status = ReadClientEvent(pReadCcb,
                                     &TransmitCcbPool,
                                     LLC_EVENT_COMMAND_COMPLETION
                                     | LLC_EVENT_TRANSMIT_COMPLETION,
                                     MaxLinks * 3,
                                     15000L,
                                     NULL
                                     );
            DBG_ASSERT(Status);
        }
    }

    //
    // Overload the also the sap transmit queues just before everything
    // is reset
    //

    pTransmitBuffer->usFrameType = LLC_UI_FRAME;
    pTransmitBuffer->uchRemoteSap = pParms->FirstServerSap;
    pTransmitBuffer->aXmitBuffer[0].pBuffer = LanHeader;
    pTransmitBuffer->aXmitBuffer[0].cbBuffer = (USHORT)LanHeaderLength;
    pTransmitBuffer->aXmitBuffer[0].eSegmentType = LLC_FIRST_DATA_SEGMENT;
    pTransmitBuffer->aXmitBuffer[0].boolFreeBuffer = FALSE;
    for (iSap = 0; iSap < MaxSapStations; iSap++) {
        pTransmitBuffer->usStationId = aSapStationIds[iSap];
        TransmitBuffers(pBuffer1,                   // DLC buffer list
                        pTransmitBuffer,            //
                        &TransmitCcbPool,           // pool for transmit CCBs
                        1,                          // repeat count
                        1,                          // current index in buffer table
                        FALSE,
                        FALSE,
                        NULL
                        );
    }

#ifdef OS2_EMU_DLC
    //
    // Wait a short time to start the data transmission
    //
    Sleep(500L);
#endif

    //
    //  Use reset to close all open sap stations and link stations
    //

    pCcb = LlcCloseInit(pParms->AdapterNumber,
                        TEST_COMMAND_COMPLETION_FLAG,
                        LLC_DLC_RESET,
                        0
                        );
    Status = AcsLan(pCcb, NULL);
    DBG_ASSERT(pCcb->uchDlcStatus);

//TraceFlagSet = TRUE;

    puts("Reading DLC.RESET & command completions");

    Status = ReadClientEvent(pReadCcb,
                             &TransmitCcbPool,
                             LLC_EVENT_COMMAND_COMPLETION
                             | LLC_EVENT_TRANSMIT_COMPLETION,
                             1 + MaxLinks * 2 + MaxSapStations,
                             15000L,
                             NULL
                             );
    DBG_ASSERT(Status);

//TraceFlagSet = FALSE;

    //
    // We must be able to allocate the same number of buffer as before
    // the test. Otherwise we have lost buffers.
    //

    Status = BufferGet(pParms->AdapterNumber, TotalBuffers, 0x100, &pBuffer, &cLeft);
    DBG_ASSERT(Status);

//    Status = BufferFree(pParms->AdapterNumber, pBuffer1, &cLeft);
//    DBG_ASSERT(Status);

    pCcb = LlcCloseInit(pParms->AdapterNumber, 0, LLC_DIR_CLOSE_ADAPTER, 0);
    Status = AcsLan(pCcb, NULL);
    while (pCcb->uchDlcStatus == LLC_STATUS_PENDING) {
        Sleep(100L);
    }
    DBG_ASSERT(pCcb->uchDlcStatus);
    FreeCcb(pCcb);

    FREE(pTransmitBuffer);
    CcbPoolDelete(&TransmitCcbPool);
    FreeCcb(pReadCcb);
    FreeCcb(pConnectCcb);

    EnterCriticalSection(&CloseSection);
    *(pParms->pCloseCounter)--;
    LeaveCriticalSection(&CloseSection);
    FREE(pParms);
    puts("****** LinkConnectStressTest complete ******");

    return STATUS_SUCCESS;
}

UINT
LinkDataTransferTest(
    IN PDLC_TEST_THREAD_PARMS pParms
    )

/*++

    This procedure stress tests the heavy data transfer on
    several link stations.

--*/

{
    //
    //  The idea:
    //  Allocate buffer, fill it with the data, do various tests, that
    //  sends and receives the orginal data and check the received
    //  data buffer again in the end.  To minimize the network load
    //  the main part of test is made with a small packet size.
    //  The test does the given number of loops or it
    //  takes forever.
    //
    //  Test types:
    //      - sending from user buffers
    //      - full/half duplex data flow: having simultaneous data flow
    //        to both directions / or having data flow first to
    //      - sharing the sap by several link stations.
    //      - Packet size:
    //       * using variable length packets (including 0 length packets)
    //       * using very small packets
    //       * using maximum packet size
    //      - taking the time stamps?
    //

    //
    //  This thread is the main thread of the DLC data transfer
    //  stress.  It opens the sap, open and connects the link stations
    //  for the test, create the worker threads and then starts the
    //  testing simultaneously in all threads.
    //  This thread also handles
    //

    UINT Status;
    UINT i;
    UINT OpenError;
    UINT MaxFrameLength;
    USHORT SapStationId;
    USHORT LinkStationId;
    PVOID hPool;
    PLLC_BUFFER pFirstBuffer;
    DLC_TEST_PACKET TestPacket;
    PLLC_CCB pCcb;
    PLLC_CCB pReadCcb;
    PLLC_CCB pReceiveCcb;
    LLC_TEST_CCB_POOL TransmitCcbPool;
    UCHAR LinksAvail;
    PLLC_READ_PARMS pReadParms;
    PUCHAR pBuffer;
    UCHAR Ch;
    ULONG Tid = 0;
    PXMIT_STRESS_WORKER pWorkerParms;
    PXMIT_STRESS_WORKER aWorkerParms[40];
    UCHAR SendBuffer[sizeof(TestPacket) + FOOBAR_HEADER_LENGTH];
    UCHAR RemoteSap;
    UCHAR LanHeader[32];
    UINT LanHeaderLength;
    LLC_DLC_MODIFY_PARMS DlcModifyParms;
    HANDLE hThread;
    BOOLEAN FirstTime = TRUE;
    PUCHAR pSourceRoutingInfo;

    puts("****** LinkDataTransferTest ******");

    memset(&DlcModifyParms, 0, sizeof(DlcModifyParms));
    pBuffer = ALLOC(pParms->TestBufferSize);
    Ch = 'A';
    for (i = 0; i < pParms->TestBufferSize; i++) {
        if ((pBuffer[i] = Ch++) == 'Z') {
            Ch = 'A';
        }
    }

    //
    // Initialize the Transmit ccb pool
    //

    CcbPoolInitialize(&TransmitCcbPool,
                      pParms->AdapterNumber,
                      LLC_TRANSMIT_FRAMES,
                      TEST_TRANSMIT_COMPLETION_FLAG
                      );
    Status = LlcDirOpenAdapter(pParms->AdapterNumber,
                               NULL,
                               NULL,
                               &OpenError,
                               NULL,
                               &MaxFrameLength,
                               NULL
                               );
    DBG_ASSERT(Status);

    Status = BufferCreate(pParms->AdapterNumber,
                          pParms->pPool,
                          DEFAULT_BUFFER_SIZE,
                          min(0xff00, DEFAULT_BUFFER_SIZE),
                          0x1000,
                          &hPool
                          );
    DBG_ASSERT(Status);

    Status = LlcDlcOpenSap(pParms->AdapterNumber,
                           MaxFrameLength,
                           pParms->FirstClientSap,
                           LLC_INDIVIDUAL_SAP,
                           255,
                           0,
                           NULL,
                           TEST_DLC_STATUS_FLAG,
                           NULL,
                           &SapStationId,
                           &LinksAvail
                           );
    DBG_ASSERT(Status);

    //
    // Setup immediately a pending receive
    //

    pReceiveCcb = ReceiveInit(pParms->AdapterNumber,
                              TEST_COMMAND_COMPLETION_FLAG,
                              SapStationId,
                              0,
                              TEST_RECEIVE_FLAG,
                              LLC_NOT_CONTIGUOUS_DATA,
                              LLC_RCV_READ_INDIVIDUAL_FRAMES
                              );
    Status = AcsLan(pReceiveCcb, NULL);

    pCcb = TransmitInit(pParms->AdapterNumber,
                        LLC_TRANSMIT_UI_FRAME,
                        TEST_TRANSMIT_COMPLETION_FLAG,
                        SapStationId,
                        pParms->FirstServerSap,
                        LLC_CHAIN_XMIT_COMMANDS_ON_SAP
                        );
    pCcb->u.pParameterTable->Transmit.pBuffer1 = BroadcastHeader;
    pCcb->u.pParameterTable->Transmit.cbBuffer1 = sizeof(BroadcastHeader);
    memcpy(&BroadcastHeader[4], FunctionalAddress1, 4);
    pCcb->u.pParameterTable->Transmit.pBuffer2 = SendBuffer;
    pCcb->u.pParameterTable->Transmit.cbBuffer2 = sizeof(SendBuffer);
    TestPacket.PacketId = DLC_TEST_ID;
    TestPacket.Command = TEST_COMMAND_ECHO_BACK;
    TestPacket.RepeatCount = 1;
    memcpy(&SendBuffer[FOOBAR_HEADER_LENGTH], &TestPacket, sizeof(TestPacket));

    //
    // Send a broadcast to figure out the destination
    //

    pReadCcb = ReadInit(pParms->AdapterNumber,
                        SapStationId,
                        LLC_OPTION_READ_STATION,
                        LLC_EVENT_RECEIVE_DATA
                        );
    pReadParms = &pReadCcb->u.pParameterTable->Read;

    do {

        Status = AcsLan(pCcb, NULL);
        DBG_ASSERT(pCcb->uchDlcStatus);

        Status = ReadClientEvent(pReadCcb,
                                 &TransmitCcbPool,
                                 LLC_EVENT_TRANSMIT_COMPLETION,
                                 1,
//                                 -1L
                                 5000L,
                                 NULL
                                 );
        DBG_ASSERT(Status);

        Status = ReadClientEvent(pReadCcb,
                                 &TransmitCcbPool,
                                 LLC_EVENT_RECEIVE_DATA,
                                 1,
                                 5000L,
                                 NULL
                                 );
        DBG_ASSERT(Status);

    } while (Status != STATUS_SUCCESS);

    //
    // Save the lan header for later use
    // BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG
    //     pFirstBuffer has already been returned to driver,
    //     the driver can reuse it in any moment and overwrite
    //     the data!!!!
    // BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG-BUG
    //

    pFirstBuffer = pReadCcb->u.pParameterTable->Read.Type.Event.pReceivedFrame;
    LanHeaderLength = pFirstBuffer->NotContiguous.cbLanHeader;
    memcpy(LanHeader,
           pFirstBuffer->NotContiguous.auchLanHeader,
           pFirstBuffer->NotContiguous.cbLanHeader
           );
    memcpy(&LanHeader[2],
           &LanHeader[8],
           6
           );

    //
    // Toggle the direction bit and reset the source routing info bit
    // in the destination address (leave the source address as it, because
    // it will be updated by DLC)
    //

    LanHeader[2] &= (UCHAR)0x7f;
    LanHeader[15] ^= (UCHAR)0x80;
    pSourceRoutingInfo = ((LanHeader[8] & 0x80) ? &LanHeader[14] : NULL);

    //
    // No we cancel the old receive command and make a new one,
    // that by default links all received frames on link stations
    //

    Status = ReceiveCancel(pReceiveCcb);
    DBG_ASSERT(Status);

    pReceiveCcb = ReceiveInit(pParms->AdapterNumber,
                              TEST_COMMAND_COMPLETION_FLAG,
                              SapStationId,
                              0,
                              TEST_RECEIVE_FLAG,
                              LLC_NOT_CONTIGUOUS_DATA,
                              LLC_RCV_CHAIN_FRAMES_ON_LINK
                              );
    Status = AcsLan(pReceiveCcb, NULL);

    //
    // Open a link station for each worker thread.
    // We have the destination station node address
    // and sap number in the received frame.
    //

    hStartSignal = CreateEvent(NULL, FALSE, TRUE, NULL);

    pCcb = DlcConnectStationInit(pParms->AdapterNumber,
                                 0, // no completion flag this time
                                 LinkStationId,
                                 pSourceRoutingInfo
                                 );
    RemoteSap = pParms->FirstServerSap;
    for (i = 0; i < pParms->AllocatedStations; i++) {
        pWorkerParms = ALLOC(sizeof(XMIT_STRESS_WORKER));
        Status = DlcOpenStation(pParms->AdapterNumber,
                                SapStationId,
                                RemoteSap,
                                &LanHeader[2],
                                NULL,
//                                AlternateParms[i % MAX_ALTERNATE_PARMS],
                                &LinkStationId
                                );
        RemoteSap += 2;

        //
        // Connect to the remote node
        //

        pCcb->u.pParameterTable->DlcConnectStation.usStationId = LinkStationId;
        Status = AcsLan(pCcb, NULL);

        //
        // It takes a long time for the server end to print all trace
        // in the link connect stratess test.
        //

        if (FirstTime == TRUE) {
            FirstTime = FALSE;
            Status = WaitForSingleObject(pCcb->hCompletionEvent, 40000L);
        } else {
            Status = WaitForSingleObject(pCcb->hCompletionEvent, 10000L);
        }

        DBG_ASSERT(pCcb->uchDlcStatus);
        DBG_ASSERT(Status);

        Status = DlcModify(pParms->AdapterNumber, LinkStationId, &DlcModifyParms);
        DBG_ASSERT(Status);

        pWorkerParms->MaxI_Field = DlcModifyParms.usMaxInfoFieldLength;
        printf("MaxI_Field: %u\n", DlcModifyParms.usMaxInfoFieldLength);

        pReceiveCcb = ReceiveInit(pParms->AdapterNumber,
                                  TEST_COMMAND_COMPLETION_FLAG,
                                  LinkStationId,
                                  0,
                                  TEST_RECEIVE_FLAG,
                                  LLC_NOT_CONTIGUOUS_DATA,
                                  LLC_RCV_CHAIN_FRAMES_ON_LINK
                                  );
        Status = AcsLan(pReceiveCcb, NULL);
        DBG_ASSERT(pCcb->uchDlcStatus);

        pWorkerParms->hPool = hPool;
        pWorkerParms->LinkStationId = LinkStationId;
        pWorkerParms->pBuffer = pBuffer;
        pWorkerParms->pParms = pParms;
        aWorkerParms[i] = pWorkerParms;
    }
    FreeCcb(pCcb);

    for (i = 0; i < pParms->AllocatedStations; i++) {

        //
        // This here because of the stupid os/2 emulation env.
        // 64kb buffer pool is too small to run all 4 tests
        // simultanously.  Change this when the test has been
        // ported to Windows/Nt
        //

        if (pParms->ResetCommand == 0) {
            SetEvent(hStartSignal);
            DataTransferWorker(aWorkerParms[i]);
        } else {
            hThread = CreateThread(NULL,
                                   0,
                                   DataTransferWorker,
                                   aWorkerParms[i],
                                   0,
                                   &Tid
                                   );
            if (hThread != NULL) {
                SetThreadPriority(hThread, THREAD_PRIORITY_ABOVE_NORMAL);
            }
        }
    }

    //
    // Wait until all threads are ready to start.
    //

    Sleep(1000L);
    SetEvent(hStartSignal);

    FreeCcb(pReadCcb);

    pReadCcb = ReadInit(pParms->AdapterNumber,
                        SapStationId,
                        LLC_OPTION_READ_ALL,
                        0
                        );
    pReadParms = &pReadCcb->u.pParameterTable->Read;

    //
    // Here we can test all global close commands, when the stations
    // are sending data in the full speed. The upper level may
    // define an optional global close command (dir.initialize,
    // dir.close.adapter or dlc.reset)
    //

    if (pParms->ResetCommand != 0) {
        pCcb = LlcCloseInit(pParms->AdapterNumber,
                            TEST_COMMAND_COMPLETION_FLAG,
                            pParms->ResetCommand,
                            0
                            );
        pCcb->pNext = pReadCcb;
        pCcb->uchReadFlag = 1;

        Sleep(1000L);
        Status = AcsLan(pCcb, NULL);

        //
        // pParms->ResetCommand may be invalid on purpose
        //

        if (Status == STATUS_SUCCESS || pParms->ResetCommand != LLC_DIR_OPEN_ADAPTER) {
            DBG_ASSERT(Status);
            Status = WaitForSingleObject(pReadCcb->hCompletionEvent, 10000L);
            DBG_ASSERT(Status);
        }

        DBG_ASSERT(pReadCcb->uchDlcStatus);
        FreeCcb(pCcb);
    } else {

        //
        // We can wait until all worker threads have completed
        // (ie. link station close command is completed
        // by sap read).
        //

        Status = ReadClientEvent(pReadCcb,
                                 &TransmitCcbPool,
                                 LLC_EVENT_COMMAND_COMPLETION,
                                 pParms->AllocatedStations * 2,
                                 -1L,
                                 NULL
                                 );
        //
        // close sap
        //

        pCcb = LlcCloseInit(pParms->AdapterNumber,
                            TEST_COMMAND_COMPLETION_FLAG,
                            LLC_DLC_CLOSE_SAP,
                            SapStationId
                            );

        Status = AcsLan(pCcb, NULL);
        DBG_ASSERT(pCcb->uchDlcStatus);

        Status = ReadClientEvent(pReadCcb,
                                 &TransmitCcbPool,
                                 LLC_EVENT_COMMAND_COMPLETION,
                                 1,
                                 5000L,
                                 NULL
                                 );
        DBG_ASSERT(Status);
    }

    //
    // This will terminate the DLC server, if this
    // is the last operation against it.
    //

    if (pParms->CloseServer) {
        Status = LlcDlcOpenSap(pParms->AdapterNumber,
                               MaxFrameLength,
                               pParms->FirstClientSap,
                               LLC_INDIVIDUAL_SAP | LLC_MEMBER_OF_GROUP_SAP,
                               0,
                               0,
                               NULL,
                               TEST_DLC_STATUS_FLAG,
                               NULL,
                               &SapStationId,
                               &LinksAvail
                               );
        DBG_ASSERT(Status);

        pCcb = TransmitInit(pParms->AdapterNumber,
                            LLC_TRANSMIT_UI_FRAME,
                            0,
                            SapStationId,
                            pParms->FirstServerSap,
                            LLC_CHAIN_XMIT_COMMANDS_ON_SAP
                            );
        pCcb->u.pParameterTable->Transmit.pBuffer1 = LanHeader;
        pCcb->u.pParameterTable->Transmit.cbBuffer1 = sizeof(LanHeader);
        pCcb->u.pParameterTable->Transmit.pBuffer2 = SendBuffer;
        pCcb->u.pParameterTable->Transmit.cbBuffer2 = sizeof(SendBuffer);
        TestPacket.PacketId = DLC_TEST_ID;
        TestPacket.Command = TEST_COMMAND_EXIT;
        TestPacket.RepeatCount = 1;
        memcpy(&SendBuffer[FOOBAR_HEADER_LENGTH], &TestPacket, sizeof(TestPacket));
        Status = AcsLan(pCcb, NULL);
        DBG_ASSERT(Status);
        Status = WaitForSingleObject(pCcb->hCompletionEvent, 10000L);
        DBG_ASSERT(Status);
        DBG_ASSERT(pCcb->uchDlcStatus);

        FreeCcb(pCcb);
    }

    if (pParms->ResetCommand != LLC_DIR_CLOSE_ADAPTER && pParms->ResetCommand != LLC_DIR_INITIALIZE) {
        pCcb = LlcCloseInit(pParms->AdapterNumber, 0, LLC_DIR_CLOSE_ADAPTER, 0);
        Status = AcsLan(pCcb, NULL);
        DBG_ASSERT(Status);
        Status = WaitForSingleObject(pCcb->hCompletionEvent, 10000L);
        DBG_ASSERT(Status);
        DBG_ASSERT(pCcb->uchDlcStatus);
        FreeCcb(pCcb);
    }
    CcbPoolDelete(&TransmitCcbPool);
    FreeCcb(pReadCcb);
    CloseHandle(hStartSignal);

    *(pParms->pCloseCounter)--;

    FREE(pParms);

    puts("****** LinkDataTransferTest complete ******");

    return STATUS_SUCCESS;
}

ULONG
DataTransferWorker(
    PVOID hXmitParms
    )

/*++

    This procedure stress tests the heavy data transfer on
    several link stations.

--*/

{
    PXMIT_STRESS_WORKER pXmitParms = (PXMIT_STRESS_WORKER)hXmitParms;
    UINT Status;
    UINT i;
    UINT PacketLength;
    UINT TransmitElements;
    PLLC_TRANSMIT2_COMMAND pTransmitBuffer;
    PDLC_TEST_PACKET pTestPacket;
    PDLC_TEST_PACKET pTestPacket2;
    PLLC_CCB pCcb;
    PLLC_CCB pReadCcb;
    PLLC_READ_PARMS pReadParms;
    LLC_TEST_CCB_POOL TransmitCcbPool;
    PDLC_TEST_THREAD_PARMS pParms = pXmitParms->pParms;
    PLLC_BUFFER pBuffer;
    PLLC_BUFFER pBuffer1;
    PLLC_BUFFER pBuffer2;
    ULONG TotalDataLength;
    ULONG ReceiveOffset;
    ULONG TransmitOffset;
    ULONG DataLength;
    UINT FrameCount;
    UINT ReceivedFrameCount;
    PLLC_BUFFER pReceivedFrames;
    UINT cLeft;

    puts("\nDataTransferWorker\n");

    //
    // Initialize the Transmit ccb pool
    //

    CcbPoolInitialize(&TransmitCcbPool,
                      pParms->AdapterNumber,
                      LLC_TRANSMIT_FRAMES,
                      TEST_TRANSMIT_COMPLETION_FLAG
                      );

    //
    // Send a broadcast to figure out the destination
    //

    pReadCcb = ReadInit(pParms->AdapterNumber,
                        pXmitParms->LinkStationId,
                        LLC_OPTION_READ_STATION,
                        LLC_EVENT_RECEIVE_DATA
                        );
    pReadParms = &pReadCcb->u.pParameterTable->Read;

    pTransmitBuffer = (PLLC_TRANSMIT2_COMMAND)ALLOC(
        sizeof(LLC_TRANSMIT2_COMMAND) +
        sizeof(LLC_TRANSMIT_DESCRIPTOR) * MAX_XMIT_BUFFERS
        );

    //
    // Send data and receive it back
    //

    pTransmitBuffer->usStationId = pXmitParms->LinkStationId;
    pTransmitBuffer->usFrameType = LLC_I_FRAME;
    pTransmitBuffer->uchXmitReadOption = LLC_CHAIN_XMIT_COMMANDS_ON_LINK;

    //
    // We must keep the buffer headers in the buffer pool, because
    // otherwise the tranmsit commands may fail, because this process
    // has too many locked pages.
    //

    Status = BufferGet(pParms->AdapterNumber, 1, 0x100, &pBuffer1, &cLeft);
    pTestPacket = (PDLC_TEST_PACKET)&pBuffer1->Contiguous.usStationId;

    Status = BufferGet(pParms->AdapterNumber, 1, 0x100, &pBuffer2, &cLeft);
    pTestPacket2 = (PDLC_TEST_PACKET)&pBuffer2->Contiguous.usStationId;

    pTestPacket->PacketId = DLC_TEST_ID;
    pTestPacket->Command = TEST_COMMAND_ECHO_BACK;
    pTestPacket->RepeatCount = 1;

    TotalDataLength = (ULONG)pParms->LoopCount * (ULONG)pParms->TestBufferSize;
    TransmitOffset = 0;
    ReceiveOffset = 0;
    DataLength = 0;

    switch ((pXmitParms->LinkStationId & 0xff) % 4) {
    case 0:

        //
        // Small packets, full speed, full duplex:
        // two-way data stream
        //

        for (i = 0; DataLength < pParms->TestBufferSize; i += 2) {
            if (DataLength + SMALL_PACKET_SIZE < pParms->TestBufferSize) {
                PacketLength = SMALL_PACKET_SIZE;
            } else {
                PacketLength = (UINT)(pParms->TestBufferSize - DataLength);
            }
            pTransmitBuffer->aXmitBuffer[i].pBuffer = pTestPacket;
            pTransmitBuffer->aXmitBuffer[i].cbBuffer = sizeof(*pTestPacket);
            pTransmitBuffer->aXmitBuffer[i].eSegmentType = LLC_FIRST_DATA_SEGMENT;
            pTransmitBuffer->aXmitBuffer[i].boolFreeBuffer = FALSE;

            pTransmitBuffer->aXmitBuffer[i+1].pBuffer = &pXmitParms->pBuffer[DataLength];
            pTransmitBuffer->aXmitBuffer[i+1].cbBuffer = (USHORT)PacketLength;
            pTransmitBuffer->aXmitBuffer[i+1].eSegmentType = LLC_NEXT_DATA_SEGMENT;
            pTransmitBuffer->aXmitBuffer[i+1].boolFreeBuffer = FALSE;
            DataLength += PacketLength;
        }
        pTransmitBuffer->cXmitBufferCount = i;
        break;

    case 1:

        //
        // Variable length packets & full-duplex:
        // two-way data stream
        //

        PacketLength = 0;
        for (i = 0; DataLength < pParms->TestBufferSize; i += 2) {
            if (DataLength + PacketLength > pParms->TestBufferSize) {
                PacketLength = (UINT)(pParms->TestBufferSize - DataLength);
            }
            pTransmitBuffer->aXmitBuffer[i].pBuffer = pTestPacket;
            pTransmitBuffer->aXmitBuffer[i].cbBuffer = sizeof(*pTestPacket);
            pTransmitBuffer->aXmitBuffer[i].eSegmentType = LLC_FIRST_DATA_SEGMENT;
            pTransmitBuffer->aXmitBuffer[i].boolFreeBuffer = FALSE;

            pTransmitBuffer->aXmitBuffer[i+1].pBuffer = &pXmitParms->pBuffer[DataLength];
            pTransmitBuffer->aXmitBuffer[i+1].cbBuffer = (USHORT)PacketLength;
            pTransmitBuffer->aXmitBuffer[i+1].eSegmentType = LLC_NEXT_DATA_SEGMENT;
            pTransmitBuffer->aXmitBuffer[i+1].boolFreeBuffer = FALSE;
            DataLength += PacketLength;
            PacketLength++;
        }
        pTransmitBuffer->cXmitBufferCount = i;
        break;

    case 2:

        //
        // Half-duplex data transfer:
        //

        *pTestPacket2 = *pTestPacket;

//
//  IDIOT!!!, This was worth of almost 2 days work, we cannot use
//  delayed echoing when the data transfer may be stopped in the middle!!!
//
//        pTestPacket->Command = TEST_COMMAND_DELAYED_ECHO;

        for (i = 0; DataLength < pParms->TestBufferSize; i += 2) {
            if (DataLength + SMALL_PACKET_SIZE < pParms->TestBufferSize) {
                PacketLength = SMALL_PACKET_SIZE;
            } else {
                PacketLength = (UINT)(pParms->TestBufferSize - DataLength);
            }
            pTransmitBuffer->aXmitBuffer[i].pBuffer = pTestPacket;
            pTransmitBuffer->aXmitBuffer[i].cbBuffer = sizeof(*pTestPacket);
            pTransmitBuffer->aXmitBuffer[i].eSegmentType = LLC_FIRST_DATA_SEGMENT;
            pTransmitBuffer->aXmitBuffer[i].boolFreeBuffer = FALSE;

            pTransmitBuffer->aXmitBuffer[i+1].pBuffer = &pXmitParms->pBuffer[DataLength];
            pTransmitBuffer->aXmitBuffer[i+1].cbBuffer = (USHORT)PacketLength;
            pTransmitBuffer->aXmitBuffer[i+1].eSegmentType = LLC_NEXT_DATA_SEGMENT;
            pTransmitBuffer->aXmitBuffer[i+1].boolFreeBuffer = FALSE;
            DataLength += PacketLength;
        }
        pTransmitBuffer->aXmitBuffer[i-2].pBuffer = pTestPacket2;
        pTransmitBuffer->cXmitBufferCount = i;
        break;

    case 3:

        //
        // Test the maximum length packets
        //

        for (i = 0; DataLength < pParms->TestBufferSize; i += 2) {
            if (DataLength + pXmitParms->MaxI_Field - sizeof(*pTestPacket) < pParms->TestBufferSize) {
                PacketLength = pXmitParms->MaxI_Field - sizeof(*pTestPacket);
            } else {
                PacketLength = (UINT)(pParms->TestBufferSize - DataLength);
            }
            pTransmitBuffer->aXmitBuffer[i].pBuffer = pTestPacket;
            pTransmitBuffer->aXmitBuffer[i].cbBuffer = sizeof(*pTestPacket);
            pTransmitBuffer->aXmitBuffer[i].eSegmentType = LLC_FIRST_DATA_SEGMENT;
            pTransmitBuffer->aXmitBuffer[i].boolFreeBuffer = FALSE;

            pTransmitBuffer->aXmitBuffer[i+1].pBuffer = &pXmitParms->pBuffer[DataLength];
            pTransmitBuffer->aXmitBuffer[i+1].cbBuffer = (USHORT)PacketLength;
            pTransmitBuffer->aXmitBuffer[i+1].eSegmentType = LLC_NEXT_DATA_SEGMENT;
            pTransmitBuffer->aXmitBuffer[i+1].boolFreeBuffer = FALSE;
            DataLength += PacketLength;
        }
        pTransmitBuffer->cXmitBufferCount = i;
        break;
    }
    TransmitOffset = DataLength;

    TransmitElements = i;

    //
    // This loop rotates the given amount the data from a data buffer.
    // All received data is compared.
    //

    pReadParms->Type.Event.pReceivedFrame = NULL;
    do {

        //
        // Free all extra buffers, when the transmit offset
        // exceeds the given amout of the data.
        //

/*
        TransmitOffset = FreeExtraBuffers(&pReadParms->Type.Event.pReceivedFrame,
                                          TransmitOffset,
                                          TotalDataLength,
                                          pParms->AdapterNumber,
                                          pXmitParms->LinkStationId
                                          );
*/

puts("Transmitting the packets.");
//getchar();

        Status = TransmitBuffers(NULL,
                                 pTransmitBuffer,
                                 &TransmitCcbPool,  // pool for transmit CCBs
                                 1,                 // repeat count
                                 TransmitElements,  // current index in buffer table
                                 //i,               // current index in buffer table
                                 FALSE,             // free all sent buffers
                                 FALSE,
                                 &FrameCount
                                 );
        WORKER_ASSERT(Status);

puts("Reading the transmit completion.");

        Status = ReadClientEvent(pReadCcb,
                                 &TransmitCcbPool,
                                 LLC_EVENT_TRANSMIT_COMPLETION,
                                 1,
                                 //-1L
                                 //20000L,
                                 60000L,
                                 NULL
                                 );
        WORKER_ASSERT(Status);

puts("Receiving the data.");

        //
        // Receive all sent packets back and compare them with the
        // orginal data.
        //

        ReceivedFrameCount = 0;
        i = 0;
        do {
            pReceivedFrames = NULL;

            Status = ReadClientEvent(pReadCcb,
                                     NULL,
                                     LLC_EVENT_RECEIVE_DATA,
                                     1,
                                     10000L,
                                     //1000L,
                                     &pReceivedFrames
                                     );
            WORKER_ASSERT(Status);

            ReceivedFrameCount += pReadParms->Type.Event.usReceivedFrameCount;

            ReceiveOffset = CompareReceiveBuffers(pReceivedFrames,
                                                  pXmitParms->pBuffer,
                                                  (ULONG)pParms->TestBufferSize,
                                                  ReceiveOffset,
                                                  pParms->CompareAllData,
                                                  &i
                                                  );
            if (FreeAllFrames(pParms->AdapterNumber,
                              pReceivedFrames,
                              &cLeft
                              ) != pReadParms->Type.Event.usReceivedFrameCount) {

                printf("Error: FreeAllFrames() != ReceivedFrameCount\n");
            }
        } while (ReceivedFrameCount < FrameCount);

    } while (ReceiveOffset < TotalDataLength);

    ReportDlcStatistics(pParms->AdapterNumber, pXmitParms->LinkStationId);

    Status = BufferGet(pParms->AdapterNumber, 1, 0x100, &pBuffer, &cLeft);
    WORKER_ASSERT(Status);

    Status = BufferFree(pParms->AdapterNumber, pBuffer, &cLeft);
    WORKER_ASSERT(Status);

    //
    // Free the transmit headers
    //

    Status = BufferFree(pParms->AdapterNumber, pBuffer1, &cLeft);
    WORKER_ASSERT(Status);
    Status = BufferFree(pParms->AdapterNumber, pBuffer2, &cLeft);
    WORKER_ASSERT(Status);

#ifdef OS2_EMU_DLC
    LlcTraceDumpAndReset(-1, 1, NULL);
#endif

    //
    // close the link station
    //

    pCcb = LlcCloseInit(pParms->AdapterNumber,
                        TEST_COMMAND_COMPLETION_FLAG,
                        LLC_DLC_CLOSE_STATION,
                        pXmitParms->LinkStationId
                        );
    pCcb->hCompletionEvent = CreateEvent(NULL, FALSE, TRUE, NULL);
    Status = AcsLan(pCcb, NULL);
    if (Status == STATUS_PENDING) {
        Status = WaitForSingleObject(pCcb->hCompletionEvent, 10000L);
        WORKER_ASSERT(Status);
    }
    WORKER_ASSERT(pCcb->uchDlcStatus);

    CcbPoolDelete(&TransmitCcbPool);
    FREE(pTransmitBuffer);
    FREE(pXmitParms);
    FreeCcb(pReadCcb);

    return STATUS_SUCCESS;
}


ULONG
FreeExtraBuffers(
    IN PLLC_BUFFER *ppFirstBuffer,
    IN ULONG TransmitOffset,
    IN ULONG TotalDataLength,
    IN UCHAR AdapterNumber,
    IN USHORT StationId
    )

{
    PLLC_BUFFER pNext;
    UINT cLeft;

    UNREFERENCED_PARAMETER(StationId);

    for (; *ppFirstBuffer != NULL; ppFirstBuffer = &(*ppFirstBuffer)->Contiguous.pNextFrame) {

        //
        // We know, that total length is even with the frame data length
        //

        if (TransmitOffset < TotalDataLength) {
            for (pNext = *ppFirstBuffer; pNext != NULL; pNext = pNext->Next.pNextBuffer) {
                TransmitOffset += pNext->Next.cbBuffer - sizeof(DLC_TEST_PACKET);
            }
        } else {
            FreeAllFrames(AdapterNumber, *ppFirstBuffer, &cLeft);
            *ppFirstBuffer = NULL;
            break;
        }
    }
    return TransmitOffset;
}

UINT
FreeAllFrames(
    IN UCHAR AdapterNumber,
    IN PLLC_BUFFER pFirstBuffer,
    IN PUINT pcLeft
    )
{
    PLLC_BUFFER pNextFrame;
    UINT cReleased = 0;
    UINT Status;

    for (; pFirstBuffer != NULL; pFirstBuffer = pNextFrame) {
        pNextFrame = pFirstBuffer->Contiguous.pNextFrame;
        Status = BufferFree(AdapterNumber, pFirstBuffer, pcLeft);

        //
        // Stop immediately, if the buffer pool is deleted!
        //

        if (Status == STATUS_SUCCESS) {
            cReleased++;
        } else if (Status == LLC_STATUS_ADAPTER_CLOSED) {
            ExitThread(STATUS_SUCCESS);
        }
    }
    return cReleased;
}

ULONG
CompareReceiveBuffers(
    IN PLLC_BUFFER pFirstBuffer,
    IN PUCHAR pBuffer,
    IN ULONG BufferLength,
    IN ULONG DataOffset,
    IN BOOLEAN CompareData,
    IN PUINT pIndex
    )
{
    PLLC_BUFFER pNext;
    PLLC_BUFFER pPrevFirst;
    UINT ProtocolHeaderLength, BufferOffset, Status;
    PLLC_CCB pCcb;
    static BOOLEAN CompareError = FALSE;

    PLLC_BUFFER originalRxBuffer = pFirstBuffer;
    PUCHAR originalTxBuffer = pBuffer;
    ULONG originalLength = BufferLength;
    ULONG originalOffset = DataOffset;
    ULONG whichFrame = 0;
    static FramesChecked = 0;

    for (; pFirstBuffer != NULL; pFirstBuffer = pFirstBuffer->Contiguous.pNextFrame) {
        ProtocolHeaderLength = sizeof(DLC_TEST_PACKET);
        (*pIndex)++;

        //
        // Invalid response frame!!!  The protocol header is missing!
        //

        if (ProtocolHeaderLength > pFirstBuffer->Next.cbBuffer) {
            DBG_ASSERT(-1);
        }

        for (pNext = pFirstBuffer; pNext != NULL; pNext = pNext->Next.pNextBuffer) {
            BufferOffset = pNext->Next.cbUserData
                         + pNext->Next.offUserData
                         + ProtocolHeaderLength;

            if (CompareError) {
                CompareData = FALSE;
            }

            if (CompareData && memcmp(&pBuffer[DataOffset % BufferLength],
                                      (PUCHAR)pNext + BufferOffset,
                                      pNext->Next.cbBuffer - ProtocolHeaderLength
                                      )) {

                PUCHAR Source = (PUCHAR)pNext + BufferOffset;
                PUCHAR Dest = &pBuffer[DataOffset % BufferLength];
                UINT i;
                UINT Length = pNext->Next.cbBuffer - ProtocolHeaderLength;

                //for (i = 0; Source[i] != Dest[i];  i++);
                //if (i > 0)  i--;
                //if (i + 40 < Length) Length = i + 40;
                //Source[Length - 1] = 0;
                //Dest[Length - 1] = 0;

                puts("The data is corrupted!!");
                printf("data offset   : %u\n", DataOffset);
                printf("packet number : %u\n", *pIndex);
                printf("fail offset   : %d\n", i);
                printf("original rx buf = %x\n", originalRxBuffer);
                printf("failing  rx buf = %x\n", pFirstBuffer);
                printf("original tx buf = %x\n", originalTxBuffer);
                printf("original length = %d\n", originalLength);
                printf("original offset = %d\n", originalOffset);
                printf("frame # %d\n", whichFrame);
                printf("FramesChecked = %d\n", FramesChecked);

                //{
                //    UINT i, j;
                //
                //    for (j = pNext->Next.cbBuffer, i=0; j; --j, ++i) {
                //        if () {
                //        }
                //    }
                //    printf("characters    : s=%02.2x, d=%02.2x\n", Source
                //}

//#ifdef i386
//                SetAcslanDebugFlags(DEBUG_DUMP_RX_DATA
//                                    | DEBUG_DUMP_DATA_CHAIN
//                                    | DEBUG_DUMP_FRAME_CHAIN
//                                    | DEBUG_DUMP_RX_ASCII
//                                    );
//                DumpReceiveDataBuffer(originalRxBuffer);
//#endif

                DebugBreak();

//                DlcDebugBreak();

                pCcb = LlcCloseInit(pFirstBuffer->NotContiguous.uchAdapterNumber,
                                    0,
                                    LLC_DIR_CLOSE_ADAPTER,
                                    0
                                    );
                Status = AcsLan(pCcb, NULL);
                DBG_ASSERT(pCcb->uchDlcStatus);
                FreeCcb(pCcb);
                DBG_ASSERT(-1);

                CompareError = TRUE;
            }
            DataOffset += pNext->Next.cbBuffer - ProtocolHeaderLength;
            ProtocolHeaderLength = 0;
        }
        pPrevFirst = pFirstBuffer;
        ++whichFrame;
        ++FramesChecked;
    }
    return DataOffset;
}

UINT
TransmitBuffers(
    PLLC_BUFFER pFirstBuffer,
    PLLC_TRANSMIT2_COMMAND pTransmitBuffer,
    PLLC_TEST_CCB_POOL pTransmitCcbPool,
    UINT RepeatCount,
    UINT cElement,
    BOOLEAN FreeTransmitBuffers,
    BOOLEAN SendReceiveBuffer,
    OUT PUINT pFrameCount
    )
{
    UINT cTotalElements;
    UINT cTransmitElements;
    UINT i;
    UINT Status = LLC_STATUS_LINK_NOT_TRANSMITTING;
    PLLC_CCB pCcb;
    PLLC_BUFFER pBuffer;
    UINT FrameCount = 0;

    for (; pFirstBuffer != NULL; pFirstBuffer = pFirstBuffer->NotContiguous.pNextFrame) {
        if (cElement >= MAX_XMIT_BUFFERS) {
            return LLC_STATUS_NO_MEMORY;
        }

        if (cElement == 0 || SendReceiveBuffer) {
            pTransmitBuffer->aXmitBuffer[cElement].eSegmentType = LLC_FIRST_DATA_SEGMENT;
        } else {
            pTransmitBuffer->aXmitBuffer[cElement].eSegmentType = LLC_NEXT_DATA_SEGMENT;
        }

        for (pBuffer = pFirstBuffer; pBuffer != NULL; pBuffer = pBuffer->Next.pNextBuffer) {
            if (cElement >= MAX_XMIT_BUFFERS) {
                return LLC_STATUS_NO_MEMORY;
            }

            //
            // The first data buffer is exceptional
            //

            if (pBuffer != pFirstBuffer) {
                pTransmitBuffer->aXmitBuffer[cElement].eSegmentType = LLC_NEXT_DATA_SEGMENT;
            }
            pTransmitBuffer->aXmitBuffer[cElement].boolFreeBuffer = FALSE;
            pTransmitBuffer->aXmitBuffer[cElement].cbBuffer = pBuffer->Next.cbBuffer;
            pTransmitBuffer->aXmitBuffer[cElement].pBuffer = (PUCHAR)pBuffer + pBuffer->Next.cbUserData + pBuffer->Next.offUserData;

            //
            // All the rest buffers have the default format:
            //

            cElement++;
        }
        if (!SendReceiveBuffer)
            break;
        }

        //
        // Send as many packets in the same time as possible.
        //

        cTotalElements =  RepeatCount * cElement;
        cTransmitElements = cElement;

        if (RepeatCount > 1 && (RepeatCount * cElement) < MAX_XMIT_BUFFERS) {
            for (i = 1; i < RepeatCount; i++) {
                memcpy(&pTransmitBuffer->aXmitBuffer[cTransmitElements],
                       &pTransmitBuffer->aXmitBuffer[0],
                       sizeof(pTransmitBuffer->aXmitBuffer[0]) * cElement
                       );
            cTransmitElements += cElement;
        }
    }

    while (cTotalElements > 0) {
        for (i = 0; i < cTransmitElements; i++) {
            if (pTransmitBuffer->aXmitBuffer[i].eSegmentType == LLC_FIRST_DATA_SEGMENT) {
                FrameCount++;
            }

            //
            // Free the transmit buffers in the last loop.
            //

            if (FreeTransmitBuffers && cTotalElements == cTransmitElements) {
                pTransmitBuffer->aXmitBuffer[i].boolFreeBuffer = TRUE;
            }
        }
        pTransmitBuffer->cXmitBufferCount = cTransmitElements;
        pCcb = CcbPoolAlloc(pTransmitCcbPool);
        pCcb->u.pParameterTable = (PVOID)pTransmitBuffer;

        //
        // The link returns a protocol error, when the remote link
        // station is lost or disconnected.  It is completely normal state,
        // we just sleep for a while for the other side to clear
        // up the busy state.  It's the driver's business to handle
        // a situation, when
        //

        Status = AcsLan(pCcb, NULL);

        Status = pCcb->uchDlcStatus;
        if (Status != LLC_STATUS_SUCCESS
        && Status != LLC_STATUS_PENDING
        && Status != LLC_STATUS_CANCELLED_BY_SYSTEM_ACTION
        && Status != LLC_STATUS_CANCELLED_BY_USER
        && Status != LLC_STATUS_LINK_NOT_TRANSMITTING
        && Status != LLC_STATUS_INVALID_STATION_ID) {
            printf("Xmit: %x)\n", Status);
            break;
        }
        cTotalElements -= cTransmitElements;
    }

    if (pFrameCount != NULL) {
        *pFrameCount = FrameCount;
    }

    return Status;
}

VOID
SetBuffers(
    IN PLLC_BUFFER pBuffer,
    IN USHORT Command,
    IN USHORT RepeatCount,
    IN UCHAR FirstChar,
    IN UCHAR LastChar
    )
{
    UINT i;
    UINT cbData;
    PCHAR pData;
    UCHAR Ch;
    PDLC_TEST_PACKET pTestPacket;
    BOOLEAN IsFirstTime = TRUE;

    pTestPacket = (PDLC_TEST_PACKET)((PUCHAR)pBuffer
                + pBuffer->Next.offUserData
                + pBuffer->Next.cbUserData);

    pTestPacket->PacketId = DLC_TEST_ID;
    pTestPacket->Command = Command;
    pTestPacket->RepeatCount = RepeatCount;

    for (; pBuffer != NULL; pBuffer = pBuffer->Next.pNextBuffer) {
        pData = (PUCHAR)pBuffer
              + pBuffer->Next.offUserData
              + pBuffer->Next.cbUserData;
        cbData = pBuffer->Next.cbBuffer
               - (pBuffer->Next.offUserData
               + pBuffer->Next.cbUserData);
        if (IsFirstTime) {
            IsFirstTime = FALSE;
            cbData -= sizeof(DLC_TEST_PACKET);
            pData += sizeof(DLC_TEST_PACKET);
        }
        Ch = FirstChar;
        for (i = 0; i < cbData; i++) {
            pData[i] = Ch;
            if ((++Ch) > LastChar) {
                Ch = FirstChar;
            }
        }
    }
}

UINT
DlcTestTransact(
    IN PLLC_CCB pCcb,
    IN PLLC_CCB pReadCcb,
    IN UCHAR ExpectedMessageType
    ) {

    UINT Status;

    pCcb->pNext = NULL;
    Status = AcsLan(pCcb, NULL);
    DBG_ASSERT(pCcb->uchDlcStatus);

    if (pCcb->ulCompletionFlag == 0) {
        Status = WaitForSingleObject(pCcb->hCompletionEvent, 10000L);
        DBG_ASSERT(Status);
        DBG_ASSERT(pCcb->uchDlcStatus);
    } else {
        Status = ReadClientEvent(pReadCcb,
                                 NULL,
                                 LLC_EVENT_TRANSMIT_COMPLETION,
                                 1,
                                 10000L,
                                 NULL
                                 );
        DBG_ASSERT(Status);
    }

    do {
        Status = ReadClientEvent(pReadCcb,
                                 NULL,
                                 LLC_EVENT_RECEIVE_DATA,
                                 1,
//                                 2000L
//                                 500L
                                 5000L,
                                 NULL
                                 );
        if (Status != STATUS_SUCCESS) {
            return Status;
        }
    } while (ExpectedMessageType != pReadCcb->u.pParameterTable->Read.Type.Event.pReceivedFrame->Contiguous.uchMsgType);

    return Status;
}


UINT
ReadClientEvent(
    IN PLLC_CCB pReadCcb,
    IN PLLC_TEST_CCB_POOL pTransmitCcbPool,
    IN UCHAR EventMask,
    IN UINT EventCount,
    IN ULONG Timeout,
    PLLC_BUFFER *ppBuffer OPTIONAL      // chain of all received buffers
    )

/*++

    Generic DLC READ procedure, that handles all requested events
    and returns.

--*/

{
    UINT Status = STATUS_SUCCESS;
    UINT DlcStatus;
    PLLC_READ_PARMS pReadParms;
    PDLC_TEST_PACKET pDlcPacket;
    PLLC_BUFFER pFirstBuffer, pNextFrame;
    PLLC_CCB pNextCcb, pCcb;
    struct {
        UINT Receives;
        UINT CmdCompletion;
        UINT XmitCompletion;
        UINT Status;
    } Count;
    UINT NewEvents;
    UINT cBuffersLeft = -1;
    UINT cLeft = -1;
    BOOLEAN LocalBusy = FALSE;
    PLLC_BUFFER* ppCurFrame = ppBuffer;
    UINT AllEvents;
//    UINT CurrentSap;

    Count.Receives  = 0;
    Count.CmdCompletion = 0;
    Count.XmitCompletion = 0;
    Count.Status = 0;

    pReadParms = &pReadCcb->u.pParameterTable->Read;

    //
    // We must always monitor the link station status in receive,
    // because the out of buffers state must be released
    // by the DlcFlowControl command.
    //

    pReadParms->uchEventSet = EventMask | (UCHAR)LLC_EVENT_STATUS_CHANGE;

    while (EventCount != 0) {
        Status = AcsLan(pReadCcb, NULL);
        if (pReadCcb->uchDlcStatus == LLC_STATUS_PENDING) {
            Status = WaitForSingleObject(pReadCcb->hCompletionEvent, Timeout);
            if (Status != STATUS_SUCCESS) {
                if (Timeout > 2000) {
                    printf("Read timeout,  events left: %u\n", EventCount);
                    printf("Count.Receives: %u\n", Count.Receives);
                    printf("Count.CmdCompletion: %u\n", Count.CmdCompletion);
                    printf("Count.XmitCompletion: %u\n", Count.XmitCompletion);
                    printf("Count.Status: %u\n", Count.Status);
                }
                ReadCancel(pReadCcb);
                pReadCcb->uchDlcStatus = LLC_STATUS_PENDING;
                return Status;
            }
        } else if (pReadCcb->uchDlcStatus != STATUS_SUCCESS) {
            printf("Error in READ CCB: %xH\n", pReadCcb->uchDlcStatus);
            return pReadCcb->uchDlcStatus;
        }

        NewEvents = 1;
        switch (pReadParms->uchEvent) {
        case LLC_EVENT_RECEIVE_DATA:
            if (pReadParms->ulNotificationFlag != TEST_RECEIVE_FLAG) {
                DlcDebugBreak();
            }

/*
*******************************************************************************
AllEvents = 0;
for (
    pFirstBuffer = pReadParms->Type.Event.pReceivedFrame;
    pFirstBuffer != NULL;
    pFirstBuffer = pFirstBuffer->Contiguous.pNextFrame)
{
    AllEvents++;
}

if (AllEvents > 40)
{
    printf("\n");
}
*******************************************************************************
*/

//CurrentSap = pReadParms->Type.Event.pReceivedFrame->Contiguous.usStationId >> 8;

            NewEvents = 0;
            for (pFirstBuffer = pReadParms->Type.Event.pReceivedFrame;
                 pFirstBuffer != NULL;
                 pFirstBuffer = pNextFrame) {

                cBuffersLeft = pFirstBuffer->NotContiguous.cBuffersLeft;
                pDlcPacket = (PDLC_TEST_PACKET)((PUCHAR)pFirstBuffer
                           + pFirstBuffer->NotContiguous.cbUserData
                           + pFirstBuffer->NotContiguous.offUserData);

                //
                // Discard all UI frames sent to the sap
                // station and having wrong id.  The connection oriented
                // packets and TEST and XID frames cannot be wrong (????).
                // The contiguous frames are too complicated to be
                // intepreted here.  We must always accept them.
                //

                if ((pFirstBuffer->Contiguous.uchOptions == LLC_CONTIGUOUS_DATA)
                || !((pFirstBuffer->NotContiguous.uchMsgType == LLC_UI_FRAME)
                && (pDlcPacket->PacketId != DLC_TEST_ID)
                && (pReadParms->uchEvent & EventMask))) {
                    NewEvents++;
                }
                pNextFrame = pFirstBuffer->Contiguous.pNextFrame;
                Count.Receives++;

                //
                // The caller may optionally gather all received frames
                // into a list.
                //

                if (ppCurFrame == NULL) {

/*
*******************************************************************************
if (AllEvents > 40)
{
    if ((NewEvents % 5) == 0)
    {
        printf("%4x: %08X\n",
            pFirstBuffer->Contiguous.usStationId, pFirstBuffer);
    }
    else
    {
        printf("%4x: %08X, ",
            pFirstBuffer->Contiguous.usStationId, pFirstBuffer);
    }
}
*******************************************************************************
*/

                    Status = BufferFree(pReadCcb->uchAdapterNumber,
                                        pFirstBuffer,
                                        &cLeft
                                        );
//                    DBG_ASSERT(Status);
                } else {
                    *ppCurFrame = pFirstBuffer;
                    ppCurFrame = &(pFirstBuffer->Contiguous.pNextFrame);
                }
                if (Status != STATUS_SUCCESS) {
                    printf("\nBufferFree= %xH (%u)\n", Status, NewEvents);
                    return Status;
                }
            }

//printf("R(%x:%u) ", CurrentSap, NewEvents);
//printf("R(%u) ", NewEvents);

            break;


        //
        // ******************** TRANSMIT COMPLETION ********************
        //

        case LLC_EVENT_TRANSMIT_COMPLETION:
            if (pReadParms->ulNotificationFlag != TEST_TRANSMIT_COMPLETION_FLAG) {
                DlcDebugBreak();
            }

// Can't do this, param table is static
//CurrentSap =
//pReadParms->Type.Event.pCcbCompletionList->u.pParameterTable->Transmit2.usStationId
//>> 8;

            NewEvents = 0;
            for (pCcb = pReadParms->Type.Event.pCcbCompletionList;
                 pCcb != NULL && pReadParms->Type.Event.usCcbCount != 0;
                 pCcb = pNextCcb) {

                NewEvents++;
                Count.XmitCompletion++;
//                DBG_ASSERT(Status = pCcb->uchDlcStatus);
                pNextCcb = pCcb->pNext;
                if (pTransmitCcbPool != NULL) {
                    CcbPoolFree(pTransmitCcbPool, pCcb);
                } else {
                    FreeCcb(pCcb);
                }
            }

//printf("T(%u) ", NewEvents);

            break;

        //
        // ******************** COMMAND COMPLETION ********************
        //

        case LLC_EVENT_COMMAND_COMPLETION:
//putchar('C');
            NewEvents = 0;
            if (pReadParms->ulNotificationFlag != TEST_COMMAND_COMPLETION_FLAG) {
                DlcDebugBreak();
            }
if (TraceFlagSet)
    printf("\nCompleted CCB: ");
            for (pCcb = pReadParms->Type.Event.pCcbCompletionList;
                 pCcb != NULL && pReadParms->Type.Event.usCcbCount != 0;
                 pCcb = pNextCcb
                 )
            {
if (TraceFlagSet)
    printf("%u, ", pCcb->uchDlcCommand);
                NewEvents++;
                DBG_ASSERT(Status = pCcb->uchDlcStatus);
                pNextCcb = pCcb->pNext;
                if (pCcb->uchDlcCommand >= LLC_TRANSMIT_FRAMES &&
                     pCcb->uchDlcCommand <= LLC_TRANSMIT_TEST_CMD)
                {
                    Count.XmitCompletion++;
                    if (pTransmitCcbPool != NULL)
                    {
                        CcbPoolFree(pTransmitCcbPool, pCcb);
                }
                    else
                    {
                        FreeCcb(pCcb);
                    }
                }
                else
                {
                    Count.CmdCompletion++;
                    FreeCcb(pCcb);
                }
            }
        if (pReadParms->Type.Event.usReceivedFrameCount != 0)
        {
            FreeAllFrames(
                    pReadCcb->uchAdapterNumber,
                    pReadParms->Type.Event.pReceivedFrame,
                    &cLeft
                    );
            }
            break;

        case LLC_EVENT_STATUS_CHANGE:
            Count.Status++;
            if (pReadParms->ulNotificationFlag != TEST_DLC_STATUS_FLAG) {
                DlcDebugBreak();
            }

            PRINTF("Link (%u,%x):",
                   pReadCcb->uchAdapterNumber,
                   pReadParms->Type.Status.usStationId
                   );

            DlcStatus = pReadParms->Type.Status.usDlcStatusCode;

            if (DlcStatus & LLC_INDICATE_LINK_LOST) {
                printf("Error: Link Lost: StationId: %04x, Status Code: %04x, RSap: %02x\n",
                        pReadParms->Type.Status.usStationId,
                        pReadParms->Type.Status.usDlcStatusCode,
                        pReadParms->Type.Status.uchRemoteSap
                        );

                PRINTF("LLC_INDICATE_LINK_LOST, StationID: %X\n",
                        pReadParms->Type.Status.usStationId
                        );

                //
                // close the link station. If an error occurs from the close
                // return that
                //

                pCcb = LlcCloseInit(pReadCcb->uchAdapterNumber,
                                    0,
                                    LLC_DLC_CLOSE_STATION,
                                    pReadParms->Type.Status.usStationId
                                    );
                Status = AcsLan(pCcb, NULL);
                if (Status != STATUS_SUCCESS || (Status = pCcb->uchDlcStatus) != STATUS_SUCCESS) {
                    return Status;
                }

                //
                // else return arbitrary error code
                //

                return -1;
            }

            if (DlcStatus & LLC_INDICATE_DM_DISC_RECEIVED) {
                PRINTF("LLC_INDICATE_DM_DISC_RECEIVED\n");
                pCcb = LlcCloseInit(pReadCcb->uchAdapterNumber,
                                    0,
                                    LLC_DLC_CLOSE_STATION,
                                    pReadParms->Type.Status.usStationId
                                    );
                Status = AcsLan(pCcb, NULL);
                if (Status != STATUS_SUCCESS || (Status = pCcb->uchDlcStatus) != STATUS_SUCCESS) {
                    return Status;
                }
            }

            if (DlcStatus & LLC_INDICATE_FRMR_RECEIVED) {
                printf(" LLC_INDICATE_FRMR_RECEIVED\n");
                return LLC_STATUS_LINK_PROTOCOL_ERROR;
            }

            if (DlcStatus & LLC_INDICATE_FRMR_SENT) {
                printf(" LLC_INDICATE_FRMR_SENT\n");
                return LLC_STATUS_LINK_PROTOCOL_ERROR;
            }

            if (DlcStatus & LLC_INDICATE_CONNECT_REQUEST) {
                pCcb = LlcCloseInit(pReadCcb->uchAdapterNumber,
                                    0,
                                    LLC_DLC_CLOSE_STATION,
                                    pReadParms->Type.Status.usStationId
                                    );
                Status = AcsLan(pCcb, NULL);
                DBG_ASSERT(pCcb->uchDlcStatus);
                PRINTF(" ERROR: LLC_INDICATE_CONNECT_REQUEST\n");
            }

            if (DlcStatus & LLC_INDICATE_RESET) {
                PRINTF(" LLC_INDICATE_RESET\n");
            }

            if (DlcStatus & LLC_INDICATE_REMOTE_BUSY) {
//putchar('r');
                PRINTF(" LLC_INDICATE_REMOTE_BUSY\n");
            }

            if (DlcStatus & LLC_INDICATE_REMOTE_READY) {
                PRINTF(" LLC_INDICATE_REMOTE_READY\n");
            }

            if (DlcStatus & LLC_INDICATE_TI_TIMER_EXPIRED) {
                PRINTF(" LLC_INDICATE_TI_TIMER_EXPIRED\n");
            }

            if (DlcStatus & LLC_INDICATE_DLC_COUNTER_OVERFLOW) {
                PRINTF(" LLC_INDICATE_DLC_COUNTER_OVERFLOW\n");
                ReportDlcStatistics(pReadCcb->uchAdapterNumber,
                                    pReadParms->Type.Status.usStationId
                                    );
            }

            if (DlcStatus & LLC_INDICATE_ACCESS_PRTY_LOWERED) {
                PRINTF(" LLC_INDICATE_ACCESS_PRTY_LOWERED\n");
            }

            if (DlcStatus & LLC_INDICATE_LOCAL_STATION_BUSY) {
                PRINTF(" LLC_INDICATE_LOCAL_STATION_BUSY\n");

                //
                // Let's hope there is now enough buffers to
                // receive the data. Clear the out of buffer busy state.
                //

                DlcFlowControl(pReadCcb->uchAdapterNumber,
                               pReadParms->Type.Status.usStationId,
                               LLC_RESET_LOCAL_BUSY_BUFFER
                               );
                LocalBusy = TRUE;
/*
{
    PLLC_BUFFER pBuffer;

putchar('l');
BufferGet(pReadCcb->uchAdapterNumber, 0, 0, &pBuffer, &cLeft);
printf("(%u)",  cLeft);
}
*/
            }
            break;
        } // switch

        if (pReadParms->uchEvent & EventMask) {
            if (EventCount >= NewEvents) {
                EventCount -= NewEvents;
            } else {
                EventCount = 0;
            }
        }
    }

    if (cLeft != -1) {
        PRINTF("Buffers left:  %u\n",  cLeft);
    }

    return STATUS_SUCCESS;
}





//
//  This table is used to translate the bits within a byte.
//  The bits are in a reverse order in the token-ring frame header.
//  We must swap the bits in a ethernet frame header, whent the header
//  is copied to a user buffer.
//
UCHAR BitSwap[256] =
{
0x00,0x80,0x40,0xc0,0x20,0xa0,0x60,0xe0,0x10,0x90,0x50,0xd0,0x30,0xb0,0x70,0xf0,
0x08,0x88,0x48,0xc8,0x28,0xa8,0x68,0xe8,0x18,0x98,0x58,0xd8,0x38,0xb8,0x78,0xf8,
0x04,0x84,0x44,0xc4,0x24,0xa4,0x64,0xe4,0x14,0x94,0x54,0xd4,0x34,0xb4,0x74,0xf4,
0x0c,0x8c,0x4c,0xcc,0x2c,0xac,0x6c,0xec,0x1c,0x9c,0x5c,0xdc,0x3c,0xbc,0x7c,0xfc,
0x02,0x82,0x42,0xc2,0x22,0xa2,0x62,0xe2,0x12,0x92,0x52,0xd2,0x32,0xb2,0x72,0xf2,
0x0a,0x8a,0x4a,0xca,0x2a,0xaa,0x6a,0xea,0x1a,0x9a,0x5a,0xda,0x3a,0xba,0x7a,0xfa,
0x06,0x86,0x46,0xc6,0x26,0xa6,0x66,0xe6,0x16,0x96,0x56,0xd6,0x36,0xb6,0x76,0xf6,
0x0e,0x8e,0x4e,0xce,0x2e,0xae,0x6e,0xee,0x1e,0x9e,0x5e,0xde,0x3e,0xbe,0x7e,0xfe,
0x01,0x81,0x41,0xc1,0x21,0xa1,0x61,0xe1,0x11,0x91,0x51,0xd1,0x31,0xb1,0x71,0xf1,
0x09,0x89,0x49,0xc9,0x29,0xa9,0x69,0xe9,0x19,0x99,0x59,0xd9,0x39,0xb9,0x79,0xf9,
0x05,0x85,0x45,0xc5,0x25,0xa5,0x65,0xe5,0x15,0x95,0x55,0xd5,0x35,0xb5,0x75,0xf5,
0x0d,0x8d,0x4d,0xcd,0x2d,0xad,0x6d,0xed,0x1d,0x9d,0x5d,0xdd,0x3d,0xbd,0x7d,0xfd,
0x03,0x83,0x43,0xc3,0x23,0xa3,0x63,0xe3,0x13,0x93,0x53,0xd3,0x33,0xb3,0x73,0xf3,
0x0b,0x8b,0x4b,0xcb,0x2b,0xab,0x6b,0xeb,0x1b,0x9b,0x5b,0xdb,0x3b,0xbb,0x7b,0xfb,
0x07,0x87,0x47,0xc7,0x27,0xa7,0x67,0xe7,0x17,0x97,0x57,0xd7,0x37,0xb7,0x77,0xf7,
0x0f,0x8f,0x4f,0xcf,0x2f,0xaf,0x6f,0xef,0x1f,0x9f,0x5f,0xdf,0x3f,0xbf,0x7f,0xff
};


//
//  Copies and swaps the memory unconditionally
//
//VOID
//SwappingMemCpy(
//    IN PUCHAR pDest,
//    IN PUCHAR pSrc,
//    IN UINT Len
//    )
//
#define SwappingMemCpy(pDest, pSrc, Len) {\
    UINT i; \
    for (i = 0; i < Len; i++) \
        ((PUCHAR)pDest)[i] = BitSwap[((PUCHAR)pSrc)[i]]; \
}


UCHAR
TranslateLanHeader(
    IN UINT AddressTranslationMode,
    IN PUCHAR pSrcLanHeader,
    OUT PUCHAR pDestLanHeader
    )

/*++

Routine Description:

    The primitive translates the given lan header and its type to
    a real network header and patches the local node address to
    the source address field.  It also returns the length
    of the lan header (== offset of llc header).

Arguments:

    IN UINT AddressTranslationMode - the network format mapping case
    IN PUCHAR pSrcLanHeader - the initial lan header
    IN PUCHAR pNodeAddress - the current node address
    OUT PUCHAR pDestLanHeader - storage for the new lan header

Return Value:

    Length of the built network header.

--*/

{
    UCHAR LlcOffset = 14;
    UCHAR NodeAddressOffset = 6;
    UCHAR SourceRoutingFlag = 0;

    //
    // LLC driver API supports both 802.3 (ethernet) and 802.5 (token-ring)
    // address presentation formats. The 802.3 header may include the source
    // routing information, when it is used on token-ring.
    //
    // Internally LLC supports 802.3, DIX and 802.5 networks.
    // The transport level driver needs to support only one format.  It is
    // translated to the actual network header by LLC.
    // Thus we have these six address mappings.
    //

    switch (AddressTranslationMode) {
    case LLC_SEND_802_5_TO_802_5:

        //
        // TOKEN-RING 802.5 -> TOKEN-RING 802.5
        //

        NodeAddressOffset = 8;
        memcpy(pDestLanHeader, pSrcLanHeader, 8);
        SourceRoutingFlag = pSrcLanHeader[8] & (UCHAR)0x80;
        if (SourceRoutingFlag) {

            //
            // Copy the source routing info
            //

            pDestLanHeader[8] |= 0x80;
            LlcOffset += pSrcLanHeader[14] & 0x1f;
            memcpy(&pDestLanHeader[14],
                   &pSrcLanHeader[14],
                   pSrcLanHeader[14] & 0x1f
                   );
        }
        break;

    case LLC_SEND_802_5_TO_DIX:

        //
        // TOKEN-RING -> DIX-ETHERNET
        //
        //
        // The ethernet type is a small endiand!!
        //

        pDestLanHeader[12] = 0x80;
        pDestLanHeader[13] = 0xD5;
        LlcOffset = 17;

    case LLC_SEND_802_5_TO_802_3:

        //
        // TOKEN-RING 802.5 -> ETHERNET 802.3
        //

        SwappingMemCpy(pDestLanHeader, &pSrcLanHeader[2], 12);
        break;

    case LLC_SEND_DIX_TO_DIX:
        LlcOffset = 12;

    case LLC_SEND_802_3_TO_802_3:

        //
        // ETHERNET 802.3 -> ETHERNET 802.3
        //

        memcpy(pDestLanHeader, pSrcLanHeader, 12);
        break;

    case LLC_SEND_802_3_TO_DIX:

        //
        // The ethernet type is a small endiand!!
        //

        pDestLanHeader[12] = 0x80;
        pDestLanHeader[13] = 0xD5;
        LlcOffset = 17;

        //
        // ETHERNET 802.3 -> DIX-ETHERNET
        //

        memcpy(pDestLanHeader, pSrcLanHeader, 12);
        break;

    case LLC_SEND_802_3_TO_802_5:

        //
        // ETHERNET 802.3 -> TOKEN-RING 802.5
        //

        NodeAddressOffset = 8;
        pDestLanHeader[0] = 0;      // AC = no pritority
        pDestLanHeader[1] = 0x40;   // FS = Non-MAC
        SwappingMemCpy(pDestLanHeader + 2, pSrcLanHeader, 12);

        //
        // Note: Ethernet source routing info indication flag is swapped!
        //

        if (pSrcLanHeader[6] & 0x01) {
            SourceRoutingFlag = 0x80;

            //
            // Copy the source routing info, the source routing info
            // must always be in token-ring bit order (reverse)
            //

            pDestLanHeader[8] |= 0x80;
            LlcOffset += pSrcLanHeader[12] & 0x1f;
            memcpy(&pDestLanHeader[14],
                   &pSrcLanHeader[12],
                   pSrcLanHeader[12] & 0x1f
                   );
        }
        break;

    case LLC_SEND_UNMODIFIED:
        return 0;
        break;
    }

    pDestLanHeader[NodeAddressOffset] |= SourceRoutingFlag;
    return LlcOffset;
}
