PAPServerSample.c

/*
    File:       PAPServerSample.c
 
    Contains:   This sample demonstrates the implementation of a PAP server using Open Transport.
    
                Issues:
                1.  PostScript Query responses are default only
                    To respond to the LaserWriter client, the PostScript query handler code is 
                    designed to return the default responses.  A real PAP spooler would have to
                    respond more appropriately for the printer that it is supporting.  This could mean
                    delaying the response back to the client until the spooler can actually
                    pass the query to the printer and get a real response.  The code for detecting
                    a PostScript query is not very robust.  The purpose of this sample was not to
                    demonstrate PostScript support, but to packet flow via OT.
    
                2.  If the received data is not part of a PostScript query, the spooler sample
                    assumes that all of the incoming data is part of the same job.  There is no check
                    for the PostScript EOF indicator to distinguish different spooler jobs.  All of the 
                    incoming data is saved to a temporary disk file at the root of the boot hard drive.
                    The code does not try to preflight the amount of storage present, nor protect  
                    some minimum hard drive storage in case the hard drive is close to full.
    
                3.  This sample requires OT 1.1.2 or greater.
    
                4.  This sample support multiple handoff endpoints.  The constant kMaxHandoffEPs
                    defines the maximum number of handoff endpoints that this sample supports.  I have
                    tested that the server supports 2 endpoint connections at the same time, but have not
                    gone into testing more simultaneous connections.    
 
    Written by: Rich Kubota 
 
    Copyright:  Copyright © 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
                
 
*/
#define  HANDOFF_EP         1   // set to to to indicate use of handoff endpoint
#include <GestaltEqu.h>
#include <TextUtils.h>
#include <DiskInit.h>
#include <Types.h>
#include <Errors.h>
#include <String.h>
#include <ToolUtils.h>
#include "StringUtils.h"
#include <AppleTalk.h>
#include "OpenTransport.h"
#include "OpenTptAppleTalk.h"
#include "ATalkSampleUtils.h"
#include "PAPServerUtilities.h"
#include "PAPServerSample.h"
#include "PAPPostScriptStuff.h"
extern  OSStatus OTSetMemoryLimits(size_t growSize, size_t maxSize);
 
/********************/
/*  globals         */
/********************/
DDPAddress      gAddr;
MyEndpointRef   gEp;
 
#if HANDOFF_EP
MyEndpointRef   gHandoffEp[kMaxHandoffEPs];
#endif
 
OTLIFO*         gFreeQ;         // note that an OTLIFO structure must be on a 4-byte boundary
                                // instead of trying to force it to be on a 4-byte boundary in
                                // global memory, we use a pointer, then allocate memory
                                // which will force the global to be on a 4-byte boundary.
UInt32          gFlags  = 0;
UInt32          gBufsAvail;
PacketPtr       gTempPackPtr;
short           gFRefNumToClose = 0;
Boolean         gDone   = false;
Str255          gIdleStr;
Str255          gBusyStr;
Str255          gServerNBPNameStr;
UInt32          gOptionCompleted; // used to flag completion of Option Management calls
extern char     gEOFStr[8];
extern char     gBeginPSStr[8];
extern char     qBeginQueryStr[8];
extern char     gEndStr[8];
extern char     gQueryStr[8];
 
OSType          gOTVersionSelector  = 'otvr';
UInt32          gOTVersion;
 
 
/*******************************************************************************
** Prototypes
********************************************************************************/
 
pascal  void    EventHandler(void*, OTEventCode, OTResult, void*);
static void     EnterListenAccept(MyEndpointRef *myServerEp);
void            DoListenAccept(MyEndpointRef *myServerEp);
Boolean         ReceiveOnePacket(MyEndpointRef *myEp, PacketPtr packetPtr);
void            DoReceiveData(MyEndpointRef *myEp);
OSStatus        DoDisconnect(MyEndpointRef *myEp);
OSStatus        DoOrderlyDisconnect(MyEndpointRef *myEp);
OSStatus        PollQueueList(void);
OSErr           ProcessIncomingDataFile(MyEndpointRef *theEp, PacketPtr packetPtr);
Boolean         TestDataIsPSQuery(PacketPtr packetPtr);
OSStatus        ProcessPSQuery(PacketPtr packetPtr);
Boolean         DoProcessPSQuery(PacketPtr packetPtr);
OSErr           CheckFileToClose(void);
void            DoServer(void);
void            DoEvent(EventRecord *event);
OSStatus        DoBind(EndpointRef ep, UInt8 socket, UInt8 type, OTQLen qlen, 
                        char *nbpName, UInt32 nbpNameLen);
OSStatus        ActivatePAPEndpoint(MyEndpointRef *myEp);
OSStatus        InitPAPServerStuff(void);
void            ClosePAPServerStuff(void);
OSStatus            InitMyEndpointRef(MyEndpointRef *myEp);
OSStatus        InitPAPBuffers(void);
void            ReleasePAPMemory(void);
extern OSStatus DoNegotiateEOMOption(EndpointRef ep, Boolean enableEOM);
extern OSStatus DoSetServerStatusOption(EndpointRef ep, char *statusStr);
extern OSStatus DoNegotiateSelfSendOption(EndpointRef ep, long enableSelfSend);
void            DoValueBreak(long value, const char* message);
OSStatus        BeginSetServerStatusOption(EndpointRef ep, UInt16 whichStr);
UInt32          GetYesNoOption(void);
Boolean         EndpointsAllBusy(void);
#if HANDOFF_EP
MyEndpointRef*  FindFreeHandoffEp(void);
#endif
 
/*******************************************************************************
** EventHandler
**  The event handler can be called at times when it is not safe to do console I/O,
**  so this routine takes the event and jams it on a list to be handled when
**  we know it's safe to print informative messages
********************************************************************************/
 
