OTLLCTest.c

/*
    File:       OTLLCTest.c
 
    Contains:   Simple app write or receive 8022 Ethernet packets using a multicast address
                This program implements both a sender and receiver such that both sides
                open an 802.2 Ethernet endpoint. The user can then select whether to run the 
                program as a sender or receiver. If implemented as a receiver, the endpoint
                is bound, and the multicast option is turned on. The receiver waits in a 
                spin loop for a specified period of time before quitting.  The receiver will process
                all incoming ethernet packets destined to the endpoint for the specified
                protocol.  Upon receipt, the program checks to see whether the packet was sequential
                to the previous packet.  A collection of global counter maintains the number of
                inOrder, outOfOrder packets, and the number of packets reads resulting in an error,
                plus the number of packets which come in back to back while in the handler.
    
                Note the this sample turns on the rawmode option so that the handler will be passed the
                14 byte 802.2 header.
                Also note that the sender may also implement the rawmode option so that it can also
                fill in the header bytes itself.  If this is done, then the buffer needs to 
                be enlarged to include these additional bytes.  These additional bytes will not affect
                the maximum tsdu size since the tsdu size is the i-frame limit and does not include
                the header size.
    
                The sender process, sends 10005 x 1500 byte packets as fast as possible.  The user 
                can select to to turn on AckSends mode where the packet is handed to OT and not
                
                released until OT sends the information to the lower layer. 
    Written by: Rich Kubota 
 
    Copyright:  Copyright © 1993-1999 by Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
 
    Change History (most recent first):
                7/22/1999   Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                01/98                       added threshold timer to control how often WNE gets called.  
                01/98                       fixed sample so that you can send in raw mode and receive in
                                            regular mode or vice versa and things work.  
                                            Fixed problem that when setting the max data packet size to
                                            1500 bytes and are sending data in regular mode, adjust the
                                            unitdata.data.len field to account for the fact that the
                                            endpoint will insert the LLC header and the SNAP header.  
                                            For raw mode, this is not an issue. Added feature to 1. allow
                                            the program to be re-used without having to quit 2. ask user for
                                            the drivername to use e.g. 'enet0, 'enet1', etc.,
                01/98                       modified the sample to use the revised NegotiateRawModeOption
                                            code which returns the template type.  The Mentat template driver
                                            returns an additional 24 bytes of info at the beginning of the
                                            raw data packet.
                01/98                       modified DoBind so that one can use either a regular LLC to SNAP
                                            endpoint.
                10/97                       fixed bug in OT 8022 module which prevented more than 1483 data
                                            bytes from being sent in raw mode.  Requires OT 1.3 to send > 1483
                                            data bytes with endpoint in rawdata mode.
 
*/
#include <stdio.h>
#include <Types.h>
#include <Memory.h>
#include <Resources.h>
#include <Events.h>
#include <OpenTransport.h>          // open transport files         
#include <OpenTptLinks.h>
#include <OpenTptAppleTalk.h>
#include <OpenTptConfig.h>
#include <Time.h>
#include <Errors.h>
#include <String.h>
#include "OTLLCTest.h"
#include "NegotiateRawModeSample.h"
 
// Comment out the following line if synchronous sends desired.
#define __ASYNCSEND__   1
 
//-----------------------------------------------------------------------------------------
// Globals
//-----------------------------------------------------------------------------------------
 
EndpointRef     gEndpoint;
OSStatus        gstatus;
UInt32          gFlags;
UInt32          gNumBack, gNumFore;
UInt32          gPacketsRead;
UInt32          gBackToBackPackets; 
UInt32          gInOrder;
UInt32          gOutOfOrder;
UInt32          gCounter;
UInt32          gNumDataEvents;
UInt32          gReadErrors;
UInt32          gTemplateType;
UInt32          gNumMemErrs;
UInt32          gTimerThreshold;
UInt8           *gBuffer;
UInt8           *gDummyBuffer;
struct T8022Address     gAddr;
UInt8           gmcAddr[k48BitAddrLength] = {MCASTADDR0,MCASTADDR1,MCASTADDR2,MCASTADDR3,MCASTADDR4,MCASTADDR5};
PacketBuffer    gPacket;
UInt8           gFlag1;
Boolean         gDone;
Boolean         gAbort;
//-----------------------------------------------------------------------------------------
// Prototypes
//-----------------------------------------------------------------------------------------
extern OSStatus OTSetMemoryLimits(size_t growSize, size_t mazSize);
OSStatus        DoBind();
OSStatus        DoAddMulticast(EndpointRef ep, unsigned char *mcAddr);
OSStatus        DoRemoveMulticast(EndpointRef ep, unsigned char *mcAddr);
void            WriteApplIntro(void);
UInt32          GetYesNoOption(void);
UInt32          GetUserOption(void);
Boolean         CanDoMDATAMode(EndpointRef ep);
void            DoOTLLCWriteTest(void);
Boolean         DoSendPacket(EndpointRef ep);
void            DoOTLLCReadTest(void);
OSStatus        DoReadPacket(EndpointRef ep, UInt8 *mainBuffer);
pascal void     LLCEventHandler(void* ref, OTEventCode event, OTResult result, void* cookie);
void            CallWNE(void);
void            MyIdle(void);
void            DoValueBreak(long value, const char* message);
void            GetDriverName(char *name);
void            SetTimerThreshold(void);
void            PrintAppleTalkPortName(void);
void            ListEnetDrivers(void);
 
 
/*******************************************************************************
** DoBindENET
********************************************************************************/
 