pascal void EventHandler(void* contextPtr, OTEventCode code,
                                        OTResult result, void* cookie)
{
    #pragma unused(result,cookie)
    switch (code)
    {
        case T_LISTEN:              /* An connection request is available   */
            SetListenPendFlag(((MyEndpointRef*)contextPtr)->flags);
            DoListenAccept(contextPtr);
            break;
            
        //  T_DATA:
        //
        //  The main rule for processing T_DATA's is to remember that once you have
        //  a T_DATA, you won't get another one until you have read to a kOTNoDataErr.
        //  The advanced rule is to remember that you could get another T_DATA
        //  during an OTRcv() which will eventually return kOTNoDataErr, presenting
        //  the application with a synchronization issue to be most careful about.
        //  
        //  In this application, since an OTRcv() calls are made from inside the notifier,
        //  this particular synchronization issue doesn't become a problem.
        //
        case T_DATA:                /* Standard data is available           */
                                    // check that we are in the DATAXFER state
            SetHasDataFlag(((MyEndpointRef*)contextPtr)->flags);
            if (TstPassconFlag(((MyEndpointRef*)contextPtr)->flags))
                DoReceiveData(contextPtr);
            break;
 
        case T_DISCONNECT:          /* A disconnect is available            */
                // check whether there is a file open on this endpoint
            DoDisconnect(contextPtr);
            break;
            
        case T_ERROR:               /* obsolete/unused in library           */
        case T_UDERR:               /* A Unit Data Error has occurred       */
#if SHOW_DEBUG_FLOW
            DebugStr((const unsigned char *)"\p T_ERROR called;g");
#endif
            break;
 
        case T_ORDREL:              /* An orderly release is available      */
            DoOrderlyDisconnect(contextPtr);
            break;
 
        case T_GODATA:              /* Flow control lifted on standard data */
                                    // for this sample, I do not expect this event to occur.
#if SHOW_DEBUG_FLOW
            DebugStr((const unsigned char *)"\p T_GODATA called;g");
#endif
            break;
 
        case T_GOEXDATA:            /* Flow control lifted on expedited data*/
                                    // for this sample, I do not expect this event to occur.
#if SHOW_DEBUG_FLOW
            DebugStr((const unsigned char *)"\p T_GOEXDATA called;g");
#endif
            break;
 
        case T_PASSCON:             /* State is now case T_DATAXFER         */
                    // set the passcon flag
                // set flag to indicate that the endpoint is busy
            SetEPBusyFlag(((MyEndpointRef*)contextPtr)->flags);
                // set flag to teel system to set server status flag appropriately
                // for multiple handoff endpoint servers, this would need to be
                // handled differently.
            SetCheckOptFlag(gFlags);
 
            SetPassconFlag(((MyEndpointRef*)contextPtr)->flags);
 
                        // Set the timer for this connection
            OTGetTimeStamp(&((MyEndpointRef*)contextPtr)->timerDog);
 
                    // check to see if there is any data to process
            DoReceiveData(contextPtr);
            break;
            
        case T_RESET:               /* Protocol has been reset              */
#if SHOW_DEBUG_FLOW
            DebugStr("\p T_RESET called;g");
#endif
            break;
 
        case T_ACCEPTCOMPLETE:      /* Accept call is complete              */
#if SHOW_DEBUG_FLOW
            DebugStr("\p T_ACCEPTCOMPLETE called;g");
#endif
            ClrAcceptPendFlag(((MyEndpointRef*)contextPtr)->flags);
            break;
 
        case T_DISCONNECTCOMPLETE:  /* Disconnect call is complete          */
            break;
        
        case T_UNBINDCOMPLETE:
#if SHOW_DEBUG_FLOW
            DebugStr("\p T_UNBINDCOMPLETE called;g");
#endif
            SetCheckOptFlag(gFlags);
            ClrEPBusyFlag(((MyEndpointRef*)contextPtr)->flags);
 
            SetStatusIdleFlag(gFlags);
 
            ClrEPBoundFlag(((MyEndpointRef*)contextPtr)->flags);    // is the server endpoint bound
            break;
            
        //
        //  kStreamIoctlEvent:
        //
        //  This event is returned when an I_FLUSH ioctl has completed.
        //  The flush was done in an attempt to get back all T_MEMORYRELEASED events
        //  for outstanding OTSnd() calls with Ack Sends.   Errors are ignored at this point since it is
        //  possible that the connection will already be gone, etc.
        //
        case kStreamIoctlEvent:
            return;
            break;
        
        case T_OPTMGMTCOMPLETE:
            gOptionCompleted = 1;
            break;
 
        default:
            break;
    }
}
 
//
//  EnterListenAccept
//
//  This is a front end to DoListenAccept() which is used whenever 
//  it is not being called from inside the listener endpoint's notifier.
//  We do this for syncrhonization.   If we were processing an OTListen()
//  or an OTAccept() and we were interrupted at the listener endpoint's
//  notifier with a T_LISTEN, etc, it would be inconvenient and would require
//  some more sophisticated synchronization code to deal with the problem.
//  The easy way to avoid this is to do an OTEnterNotifier() on the listener's
//  endpoint.   
//
//  Important note - doing OTEnterNotifier on one endpoint only prevents that
//  endpoint's notifier for interrupting us.   Since the same notifier code
//  is used for lots of endpoints here, remember that the one endpoint's 
//  notifier can interrupt another.   Doing an OTEnterNotifier() on the
//  listener endpoint prevents the listener from interrupting us, but it
//  does not prevent the Notifier() routine from interrupting us via 
//  another endpoint which also uses the same routine.
//
//  Important note #2 - Don't ever do an OTEnterNotifier on an acceptor endpoint
//  before doing the OTAccept().   This confuses OT and creates problems.
//
static void EnterListenAccept(MyEndpointRef *myServerEp)
{
    Boolean doLeave;
    
    doLeave = OTEnterNotifier(myServerEp->ep);
    DoListenAccept(myServerEp);
    if (doLeave)
        OTLeaveNotifier(myServerEp->ep);
}
 
/*
    DoListenAccept is designed to handle an incoming connect request.  This module demonstrates a
    very complex task that all X/OPEN style acceptors must implement.  The problem stems from
    fact that an endpoint may be hit by a number of simultaneous connect requests.  In addition
    if there is a connection already established and the endpoint handles multiple
    handoff endpoints, then there could also be an incoming disconnect request to process before
    one can issue either an Accept or Disconnect response, all pending Connect requests must
    be consumed from the streamhead first.  This places a burden on the accept routine to have
    a place to temporarily stash all of the addresses to which discon messages will have to be
    sent.
    
    Refer to Tech Note 1059 for the 8 steps to handling an incoming connection request.
    
    Note that once all of the connect request have been consumed and we are in the process of
    sending discon responses, it could be that there will again be an kOTLookErr, which
    indicates that another incoming connect request heeds to be consumed before
    resuming with the discon responses.
    
    In this sample we consume the first listen request.  We then check whether we can accept
    the incoming request, If so, then call OTAccept.  If OTAccept fails with a lookErr, then 
    there is another connect request to be consumed.
    
    processed.
    
*/
void DoListenAccept(MyEndpointRef *myServerEp)
{
    MyEndpointRef   *myAcceptEp;
    OSStatus    err;
    TDiscon     discon;
    OTResult    result;
    TCall       call;
    Boolean     done = false;
    
        // check we have already entered DoListenAccept from the main event loop and are 
        // trying to do so again from the secondary interrupt
    if (OTAtomicSetBit(&myServerEp->semaphore, kInListenLoop))
        return; // if the bit was previous set, then we already have entered, but not exitted
                // this proc from the main event loop
 
    if (TstAcceptPendFlag(myServerEp->flags))
    {
            // we're waiting for an Accept call to complete, so don't accept a new
            // connection request as we will get an out of state error
            // set the ListenPendFlag so we will check the server endpoint later
        
        SetListenPendFlag(myServerEp->flags);
        done = true;
    }
    else
        ClrListenPendFlag(myServerEp->flags);
    
    if (done == false)
    {
            // clear out the TCall structure
        memset(&call, 0, sizeof(TCall));
        
            // set up the TCall structure
        call.addr.maxlen = sizeof(struct DDPAddress);
        call.addr.len = sizeof(struct DDPAddress);
        call.addr.buf = (unsigned char *) &gAddr;
                
        if (OTIsSynchronous(myServerEp->ep) == true)
            OTSetAsynchronous(myServerEp->ep);
            
            // step 1 for handling an incoming connection request
        err = OTListen(myServerEp->ep, &call);
        
        if (err == kOTNoDataErr)
        {
#if SHOW_DEBUG_FLOW
            DebugStr("\p kOTNoDataErr returned by OTListen");
#endif
                // don't need to do anything
        }
        else if (err == kOTLookErr)
        {
                // step 2 for handling incoming connection requests
                // handle disconnect indications for a pending connection request
                // the only look indication allowed on an OTListen call is
                // T_DISCONNECT
            result = OTLook(myServerEp->ep);
            if (result == T_DISCONNECT)
            {
                    // this is what we were expecting
                    // the disconnect is for an outstanding listen request
                err = OTRcvDisconnect(myServerEp->ep, &discon);
                    
            }
            else
            {
#if SHOW_DEBUG_FLOW
                DebugStr((const unsigned char *)"\p unknown event returned by OTLook");
#endif
            }
            done = true;
             
        }
        else if (err != kOTNoError) // there was an unknown error in doing the listen
        {
            DoValueBreak((long)err, "error occured on OTListen #");
            done = true;
 
        }
    }
    
    if (done == false)
    {
#if HANDOFF_EP
        myAcceptEp = FindFreeHandoffEp();
#else
        if (TstEPBusyFlag(myServerEp.flags) == false)
            myAcceptEp = myServerEp;
        else
            myAcceptEp = nil;
#endif       
    }
    
    if (myAcceptEp == nil)
    {
            // if there are no available handoff endpoints, then send the disconnect
        OTSndDisconnect(myServerEp->ep, &call);
        done = true;
    }
    
    if (done == false)
    {
    
            // set a flag to indicate that an accept call is pending
            // if we try to accept another connection before this
            // accept call is completed, an out of state error results 
            // need to do this before we make the OTAccept call
        SetAcceptPendFlag(myServerEp->flags);
        
            // make the accept call and handoff the connection
        err = OTAccept(myServerEp->ep, myAcceptEp->ep, &call);
 
#if SHOW_DEBUG_FLOW
            DoValueBreak((long)err, "OTAccept returned #");
#endif
 
        if (err == kOTNoError)
        {
            SetEPBusyFlag(myAcceptEp->flags);
 
                // set flag to have server check the serverstatus
            TstCheckOptFlag(gFlags);
                        
        }
        else if (err == kOTLookErr)
        {
                // an OTLookErr for an OTAccept call means that there is 
                // either an T_LISTEN or T_DISCONNECT event
                
                // check what event is on the pipe
            result = OTLook(myServerEp->ep);
            if (result == T_DISCONNECT)
            {
                err = OTRcvDisconnect(myServerEp->ep, &discon);
                    
            }
        
        }
        else
        {
#if HANDOFF_EP
            DoValueBreak((long)err, "error occured on OTAccept for handoff #");
#else
            DoValueBreak((long)err, "error occured on OTAccept #");
#endif
 
        }
    }
    
        
    OTAtomicClearBit(&myServerEp->semaphore, kInListenLoop);    // clear the bit that indicates we're in this loop
    
}
 
/*******************************************************************************
** ReceiveOnePacket - makes the call to receive just one packet
** If there is data, then the data is placed into the packetPtr
** and the incoming byte count is incremented
** if the kOTNoDataErr occurs, then the HasDataFlag is cleared.
** 
**   
********************************************************************************/
Boolean ReceiveOnePacket(MyEndpointRef *myEp, PacketPtr packetPtr)
{
    UInt32      localFlag = 0;
    OTResult    result, res;
    Boolean     done = false;
                    
    result = OTRcv(myEp->ep, &packetPtr->data, kPAPDataSize, &packetPtr->flags);
    if (result < 0)
    {
            // requeue the buffer to the freeq
        OTLIFOEnqueue(gFreeQ, &(packetPtr->fLink));
        done = true;
        
        if (result == kOTLookErr)
        {
            res = OTLook(myEp->ep);
                // for an OTRcv call, there are only 2 OTLook responses
            if (res == T_DISCONNECT)
            {
                res = DoDisconnect(myEp->ep);
                    // if we get a disconnect event, then there will be no more
                    // data in the pipe.
#if SHOW_DEBUG_FLOW
                DebugStr("\p processing disconnect in OTRcv;g");
#endif
                ClrOrdDisconFlag(myEp->flags);
                ClrInPSQueryFlag(myEp->flags);
            }
            else if (res == T_ORDREL)
            {
#if SHOW_DEBUG_FLOW
                DebugStr("\p processing orderly disconnect in OTRcv;g");
#endif
                res = DoOrderlyDisconnect(myEp->ep);
                localFlag = 1;
            }
            
        }
        else if (result == kOTOutStateErr)
        {
                // client could have issued a disconnect call while this call was about to
                // to be processed
            ClrHasDataFlag(myEp->flags);
        }
        
        else if (result == kOTNoDataErr)
        {
                // no more data so clr the flag
            ClrHasDataFlag(myEp->flags);
        }
        else 
        {
#if SHOW_DEBUG_FLOW
            DebugStr("\pUnknown error occurred in OTRcv");
#endif
        }
    }
    else if (result == 0)
    {
#if SHOW_DEBUG_FLOW
        DebugStr("\p OTRcv got nothing ;g");
#endif
        
        if (TstInPSQueryFlag(myEp->flags))  // check if the endpoint is already processing a postscript
        {
#if SHOW_DEBUG_FLOW
            DebugStr("\p 0 byte packet while processing ps query ;g");
#endif
#if SHOW_DEBUG_FLOW
            if (packetPtr->flags & T_MORE == 0)
                DebugStr("\p T_MORE was not set;g");
            else
                DebugStr("\p T_MORE was set;g");
#endif
                // send an empty response with EOF flasg set since it appears that this is what the Laserwriter 
                // client wants to see.
            packetPtr->theEp = myEp;
            SendEmptyPacket(packetPtr);
                // clear bit that indicates we are processing a postscript query
            ClrInPSQueryFlag(myEp->flags);
#if SHOW_DEBUG_FLOW
    
            DebugStr("\p Have finished ps query;g");
#endif
        }
        
            // requeue the buffer to the freeq since no data was returned
        OTLIFOEnqueue(gFreeQ, &(packetPtr->fLink));
    }
    else
    {
 
        if (TstdumpPktsFlag(gFlags))
        {
            OTLIFOEnqueue(gFreeQ, &(packetPtr->fLink));
        }
        else
        {
            
                        // save the endpoint ref associated with this data
            packetPtr->theEp = myEp;
            
                        // save the number of bytes read
            packetPtr->numBytes = result;
            
                        // intialize the lastPos field
            packetPtr->lastPos = 0;
            
                        // get the timestamp for this packet
            OTGetTimeStamp(&packetPtr->timeStamp);
            
                        // Set the timer for this connection
            OTGetTimeStamp(&myEp->timerDog);
            
            if (!DoProcessPSQuery(packetPtr))
            {
                    // packet is not a postscript query so queue to the endpoint usedQ
                    // queue the buffer to the usedQ associated with the endpoint
                OTLIFOEnqueue(myEp->usedQ, &(packetPtr->fLink));
                
            }
        
        }
 
    }
    
    return done;
        
}
 
 
/*******************************************************************************
** DoReceiveData
**   
********************************************************************************/
void DoReceiveData(MyEndpointRef *myEp)
{
    PacketPtr   packetPtr;
    OTFlags     flags = 0;
    UInt32      localFlag = 0;
    Boolean     done;
    
        // check we have already entered DoReceiveData from the main event loop
        // or from the event handler and are 
        // trying to do so again a second time
    if (OTAtomicSetBit(&myEp->semaphore, kInRcvDataFlag))
        return; // if the bit was previous set, then we already have entered, but not exitted
                // this proc from the main event loop
 
    done = gDone;
    while (done == false)
    {
        if (gFreeQ->fHead != nil)
        {
            packetPtr = (PacketPtr)OTLIFODequeue(gFreeQ);
            done = ReceiveOnePacket(myEp, packetPtr);
        }
        else
        {
            
            done = true;
        }
            
        
    }
 
    OTAtomicClearBit(&myEp->semaphore, kInRcvDataFlag); 
                // clear the bit that indicates we're in this loop
                        
}
 
 
OSStatus DoDisconnect(MyEndpointRef *myEp)
{
    OSStatus        err;
    
 
    err = OTRcvDisconnect(myEp->ep, NULL);
    if (err != kOTNoError)
    {
#if SHOW_DEBUG_FLOW
        DoValueBreak((long)err, "error occured on OTRcvDisconnect #");
#endif
    }
    else
    {
        if (TstAcceptPendFlag(myEp->flags))
            ClrAcceptPendFlag(myEp->flags);
    }
 
    return err;
}
 