OSStatus DoBind(void)
{
    OSStatus        osstatus;
    TBind           requestInfo;
    TBind           responseInfo;
    UInt32          i;
    
    gAddr.fAddrFamily = AF_8022;
    
    for (i = 0; i < k48BitAddrLength; i++)
        gAddr.fHWAddr[i] = 0x00;
        
    gAddr.fSAP = TESTSAP;
    
    if (TESTSAP == 0xAA)
    {   
        // set SNAP fields;
        
        gAddr.fSNAP[0] = MYSNAP0;       // set these values in the interface file OTLLCTest.h
        gAddr.fSNAP[1] = MYSNAP1;
        gAddr.fSNAP[2] = MYSNAP2;
        gAddr.fSNAP[3] = MYSNAP3;
        gAddr.fSNAP[4] = MYSNAP4;
        
        requestInfo.addr.len = k8022SNAPAddressLength;      
    }
    else
    {
        requestInfo.addr.len = k8022BasicAddressLength;     
    }
 
        
        // finish bind information
    requestInfo.addr.buf = (UInt8 *)&gAddr;
 
    requestInfo.addr.maxlen = 0;            
    requestInfo.qlen = 0;
    
    responseInfo.addr.buf = (UInt8 *)&gAddr;
    responseInfo.addr.len = 0;      
    responseInfo.addr.maxlen = k8022SNAPAddressLength;
    responseInfo.qlen = 0;
    
    
    
    osstatus = OTBind(gEndpoint, &requestInfo, &responseInfo);
    if (osstatus == kOTNoError)
        SetEPBoundFlag(gFlags);
        
    return osstatus;
}
 
 
 
/*******************************************************************************
** DoAddMulticast
********************************************************************************/
 
OSStatus DoAddMulticast(EndpointRef ep, unsigned char *mcAddr)
{
    OSStatus    osstatus = noErr;
    TOptMgmt    req;
    UInt8       reqOpt[64];     
    
    req.opt.buf = reqOpt;
    req.flags   = T_NEGOTIATE;
    
    ((TOption*)reqOpt)->level   = LNK_TPI;
    ((TOption*)reqOpt)->name    = OPT_ADDMCAST;
    ((TOption*)reqOpt)->len     = kOTOptionHeaderSize + k48BitAddrLength;
    ((TOption*)reqOpt)->status  = 0;
    memcpy(((TOption*)reqOpt)->value, mcAddr, k48BitAddrLength);
                
    req.opt.len = kOTOptionHeaderSize + k48BitAddrLength;
    req.opt.maxlen = sizeof(reqOpt);
                
    if ( (osstatus = OTOptionManagement(ep, &req, &req)) != kOTNoError )
        fprintf(stderr, "DoAddMulticast - OptionManagement Returned %d\n", osstatus);
    else
    {
        if (((TOption*)reqOpt)->status != T_SUCCESS)
        
        {
            fprintf(stderr, "DoAddMulticast - failed Status = %d\n", ((TOption*)reqOpt)->status);
            osstatus = -1;
        }
        else
        {
            fprintf(stderr, "DoAddMulticast - was successful\n");
            SetMCastActiveFlag(gFlags);
        }
    }
    return osstatus;
}
 
 
/*******************************************************************************
** DoRemoveMulticast
********************************************************************************/
 
OSStatus DoRemoveMulticast(EndpointRef ep, unsigned char *mcAddr)
{
    OSStatus    osstatus = noErr;
    TOptMgmt    req;
    UInt8       reqOpt[64];     
    
    req.opt.buf = reqOpt;
    req.flags   = T_NEGOTIATE;
    
    ((TOption*)reqOpt)->level   = LNK_TPI;
    ((TOption*)reqOpt)->name    = OPT_DELMCAST;
    ((TOption*)reqOpt)->status  = 0;
    ((TOption*)reqOpt)->len     = kOTOptionHeaderSize + k48BitAddrLength;
    memcpy(((TOption*)reqOpt)->value, mcAddr, k48BitAddrLength);
                    
    req.opt.len = kOTOptionHeaderSize + k48BitAddrLength;
    req.opt.maxlen  = sizeof(reqOpt);
    
    if ( (osstatus = OTOptionManagement(ep, &req, &req)) != kOTNoError )
        fprintf(stderr, "\nDoRemoveMulticast failed - OptionManagement Returned %d.", osstatus);
    else
    { 
        if (((TOption*)reqOpt)->status != T_SUCCESS)
        {
            fprintf(stderr, "nDoRemoveMulticast - failed Status = %d\n", ((TOption*)reqOpt)->status);
            osstatus = -1;
        }
        else
        {
            fprintf(stderr, "nDoRemoveMulticast - was successful\n");
            SetMCastActiveFlag(gFlags);
        }
    }
 
    return osstatus;
}
 
void WriteApplIntro(void)
{
    fprintf(stderr, "\nEthernet 802.2 LLC Test program v1.0\n");
    fprintf(stderr, "\nThis test application sets the system");
    fprintf(stderr, "\ninto send or receive mode.\n");
    fprintf(stderr, "\nThe send portion of this program sets the Ethernet");
    fprintf(stderr, "\ndriver to use a multicast address, then sends 10000");
    fprintf(stderr, "\n- 1500 byte packets out the wire.\n");
    fprintf(stderr, "\nThe receive portion of this program sets the Ethernet");
    fprintf(stderr, "\ndriver to use a multicast address, then waits for the");
    fprintf(stderr, "\n10000 - 1500 byte packets or times out after 30 seconds.\n");
    fprintf(stderr, "\n\nUsing SAP address %d.\n", TESTSAP);
    
}
 