OSStatus DoOrderlyDisconnect(MyEndpointRef *myEp)
{
    OSStatus        err;
    
    err = OTRcvOrderlyDisconnect(myEp->ep);
    if (err < kOTNoError)
    {
#if SHOW_DEBUG_FLOW
        DoValueBreak((long)err, "error occured on OTRcvOrderlyDisconnect #");
#endif
    }
    else if (err > 0)
    {           
#if SHOW_DEBUG_FLOW
        DebugStr("\p OTRcvOrderlyDisconnect returned positive result;g");
#endif
        SetOrdDisconFlag(myEp->flags);
    }
    else
    {
        SetOrdDisconFlag(myEp->flags);
    }
    
    return err;
}
 
/*******************************************************************************
** PollEventList
**  This routine checks the used queue to see whether the handler has processed
    incoming PAP data from the client.
**  If the used queue is not empty, there are two different options to deal with.
    First we check whether we are in the middle of processing a postscript data
    file.  If so, we continue to read the data into a file which we have created.
    If not currently processing postscript data, we then check to see if the incoming
    packet is part of a postscript query in progress or is the beginning of a 
    postscript query.  If so, we continue to read the query and respond as appropriate.
    If not a query, then this must be the first packet of postscript data being sent.
********************************************************************************/
 
OSStatus PollQueueList(void)
{
    PacketPtr   packetPtr, nextPacketPtr;
    MyEndpointRef *theEp;
    OSStatus    err = kOTNoError;
    short       i = 0;
    Boolean     done = false;
 
#if SHOW_DEBUG_FLOW
    short       num = 0;
    Boolean     inLoop = false;
#endif
    
#if HANDOFF_EP
    for (i = 0; (i < kMaxHandoffEPs) && (err == kOTNoError); i++)
    {
        theEp = &gHandoffEp[i];
#else
        theEp = &gEp;
#endif
 
        packetPtr = (PacketPtr)OTLIFOStealList(theEp->usedQ);
        while (packetPtr != nil)
        {
 
#if SHOW_DEBUG_FLOW
            inLoop = true;
#endif
            packetPtr = (PacketPtr)OTReverseList((OTLink*)packetPtr);
            do
            {
#if SHOW_DEBUG_FLOW
                num++;
#endif
                nextPacketPtr = (PacketPtr)packetPtr->fLink.fNext;
                
                err = ProcessIncomingDataFile(theEp, packetPtr);
        
                    // queue the packetPtr back to the freeQ
                OTLIFOEnqueue(gFreeQ, &(packetPtr->fLink));
 
                packetPtr = nextPacketPtr;
                
            } while((packetPtr != nil) && (err == kOTNoError));
            
            packetPtr = (PacketPtr)OTLIFOStealList(theEp->usedQ);
        }
 
#if HANDOFF_EP
    }
#endif
                
#if SHOW_DEBUG_FLOW
    if (inLoop)
    {
//      fprintf(stdout, "numbers packets processed by PollQueueList %d\n", num);
    }
#endif
    
    return err;
}
 
OSErr ProcessIncomingDataFile(MyEndpointRef *theEp, PacketPtr packetPtr)
{
    OSStatus        err = noErr;
    short           fRefNum;
    
    
        // For each incoming packet, we need to check whether it
        // contains postscript query requests, or if we have not yet
        // received the EOF
 
    if (!TstdumpPktsFlag(gFlags))   // check if we are dumping the packets
    {
        if (!TstTempFileFlag(theEp->flags)) // is there a temp file opened for this endpoint
        {
                // no, so create one
            err = OpenTempFile(&fRefNum);
            if (err == noErr)
            {
                fprintf(stdout, "processing incoming file\n");
                theEp->fRefNum = fRefNum;       // save the refnum of the temp file;
                SetTempFileFlag(theEp->flags);  // indicate that there is a temp file associated
                                                // with this endpoint.
                    // set the time Data In timestamp field
                BlockMove((Ptr)&packetPtr->timeStamp, (Ptr)&theEp->timeDataIn, sizeof(OTTimeStamp));
                    // reset the numBytesIn field to zero
                theEp->numBytesIn = 0;
            }
            
        }
    }
 
 
    if (err == noErr)
    {
        
        if (!TstdumpPktsFlag(gFlags))   // check if we are dumping the packets
        {
                // the temp file has been created or already exists, so read the data and write it
                // to the temp file
            err = WriteDataToTempFile(theEp->fRefNum, packetPtr->data, packetPtr->numBytes);
        }
        
        if (err != noErr)
        {
#if SHOW_DEBUG_FLOW
            DoValueBreak((long)err, "error occured writing data to tempfile #");
#endif
        }
        else
        {
                // set the time data end timestamp field
            BlockMove((Ptr)&packetPtr->timeStamp, (Ptr)&theEp->timeDataEnd, sizeof(OTTimeStamp));
                // add the number of bytes in the packet to the numBytesIn field
            theEp->numBytesIn += packetPtr->numBytes;
 
                // check if we've read the last packet for the file
            if ((packetPtr->flags & T_MORE) == 0)
            {
                if (!TstdumpPktsFlag(gFlags))
                {
                    fprintf(stdout, "T_MORE flag not set \n");
                    
                    // the proper way to detect the end of a file is to look for
                    // the EOF flag at the end of the file.
                    // It also appears that looking for the T_MORE flag to be clear
                    // is not a reliable way to detect the end of a file.
                    // When a status request is sent by the client that is sending
                    // data, the T_MORE flag appears to be cleared for the incoming 
                    // packet.
                }
                
            }
                
        }
    }
    
    return err;
}
 
 
 
 
OSErr CheckFileToClose(void)
{
    OSErr   err = noErr;
    
    if (gFRefNumToClose != 0)
    {
        fprintf(stdout, "\n\nAm about to close file with fRefNum %ld.\n", gFRefNumToClose);
        
        err = CloseTempFile(gFRefNumToClose);
        if (err != noErr)
            fprintf(stdout, "\n\nError occurred closing file - error %ld.\n", err);     
        
            // reset the tempfile to close so that we don't close this file again
        gFRefNumToClose = 0;
    }
    
    return err;
}
 
 
/*******************************************************************************
** DoServer
********************************************************************************/
 