UInt32 GetYesNoOption(void)
{
    UInt32  result;
    char    selection[32];
    Boolean done;
    
    fprintf(stdout, "\n    Enter Y - To accept option");
    fprintf(stdout, "\n    Enter N - To decline option");
    fprintf(stdout, "\n    Enter Q - To quit");
    fprintf(stdout, "\nYour selection -> ");
    fflush(stdout);
    done = false;
 
    do
    {
        scanf("%s", selection);
        switch (selection[0])
        {
            case 'y':
            case 'Y':
                result = kAcceptOption;
                done = true;
                break;
            
            case 'n':
            case 'N':
                result = kDeclineOption;
                done = true;
                break;
 
            case 'q':
            case 'Q':
                result = kQuitTest;
                done = true;
                break;
                
            default:
                fprintf(stdout, "\nInvalid entry - %c, try again -> ", selection);
                fflush(stdout);
                break;
 
        }
    } while (!done);
    
    fflush (stdout);
    return result;
}
 
 
UInt32 GetUserOption(void)
{
    UInt32  result;
    char    selection[32];
    Boolean done;
    
    fprintf(stdout, "\nSelect the type of test to run");
    fprintf(stdout, "\nMake sure that the receive program is already launched");
    fprintf(stdout, "\n    Enter S - Send test packets");
    fprintf(stdout, "\n    Enter R - Receive test packets");
    fprintf(stdout, "\n    Enter q - quit");
    fprintf(stdout, "\nYour selection -> ");
    fflush(stdout);
    done = false;
 
    do
    {
        scanf("%s", selection);
        switch (selection[0])
        {
            case 'r':
            case 'R':
                result = kReceiveTest;
                done = true;
                break;
            
            case 's':
            case 'S':
                result = kSendTest;
                done = true;
                break;
 
            case 'q':
            case 'Q':
                result = kQuitTest;
                done = true;
                break;
                
            default:
                fprintf(stdout, "\nInvalid entry - %c, try again -> ", selection);
                fflush(stdout);
                break;
 
        }
    } while (!done);
    
    fflush (stdout);
    return result;
}
 