void DoServer(void)
{
    EventRecord     theEvent;
    OTTimeStamp     eTime;
    OTResult        state;
    UInt32          eTimeMSec, sec, msec, rate;
    MyEndpointRef   *theEp;
    OSStatus        err1, err = kOTNoError;
#if HANDOFF_EP
    short           i = 0;
#endif
    
    while (gDone == false)
    {
        if (!WaitNextEvent(everyEvent, &theEvent, 15, nil))
            theEvent.what = nullEvent;
            
        DoEvent(&theEvent);
        
        if (TstListenPendFlag(gEp.flags))       // are there incoming connect requests to process
        {
            fprintf(stdout, "\n processing EnterListenAccept from even tloop");
            EnterListenAccept(&gEp);
        }
            
        err = PollQueueList();
        if (err != kOTNoError)      // we're outta here if an error occurred.
            gDone = true;
 
#if HANDOFF_EP
        theEp = &gHandoffEp[i];
        i++;
        if (i >= kMaxHandoffEPs)
            i = 0;
#else
        theEp = &gEp;
#endif
            // get the current endpoint state
        state = OTGetEndpointState(theEp->ep);
        
        switch (state)
        {
            case T_DATAXFER:
                    
                    // check if we haven't completed reading data from OT.  
                DoReceiveData(theEp);
                break;
                
            case T_INREL:   // we've processed an incoming Orderly Release request
                            // but have not issued our own orderly disconnect
                
 
                    // note that since we have already processed an incoming orderly disconnect
                    // we are in the INREL state.  After making the OTSndOrderlyDisconnect
                    // call, there will not be a T_DISCONNECTCOMPLETE call, since we complete
                    // the orderly release with the above call.  We go ahead and reset our flags
                    // and issue the OTUnbind call if we are using a handoff endpoint.
                    // if we did get the orderly release indication, then make sure we have
                    // drained the receive queue - PollQueueList does this
 
                do
                {
                    fprintf(stdout, "processing some last data\n");
                    PollQueueList();
                
                        // check if we haven't completed reading data from OT. 
                    DoReceiveData(theEp);
                    
                }while( (TstHasDataFlag(theEp->flags)) || (theEp->usedQ->fHead != nil));
 
                    // clear the flag and send a corresponding disconnect
                err = OTSndOrderlyDisconnect(theEp->ep);
                if (err)
                {
                    // the only error that makes sense here is the kOTLookErr
                    //  which the only event is the T_DISCONNECT event.  This should not happen,
                    // however, if there are multiple handoffs, this may be an issue.  Given the way
                    // incoming requests are handled, I don't expect that there would be an 
                    // unresponded listen request here that would warrant a possible T_DISCONNECT
                    // however some implementation will need to consider this possibility.
#if SHOW_DEBUG_FLOW
                    DoValueBreak((long)err, "error occured on OTSndOrderlyDisconnect #");
                    
                    DebugStr("\p quitting program");
#endif
                    gDone = true;
 
                }
                                    
                ClrOrdDisconFlag(theEp->flags);
    
                if (TstTempFileFlag(theEp->flags))
                {   
                                                        // is there an open spool file associated with this endpoint
                    gFRefNumToClose = theEp->fRefNum;   // set the global to have this file closed
                                                    // note that this method of signalling for a file to
                                                    // get closed is a quick and dirty method and assumes th
                                                    // that only one file at a time is being processed here.
                    CheckFileToClose();
                }
                
                ClrTempFileFlag(theEp->flags);
                ClrPassconFlag(theEp->flags);
 
#if HANDOFF_EP
                    // in order to re-use a handoff endpoint, it must be unbound.  Unbinding the 
                    // endpoint clears the connection information that allows to to know which
                    // endpoint, incoming PAP data is routed to.
                if (theEp->ep != gEp.ep)
                {
                    OTUnbind(theEp->ep);
    
                }
#else
                    // indicate that we are no longer busy
                ClrEPBusyFlag(theEp->flags);
                    // set flag so that we will check the status
                SetCheckOptFlag(gFlags);
                SetStatusIdleFlag(gFlags);
                    
#endif
                    
                
                    // calculate the elapsed time
                OTSubtractTimeStamps(&eTime, &theEp->timeDataIn, &theEp->timeDataEnd);
                    // convert to millisecs
                eTimeMSec = OTTimeStampInMilliseconds(&eTime);
                sec = eTimeMSec / (UInt32)1000;
                msec = eTimeMSec %(UInt32)1000;
                fprintf(stdout, "Time to transfer file was %ld.%ld seconds.\n", sec, msec);
                fprintf(stdout, "Bytes transferred was - %ld.\n", theEp->numBytesIn);
 
                if (sec != 0)
                {
                    rate = (theEp->numBytesIn / sec) / 1024;
                    fprintf(stdout, "Transfer rate - %ld KBytes/Sec. \n", rate);
                }
                else if (msec != 0)
                {
                    rate = (theEp->numBytesIn *1000 / msec) / 1024;
                    fprintf(stdout, "Transfer rate - %ld KBytes/Sec. \n", rate);
                }
                break;
                
            case T_IDLE:    // we're either really idle, or the we've processed a disconnect event
                            // if we processed a disconnect event then the busy flag for the ep would still be set
                if (TstEPBusyFlag(theEp->flags))    // we're we previously connected
                {
                    ClrPassconFlag(theEp->flags);   // clear the flag so that we can accept a new connection
                    ClrOrdDisconFlag(theEp->flags);
                    
                    ClrTempFileFlag(theEp->flags);
                    ClrPassconFlag(theEp->flags);
 
                    if (TstTempFileFlag(theEp->flags))  
                                                            // is there an open spool file associated with this endpoint
                        gFRefNumToClose = theEp->fRefNum;   // set the global to have this file closed
                                                        // note that this method of signalling for a file to
                                                        // get closed is a quick and dirty method and assumes th
                                                        // that only one file at a time is being processed here.
 
#if HANDOFF_EP
                        // in order to re-use a handoff endpoint, it must be unbound.  Unbinding the 
                        // endpoint clears the connection information that allows to to know which
                        // endpoint, incoming PAP data is routed to.
                    if (theEp->ep != gEp.ep)
                    {
                        OTUnbind(theEp->ep);
        
                    }
#else
                        // set flag so that we will check the status
                    SetCheckOptFlag(gFlags);
                        // indicate that we are no longer busy
                    ClrEPBusyFlag(theEp->flags);
                    SetStatusIdleFlag(gFlags);
 
#endif
                }
                break;
                
        }
        
        
        err1 = CheckFileToClose();
        if (err1 != noErr)
            fprintf(stdout, "\n\nCheckFileToClose error - %ld.\n", err1);
    
        if (TstCheckOptFlag(gFlags))
        {
            
            if (EndpointsAllBusy())
            {
                if (TstStatusBusyFlag(gFlags) == false)
                {
                    BeginSetServerStatusOption(gEp.ep, kSetBusyStr);
                    SetStatusBusyFlag(gFlags);
                }
            }
            else
            {
                if (TstStatusBusyFlag(gFlags) == true)
                {
                    BeginSetServerStatusOption(gEp.ep, kSetIdleStr);
                    ClrStatusBusyFlag(gFlags);
                }
            }
            ClrCheckOptFlag(gFlags);
        }
                    
 
    }
 
}
 