/*
    CanDoMDATAMode gets the endpoint info and checks whether the T_CAN_SUPPORT_MDATA bit is
    set in the endpoint info flag field and returns true if so, false otherwise.
*/
Boolean CanDoMDATAMode(EndpointRef ep)
{
    TEndpointInfo   info;
    OSStatus        err;
    Boolean         result;
    
    err = OTGetEndpointInfo(ep, &info);
    if (err != kOTNoError)
        result = false;
    else if (info.flags & T_CAN_SUPPORT_MDATA)
        result = true;      // this also means that the src addr info is in the info record
    else
        result = false;
 
    return result;
}
 
 
void DoOTLLCWriteTest(void)
{
    OSStatus    osstatus;
    UInt32      numMemErrs, last250;
    UInt32      lastFlowErrPacketNum;
    UInt32      timer;
    time_t      t1, t2, t3;
    UInt16      rawModeOffset = 0;
    Boolean     callDoIdle;
    
    
    osstatus = DoBind();
    
    OTMemzero(&gPacket, sizeof(gPacket));       // zero out the global packet buffer structure
 
    if (TstUseAckSendsFlag(gFlags))
    {
        osstatus = OTAckSends(gEndpoint);
        if (osstatus != kOTNoError)
        {
            fprintf (stdout, "\n Error turning AckSends on - error %ld", osstatus);
            ClrUseAckSendsFlag(gFlags);
            gNumFore = gNumBack = 0;
        }
    }
 
    if (TstUseRawModeFlag(gFlags))
    {
        if (CanDoMDATAMode(gEndpoint) && (DATASIZE <= 1500))
        {
            osstatus = DoNegotiateRawModeOption(gEndpoint, kOTRawRcvOn, &gTemplateType);
            if (osstatus == kOTNoError)
            {
                SetRawModeFlag(gFlags);
                    /* if rawmode is on then we want to offset the data
                    * an additional 17 bytes and set the header info
                    * ourselves.
                    * note that 17 bytes constitutes the 6 byte dAddr, 6 byte sAddr
                    * 2 byte length field, 1 byte ssap, 1 byte dsap and 1 byte control byte
                    
                    * Note that even if the DSAP is not 0xAA, we go ahead and stuff the 5 bytes
                    * following the control byte, with whatever SNAP values have been defined.
                    */
                gPacket.rawModeOffset = 17;
                    // check is we are doing SNAP
                if (TESTSAP == 0xAA)
                    gPacket.rawModeOffset += 5;
                fprintf (stdout, "\n raw mode option enabled");
            }
            else
            {
                    // if the option failed, then we don't use it'
                fprintf (stdout, "\nError negotiating raw mode option");
                    // reset the status result.
                osstatus = kOTNoError;
            }
        }
        else
            fprintf (stdout, "\nYou need a later version of OT which supports the MDataMode option");
    }
 
#if __ASYNCSEND__
    if (osstatus == kOTNoError)
    {
        osstatus = OTSetAsynchronous(gEndpoint);
        if (osstatus != kOTNoError) 
        {
            fprintf(stderr, "\n\nError making endpoint asynchronous!");
            fprintf(stderr, "\nOTSetAsynchronous returned %d\n", osstatus);
        }
    }   // now ready to handle async events
#endif      
 
    if (osstatus == kOTNoError)
    {
            // set up the first 18 bytes past the control byte for non SNAP LLC endpoint or
            // past the SNAP header for a SNAP endpoint, so that we can recognize it 
        OTStrCopy((char*)&gPacket.data[gPacket.rawModeOffset], "begin data section");
 
            // set up some specific bytes in the data buffer that begins at the same point
            // relative to the LLC or SNAP header
        gPacket.data[DATAOFFSET     + gPacket.rawModeOffset] = 0;
        gPacket.data[DATAOFFSET + 1 + gPacket.rawModeOffset] = 0;
        OTStrCopy((char*)&gPacket.data[DATAOFFSET+2 + gPacket.rawModeOffset], "end of data section");
        
        gPacket.unitdata.udata.buf = (UInt8*)gPacket.data;
    
        if (TstRawModeFlag(gFlags))
            gPacket.unitdata.udata.len = DATASIZE + 14;
        else
        {
                // the DATASIZE setting represents the total size of the packet
                // following the ethernet header.  If we are not doing a rawmode send
                // then we need to account for the fact that part of the data area
                // will be used by OT to place the SSAP, DSAP, and Control byte.  If
                // we are sending a SNAP packet, then we have to account for the 5
                // additional bytes of the SNAP header.  Otherwise if the unidata.data.len
                // field were set to the full size of 1500, the packet would not be sent
                // since OT would think it was trying to send a 1503 or 1508 byte 
                // ethernet packet (not including the ethernet header).
                
                // By doing this, we can process the packet using either a raw data or
                // regular ethernet endpoint as the receiver.
                
            if (TESTSAP == 0xAA)
                gPacket.unitdata.udata.len = DATASIZE - 8;
            else
                gPacket.unitdata.udata.len = DATASIZE - 3;
        }
            
        gPacket.unitdata.opt.len = 0;
        gPacket.unitdata.opt.buf = NULL;
 
        if (TstRawModeFlag(gFlags) == false)
        {
                // set up the destination addresss
            gPacket.dAddr.fAddrFamily = AF_8022;
            
            gPacket.dAddr.fHWAddr[0] = MCASTADDR0;
            gPacket.dAddr.fHWAddr[1] = MCASTADDR1;
            gPacket.dAddr.fHWAddr[2] = MCASTADDR2;
            gPacket.dAddr.fHWAddr[3] = MCASTADDR3;
            gPacket.dAddr.fHWAddr[4] = MCASTADDR4;
            gPacket.dAddr.fHWAddr[5] = MCASTADDR5;
                
            gPacket.dAddr.fSAP = TESTSAP;
            
            gPacket.unitdata.addr.buf = (UInt8*)&gPacket.dAddr;
            if (TESTSAP == 0xAA)
            {
                gPacket.dAddr.fSNAP[0] = MYSNAP0;
                gPacket.dAddr.fSNAP[1] = MYSNAP1;
                gPacket.dAddr.fSNAP[2] = MYSNAP2;
                gPacket.dAddr.fSNAP[3] = MYSNAP3;
                gPacket.dAddr.fSNAP[4] = MYSNAP4;
        
                gPacket.unitdata.addr.len = k8022SNAPAddressLength;
            }
            else
                gPacket.unitdata.addr.len = k8022BasicAddressLength;
 
        }
        else
        {
                // set up for a rawmode send data call
            gPacket.data[0] = MCASTADDR0;
            gPacket.data[1] = MCASTADDR1;
            gPacket.data[2] = MCASTADDR2;
            gPacket.data[3] = MCASTADDR3;
            gPacket.data[4] = MCASTADDR4;
            gPacket.data[5] = MCASTADDR5;
                // set the packet len field
            gPacket.data[12] = DATASIZE >> 8;
            gPacket.data[13] = DATASIZE & 0xFF;
                // set the dsap, ssap, and control byte fields.
            gPacket.data[14] = TESTSAP;         // set DSAP
            gPacket.data[15] = TESTSAP;         // set SSAP
            gPacket.data[16] = 0x03;            // set control byte
            
            if (TESTSAP == 0xAA)
            {
                    // set up the SNAP Addr
                gPacket.data[17] = MYSNAP0;
                gPacket.data[18] = MYSNAP1;
                gPacket.data[19] = MYSNAP2;
                gPacket.data[20] = MYSNAP3;
                gPacket.data[21] = MYSNAP4;
            }
        
            gPacket.unitdata.addr.buf = nil;    
                                        // don't want to set the destination address since we've already 
                                        // done so in the data
                                        
                                        // the following line is required for OT 1.2 and greater
                                        // where there is a bug with how OT deals with mentat template
                                        // based drivers, such that in rawmode, the total packet size - 
                                        // header + data is limited to 1500 bytes.  By using the following
                                        // line, OT will not check for this limitation and will go ahead
                                        // and send an MDATA message.
 
            gPacket.unitdata.addr.len = 0xFFFFFFFFL;        
                                        // magic constant for M_DATA mode
                        
        }
 
 
        ClrFlowClrFlag(gFlags);     // clear the flag that indicates that a T_GODATA event occurred
                                    // we do this because a race condition might occur when we make the
                                    // the OTSndUData call, a flowerr may occur, but get cleared by the time
                                    // we actually check the osstatus field
 
        fprintf (stdout, "\n starting write of %ld llc packets of %ld bytes\n", (long)SENDCOUNT, (long)DATASIZE);
        fflush(stdout);
        t1 = clock ();
        
        gDone = false;
        gPacket.i = 0;
        numMemErrs = last250 = 0;
        lastFlowErrPacketNum = 0;
        timer = 0;
        callDoIdle = false;
        
            // set the flag that will tell us to send a packet at system task time so that
            // we can send the initial packet
        SetSysTaskSendFlag(gFlags);
        
        while (!gDone && !gAbort)
        {           
            if (TstSysTaskSendFlag(gFlags))
            {
                if (DoSendPacket(gEndpoint) == true)
                {
                    gNumFore++;
                }
            }
 
            if (gNumMemErrs)
            {
                if (numMemErrs != gNumMemErrs)
                {
                    numMemErrs = gNumMemErrs;
 
                    fprintf(stderr, "\nkENOMEMErr %ld.", gPacket.i);
                    callDoIdle = true;
                }
            }
                        
#if 0   // the following code really affect the performance of this tool
        // and can consume over 95 percent of the processing time of this tool
        // on a fast system.
            n = gPacket.i / 250;
            if (n > last250)
            {
                fprintf(stderr, "%d ", n * 250);
                fflush(stderr);
                last250 = n;
 
            }
#endif
                        
            timer++;
            if (timer > gTimerThreshold)
            {
                callDoIdle = true;
            }
                    
            if (gPacket.lastFlowErrPacketNum != 0)
            {
                if (lastFlowErrPacketNum != gPacket.lastFlowErrPacketNum)
                {
                    fprintf(stderr, "\nflow error occurred while sending packet %d.", gPacket.lastFlowErrPacketNum);
                    lastFlowErrPacketNum = gPacket.lastFlowErrPacketNum;
                }
            }
            
                // check if we are flow controlled or if it's time to call WNE and call it.
            if ((callDoIdle == true) || TstFlowErrFlag(gFlags))
            {
                CallWNE();
                callDoIdle = false;
                timer = 0;
            }
            
        }   // end while loop sending data
 
        fflush(stderr);
                
        t2 = clock();
        t3 = t2 - t1;
        fprintf (stdout, "\nCompleted sending %ld llc packets.", (long)gPacket.i);
        fprintf (stdout, "\nTime start = %ld, time end = %ld, time taken %ld seconds.",
                    t1, t2, (long)t3/CLOCKS_PER_SEC);
        fprintf (stdout, "\nPackets per second = %ld, bytes/second = %ld.",
                    ((long)gPacket.i*CLOCKS_PER_SEC)/t3, ((long)gPacket.i*DATASIZE*CLOCKS_PER_SEC)/t3);
        
        fprintf (stdout, "\n Press the mouse to unbind the endpoint");
        
        if (TstUseAckSendsFlag(gFlags) == true)
        {
            fprintf (stdout, "\nThe number of packets sent from the while loop - %ld", gNumFore);
            fprintf (stdout, "\nThe number of packets sent from the handler - %ld", gNumBack);
        }
        
        fflush(stdout);
 
        while (!Button())
            MyIdle();
                        
        
    }   // end if bind successful
 
#if __ASYNCSEND__   
    if (TstEPBoundFlag(gFlags))
    {
        OTSetSynchronous(gEndpoint);
        OTUnbind(gEndpoint);
    }
#endif
 
}
 
Boolean DoSendPacket(EndpointRef ep)
{
    OSStatus        osstatus;
    Boolean         didEnter;
 
        // check to see if we have already entered this function.
        // we only want to enter into it once whether at system task time or from the
        // handler
    if (OTAtomicSetBit(&gFlag1, kInSendPacketBit))
        return false;
    
        // call OTenterNotifier so that the notifier is not entered while we are in the following
        // section of code. In this way we protect ourselves from race conditions that could 
        // happen - for example if a flowErr occured, but was cleared before we set the flowErr
        // flag, this would be a problem since we would never receive notification that flow
        // control was lifted.
        
    didEnter = OTEnterNotifier(ep);
        
    if (gDone == false)
    {
            // we already assume that the packet is ready to send
        osstatus = OTSndUData(gEndpoint, &gPacket.unitdata);
    }
    
    switch (osstatus)
    {
        case kENOMEMErr:
            gNumMemErrs++;
            break;
            
        case kOTNoError:    // send was successful
            gPacket.i++;
                            
            if (gPacket.i >= SENDCOUNT)
                gDone = true;
            else
            {
                    // increment the counter in the packet
                gPacket.data[DATAOFFSET+0+gPacket.rawModeOffset] = gPacket.i >> 8;
                gPacket.data[DATAOFFSET+1+gPacket.rawModeOffset] = gPacket.i;
                    
            }               
            break;
        
        case kOTFlowErr:
            SetFlowErrFlag(gFlags);         // a T_GODATA did not just come in
            ClrSysTaskSendFlag(gFlags);     // clear the flag that we check to send a packet
            gPacket.lastFlowErrPacketNum = gPacket.i;   // save the packet number on which the flow err occured
            break;
        
        default:
            DoValueBreak(osstatus, "Unknown error occurred in DoSendPacket #;");
            gDone = true;
            ClrSysTaskSendFlag(gFlags);
            break;
    }
    
    if (didEnter == true)
        OTLeaveNotifier(ep);
    
    OTAtomicClearBit(&gFlag1, kInSendPacketBit);
    return true;
}
        