/* Do the right thing for an event.  Determine what kind of event it is, and
** call the appropriate routines. */
 
void    DoEvent(EventRecord *event)
{
    Point   pt;
    char    key;
 
    switch(event->what) 
    {
 
        case nullEvent:
        case mouseDown:
        case activateEvt:
        case updateEvt:
        case kHighLevelEvent:
            break;
 
        case autoKey:
        case keyDown:
            key = event->message & charCodeMask;
            switch (key)
            {
                case 'q':
                case 'Q':
                    if (event->modifiers & cmdKey)
                    gDone = true;
                    break;
            }
            break;
 
        case osEvt:
            switch ((event->message >> 24) & 0xFF) 
            {
                    /* Must logical and with 0xFF to get only low byte. */
                    /* High byte of message. */
 
                case mouseMovedMessage:
                    break;
 
                case suspendResumeMessage:
                        /* Suspend/resume is also an activate/deactivate. */
                    if ((event->message) & resumeFlag)
                        ClrInBackGndFlag(gFlags);   // process has come to the foreground
                    else
                        SetInBackGndFlag(gFlags);   // process has gone into background
                    break;
            }
            break;
 
        case diskEvt:
            if (HiWord(event->message) != noErr) 
            {
                SetPt(&pt, kDILeft, kDITop);
                DIBadMount(pt, event->message);
            }       /* It is not a bad idea to at least call DIBadMount in response to */
            break;  /* a diskEvt, so that the user can format a floppy. */
    }
 
}
 
 
/*******************************************************************************
** DoBind
**  Implements the option to set the qlen field and to bing with an nbpName
**  by setting the nbpName field with a pointer to an entity name.
**  If nbpName is not nil, you must also set the nbpNameLen to the size of the
**  nbpName field. Note, nbpName is not a pascal nor a C string.  
********************************************************************************/
 
OSStatus DoBind(EndpointRef ep, UInt8 socket, UInt8 type, OTQLen qlen, 
                    char *nbpName, UInt32 nbpNameLen)
{
    TBind           req, ret;
    OSStatus        err = kOTNoError;
    DDPNBPAddress   addr;
    
 
    // init the DDPAddress variable 
    if (nbpName == nil)     // are we registering with no nbp name
    {
        addr.fAddressType = AF_ATALK_DDP;       // set the address type
        req.addr.len    = kDDPAddressLength;
    }
    else
    {
        addr.fAddressType = AF_ATALK_DDPNBP;    // registering with an nbp name
                                                // copy the name to the name buffer
        BlockMove(nbpName, &addr.fNBPNameBuffer, nbpNameLen);
                                                // set the request length to include the name len
        req.addr.len    = kDDPAddressLength + nbpNameLen;
    }
    
    addr.fNetwork = 0;
    addr.fNodeID = 0;
    addr.fSocket = socket;
    addr.fDDPType = type;
    
    // set up the request
    req.addr.buf    = (UInt8*)&addr;
    req.qlen        = qlen;
    
    // set up the response
    ret.addr.buf    = (UInt8*)&addr;
    ret.addr.maxlen = sizeof(addr);
 
    fprintf(stderr, "Doing Bind\n");
 
    //
    // Try to bind
    // 
    err = OTBind(ep, &req, &ret);
 
    if ( err != kOTNoError )
    {
        fprintf(stderr, "PAPSample: DoBind returns error %ld\n", err);
        return err;
    }
 
    fprintf(stderr, "Bound address = ");
    ShowDDPAddress((DDPAddress*)&addr);
    fprintf(stderr, "\n");
    fprintf(stderr, "Bound queue len is %ld\n", ret.qlen);
 
    fprintf(stderr, "After Bind, ");
    ShowEndpointState(ep, "");
 
    return err;
}
 
 
 
/*******************************************************************************
** ActivatePAPEndpoint
** 
********************************************************************************/
 
OSStatus ActivatePAPEndpoint(MyEndpointRef *myEp)
{
    OSStatus    err = kOTNoError;
    
    //
    // Create a PAP endpoint
    // Open listener, using the tilisten module to make 
    // listen/accept/disconnect processing much simpler.
    //
    myEp->ep = OTOpenEndpoint(OTCreateConfiguration("tilisten, pap"), 0, NULL, &err);
 
    if ( myEp->ep == NULL || err != kOTNoError )
    {
        myEp->ep = NULL;
        
        if (err == kOTNoError)
            err = memFullErr;
            
        fprintf(stderr,"ERROR: OpenEndpoint(\"pap\") failed with %ld\n", err);
    }
    else
    {
        SetEPActiveFlag(myEp->flags);
    }
 
    /*-------------------------------------------------------------------------
    Enable self send
        have to do this before we set the endpoint into async mode.
    ------------------------------------------------------------------------- */
    if (err == kOTNoError)
    {
        err = DoNegotiateSelfSendOption(myEp->ep, 1);
        
        if (err <= kOTNoError)
        {
            fprintf(stderr, "error negotiating selfsend on main endpoint\n");
            err = kOTNoError;   // don't let this stop the app.
        }
        else
        {
            fprintf(stderr, "selfsend enabled\n");
            if (err == 0)
                fprintf(stderr, "selfsend was previously off\n");
            else
                fprintf(stderr, "selfsend was previously on\n");
        }
    }
 
#if HANDOFF_EP
#else
    // don't need EOM option
    if (err == kOTNoError)
    {
 
            // turn on the EOM option
        err = DoNegotiateEOMOption(myEp->ep, true);
        if (err != kOTNoError)
        {
            ClrEOMOnFlag(myEp->flags);
            fprintf(stderr, "\n\nError setting EOM option!\n");
        }
        else
        {
            SetEOMOnFlag(myEp->flags);
        }
    }
#endif
    
    if (err == kOTNoError)
    {
        //
        // Install notifier we're going to use for testing - we do this before
        //      binding - at this point, we're still synchronous
        //
        err = OTInstallNotifier(myEp->ep, EventHandler, (void*)myEp);
        if ( err != kOTNoError )
        {
            fprintf(stderr, "ERROR: InstallNotifier() failed with %ld\n for server endpoint", err);
        }
    }
        
    if (err == kOTNoError)
    {
 
        /*-------------------------------------------------------------------------
        Prepare to Bind the endpoint by getting the NBP Name
        ------------------------------------------------------------------------- */
 
        /*-------------------------------------------------------------------------
        Bind the endpoint endpoint.
        ------------------------------------------------------------------------- */
 
        err = DoBind(myEp->ep, kDynamicSocket, kATPType, 2, (char*)gServerNBPNameStr, clen((char*)gServerNBPNameStr));
        if ( err != kOTNoError )
        {
            fprintf(stderr, "PAPSample: bind of server endpoint failed.\n");
        }
        else
        {
            SetEPBoundFlag(myEp->flags);
            OTSetAsynchronous(myEp->ep);
        }
    }
 
    /*-------------------------------------------------------------------------
    Set the server status string
    ------------------------------------------------------------------------- */
    if (err == kOTNoError)
        BeginSetServerStatusOption(myEp->ep, kSetIdleStr);
    
    
    
    return err;
}
 