void DoOTLLCReadTest(void)
{
    OSStatus    osstatus;
    long        timer;
    
    gBuffer = (UInt8*)NewPtr(DATASIZE + 2* DATASLOP);   // allocate the data buffer + some slop
    if (gBuffer)
        osstatus = DoBind();
    else
    {
        osstatus = memFullErr;
        return;
    }
 
    gBackToBackPackets = 0;
    gInOrder = 0;
    gOutOfOrder = 0;
    gCounter = 0;
    gPacketsRead = 0;
    gNumDataEvents = 0;
    gReadErrors = 0;
    gNumMemErrs = 0;
    gDone = false;
    
    if (osstatus == kOTNoError)
    {
        osstatus = DoAddMulticast(gEndpoint, gmcAddr);
    }
    
    if (osstatus == kOTNoError)
    {
        if (TstUseRawModeFlag(gFlags))
        {
            osstatus = DoNegotiateRawModeOption(gEndpoint, kOTRawRcvOn, &gTemplateType);
            if (osstatus == kOTNoError)
            {
                SetRawModeFlag(gFlags);
            }
            else
            {
                    // if the option failed, then we don't use it'
                fprintf (stdout, "\nError negotiating raw mode option");
                    // reset the status result.
                osstatus = kOTNoError;
            }
        }
    }
    
#ifdef __ASYNCSEND__
    if (osstatus == kOTNoError)
    {
        osstatus = OTSetAsynchronous(gEndpoint);
        if (osstatus != kOTNoError) 
        {
            fprintf(stderr, "\n\nError making endpoint asynchronous!");
            fprintf(stderr, "\nOTSetAsynchronous returned %d\n", osstatus);
        }
    }   // now ready to handle async events
#endif
        
    if (osstatus == kOTNoError)
    {
        gAbort = false;
        SetWantDataFlag(gFlags);
                
        fprintf (stdout, "\nStarting Read test - will terminate in %d seconds", TIMEOUT);
        fprintf (stdout, "\nor as soon as the trigger packet is received.");
        fprintf (stdout, "\nYou may use Command-Q to quit the program entirely.");
        fprintf (stdout, "\nYou may also use Command-A to terminate this test.\n");
        fprintf (stdout, "\nStarting Read");
        fflush(stdout);
        
        timer = TickCount() + TIMEOUT * 60;
            // loop until timer is less than TickCount or until the endtime value gets set
        
        while ((timer > TickCount()) && (gDone == false) && (gAbort == false))
        {
                // allow the user to exit the timer loop by 
            CallWNE();
        }
 
        
        
    }
    
    OTSetSynchronous(gEndpoint);
        
    if (TstMCastActiveFlag(gFlags))
        DoRemoveMulticast(gEndpoint, gmcAddr);
        
    
    if (osstatus == kOTNoError)
    {
        fprintf (stdout, "\n\nBufferReadCount = %ld", gPacketsRead);
        fprintf (stdout, "\nInOrder = %ld\n", gInOrder);
        fprintf (stdout, "\nOutofOrder = %ld\n", gOutOfOrder);
        fprintf (stdout, "\nlast packet read was = %ld\n", gCounter);
        fprintf (stdout, "\nNumber of data events was = %ld\n", gNumDataEvents);
        fprintf (stdout, "\nNumber of read errors was = %ld\n", gReadErrors);
        fprintf (stdout, "\nNumber of back to back packets was = %ld\n", gBackToBackPackets);
        fflush(stdout);
    }
 
    if (TstEPBoundFlag(gFlags))
        OTUnbind(gEndpoint);
 
    if (gBuffer)
        DisposePtr((Ptr)gBuffer);
    
}
 
OSStatus DoReadPacket(EndpointRef ep, UInt8 *mainBuffer)
{
    TUnitData   unitdata;
    struct T8022Address dAddr;
    OTFlags     otFlags;
    OSStatus    result;
    
    unitdata.addr.maxlen = sizeof(dAddr);
    unitdata.addr.buf = (UInt8*)&dAddr;
    unitdata.udata.buf = mainBuffer;
    unitdata.udata.maxlen = DATASIZE + DATASLOP;
    unitdata.opt.maxlen = 0;
    
    result = OTRcvUData(ep, &unitdata, &otFlags);
    return result;
}
 
pascal void LLCEventHandler(void* ref, OTEventCode event, OTResult result, void* cookie)
{
    #pragma unused(ref,cookie)
    OSStatus    osstatus;
    UInt8       *bufferToUse;
    UInt32      lcounter, offset;
    Boolean     firstTimeFlag;
    
    gstatus = result;
    switch (event)
    {
        case T_MEMORYRELEASED:
            if (DoSendPacket(gEndpoint) == false)
                SetSysTaskSendFlag(gFlags);
            else
            {
                ClrSysTaskSendFlag(gFlags);
                gNumBack++;
            }
            break;
 
        case T_OPTMGMTCOMPLETE:
            ClrWaitOptMgmtFlag(gFlags);
            break;
            
        case T_BINDCOMPLETE:
            ClrStillBindFlag(gFlags);
            break;
        
        case T_UNBINDCOMPLETE:
            break;
        
        case T_GODATA:
            SetFlowClrFlag(gFlags); // indicate that the flow data problem has now cleared.
            SetSysTaskSendFlag(gFlags);
            break;
        
        case T_DATA:
            gNumDataEvents++;
            if (TstWantDataFlag(gFlags))
                bufferToUse = gBuffer;
            else
                bufferToUse = gDummyBuffer;
                
                // initialize variables as we enter while loop
            osstatus = kOTNoError;
            firstTimeFlag = true;
            
            while ((osstatus == kOTNoError) && (!gDone))
            {
                osstatus = DoReadPacket(gEndpoint, bufferToUse);
                
                if (firstTimeFlag == true)  
                        // this is the first time through this loop 
                        // for this call to the handler
                    firstTimeFlag = false;
                else
                        // increment the counter to indicate that there was a packet to
                        // handle after reading the previous packet
                    gBackToBackPackets++;
                    
                if (osstatus != kOTNoDataErr)
                {
                    if (osstatus < 0)
                        gReadErrors++;
                    else
                        gPacketsRead++;
                }
                    
                if (TstWantDataFlag(gFlags) && (osstatus == kOTNoError))
                {
                    if (TstRawModeFlag(gFlags))
                    {
                            // if rawmode is on then we want to account for
                            // the additional 17 bytes which will be at the
                            // beginning of the packet.
                        offset = 17;
                        if (TESTSAP == 0xAA)
                        {
                            offset += 5;        // account for the SNAP header
                        }
                        
                            // check if the template is a mentat template and 
                            // adjust the offset to account for the additional
                            // bytes that the template prepends to the ethernet
                            // packet.
                        if (gTemplateType == kMentatTemplate)
                            offset += sizeof(dl_recv_status_t);
                    }
                    else
                        offset = 0;
                    
                    lcounter = gBuffer[DATAOFFSET + offset +0] << 8;
                    lcounter |= gBuffer[DATAOFFSET + offset +1];
                    if (lcounter >= TRIGGEREND)
                    {
                        gDone = true;       // we can bail now.
                    }
                    else
                    {
                        if (lcounter == gCounter)
                            gInOrder++;
                        else
                            gOutOfOrder++;
                        
                        gCounter = lcounter + 1;    // prepare gCounter for next incoming packet to compare
                    }
                }
            }
            break;
            
        default:
            DoValueBreak(event, "Unknown event  occurred #           ;g");
            break;
            
        
    }   /* end switch on event */
}
 
/*******************************************************************************
** CallWNE is implemented to call WaitNextEvent to check for the Command-Q key
** sequence.  When this happens, then the gdone flag is set to true
********************************************************************************/
 
void CallWNE(void)
{
    EventRecord     event;
    char            key;
    
    if (!WaitNextEvent(everyEvent, &event, 15, nil))
        event.what = nullEvent;
            
    switch (event.what)
    {
 
 
        case nullEvent:
        case mouseDown:
        case activateEvt:
        case updateEvt:
        case kHighLevelEvent:
        case osEvt:
        case diskEvt:
            break;
 
        case autoKey:
        case keyDown:
            key = event.message & charCodeMask;
            switch (key)
            {
                case 'q':
                case 'Q':
                    if (event.modifiers & cmdKey)
                    gDone = true;
                    break;
                    
                case 'a':
                case 'A':
                    if (event.modifiers & cmdKey)
                    gAbort = true;
                    break;
            }
            break;
 
    }
    
}
 
void MyIdle(void)
{
    EventRecord     event;
    OSErr           err;
    
    err = WaitNextEvent(everyEvent, &event, 15 + gNumMemErrs * 5, nil);
}
 
void main (void)
{
    OSStatus    osstatus = noErr;
    UInt32      selection;
    char        drvrname[64];
    Boolean     done = false;
    
    WriteApplIntro();
    gFlags = 0;
    if (osstatus = InitOpenTransport())
    {
        fprintf(stderr, "\n\nOpen Transport is not installed!\n");
        fprintf(stderr, "\nBye Bye.\n");
    }
    else
    {
        SetOTActiveFlag(gFlags);    // indicate that OT is active
 
        gDummyBuffer = (UInt8*)NewPtr(DATASIZE + 2 * DATASLOP);
        if (gDummyBuffer == NULL)
            osstatus = memFullErr;
    }
    
    PrintAppleTalkPortName();
 
    while (done == false)
    {
        osstatus = OTSetMemoryLimits(10240000, 0);
        if (osstatus != kOTNoError)
        {
            fprintf(stderr, "\n OTSetMemoryLimits returned %d\n", osstatus);
            osstatus = kOTNoError;
        }
                
        if (osstatus != kOTNoError)
        {
            done = true;
            break;
        }
        
        ListEnetDrivers();
        GetDriverName(drvrname);
        
            // open the default ethernet endpoint
        gEndpoint = OTOpenEndpoint(OTCreateConfiguration(drvrname), (OTOpenFlags)NULL, NULL, &osstatus);
        if (osstatus != kOTNoError)
        {
            fprintf(stderr, "\n\nError opening Ethernet endpoint!");
            fprintf(stderr, "\nOTOpenEndpoint returned %d\n", osstatus);
            fprintf(stderr, "\nBye Bye.\n");
            done = true;
        }
        else
            SetEPActiveFlag(gFlags);    // indicate that the endpoint is opened
    
        if (osstatus == kOTNoError)
        {
            osstatus = OTInstallNotifier(gEndpoint, LLCEventHandler, NULL);
            if (osstatus != kOTNoError)
            {
                fprintf(stderr, "\n\nError installing notifier!");
                fprintf(stderr, "\nOTInstallNotifier returned %d\n", osstatus);
            }
        }   // now ready to handle async events
    
        if (osstatus == kOTNoError)
        {
                // ask whether to use the rawmode option or not
            fprintf(stdout, "\nDo you want to use the raw mode option?");
            selection = GetYesNoOption();
            if (selection == kQuitTest)
            {
                fprintf(stderr, "\n\nBye-Bye!");
                osstatus = -1;
                done = true;
            }
            else if (selection == kAcceptOption)
                SetUseRawModeFlag(gFlags);
            else if (selection == kDeclineOption)
                ClrUseRawModeFlag(gFlags);
            
        }
    
        if (osstatus == kOTNoError)
        {
                
                // what does the user want to do
            selection = GetUserOption();
            
            switch (selection)
            {
                case kSendTest:
                    fprintf(stdout, "\nDo you want to turn on AckSends?");
                    selection = GetYesNoOption();
                    if (selection == kQuitTest)
                    {
                        fprintf(stderr, "\n\nBye-Bye!");
                        done = true;
                        break;
                    }
                    else if (selection == kAcceptOption)
                    {
                        SetUseAckSendsFlag(gFlags);
                    }
                    else if (selection == kDeclineOption)
                    {
                        ClrUseAckSendsFlag(gFlags);
                        SetTimerThreshold();
                    }
 
                        // pause until the user clicks the mouse button
                    fprintf(stderr, "Click Mouse to start test\n");
                    while (!Button());
                    DoOTLLCWriteTest();
                    break;
                
                case kReceiveTest:
                        // pause until the user clicks the mouse button
                    fprintf(stderr, "\n\nClick Mouse to start test\n");
                    while (!Button());
 
                    DoOTLLCReadTest();
                    break;
                
                case kQuitTest:
                default:
                    fprintf(stderr, "\n\nBye-Bye!");
                    done = true;
                    break;
            }
        }
            
        if (TstEPActiveFlag(gFlags))
        {
                // force endpoint to be synchronous
            OTSetSynchronous(gEndpoint);
            OTCloseProvider(gEndpoint);
        }
        
        if (done == false)
        {
            fprintf(stdout, "\n\nDo you want to repeat the test?");
            selection = GetYesNoOption();
 
            if (selection != kAcceptOption)
                done = true;
        }
    }
    
    if (gDummyBuffer)
        DisposePtr((Ptr)gDummyBuffer);
    
    OTSetMemoryLimits(0, 0);
        
    if (TstOTActiveFlag(gFlags))
        CloseOpenTransport();
    
    fprintf(stderr, "\nProgram ended");
}
 