/*******************************************************************************
** InitMyEndpointRef function - zero out the MyEndpointRef structure
**  Course we could use the memset function here.
********************************************************************************/
 
OSStatus InitMyEndpointRef(MyEndpointRef *myEp)
{
    myEp->ep = nil;
    myEp->flags = 0;
    myEp->usedQ = OTAllocMem(sizeof(OTLIFO));
    if (myEp->usedQ == nil)
        return memFullErr;
    
    myEp->usedQ->fHead = nil;       // no packets with this endpoint.
    
    return kOTNoError;
}
 
 
OSStatus InitPAPBuffers(void)
{
    PacketPtr       packetPtr;
    OSStatus        err = kOTNoError;
    short           i;
    
        // allocate memory for the freeQ
    gFreeQ = OTAllocMem(sizeof(OTLIFO));
    if (gFreeQ == nil)
        return memFullErr;
        
    /* set up the free queue */
    gFreeQ->fHead = nil;
    
        // allocate a temp buffer to be used when packet data overlaps.
    gTempPackPtr = OTAllocMem(sizeof(PacketBuffer) + kPAPDataSize); 
    if (gTempPackPtr == nil)
        err = memFullErr;
 
    /* enqueue the packet buffer records to the free queue */
    for (i=0; (i<kNumBuffers) && (err == kOTNoError); i++)
    {
            // before calling OTAllocMem, we must have already have called
            // InitOpenTransport
            
        packetPtr = OTAllocMem(sizeof(PacketBuffer));
        if (packetPtr != nil)
        {
            packetPtr->flags = 0;
            packetPtr->fLink.fNext = nil;
            OTLIFOEnqueue(gFreeQ, &(packetPtr->fLink)); // first field is fLink field
            
        }
        else
            err = memFullErr;
    }
    
    if (err == kOTNoError)
    {
        /* show that the buffers have been init'd */
        SetBufsInitdFlag(gFlags);
 
        /* note that we don't need to worry about holding memory in the
            presence of VM.  The notifier routine will be called at
            deferred task time when it is safe to page memory
         */
        
    }
    else
    {
        fprintf(stderr, "\n\nError allocating receive buffers!\n");
        fprintf(stderr, "\nBye Bye.\n");
    }
        
    return(err);
 
}
 
    
 
/*******************************************************************************
** Initialize OpenTransport and call DoTest function
********************************************************************************/
OSStatus InitPAPServerStuff(void)
{
    OSStatus    err = kOTNoError;
    UInt32      growAmt, selection;
    UInt32      i;
    char        mystr[255]; 
                
        // register ourselves with OT
    OTRegisterAsClient((OTClientName) "PAPServerSample", nil);
 
        // ask whether to alter current options
    fprintf(stdout, "\nDo you want to change the current options?");
    fprintf(stdout, "\n  Options to change 1. SetMemoryLimits");
    fprintf(stdout, "\n                    2. Dump packets");
    selection = GetYesNoOption();
    if (selection == kQuitTest)
        err = -1;
    else if (selection == kAcceptOption)
    {
            // ask whether to use the set memory limits option or not
        fprintf(stdout, "\nDo you want to set memory limits?");
        selection = GetYesNoOption();
        
        if (selection == kQuitTest)
            err = -1;
        else if (selection == kAcceptOption)
        {
            fprintf(stderr, "How much memory to grow heap ?\n");
            if (gets(mystr) != 0)
            {
                StringToNum(c2pstr(mystr), (long*)&growAmt);
                OTSetMemoryLimits(growAmt, 0);
                
            }
        }
        
        if (err == kOTNoError)
        {
                // ask whether to use the set memory limits option or not
            fprintf(stdout, "\nDo you want to just dump packets?");
            selection = GetYesNoOption();
            if (selection == kQuitTest)
                err = -1;
            else if (selection == kAcceptOption)
            {
                SetdumpPktsFlag(gFlags);
            }
        }
    }
 
    if (err == kOTNoError)
    {
 
            // load in the various strings
        GetIndString((unsigned char*)&gIdleStr, kServerStrID, kIdleStrID);
        GetIndString((unsigned char*)&gBusyStr, kServerStrID, kBusyStrID);
        GetIndString((unsigned char*)&gServerNBPNameStr, kServerStrID, kServerNBPStrID);
            // covert each string to c
        p2c(gIdleStr);
        p2c(gBusyStr);
        p2c(gServerNBPNameStr);
    
        // initialize the endpoint ref
        err = InitMyEndpointRef(&gEp);
    }
    
    if (err == kOTNoError)
    {
#if HANDOFF_EP
        for (i = 0; (i < kMaxHandoffEPs) && (err == kOTNoError) ; i++)
            err = InitMyEndpointRef(&gHandoffEp[i]);
#endif
    }
 
    if (err == kOTNoError)
    {
        //
        // Initialize the buffers
        //
        err = InitPAPBuffers();
    }
            
 
    if (err == kOTNoError)
    {
        //
        //  Init the server endpoint
        //
        err = ActivatePAPEndpoint(&gEp);
    }
 
#if HANDOFF_EP
    if (err == kOTNoError)
    {
        //
        //  Init the handoff endpoint
        //
        for (i = 0; (i < kMaxHandoffEPs) && (err == kOTNoError); i++)
            gHandoffEp[i].ep = OTOpenEndpoint(OTCreateConfiguration(kPAPName), (OTOpenFlags)NULL, NULL, &err);
 
        if (err != kOTNoError)
        {
            fprintf(stderr, "\n\nError opening the handoff endpoint!\n");
            fprintf(stderr, "\nBye Bye.\n");
        }
        else
        {
            for (i = 0; (i < kMaxHandoffEPs) && (err == kOTNoError); i++)
            { 
                SetEPActiveFlag(gHandoffEp[i].flags);
                    // turn on the EOM option
                err = DoNegotiateEOMOption(gHandoffEp[i].ep, true);
                if (err != kOTNoError)
                {
                    ClrEOMOnFlag(gHandoffEp[i].flags);
                    fprintf(stderr, "\n\nError setting EOM option!\n");
                }
                else
                {
                    SetEOMOnFlag(gHandoffEp[i].flags);
                }
            }
        }
    }
 
 
    if (err == kOTNoError)
    {
        for (i = 0; (i < kMaxHandoffEPs) && (err == kOTNoError); i++)
        {
            err = OTInstallNotifier(gHandoffEp[i].ep, EventHandler, (void*)&gHandoffEp[i]);
            if ( err != kOTNoError )
            {
                fprintf(stderr, "ERROR: InstallNotifier() failed with %ld\n for handoff endpoint", err);
            }
            else
                OTSetAsynchronous(gHandoffEp[i].ep);
        }
 
    }
#endif      // HANDOFF_EP   
        
    return err;
    
}
 
 
/*******************************************************************************
** ReleasePAPMemory
********************************************************************************/
 