void DoValueBreak(long value, const char* message)
{
    static short    sDoErrorBreak = 0;
 
    {
        Str255  s,
                n = "\p";
 
        s[0] = strlen(message);
        BlockMoveData(message,&s[1],s[0]);
        if (value < 0)
        {
            s[0] += 1;
            s[s[0]] = '-';
            value = -value;
        }
        while (value)
        {
            if (n[0])
                BlockMoveData(&n[1],&n[2],n[0]);
            n[0]++;
            n[1] = 48 + (value % 10);
            value /= 10;
        }
        BlockMoveData(&n[1],&s[s[0]+1],n[0]);
        s[0] += n[0];
 
        sDoErrorBreak++;
        {
            short   cnt = sDoErrorBreak;
 
            s[0]++;
            s[s[0]] = ',';
            s[0]++;
            s[s[0]] = ' ';
            n[0] = 0;
            while (cnt)
            {
                if (n[0])
                    BlockMoveData(&n[1],&n[2],n[0]);
                n[0]++;
                n[1] = 48 + (cnt % 10);
                cnt /= 10;
            }
            BlockMoveData(&n[1],&s[s[0]+1],n[0]);
            s[0] += n[0];
        }
        DebugStr(s);
    }
}
 
void GetDriverName(char *name)
{
        fprintf(stdout, "\nEnter the driver name to use");
        fprintf(stdout, "\nEnter an invalid driver name to quit");
        fprintf(stdout, "\n : ");
        while (true)
        {
            gets(name);
            if (*name != 0)
                break;
        }
}
 
void SetTimerThreshold(void)
{
    char    chr, str[256];
    size_t  len, i;
    Boolean done = false;
    
    while (done == false)
    {
        fprintf(stdout, "\n Enter the timer threshold");
        fprintf(stdout, "\n This is the number of times that the write loop loops before calling");
        fprintf(stdout, "\n WaitNextEvent. Enter a value of 10000 to never call WNE.");
        fprintf(stdout, "\n Enter a value of 100 to call WNE every 100 packets.\n");
        while (true)
        {
            gets(str);
            if (*str != 0)
                break;
        }
        
        gTimerThreshold = 0;
        len = strlen(str);
        
        for (i = 0; i < len; i++)
        {
            chr = str[i];
            if ((chr >= '0') && (chr <= '9'))
                gTimerThreshold = gTimerThreshold*10 + chr - '0';
        }
        
        if (gTimerThreshold != 0)
        {
            done = true;
            fprintf(stdout, "\n The timer threshold is set at %ld\n\n", gTimerThreshold);
            fflush(stdout);
        }
    }
}
 
/*
    The following routine prints the name of the port currently being used by AppleTalk
    If AppleTalk is using Ethernet, then the fPortName field could be used in the 
    OpenEndpoint call to open an ethernet endpoint on the same hardware port as AppleTalk
*/
void PrintAppleTalkPortName(void)
{
    ATSvcRef            atref;
    OSStatus            err;
    OTPortRef           portref;
    OTPortRecord        portrecord;
    TEndpointInfo       info;
    
    atref = OTOpenEndpoint(OTCreateConfiguration(kDDPName), 0, &info, &err);
    
    if (err == kOTNoError)
    {
        portref = OTGetProviderPortRef(atref);
        if (portref != nil)
        {
            if (OTFindPortByRef(&portrecord, portref) == true)
            {
//              fprintf(stderr, "\n Appletalk port name is %s", portrecord.fPortName);
                portref = portrecord.fChildPorts[0];
                if (OTFindPortByRef(&portrecord, portref) == true)
                    fprintf(stdout, "\n Appletalk child port name is %s", portrecord.fPortName);
                else
                    fprintf(stdout, "\n Appletalk child port record could not be found using PortRef %lX", portref);
            }
            else
                fprintf(stdout, "\n Appletalk port record could not be found using PortRef %lX", portref);
        }
        else
            fprintf(stdout, "\n OTGetProviderPortRef returned nil result");
            
        OTCloseProvider(atref);
    }
    else
        fprintf(stdout, "\n OTOpenAppleTalkServices returned error %d", err);
        
 
}
 
void ListEnetDrivers(void)
{
    OTPortRecord    portRecord;
    Boolean         foundAPort;
    UInt32          index;
    Str255          userFriendlyName;
    
    fprintf(stdout, "\n The list of drivers present is:\n");        
    index = 0;
        // iterate thru each OT port record for ethernet ports. 
    while (foundAPort = OTGetIndexedPort(&portRecord,index))
    {
        if ((portRecord.fCapabilities & kOTPortIsDLPI) &&
            (portRecord.fCapabilities & kOTPortIsTPI) &&
            (kOTEthernetDevice == OTGetDeviceTypeFromPortRef(portRecord.fRef)))
        {
            OTGetUserPortNameFromPortRef(portRecord.fRef, userFriendlyName);
            fprintf(stdout, "\n Driver name - %s, ", portRecord.fPortName);
            fprintf(stdout, "user readable name is - %#s", userFriendlyName);
                
        }
        index++;
    }
    fprintf(stdout, "\n");  
    
}