void ReleasePAPMemory(void)
{
    PacketPtr   packetPtr;
    OTLIFO      *otLIFOPtr;
    short       i;
    
    if (gTempPackPtr)
        OTFreeMem(gTempPackPtr);
        
        // first release the freeQ memory
    otLIFOPtr = gFreeQ;
    packetPtr = (PacketPtr)OTLIFODequeue(otLIFOPtr);
    
    while (packetPtr != nil)    // while the queue is not empty
    {
                // free the packetbuffer
        OTFreeMem(packetPtr);
        packetPtr = (PacketPtr)OTLIFODequeue(otLIFOPtr);            
    }
 
#if HANDOFF_EP
    for (i = 0; i < kMaxHandoffEPs; i++)        // 
    {
        otLIFOPtr = gHandoffEp[i].usedQ;
#else
        otLIFOPtr = gEp.usedQ;
#endif
        if (otLIFOPtr->fHead)
            fprintf(stderr, "Am releasing buffer from the used queue.\n\n");
        
        packetPtr = (PacketPtr)OTLIFODequeue(otLIFOPtr);
        
        while (packetPtr != nil)    // while the queue is not empty
        {
                    // free the packetbuffer
            OTFreeMem(packetPtr);
            packetPtr = (PacketPtr)OTLIFODequeue(otLIFOPtr);            
        }
    
#if HANDOFF_EP
    }
#endif
    
    OTFreeMem(gFreeQ);
    
}
 
/*******************************************************************************
** Close endpoints, and OpenTransport 
********************************************************************************/
 
void ClosePAPServerStuff(void)
{
 
    short i;
 
    if (gEp.ep)
        OTSetSynchronous(gEp.ep);
 
    // note that we don't check whether the endpoint is still connected.  Since
    // we are leving anyway, let the UnBind call clear things up.  If we did do a
    // disconnect here, we would meed to also implement a timer mechanism. so that
    // we didn't get hung awaiting a DisconnectComplete event that might not come.
    
#if HANDOFF_EP
    for (i = 0; i < kMaxHandoffEPs; i++)
    {
 
        if (gHandoffEp[i].ep)
            OTSetSynchronous(gHandoffEp[i].ep);
        
        if (TstEPBoundFlag(gHandoffEp[i].flags))    // is the handoff endpoint bound
            OTUnbind(gHandoffEp[i].ep);         // unbind the endpoint
        
        if (gHandoffEp[i].usedQ != nil)         // free allocated memory with usedQ.
            OTFreeMem(gHandoffEp[i].usedQ);
            
        if (TstEPActiveFlag(gHandoffEp[i].flags))   // is the handoff endpoint open
            OTCloseProvider(gHandoffEp[i].ep);      // close the endpoint
            
    }
 
#endif
            
    if (TstEPBoundFlag(gEp.flags))          // is the server endpoint bound
        OTUnbind(gEp.ep);                   // unbind the endpoint
        
    if (gEp.usedQ != nil)                   // free allocated memory with usedQ.
        OTFreeMem(gEp.usedQ);
        
    if (TstEPActiveFlag(gEp.flags))         // is the server endpoint open
        OTCloseProvider(gEp.ep);            // close the endpoint
        
    // ReleasePAPMemory();                      // release memory before OT goes away
                                            // not really necessary since we are just going to 
                                            // exit after this.
 
    if (gFreeQ != nil)
        OTFreeMem(gFreeQ);
    
    if (TstOTActiveFlag(gFlags))            // is OpenTransport active
        CloseOpenTransport();               // shutdown OT services
    
}
 
//
//  NetInit:
//
//  This routine does various networking related startup tasks:
//
//  (1) it does InitOpenTransport
//  (2) it records the OT version for us.
//
//  result - true - OpenTransport init'd and the correct version is present
//           false - problem opening OT or OT version not correct
//
static Boolean NetInit()
{
    OSStatus err;
 
    err = Gestalt(gOTVersionSelector, (long*) &gOTVersion);
    if (err || (gOTVersion < kOTVersion111))
    {
        fprintf(stderr, "Please install Open Transport 1.1.1 or later");
        return false;
    }
    
    err = InitOpenTransport();
    if (err)
    {
        fprintf(stderr, "NetInit: InitOpenTransport error %d", err);
        return false;
    }
    return true;
}
 
 
/*******************************************************************************
** main function
********************************************************************************/
 
main(void) 
{
    OSStatus    osstatus;
    
    InitGraf(&qd.thePort);                  // initialize quickdraw so we can use regions
 
    fprintf(stderr, "PAPServerSample showing implementation of PAP server.\n\n");
 
    // init the flags variable
    gFlags = 0;
 
#if DO_DEBUG_LOG
    DebugStr("\p doing log;dx;log papserver;g");
#endif
 
    if (NetInit() == false)
        return 0;
    
    //
    // Initialize the OT and the global Endpoints
    //
    osstatus = InitPAPServerStuff();
    
    if (osstatus == kOTNoError)
    {
    
 
        //
        // Run the PAPServer code
        //
        DoServer();
        //
    }
    
    //
    // Close things down
    //
    ClosePAPServerStuff();
    
    CheckFileToClose();
    
    fprintf(stderr, "\n\nDone\n");
        
    return 0;
}
 
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);
    }
}
 
 
OSStatus BeginSetServerStatusOption(EndpointRef ep, UInt16 whichStr)
{
    OSStatus    err;
    unsigned char   *in = "\p._in";
    
    if (whichStr == kSetIdleStr)
    {
        err = DoSetServerStatusOption(ep, (char*)&gIdleStr);
    }
    else
    {
        err = DoSetServerStatusOption(ep, (char*)&gBusyStr);
    }
    
    if (err != kOTNoError)
    {
        fprintf(stderr, "ServerStatus option call failed\n");
        fprintf(stderr, "however, this will not keep the server from running\n");
        err = kOTNoError;
    }
    
    return err;
}
 
 
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
    {
        if (gets(selection) != 0)
        {
            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;
}
 
 
#if HANDOFF_EP
/*
    Iterate through the handoff endpoints and find one that doesn't have the busy flag
    set - return the pointer else return null
*/
MyEndpointRef*  FindFreeHandoffEp(void)
{
    short   i;
    
    for (i = 0; i < kMaxHandoffEPs; i++)
    {
        if (TstEPBusyFlag(gHandoffEp[i].flags) == false)
            return &gHandoffEp[i];
    }
    
    return nil;
}
#endif
 
Boolean EndpointsAllBusy(void)
{
    Boolean     result = true;  
#if HANDOFF_EP
    short       i;
    
    for (i = 0; (i < kMaxHandoffEPs) && (result == true); i++)
    {
        if (TstEPBusyFlag(gHandoffEp[i].flags) == false)
            result = false; // any endpoint not busy means that not all eps are busy
    }
 
#else
    result = TstEPBusyFlag(gEp.flags);
        
#endif
    
    return result;
}