/*   adsp.c,  /atalk-ii/source,  Garth Conboy,  03/30/90  */
/*   Copyright (c) 1989 by Pacer Software Inc., La Jolla, CA  */

/*   GC - Initial coding.
     GC - (04/01/90): April fools!
     GC - (05/14/90): Back to testing...
     GC - (06/11/90): Added, at PFC's request, a little better error checking
                      in incoming attention handling.
     GC - (06/12/90): In AdspPacketIn() an ATsocketClosed should be handled
                      AFTER packet defer logic has been executed.
     GC - (06/19/90): One call to AdspGetAttention will yield only one call
                      to the completion routine... must re-arm for the next
                      attention.
     GC - (06/28/90): AdspGetAttention should take a user buffer, and should
                      "complete" if the connection closes.
     GC - (06/28/90): AdspSendAttention should be async, and should "complete"
                      if the connection closes.
     GC - (08/18/90): New error logging mechanism.
     GC - (11/03/90): Some fixes from OS/2.
     GC - (07/29/91): Use AdspCloseConnection rather than RemoveConnectionEnd
                      in ProbeTimerExpired.
     GC - (08/07/91): Ack request in SendData when send window closes to zero.
     GC - (12/17/91): Corrected a couple of off-by-one errors when comparing
                      SendSeqNum with sendWindowSeqNum; could have caused
                      extra sends.  Fix from GMP at Iris.
     GC - (12/17/91): Send "ack" *after* calculating receiveSeqNum and
                      receiveWindowSize.  Fix from GMP at Iris.
     GC - (03/30/92): Updated for BufferDescriptors.
     GC - (06/27/92): All buffers coming from user space are now "opaque," they
                      may or may not be "char *."  We now use the new routines
                      MoveFromOpaque() and MoveToOpaque() to access these
                      "buffer"s rather than MemMove().
     GC - (09/02/92): When creating a new connection listener, allow an already
                      open Ddp socket to be passed in.  Changes from Nikki at
                      Microsoft.
     GC - (09/02/92): Added AdspGetAnything to have a single completion
                      routine that can handle both incoming data and incoming
                      attentions.  A GetAnything will take precidence over
                      and AdspReceive and an AdspGetAttention. Also, check
                      buffer size at entry to AspGetAttention.
     GC - (10/06/92): On an AdspSend(), try to flush the data onto the wire
                      (regardless of the setting of the flushFlag) if we've
                      completely filled our send queue.
     GC - (11/08/92): AdspSend may now complete partial sends, and will return
                      the number of bytes enqueued.
     GC - (11/15/92): Integrated Nikki's (Microsoft) changes for Adsp event
                      handler support.  See "adsp.h" and "socket.h" for more
                      information.
     GC - (11/15/92): Corrected a bug that could cause BufferQueueSize() and
                      MaxNextReadSizeOfBufferQueue() to return "-1" (or be off
                      by one of the low side) if a chunk in a BufferQueue was
                      completly processed (i.e. "startIndex is (dataSize +
                      endOfMessage)").  This would eventually cause recoverable
                      BufferQueue corruption.  Tracked down with the help of
                      Eric Smith at Telebit.
     GC - (11/15/92): Added support for incoming attention event handlers.
     GC - (11/15/92): Added AdspSetCookieForConnection() and
                      AdspGetCookieForConnection().
     GC - (11/15/92): A AdspGetAnything handler will now return the attention
                      code as the first two bytes of the buffer rather than
                      as a unqiue argument.
     GC - (11/17/92): Added AdspCancelGetConnectionRequest();
     GC - (11/24/92): Added "send okay event" support.
     GC - (12/10/92): Added AdspPeek(), a rather cool idea from Microsoft
                      (Nikki).

     *** Make the PVCS source control system happy:
     $Header$
     $Log$
     ***

     The Adsp portion of PacerTalk.

*/

#define IncludeAdspErrors 1

#include "atalk.h"

#define VerboseMessages 0              /* 0 for quiet */
#define DebugCode       1              /* 0 for none */

                   /* ************************************ */
                   /* *** Static data and definitions. *** */
                   /* ************************************ */

#if not defined(DeferAdspPackets)
  /* Deferred ADSP packet queue: */

  #define MaximumDeferredPackets 10
  typedef struct dp { struct dp far *next;
                      Boolean forConnectionListener;
                      AppleTalkErrorCode errorCode;
                      long unsigned userData;
                      int port;
                      AppleTalkAddress source;
                      long destinationSocket;
                      short datagramLength;
                      AppleTalkAddress actualDestination;
                      char datagram[1];
                    } far *DeferredPacket;
  static volatile DeferredPacket headOfDeferredPacketList = empty;
  static volatile DeferredPacket tailOfDeferredPacketList = empty;
  static volatile short currentDeferredPacketCount = 0;

  static volatile short handleAdspPacketsNesting = 0;
  static volatile short deferIncomingAdspPacketsCount = 0;
#endif

static AppleTalkAddress unknownAddress;

                   /* ********************************* */
                   /* *** Internal static routines. *** */
                   /* ********************************* */

ExternForVisibleFunction IncomingDdpHandler AdspPacketIn;
ExternForVisibleFunction IncomingDdpHandler AdspConnectionListenerPacketIn;

ExternForVisibleFunction long MaxSendSize(ConnectionEnd connectionEnd);

ExternForVisibleFunction Boolean UnsignedBetweenWithWrap(long unsigned low,
                                                         long unsigned high,
                                                         long unsigned target);

ExternForVisibleFunction Boolean UnsignedGreaterWithWrap(long unsigned high,
                                                         long unsigned low);

ExternForVisibleFunction void
     DecodeAdspHeader(char far *datagram,
                      short unsigned far *connectionId,
                      long unsigned far *firstByteSeqNum,
                      long unsigned far *nextReceiveSeqNum,
                      long far *receiveWindowSize,
                      int far *descriptor);

ExternForVisibleFunction void BuildAdspHeader(ConnectionEnd connectionEnd,
                                              char *datagram,
                                              int descriptor);

ExternForVisibleFunction void SendOpenControl(ConnectionEnd connectionEnd);

ExternForVisibleFunction void SendAttention(ConnectionEnd connectionEnd);

ExternForVisibleFunction void SendData(ConnectionEnd connectionEnd);

ExternForVisibleFunction AppleTalkErrorCode
                              ReadData(ConnectionEnd connectionEnd,
                                       Boolean peekOnly,
                                       long far *peekBytesReturned,
                                       Boolean far *peekEndOfMessage);

ExternForVisibleFunction TimerHandler OpenTimerExpired;
ExternForVisibleFunction TimerHandler SendAttentionTimerExpired;
ExternForVisibleFunction TimerHandler ProbeTimerExpired;
ExternForVisibleFunction TimerHandler RetransmitTimerExpired;
ExternForVisibleFunction TimerHandler ForwardResetTimerExpired;

ExternForVisibleFunction void RemoveConnectionEnd(ConnectionEnd target);

ExternForVisibleFunction short unsigned
                   GetNextConnectionIdForSocket(long socket);

ExternForVisibleFunction long GetNextRefNum(void);

ExternForVisibleFunction ConnectionListenerInfo
                   FindConnectionListenerByRefNum(long refNum);

ExternForVisibleFunction ConnectionEnd
              FindConnectionEndByLocalInfo(long socket,
                                           short unsigned connectionId);

ExternForVisibleFunction ConnectionEnd
              FindConnectionEndByRemoteInfo(AppleTalkAddress remoteAddress,
                                            short unsigned remoteConnectionId);

ExternForVisibleFunction ConnectionEnd FindConnectionEndByRefNum(long refNum);

ExternForVisibleFunction Boolean
        AddToBufferQueue(BufferQueue far *bufferQueue, void far *data,
                         long offset, long dataSize, Boolean dataIsOpaque,
                         Boolean endOfMessage, BufferQueue *auxBufferQueue);

ExternForVisibleFunction long
     ReadFromBufferQueue(BufferQueue far *bufferQueue, void far *data,
                         long offset, long dataSize, Boolean dataIsOpaque,
                         Boolean far *endOfMessage);

ExternForVisibleFunction long
     ReadAndDiscardFromBufferQueue(BufferQueue far *bufferQueue,
                                   void far *data,
                                   long offset, long dataSize,
                                   Boolean dataIsOpaque,
                                   Boolean far *endOfMessage);

ExternForVisibleFunction Boolean
        DiscardFromBufferQueue(BufferQueue far *bufferQueue, long dataSize,
                               BufferQueue far *auxBufferQueue);

ExternForVisibleFunction long
     MaxNextReadSizeOfBufferQueue(BufferQueue far *bufferQueue,
                                  Boolean far *endOfMessage);

ExternForVisibleFunction long BufferQueueSize(BufferQueue far *bufferQueue);

ExternForVisibleFunction void FreeBufferQueue(BufferQueue far *bufferQueue);

ExternForVisibleFunction char far
     *GetLookaheadPointer(BufferQueue far *bufferQueue, long far *size);

#if not defined(DeferAdspPackets)
  ExternForVisibleFunction void DeferAdspPackets(void);
  ExternForVisibleFunction void HandleAdspPackets(void);
#endif

                        /* ************************** */
                        /* *** External routines. *** */
                        /* ************************** */

AppleTalkErrorCode far AdspSetWindowSizes(
  long newSendWindow,             /* Max send window in bytes. */
  long newReceiveWindow)          /* Max receive window in bytes. */
{
  /* Validate args... */

  if (newSendWindow < 0 or
      newReceiveWindow < 0 or
      newSendWindow > MaxSendReceiveWindowSize or
      newReceiveWindow > MaxSendReceiveWindowSize)
     return(ATadspBadWindowSize);

  /* Set new values; zero means use default. */

  if (newSendWindow is 0)
     newSendWindow = DefaultSendReceiveWindowSize;
  if (newReceiveWindow is 0)
     newReceiveWindow = DefaultSendReceiveWindowSize;

  maxSendWindowSize = newSendWindow;
  maxReceiveWindowSize = newReceiveWindow;
  return(ATnoError);

}  /* AdspSetWindowSizes */

AppleTalkErrorCode far AdspCreateConnectionListener(
  int port,                                 /* What port? */
  ExtendedAppleTalkNodeNumber *desiredNode, /* On specified node? */
  long  existingDdpSocket,                  /* If >= 0, existing ddp socket to use; -1
                                                to open a new one. */
  int desiredSocket,                        /* Specified socket; 0 = dynamic; ignored if
                                                above arg is -1. */
  long far *listenerRefNum,                 /* New connection listener ref num. */
  long far *socketHandle,                   /* If non-empty, the socket that we've pened. */
  AdspConnectionEventHandler far *eventHandler,
                                            /* Optional incoming connections
                                               event handler. */
  long unsigned eventContext)               /* Context for above. */
{
  AppleTalkErrorCode returnCode;
  long socket;
  ConnectionListenerInfo connectionListenerInfo;

  /* Okay, set up to create the session listener node... privacy please! */

  DeferTimerChecking();
  DeferAdspPackets();

  /* Find a new connectionListenerRefNum */

  while (True)
  {
     for (connectionListenerInfo = connectionListenerList;
          connectionListenerInfo isnt empty;
          connectionListenerInfo = connectionListenerInfo->next)
        if (connectionListenerInfo->connectionListenerRefNum is
            nextConnectionListenerRefNum)
           break;
     if (connectionListenerInfo is empty)
        break;
     nextConnectionListenerRefNum += 1;
  }
  *listenerRefNum = nextConnectionListenerRefNum;
  nextConnectionListenerRefNum += 1;

  /* Can we open a socket for the listener? */

  if (existingDdpSocket >= 0)
  {
     /* Set the handler for this socket to be the Adsp handler. */

     socket = existingDdpSocket;
     returnCode = NewHandlerForSocket(socket, AdspConnectionListenerPacketIn,
                                      (long unsigned)*listenerRefNum, False);
  }
  else
     returnCode = OpenSocketOnNode(&socket, port, desiredNode, desiredSocket,
                                   AdspConnectionListenerPacketIn,
                                   (long unsigned)*listenerRefNum, False,
                                   empty, 0, empty);

  if (returnCode < ATnoError)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(returnCode);
  }

  if (socketHandle isnt empty)
     *socketHandle = socket;

  /* Okay, build a new connectionListenerInfo node. */

  if ((connectionListenerInfo =
       Calloc(sizeof(*connectionListenerInfo), 1)) is empty)
  {
     if (existingDdpSocket < 0)
        CloseSocketOnNode(socket, Empty, (long)0);
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(AToutOfMemory);
  }
  connectionListenerInfo->connectionListenerRefNum = *listenerRefNum;
  connectionListenerInfo->socket = socket;
  connectionListenerInfo->closeSocket = (existingDdpSocket < 0);

  /* Set the event handler info. */

  connectionListenerInfo->connectionEventHandler = eventHandler;
  connectionListenerInfo->connectionEventContext = eventContext;

  connectionListenerInfo->next = connectionListenerList;
  connectionListenerList = connectionListenerInfo;

  /* All set. */

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* AdspCreateConnectionListener */

AppleTalkErrorCode far AdspDeleteConnectionListener(
  long listenerRefNum)            /* Connection listener to delete. */
{
  ConnectionListenerInfo connectionListenerInfo;

  /* Can we find the guy? */

  DeferTimerChecking();
  DeferAdspPackets();

  connectionListenerInfo = FindConnectionListenerByRefNum(listenerRefNum);
  if (connectionListenerInfo is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnectionListener);
  }

  /* Closing the socket will cause an ATsocketClosed to arrive at
     AdspConnectionListenerPacketIn() and he will free up all of our
     resources. */

  if (connectionListenerInfo->closeSocket)
     CloseSocketOnNode(connectionListenerInfo->socket, Empty, (long)0);
  else
  {
     AppleTalkAddress dummy;

     HandleAdspPackets();
     HandleDeferredTimerChecks();
     AdspConnectionListenerPacketIn(ATsocketClosed,
                                    (long unsigned)listenerRefNum, 0, dummy,
                                    0, DdpProtocolAdsp, Empty, 0, dummy);
     return(ATnoError);
  }

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* AdspDeleteConnectionListener */

AppleTalkErrorCode far AdspSetConnectionEventHandler(
  long listenerRefNum,                      /* Connection listener ref num. */
  AdspConnectionEventHandler eventHandler,  /* Incoming connections event
                                               handler. */
  long unsigned eventContext)               /* Context for above. */
{
  ConnectionListenerInfo connectionListenerInfo;

  DeferTimerChecking();
  DeferAdspPackets();
  if ((connectionListenerInfo = FindConnectionListenerByRefNum(listenerRefNum))
      is Empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnectionListener);
  }

  /* Set the event handler info. */

  EnterCriticalSection();
  connectionListenerInfo->connectionEventHandler = eventHandler;
  connectionListenerInfo->connectionEventContext = eventContext;
  LeaveCriticalSection();

  /* All set. */

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* AdspSetConnectionEventHandler */

AppleTalkErrorCode far AdspGetConnectionRequest(
  long listenerRefNum,            /* Connection listener to get request from */
  long far *getConnectionRequestRefNum,
                                  /* RefNum for this get connection request;
                                     so we can cancel it. */
  AdspIncomingOpenRequestHandler *completionRoutine,
                                  /* Routine to call with the request */
  long unsigned userData)         /* To be passed to above routine */
{
  ConnectionListenerInfo connectionListenerInfo;
  OpenRequestHandler openRequestHandler;
  long refNum;
  Boolean match;

  /* Register a handler for an incoming open request, when a open request comes
     in the completionRoutine will be called with the following arguments:

         errorCode - AppleTalkErrorCode; status of operation.
         userData  - long unsigned; as passed to us.
         source    - AppleTalkAddress; source of the open request.
         listenerRefNum
                   - long; as passed to us - the connection listener that the
                     open request has come in on.
         refNum    - long; refNum for connection if it is accepted; this value
                     must be passed to AdspAcceptConnectionRequest() and
                     AdspDenyConnectionRequest() to identify the request.
  */

  /* Find our connection listener */

  DeferTimerChecking();
  DeferAdspPackets();

  connectionListenerInfo = FindConnectionListenerByRefNum(listenerRefNum);
  if (connectionListenerInfo is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnectionListener);
  }

  /* Find a new get connection request ref num. */

  match = True;
  while(match)
  {
     match = False;
     for (openRequestHandler = connectionListenerInfo->openRequestHandlers;
          not match and openRequestHandler isnt Empty;
          openRequestHandler = openRequestHandler->next)
        if (openRequestHandler->getConnectionRequestRefNum is
            nextGetConnectionRequestRefNum)
           match = True;
     if (not match)
        break;
     nextGetConnectionRequestRefNum += 1;
  }
  refNum = nextGetConnectionRequestRefNum;
  nextGetConnectionRequestRefNum += 1;
  if (getConnectionRequestRefNum isnt Empty)
     *getConnectionRequestRefNum = refNum;

  /* Build a new open request handler node. */

  if ((openRequestHandler = Calloc(sizeof(*openRequestHandler), 1)) is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(AToutOfMemory);
  }

  /* Copy in the info, thread into the listener request handler list, and
     we're set! */

  openRequestHandler->getConnectionRequestRefNum = refNum;
  openRequestHandler->completionRoutine = completionRoutine;
  openRequestHandler->userData = userData;
  openRequestHandler->next = connectionListenerInfo->openRequestHandlers;
  connectionListenerInfo->openRequestHandlers = openRequestHandler;

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* AdspGetConnectionRequest */

AppleTalkErrorCode far AdspCancelGetConnectionRequest(
  long listenerRefNum,            /* Connection listener on which to cancel
                                     a GetConnectionRequest. */
  long getConnectionRequestRefNum)
                                  /* Request handler to cancel. */
{
  ConnectionListenerInfo connectionListenerInfo;
  OpenRequestHandler openRequestHandler, previousOpenRequestHandler;

  /* Find our connection listener */

  DeferTimerChecking();
  DeferAdspPackets();

  connectionListenerInfo = FindConnectionListenerByRefNum(listenerRefNum);
  if (connectionListenerInfo is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnectionListener);
  }

  /* Find the specified GetConnectionRequest. */

  for (previousOpenRequestHandler = Empty,
           openRequestHandler = connectionListenerInfo->openRequestHandlers;
       openRequestHandler isnt Empty;
       previousOpenRequestHandler = openRequestHandler,
           openRequestHandler = openRequestHandler->next)
     if (openRequestHandler->getConnectionRequestRefNum is
         getConnectionRequestRefNum)
        break;

  /* We better have found one, and he better not be in use. */

  if (openRequestHandler is Empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchGetConnectionReq);
  }
  if (openRequestHandler->inUse)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspGetConnectionRequestInUse);
  }

  /* Okay, remove our target. */

  if (previousOpenRequestHandler is Empty)
     connectionListenerInfo->openRequestHandlers = openRequestHandler->next;
  else
     previousOpenRequestHandler->next = openRequestHandler->next;
  Free(openRequestHandler);

  /* All set. */

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* AdspCancelGetConnectionRequest */

AppleTalkErrorCode AdspDenyConnectionRequest(
  long listenerRefNum,            /* Listener that to open request that we're
                                     denying was targeted at. */
  long refNum)                    /* The request that we're denying. */
{
  ConnectionListenerInfo connectionListenerInfo;
  OpenRequestHandler previousOpenRequestHandler, openRequestHandler;
  ConnectionEnd connectionEnd;

  /* Find the listener and the inUse handler. */

  DeferTimerChecking();
  DeferAdspPackets();

  connectionListenerInfo = FindConnectionListenerByRefNum(listenerRefNum);
  if (connectionListenerInfo is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnectionListener);
  }
  for (previousOpenRequestHandler = empty,
              openRequestHandler = connectionListenerInfo->openRequestHandlers;
       openRequestHandler isnt empty;
       previousOpenRequestHandler = openRequestHandler,
              openRequestHandler = openRequestHandler->next)
     if (openRequestHandler->inUse and
         openRequestHandler->refNum is refNum)
        break;
  if (openRequestHandler is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnection);
  }

  /* Go ahead and unthread the open request handler -- we'll free it before we
     flee. */

  if (previousOpenRequestHandler is empty)
     connectionListenerInfo->openRequestHandlers = openRequestHandler->next;
  else
     previousOpenRequestHandler->next = openRequestHandler->next;

  /* Okay, I admit it, this is going to sound a little stange:  We want to set
     up a dummy connection end so that we can use SendOpenControl() to really
     send the deny request -- we pretend for this single send that "this" end
     of the connection is really the connection lister.  Yes, it's a little bit
     easier this way. */


  if ((connectionEnd = Calloc(sizeof(*connectionEnd), 1)) is empty)
  {
     Free(openRequestHandler);
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(AToutOfMemory);
  }
  connectionEnd->connectionState = AdspClosed;    /* This will cause deny */
  connectionEnd->refNum = openRequestHandler->refNum;
  connectionEnd->socket = connectionListenerInfo->socket;
  connectionEnd->remoteConnectionId = openRequestHandler->remoteConnectionId;
  connectionEnd->remoteAddress = openRequestHandler->remoteAddress;
  connectionEnd->seenRemoteOpenRequest = True;

  /* Okay, deny the connection. */

  SendOpenControl(connectionEnd);
  Free(connectionEnd);

  /* We're set now. */

  Free(openRequestHandler);
  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* AdspDenyConnectionRequest */

AppleTalkErrorCode AdspAcceptConnectionRequest(
  long listenerRefNum,            /* Listener that to open request that we're
                                     accepting was targeted at. */
  long refNum,                    /* The request that we're accepting. */
  long far *socketHandle,         /* >= 0 if new Adsp session should be
                                     opened on specified handle; < 0 or
                                     empty ignored as input; non-empty
                                     socket handle used will be returned. */
  int port,                       /* If new socket, what port? */
  ExtendedAppleTalkNodeNumber *desiredNode,
                                  /* If new socket, on specified node? */
  int desiredSocket,              /* If new socket, 0 = dynamic. */
  AdspOpenCompleteHandler *completionRoutine,
                                  /* Who to call when open completes. */
  long unsigned userData,         /* User data to pass to above routine. */
  AdspReceiveEventHandler far *receiveEventHandler,
                                  /* Optional receive data event handler. */
  long unsigned receiveEventContext,
                                  /* Context to pass to above. */
  AdspReceiveAttnEventHandler far *receiveAttentionEventHandler,
                                  /* Optional receive attention event handler. */
  long unsigned receiveAttentionEventContext,
                                  /* Context for the above. */
  AdspSendOkayEventHandler far *sendOkayEventHandler,
                                  /* Send window now non-zero event handler. */
  long unsigned sendOkayEventContext,
                                  /* Context for the above. */
  AdspDisconnectEventHandler far *disconnectEventHandler,
                                  /* Optional disconnect event handler. */
  long unsigned disconnectEventContext)
                                  /* Context for the above. */
{
  ConnectionListenerInfo connectionListenerInfo;
  OpenRequestHandler previousOpenRequestHandler, openRequestHandler;
  ConnectionEnd connectionEnd;
  long ourSocket;
  short unsigned connectionId;
  long index;
  AppleTalkErrorCode errorCode;
  AppleTalkAddress dummyAddress;
  Boolean closeSocket = True;

  /* Find the listener and the inUse handler. */

  DeferTimerChecking();
  DeferAdspPackets();

  connectionListenerInfo = FindConnectionListenerByRefNum(listenerRefNum);
  if (connectionListenerInfo is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnectionListener);
  }
  for (previousOpenRequestHandler = empty,
              openRequestHandler = connectionListenerInfo->openRequestHandlers;
       openRequestHandler isnt empty;
       previousOpenRequestHandler = openRequestHandler,
              openRequestHandler = openRequestHandler->next)
     if (openRequestHandler->inUse and
         openRequestHandler->refNum is refNum)
        break;
  if (openRequestHandler is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnection);
  }

  /* Go ahead and unthread the open request handler -- we'll free it before we
     flee. */

  if (previousOpenRequestHandler is empty)
     connectionListenerInfo->openRequestHandlers = openRequestHandler->next;
  else
     previousOpenRequestHandler->next = openRequestHandler->next;

  /* If socket handle is valid, it be must a currently open.  If it's already
     an Adsp socket, we replicate the "closeSocket" info, if it's a Ddp
     socket we set "closeSocket" to False. */

  if (socketHandle isnt empty and *socketHandle >= 0)
  {
     /* First check to see if it's already an Adsp socket. */

     CheckMod(index, *socketHandle, NumberOfConnectionEndHashBkts,
              "AdspAcceptConnectionRequest");
     for (connectionEnd = connectionEndLocalHashBuckets[index];
          connectionEnd isnt empty;
          connectionEnd = connectionEnd->nextByLocalInfo)
        if (connectionEnd->socket is *socketHandle)
           break;
     if (connectionEnd isnt Empty)
        closeSocket = connectionEnd->closeSocket;
     else
     {
        /* Now check to make sure it's a Ddp socket... if so, switch handlers
           to Adsp. */

        if ((errorCode = MapSocketToAddress(*socketHandle, &dummyAddress)) isnt
            ATnoError or
           (errorCode = NewHandlerForSocket(*socketHandle, AdspPacketIn,
                                             (long)0, False)) isnt ATnoError)
        {
           Free(openRequestHandler);
           HandleAdspPackets();
           HandleDeferredTimerChecks();
           return(errorCode);
        }
        closeSocket = False;
     }
     ourSocket = *socketHandle;
  }
  else
  {
     /* Otherwise use the requested "new socket info" to open one. */

     if ((errorCode = OpenSocketOnNode(&ourSocket, port, desiredNode,
                                       desiredSocket, AdspPacketIn,
                                       (long)0, False, empty, 0,
                                       empty)) isnt ATnoError)
     {
        Free(openRequestHandler);
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        return(errorCode);
     }
  }
  if (socketHandle isnt empty)
     *socketHandle = ourSocket;

  /* Find a local connectionId. */

  if ((connectionId = GetNextConnectionIdForSocket(ourSocket)) is 0)
  {
     if (closeSocket)
        CloseSocketOnNode(ourSocket, Empty, (long)0);
     Free(openRequestHandler);
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATinternalError);
  }

  /* Okay, start building a connection end. */

  if ((connectionEnd = Calloc(sizeof(*connectionEnd), 1)) is empty)
  {
     if (closeSocket)
        CloseSocketOnNode(ourSocket, Empty, (long)0);
     Free(openRequestHandler);
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(AToutOfMemory);
  }

  /* Fill in the skeleton, so that, as least, we can use RemoveConnectionEnd
     if things go wrong from here on in. */

  connectionEnd->connectionState = AdspHalfOpen;
  connectionEnd->passiveOpen = True;
  connectionEnd->refNum = refNum;
  connectionEnd->connectionId = connectionId;
  connectionEnd->socket = ourSocket;
  connectionEnd->closeSocket = closeSocket;
  connectionEnd->openCompletionRoutine = completionRoutine;
  connectionEnd->openUserData = userData;
  connectionEnd->receiveWindowSize = maxReceiveWindowSize;
  connectionEnd->sendQueueMax = maxSendWindowSize;
  connectionEnd->receiveQueueMax = maxReceiveWindowSize;

  /* Setup the event handlers - they could all be empty. */

  connectionEnd->receiveEventHandler = receiveEventHandler;
  connectionEnd->receiveEventContext = receiveEventContext;
  connectionEnd->receiveAttentionEventHandler = receiveAttentionEventHandler;
  connectionEnd->receiveAttentionEventContext = receiveAttentionEventContext;
  connectionEnd->sendOkayEventHandler = sendOkayEventHandler;
  connectionEnd->sendOkayEventContext = sendOkayEventContext;
  connectionEnd->disconnectEventHandler = disconnectEventHandler;
  connectionEnd->disconnectEventContext = disconnectEventContext;

  /* Use the information stored in our open request handler to build the
     remote side of the connection end. */

  connectionEnd->seenRemoteOpenRequest = True;
  connectionEnd->remoteAddress = openRequestHandler->remoteAddress;
  connectionEnd->remoteConnectionId = openRequestHandler->remoteConnectionId;
  connectionEnd->sendSeqNum = openRequestHandler->remoteNextReceiveSeqNum;
  connectionEnd->retransmitSeqNum = openRequestHandler->remoteNextReceiveSeqNum;
  connectionEnd->sendWindowSeqNum =
                   openRequestHandler->remoteNextReceiveSeqNum +
                   (long unsigned)openRequestHandler->remoteReceiveWindowSize -
                   (long unsigned)1;
  connectionEnd->receiveAttentionSeqNum =
                   openRequestHandler->receiveAttentionSeqNum;

  /* Start our open timer, and send our first ReqAck... */

  connectionEnd->openTimerId = StartTimer(OpenTimerExpired,
                                          AdspOpenIntervalSeconds,
                                          sizeof(connectionEnd->refNum),
                                          (char *)&connectionEnd->refNum);
  SendOpenControl(connectionEnd);

  /* Thread this guy into the two of the three lookup tables... we'll fill
     in the RemoteInfo lookup table when we actually get the Ack. */

  CheckMod(index, connectionEnd->refNum, NumberOfConnectionEndHashBkts,
           "AdspAcceptConnectionRequest");
  connectionEnd->next = connectionEndRefNumHashBuckets[index];
  connectionEndRefNumHashBuckets[index] = connectionEnd;
  CheckMod(index, connectionEnd->socket, NumberOfConnectionEndHashBkts,
           "AdspAcceptConnectionRequest");
  connectionEnd->nextByLocalInfo = connectionEndLocalHashBuckets[index];
  connectionEndLocalHashBuckets[index] = connectionEnd;

  /* Such a deal! */

  Free(openRequestHandler);
  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* AdspAcceptConnectionRequest */

AppleTalkErrorCode far AdspOpenConnectionOnNode(
  AdspOpenType openType,          /* Active or passive */
  long far *socketHandle,         /* >= 0 if new Adsp session should be
                                     opened on specified handle; < 0 or
                                     empty ignored as input; non-empty
                                     socket handle used will be returned. */
  int port,                       /* If new socket, what port? */
  ExtendedAppleTalkNodeNumber *desiredNode,
                                  /* If new socket, on specified node? */
  int desiredSocket,              /* If new socket, 0 = dynamic. */
  AppleTalkAddress remoteAddress, /* Target of open (ignored for passive) */
  long far *refNum,               /* New connection end refNum. */
  AdspOpenCompleteHandler *completionRoutine,
                                  /* Who to call when open completes. */
  long unsigned userData,         /* User data to pass to above routine. */
  AdspReceiveEventHandler far *receiveEventHandler,
                                  /* Optional receive data event handler. */
  long unsigned receiveEventContext,
                                  /* Context to pass to above. */
  AdspReceiveAttnEventHandler far *receiveAttentionEventHandler,
                                  /* Optional receive attention event handler. */
  long unsigned receiveAttentionEventContext,
                                  /* Context for the above. */
  AdspSendOkayEventHandler far *sendOkayEventHandler,
                                  /* Send window now non-zero event handler. */
  long unsigned sendOkayEventContext,
                                  /* Context for the above. */
  AdspDisconnectEventHandler far *disconnectEventHandler,
                                  /* Optional disconnect event handler. */
  long unsigned disconnectEventContext)
                                  /* Context for the above. */
{
  /* Start an Adsp open to the specifed remote address, when complete call
     the completion routine with the following arguments:

         errorCode     - AppleTalkErrorCode; Completion status.
         userData      - long unsigned; as passed to us.
         refNum        - long; refNum of connection (as returned in *refNum).
         socket        - long; socket on which connection is now open.
         remoteAddress - AppleTalkAddress; real remote address.
  */

  long index;
  long ourSocket;
  ConnectionEnd connectionEnd;
  short unsigned connectionId;
  AppleTalkErrorCode errorCode;
  AppleTalkAddress dummyAddress;
  Boolean closeSocket = True;

  /* Privacy please. */

  DeferTimerChecking();
  DeferAdspPackets();

  /* If socket handle is valid, it be must a currently open.  If it's already
     an Adsp socket, we replicate the "closeSocket" info, if it's a Ddp
     socket we set "closeSocket" to False. */

  if (socketHandle isnt empty and *socketHandle >= 0)
  {
     /* First check to see if it's already an Adsp socket. */

     CheckMod(index, *socketHandle, NumberOfConnectionEndHashBkts,
              "AdspAcceptConnectionRequest");
     for (connectionEnd = connectionEndLocalHashBuckets[index];
          connectionEnd isnt empty;
          connectionEnd = connectionEnd->nextByLocalInfo)
        if (connectionEnd->socket is *socketHandle)
           break;
     if (connectionEnd isnt Empty)
        closeSocket = connectionEnd->closeSocket;
     else
     {
        /* Now check to make sure it's a Ddp socket... if so, switch handlers
           to Adsp. */

        if ((errorCode = MapSocketToAddress(*socketHandle, &dummyAddress)) isnt
            ATnoError or
           (errorCode = NewHandlerForSocket(*socketHandle, AdspPacketIn,
                                            (long)0, False)) isnt ATnoError)
        {
           HandleAdspPackets();
           HandleDeferredTimerChecks();
           return(errorCode);
        }
        closeSocket = False;
     }
     ourSocket = *socketHandle;
  }
  else
  {
     /* Otherwise use the requested "new socket info" to open one. */

     if ((errorCode = OpenSocketOnNode(&ourSocket, port, desiredNode,
                                       desiredSocket, AdspPacketIn,
                                       (long)0, False, empty, 0,
                                       empty)) isnt ATnoError)
     {
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        return(errorCode);
     }
  }
  if (socketHandle isnt empty)
     *socketHandle = ourSocket;

  /* Find a new refNum and connectionId. */

  if ((*refNum = GetNextRefNum()) < 0 or
      (connectionId = GetNextConnectionIdForSocket(ourSocket)) is 0)
  {
     if (closeSocket)
        CloseSocketOnNode(ourSocket, Empty, (long)0);
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATinternalError);
  }

  /* Okay, start building a connection end. */

  if ((connectionEnd = Calloc(sizeof(*connectionEnd), 1)) is empty)
  {
     if (closeSocket)
        CloseSocketOnNode(ourSocket, Empty, (long)0);
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(AToutOfMemory);
  }

  /* Fill in the skeleton, so that, as least, we can use RemoveConnectionEnd
     if things go wrong from here on in. */

  connectionEnd->connectionState = AdspHalfOpen;
  if (openType is AdspPassiveOpen)
     connectionEnd->passiveOpen = True;
  connectionEnd->refNum = *refNum;
  connectionEnd->connectionId = connectionId;
  connectionEnd->socket = ourSocket;
  connectionEnd->closeSocket = closeSocket;
  if (openType is AdspActiveOpen)
     connectionEnd->remoteAddress = remoteAddress;
  connectionEnd->openCompletionRoutine = completionRoutine;
  connectionEnd->openUserData = userData;
  connectionEnd->receiveWindowSize = maxReceiveWindowSize;
  connectionEnd->sendQueueMax = maxSendWindowSize;
  connectionEnd->receiveQueueMax = maxReceiveWindowSize;

  /* Setup the event handlers - they could all be empty. */

  connectionEnd->receiveEventHandler = receiveEventHandler;
  connectionEnd->receiveEventContext = receiveEventContext;
  connectionEnd->receiveAttentionEventHandler = receiveAttentionEventHandler;
  connectionEnd->receiveAttentionEventContext = receiveAttentionEventContext;
  connectionEnd->sendOkayEventHandler = sendOkayEventHandler;
  connectionEnd->sendOkayEventContext = sendOkayEventContext;
  connectionEnd->disconnectEventHandler = disconnectEventHandler;
  connectionEnd->disconnectEventContext = disconnectEventContext;

  /* Thread this guy into the two lookup tables that we know enough about
     now (by refNum and by localInfo). */

  CheckMod(index, connectionEnd->refNum, NumberOfConnectionEndHashBkts,
           "AdspOpenConnectionOnNode");
  connectionEnd->next = connectionEndRefNumHashBuckets[index];
  connectionEndRefNumHashBuckets[index] = connectionEnd;
  CheckMod(index, connectionEnd->socket, NumberOfConnectionEndHashBkts,
           "AdspOpenConnectionOnNode");
  connectionEnd->nextByLocalInfo = connectionEndLocalHashBuckets[index];
  connectionEndLocalHashBuckets[index] = connectionEnd;

  /* Okay, send the first open request and start the retry timer. */

  if (openType is AdspActiveOpen)
  {
     SendOpenControl(connectionEnd);
     connectionEnd->openTimerId = StartTimer(OpenTimerExpired,
                                             AdspOpenIntervalSeconds,
                                             sizeof(connectionEnd->refNum),
                                             (char *)&connectionEnd->refNum);
  }
  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* AdspOpenConnectionOnNode */

AppleTalkErrorCode AdspSetDataEventHandlers(
  long refNum,                    /* The request that we're accepting. */
  AdspReceiveEventHandler far *receiveEventHandler,
                                  /* Optional receive data event handler. */
  long unsigned receiveEventContext,
                                  /* Context to pass to above. */
  AdspReceiveAttnEventHandler far *receiveAttentionEventHandler,
                                  /* Optional receive attention event handler. */
  long unsigned receiveAttentionEventContext,
                                  /* Context for the above. */
  AdspSendOkayEventHandler far *sendOkayEventHandler,
                                  /* Send window now non-zero event handler. */
  long unsigned sendOkayEventContext,
                                  /* Context for the above. */
  AdspDisconnectEventHandler far *disconnectEventHandler,
                                  /* Optional disconnect event handler. */
  long unsigned disconnectEventContext)
                                  /* Context for the above. */
{
  ConnectionEnd connectionEnd;

  /* Find the connection end. */

  DeferTimerChecking();
  DeferAdspPackets();
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is Empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnection);
  }

  /* Setup the event handlers - they could all be empty. */

  EnterCriticalSection();
  connectionEnd->receiveEventHandler = receiveEventHandler;
  connectionEnd->receiveEventContext = receiveEventContext;
  connectionEnd->receiveAttentionEventHandler = receiveAttentionEventHandler;
  connectionEnd->receiveAttentionEventContext = receiveAttentionEventContext;
  connectionEnd->sendOkayEventHandler = sendOkayEventHandler;
  connectionEnd->sendOkayEventContext = sendOkayEventContext;
  connectionEnd->disconnectEventHandler = disconnectEventHandler;
  connectionEnd->disconnectEventContext = disconnectEventContext;
  LeaveCriticalSection();

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

} /* AdspSetDataEventHandlers */

AppleTalkErrorCode far AdspCloseConnection(
  long refNum,               /* The connection refNum to close. */
  Boolean remoteClose)       /* All external callers should pass
                                pass "False;" Adsp internally will use
                                either True or False as required. */
{
  ConnectionEnd connectionEnd;
  BufferDescriptor datagram;

  /* Find our connection. */

  DeferTimerChecking();
  DeferAdspPackets();
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnection);
  }

  /* Should we send a CloseAdvice to the other side? */

  if (not remoteClose and connectionEnd->connectionState is AdspOpen)
  {
     if ((datagram = NewBufferDescriptor(AdspDataOffset)) is Empty)
     {
        ErrorLog("AdspCloseConnection", ISevError, __LINE__, UnknownPort,
                 IErrAdspOutOfMemory, IMsgAdspOutOfMemory,
                 Insert0());
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        return(AToutOfMemory);
     }
     BuildAdspHeader(connectionEnd, datagram->data, AdspControlFlag +
                     AdspCloseConnectionCode);
     if (DeliverDdp(connectionEnd->socket,
                    connectionEnd->remoteAddress,
                    DdpProtocolAdsp,
                    datagram,
                    AdspDataOffset,
                    Empty, Empty, 0) isnt ATnoError)
        ErrorLog("AdspCloseConnection", ISevError, __LINE__, UnknownPort,
                 IErrAdspBadCloseAdviseSend, IMsgAdspBadCloseAdviseSend,
                 Insert0());
  }

  /* If we're in the process of opening, call the open completion routine. */

  /* BUGBUG: We're still an active connection here, so what if this completion
             routine calls AdspCloseConnection... it'll get invoked again!
             Temporarilly solve this with resetting the flag first... MP
             issue here, though. */

  if (connectionEnd->connectionState is AdspHalfOpen)
  {
     connectionEnd->connectionState = AdspClosed;
     (*connectionEnd->openCompletionRoutine)(ATadspConnectionClosed,
                                             connectionEnd->openUserData,
                                             refNum, (long)0,
                                             unknownAddress);
     connectionEnd->connectionState = AdspHalfOpen;
     if (not connectionEnd->passiveOpen or
         connectionEnd->seenRemoteOpenRequest)
        CancelTimer(connectionEnd->openTimerId);
  }

  /* If we have a read pending, terminate it. */

  if (connectionEnd->receivePending)
  {
     connectionEnd->receivePending = False;
     (*connectionEnd->receiveCompletionRoutine)(ATadspConnectionClosed,
                                                connectionEnd->receiveUserData,
                                                connectionEnd->refNum,
                                                empty, (long)0, False);
  }
  if (connectionEnd->getAnythingPending)
  {
     connectionEnd->getAnythingPending = False;
     (*connectionEnd->getAnythingCompletionRoutine)(ATadspConnectionClosed,
                                                    connectionEnd->
                                                      getAnythingUserData,
                                                    connectionEnd->refNum,
                                                    False, Empty, 0, False);
  }

  /* If we have a forward reset pending close it. */

  if (connectionEnd->outgoingForwardReset)
  {
     connectionEnd->outgoingForwardReset = False;
     (*connectionEnd->forwardResetAckHandler)(ATadspConnectionClosed,
                                              connectionEnd->
                                                 forwardResetAckUserData,
                                              connectionEnd->refNum);
     CancelTimer(connectionEnd->forwardResetTimerId);
  }

  /* If we have an incoming attention handler, notify it of the close. */

  if (connectionEnd->incomingAttentionHandler isnt empty)
  {
     AdspIncomingAttentionRoutine *incomingAttentionHandler;
     incomingAttentionHandler = connectionEnd->incomingAttentionHandler;
     connectionEnd->incomingAttentionHandler = Empty;
     (*incomingAttentionHandler)(ATadspConnectionClosed,
                                 connectionEnd-> incomingAttentionUserData,
                                 connectionEnd->refNum, 0, empty, 0);
  }

  /* If we're waiting for an attention to complete, notify it of the close.
     Stop the retry timer too. */

  if (connectionEnd->waitingForAttentionAck)
  {
     connectionEnd->waitingForAttentionAck = False;
     if (connectionEnd->outgoingAttentionAckHandler isnt empty)
        (*connectionEnd->outgoingAttentionAckHandler)
                   (ATadspConnectionClosed,
                    connectionEnd->outgoingAttentionAckUserData,
                    connectionEnd->refNum);
     CancelTimer(connectionEnd->outgoingAttentionTimerId);
     if (connectionEnd->outgoingAttentionBuffer isnt empty)
        Free(connectionEnd->outgoingAttentionBuffer);
  }

  /* If we're open, cancel the connection maintenance and retransmit timers. */

  if (connectionEnd->connectionState is AdspOpen)
  {
     CancelTimer(connectionEnd->probeTimerId);
     CancelTimer(connectionEnd->retransmitTimerId);
  }

  /* If there is a disconnect event handler, and if this was a remote or a
     non-client disconnect, call it with the error. */

  if (remoteClose and (connectionEnd->disconnectEventHandler isnt Empty))
  {
      (*connectionEnd->disconnectEventHandler)(connectionEnd->refNum,
                                               connectionEnd->disconnectEventContext,
                                               ATadspConnectionClosed);
      connectionEnd->disconnectEventHandler = empty;
  }

  /* All set, remove the beast. */

  RemoveConnectionEnd(connectionEnd);
  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* AdspCloseConnection */

AppleTalkErrorCode far AdspSetCookieForConnection(
  long refNum,               /* The connection on which to set the cookie. */
  long unsigned cookie)      /* The new cookie. */
{
  ConnectionEnd connectionEnd;

  /* Find our connection. */

  DeferTimerChecking();
  DeferAdspPackets();
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnection);
  }

  /* Do the deed. */

  connectionEnd->usersCookie = cookie;

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* AdspSetCookieForConnection */

AppleTalkErrorCode far AdspGetCookieForConnection(
  long refNum,               /* The connection from which to get the cookie. */
  long unsigned far *cookie) /* Where to stick the cookie. */
{
  ConnectionEnd connectionEnd;

  /* Find our connection. */

  DeferTimerChecking();
  DeferAdspPackets();
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnection);
  }

  /* Do the deed. */

  *cookie = connectionEnd->usersCookie;

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* AdspGetCookieForConnection */

AppleTalkErrorCode far AdspForwardReset(
  long refNum,               /* The connection refNum to reset. */
  AdspForwardResetAckHandler *completionRoutine,
                             /* Routine to call when forward reset is
                                Acked; may be empty. */
  long unsigned userData)    /* User data for above call. */
{
  /* Call the specified completion routine (which may be empty) when our
     forward reset is Acked:

         errorCode - AppleTalkErrorCode; status of operation.
         userData  - long unsigned; as passed to us.
         refNum    - long; our connection ref num.
  */

  ConnectionEnd connectionEnd;
  AppleTalkErrorCode errorCode;
  BufferDescriptor datagram;

  /* Find our connection. */

  DeferTimerChecking();
  DeferAdspPackets();
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnection);
  }

  /* Only one at a time. */

  if (connectionEnd->outgoingForwardReset)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspFwdResetAlreadyPending);
  }

  /* Discard any unsent and unacked data in the send queue. */

  FreeBufferQueue(&connectionEnd->sendQueue);
  connectionEnd->nextSendQueue = connectionEnd->sendQueue;
  connectionEnd->retransmitSeqNum = connectionEnd->sendSeqNum;

  /* Build the ForwardReset packet and send it on its way. */

  if ((datagram = NewBufferDescriptor(AdspDataOffset)) is Empty)
  {
     ErrorLog("AdspForwardReset", ISevError, __LINE__, UnknownPort,
              IErrAdspOutOfMemory, IMsgAdspOutOfMemory,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(AToutOfMemory);
  }
  BuildAdspHeader(connectionEnd, datagram->data, AdspControlFlag +
                  AdspForwardResetCode);
  if ((errorCode = DeliverDdp(connectionEnd->socket,
                              connectionEnd->remoteAddress,
                              DdpProtocolAdsp,
                              datagram,
                              AdspDataOffset,
                              Empty, Empty, 0)) isnt ATnoError)
  {
     ErrorLog("AdspForwardReset", ISevError, __LINE__, UnknownPort,
              IErrAdspBadFrwdResetSend, IMsgAdspBadFrwdResetSend,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(errorCode);
  }

  /* Save the info in the connection end, and start the retry timer. */

  connectionEnd->outgoingForwardReset = True;
  connectionEnd->forwardResetAckHandler = completionRoutine;
  connectionEnd->forwardResetAckUserData = userData;
  connectionEnd->forwardResetTimerId =
              StartTimer(ForwardResetTimerExpired,
                         ForwardResetTimerIntenvalSecs,
                         sizeof(connectionEnd->refNum),
                         (char *)&connectionEnd->refNum);

  /* All set. */

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* AdspForwardReset */

AppleTalkErrorCode far AdspGetAttention(
  long refNum,               /* Connection to handle attentions for. */
  void far *opaqueBuffer,    /* "Buffer" to fill in with attention data;
                                must be able to hold 570 bytes. */
  long bufferSize,           /* Size of buffer. */
  AdspIncomingAttentionRoutine *completionRoutine,
                             /* Routine to call when attentions come in */
  long unsigned userData)    /* Data to be passed to above routine. */
{
  ConnectionEnd connectionEnd;

  if (opaqueBuffer is empty or bufferSize < AdspMaxAttentionDataSize)
     return(ATadspBadBufferSize);

  /* Find our connection. */

  DeferTimerChecking();
  DeferAdspPackets();
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnection);
  }
  if (connectionEnd->incomingAttentionHandler isnt empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspHandlerAlreadyQueued);
  }

  /* Copy in the suppiled data. */

  connectionEnd->incomingAttentionHandler = completionRoutine;
  connectionEnd->incomingAttentionUserData = userData;
  connectionEnd->incomingAttentionOpaqueBuffer = opaqueBuffer;

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* AdspGetAttention */

AppleTalkErrorCode far AdspSendAttention(
  long refNum,                    /* Connection to send attention on. */
  short unsigned attentionCode,   /* Attention code to send. */
  void far *attentionOpaqueBuffer,
                                  /* Attention "buffer" to send. */
  int attentionBufferSize,        /* Size of above. */
  AdspAttentionAckHandler *completionRoutine,
                                  /* Routine to call when Ack comes in
                                     (may be empty). */
  long unsigned userData)         /* To be passed to the above. */
{
  ConnectionEnd connectionEnd;

  /* Good attention code?  MinAdspAttentionCode is zero, so there's no need
     to test for "attentionCode < MinAdspAttentionCode" */

  if (attentionCode > MaxAdspAttentionCode)
     return(ATadspBadAttentionCode);
  if (attentionBufferSize < 0 or
      attentionBufferSize > AdspMaxAttentionDataSize)
     return(ATadspBadAttentionBufferSize);

  /* Find our connection. */

  DeferTimerChecking();
  DeferAdspPackets();
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnection);
  }

  if (attentionOpaqueBuffer is empty)
     attentionBufferSize = 0;
  if (attentionBufferSize is 0)
     attentionOpaqueBuffer = empty;

  /* Do we already have an attention pending? */

  if (connectionEnd->waitingForAttentionAck)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspAttentionAlreadyPending);
  }

  /* Copy the needed fields into the connection end. */

  connectionEnd->waitingForAttentionAck = True;
  connectionEnd->outgoingAttentionCode = attentionCode;
  connectionEnd->outgoingAttentionBufferSize = attentionBufferSize;
  connectionEnd->outgoingAttentionAckHandler = completionRoutine;
  connectionEnd->outgoingAttentionAckUserData = userData;
  if (attentionBufferSize isnt 0)
  {
     if ((connectionEnd->outgoingAttentionBuffer = Malloc(attentionBufferSize))
         is empty)
     {
        connectionEnd->waitingForAttentionAck = False;
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        return(AToutOfMemory);
     }
     MoveFromOpaque(connectionEnd->outgoingAttentionBuffer,
                    attentionOpaqueBuffer, 0, attentionBufferSize);
  }

  /* Send the Attention and start the retry timer. */

  SendAttention(connectionEnd);
  connectionEnd->outgoingAttentionTimerId =
                        StartTimer(SendAttentionTimerExpired,
                                   AttentionTimerIntervalSeconds,
                                   sizeof(connectionEnd->refNum),
                                   (char far *)&connectionEnd->refNum);

  /* All set! */

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* AdspSendAttention */

AppleTalkErrorCode far AdspMaxCurrentSendSize(
  long refNum,               /* Connection to query. */
  long far *size)            /* Returned size. */
{
  ConnectionEnd connectionEnd;

  /* Find our connection. */

  DeferTimerChecking();
  DeferAdspPackets();
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnection);
  }

  *size = MaxSendSize(connectionEnd);

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* AdspMaxCurrentSendSize */

AppleTalkErrorCode far AdspMaxCurrentReceiveSize(
  long refNum,               /* Connection to query. */
  long far *size,            /* Returned size. */
  Boolean *endOfMessage)     /* Is an EOM pending? */
{
  ConnectionEnd connectionEnd;

  /* Find our connection. */

  DeferTimerChecking();
  DeferAdspPackets();
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnection);
  }

  *size = MaxNextReadSizeOfBufferQueue(&connectionEnd->receiveQueue,
                                       endOfMessage);

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* AdspMaxCurrentReceiveSize */

AppleTalkErrorCode far AdspSend(
  long refNum,               /* Connection to send data on. */
  void far *opaqueBuffer,    /* Data "buffer." */
  long bufferSize,           /* Its size. */
  Boolean endOfMessage,      /* End of logical message? */
  Boolean flushFlag,         /* Flush before we return? */
  long far *bytesSent)       /* How may bytes did we enqueue?  The
                                EOM bit counts as one byte. */
{
  ConnectionEnd connectionEnd;
  long sendSize;
  AppleTalkErrorCode errorCode = ATnoError;

  /* Nop? */

  if (bufferSize is 0 and not endOfMessage and not flushFlag)
     return(ATnoError);
  if (bufferSize < 0)
     return(ATadspBadBufferSize);

  /* Find our connection. */

  DeferTimerChecking();
  DeferAdspPackets();
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnection);
  }

  /* If we can't fit any of the buffer, give up.  If it might do some good,
     try a flush -- maybe that will dislodge things. */

  sendSize = MaxSendSize(connectionEnd);
  if (sendSize is 0)
  {
     if (BufferQueueSize(&connectionEnd->nextSendQueue) isnt 0 and
         connectionEnd->sendSeqNum isnt connectionEnd->sendWindowSeqNum + 1)
        SendData(connectionEnd);
     connectionEnd->sendWindowHasClosed = True;
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspCouldNotEnqueueSend);
  }

  /* If we can't process the entire send, do what we can. */

  if (bufferSize + endOfMessage > sendSize)
  {
     bufferSize = sendSize;
     endOfMessage = False;
     errorCode = ATadspCouldNotFullyEnqueueSend;
  }

  /* Add the data to the send queue. */

  if (not AddToBufferQueue(&connectionEnd->sendQueue, opaqueBuffer, 0,
                           bufferSize, True, endOfMessage,
                           &connectionEnd->nextSendQueue))
  {
     ErrorLog("AdspSend", ISevError, __LINE__, UnknownPort,
              IErrAdspBufferQueueError, IMsgAdspBufferQueueError,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATinternalError);
  }

  /* Flush if requested (or we're full), (and it would do some good), else,
     we're done! */

  if ((flushFlag or MaxSendSize(connectionEnd) is 0) and
      BufferQueueSize(&connectionEnd->nextSendQueue) isnt 0 and
      connectionEnd->sendSeqNum isnt connectionEnd->sendWindowSeqNum + 1)
     SendData(connectionEnd);

  if (bytesSent isnt Empty)
     *bytesSent = bufferSize + endOfMessage;
  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(errorCode);

}  /* AdspSend */

AppleTalkErrorCode far AdspReceive(
  long refNum,                    /* Session to read from. */
  void far *opaqueBuffer,         /* "Buffer" to read into. */
  long bufferSize,                /* Size of above. */
  AdspReceiveHandler *completionRoutine,
                                  /* Routine to call with data. */
  long unsigned userData)         /* UserData to pass to above. */
{
  ConnectionEnd connectionEnd;

  /* When the read completes call our completion routine with the following
     arguments:

         errorCode    - AppleTalkErrorCode; completion code.
         userData     - long unsigned; as passed to us.
         refNum       - long; as passed to us.
         opaqueBuffer - void far *; as passed to us.
         bufferSize   - long; actual returned buffer length.
         endOfMessage - Boolean; logical end of message?
  */

  /* Buffer size of zero if okay... justing looking for EOM. */

  if (bufferSize < 0)
     return(ATadspBadBufferSize);

  /* Find our connection. */

  DeferTimerChecking();
  DeferAdspPackets();
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnection);
  }

  /* Too many? */

  if (connectionEnd->receivePending)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspReadAlreadyPending);
  }

  /* Okay, copy the information into the connection end. */

  connectionEnd->receiveCompletionRoutine = completionRoutine;
  connectionEnd->receiveUserData = userData;
  connectionEnd->receiveOpaqueBuffer = opaqueBuffer;
  connectionEnd->receiveBufferSize = bufferSize;
  connectionEnd->receivePending = True;

  /* Try to return any data that's waiting now.

              ** ReadData() will undefer **
  */

  if (not connectionEnd->dataEventInProgress)
     ReadData(connectionEnd, False, Empty, Empty);
  else
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
  }
  return(ATnoError);

}  /* AdspReceive */

AppleTalkErrorCode far AdspPeek(
  long refNum,                    /* Session to peek into. */
  void far *opaqueBuffer,         /* Buffer to fill. */
  long bufferSize,                /* Size of buffer. */
  long far *bytesReturned,        /* Bytes filled in. */
  Boolean *endOfMessage)          /* EOM too? */
{
  ConnectionEnd connectionEnd;

  /* Buffer size of zero if okay... justing peeking for EOM. */

  if (bufferSize < 0)
     return(ATadspBadBufferSize);

  /* Find our connection. */

  DeferTimerChecking();
  DeferAdspPackets();
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnection);
  }

  /* Too many? */

  if (connectionEnd->receivePending)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspReadAlreadyPending);
  }

  /* Okay, copy the information into the connection end. */

  connectionEnd->receiveOpaqueBuffer = opaqueBuffer;
  connectionEnd->receiveBufferSize = bufferSize;

  /* Try to return any data that's waiting now.

              ** ReadData() will undefer **
  */

  return(ReadData(connectionEnd, True, bytesReturned, endOfMessage));

}  /* AdspPeek */

AppleTalkErrorCode far AdspGetAnything(
  long refNum,                    /* Session to read from. */
  void far *opaqueBuffer,         /* "Buffer" to read into. */
  long bufferSize,                /* Size of above. */
  AdspGetAnythingHandler *completionRoutine,
                                  /* Routine to call with data. */
  long unsigned userData)         /* UserData to pass to above. */
{
  ConnectionEnd connectionEnd;

  /* When either real data comes in or an attention comes in, call our
     completion routine with the following arguments:

         errorCode    - AppleTalkErrorCode; completion code.
         userData     - long unsigned; as passed to us.
         refNum       - long; as passed to us.
         attentionData
                      - Boolean; is the "stuff" attention data?
         attentionCode
                      - short unsigned; if above is True, this is the
                        attention code.
         opaqueBuffer - void far *; as passed to us.
         bufferSize   - long; actual returned buffer length.
         endOfMessage - Boolean; if "attentionData" is False, does this
                        "real buffer" include an end-of-message?

     A "GetAnything" takes precidence over a AdspRead or an AdspGetAttention.
  */

  /* Buffer size must be able to hold a full attention, since we don't
     know what we're going to get! */

  if (opaqueBuffer is Empty or bufferSize < 0)
     return(ATadspBadBufferSize);

  /* Find our connection. */

  DeferTimerChecking();
  DeferAdspPackets();
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspNoSuchConnection);
  }

  /* Too many? */

  if (connectionEnd->getAnythingPending)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATadspGetAnythingAlreadyPending);
  }

  /* Okay, copy the information into the connection end. */

  connectionEnd->getAnythingCompletionRoutine = completionRoutine;
  connectionEnd->getAnythingUserData = userData;
  connectionEnd->getAnythingOpaqueBuffer = opaqueBuffer;
  connectionEnd->getAnythingBufferSize = bufferSize;
  connectionEnd->getAnythingPending = True;

  /* Try to return any data that's waiting now.

              ** ReadData() will undefer **
  */

  if (not connectionEnd->dataEventInProgress)
     ReadData(connectionEnd, False, Empty, Empty);
  else
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
  }
  return(ATnoError);

}  /* AdspGetAnything */

/* @Add new external routines here@ */

                        /* ************************ */
                        /* *** Static routines. *** */
                        /* ************************ */

ExternForVisibleFunction long far
         AdspConnectionListenerPacketIn(AppleTalkErrorCode errorCode,
                                        long unsigned userData,
                                        int port,
                                        AppleTalkAddress source,
                                        long destinationSocket,
                                        int protocolType,
                                        char far *datagram,
                                        int datagramLength,
                                        AppleTalkAddress actualDestination)
{
  ConnectionListenerInfo connectionListenerInfo,
                         previousConnectionListenerInfo;
  OpenRequestHandler openRequestHandler, nextOpenRequestHandler;
  long listenerRefNum = (long)userData;
  #if not defined(DeferAdspPackets)
     DeferredPacket deferredPacket;
  #endif
  short unsigned remoteConnectionId;
  long unsigned remoteFirstByteSeqNum;
  long unsigned remoteNextReceiveSeqNum;
  long remoteReceiveWindowSize;
  int descriptor, controlCode;
  short unsigned destinationConnectionId;
  short unsigned adspVersionStamp;
  long unsigned receiveAttentionSeqNum;
  AdspIncomingOpenRequestHandler *completionRoutine;
  AdspConnectionEventHandler far *connectionEventHandler;
  long unsigned connectionEventContext;
  long newRefNum;

  /* Do we like the error code? */

  if (errorCode isnt ATnoError and
      errorCode isnt ATsocketClosed)
  {
     ErrorLog("AdspConnectionListenerPacketIn", ISevError, __LINE__, port,
              IErrAdspFunnyErrorCode, IMsgAdspFunnyErrorCode,
              Insert0());
     return((long)True);
  }

  #if not defined(DeferAdspPackets)
     /* Should we be defering incoming packets? */

     EnterCriticalSection();
     if (deferIncomingAdspPacketsCount > 0)
     {
        #if VerboseMessages & 0
           printf("AdspConnectionListenerPacketIn: from %d:%d:%d to %d; Deferred.\n",
                  source.networkNumber, source.nodeNumber, source.socketNumber,
                  destinationSocket);
        #endif
        if (currentDeferredPacketCount is MaximumDeferredPackets)
        {
           LeaveCriticalSection();
           ErrorLog("AdspConnectionListenerPacketIn", ISevWarning, __LINE__, port,
                    IErrAdspLosingData, IMsgAdspLosingData,
                    Insert0());
           return((long)True);
        }
        LeaveCriticalSection();
        deferredPacket = (DeferredPacket)Malloc(sizeof(*deferredPacket) +
                                              datagramLength);
        if (deferredPacket is empty)
        {
           ErrorLog("AdspConnectionListenerPacketIn", ISevError, __LINE__, port,
                    IErrAdspOutOfMemory, IMsgAdspOutOfMemory,
                    Insert0());
           return((long)True);
        }

        /* Fill in the data strcuture, and place the packet at the end of the
           queue. */

        deferredPacket->forConnectionListener = True;
        deferredPacket->errorCode = errorCode;
        deferredPacket->userData = userData;
        deferredPacket->next = empty;
        deferredPacket->port = port;
        deferredPacket->source = source;
        deferredPacket->destinationSocket = destinationSocket;
        deferredPacket->datagramLength = (short)datagramLength;
        deferredPacket->actualDestination = actualDestination;
        if (datagram isnt empty)
           MoveMem(deferredPacket->datagram, datagram, datagramLength);

        EnterCriticalSection();
        if (tailOfDeferredPacketList is empty)
           tailOfDeferredPacketList = headOfDeferredPacketList = deferredPacket;
        else
        {
           tailOfDeferredPacketList->next = deferredPacket;
           tailOfDeferredPacketList = deferredPacket;
        }

        /* All set... return. */

        currentDeferredPacketCount += 1;
        LeaveCriticalSection();

        return((long)True);
     }
     else
        LeaveCriticalSection();
  #endif

  DeferTimerChecking();
  DeferAdspPackets();

  /* If we get a "socket closed", we should remove the connection listener
     and notify any queued open request handlers. */

  if (errorCode is ATsocketClosed)
  {
     for (previousConnectionListenerInfo = empty,
                 connectionListenerInfo = connectionListenerList;
          connectionListenerInfo isnt empty;
          previousConnectionListenerInfo = connectionListenerInfo,
                 connectionListenerInfo = connectionListenerInfo->next)
        if (connectionListenerInfo->connectionListenerRefNum is
            listenerRefNum)
           break;
     if (connectionListenerInfo is empty)
     {
        ErrorLog("AdspConnectionListenerPacketIn", ISevWarning, __LINE__, port,
                 IErrAdspListenerMissing, IMsgAdspListenerMissing,
                 Insert0());
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        return((long)True);
     }

     /* Remove the connection listener from the master list. */

     if (previousConnectionListenerInfo is empty)
        connectionListenerList = connectionListenerInfo->next;
     else
        previousConnectionListenerInfo->next = connectionListenerInfo->next;

     /* If we have any open handlers, close 'em out. */

     for (openRequestHandler = connectionListenerInfo->openRequestHandlers;
          openRequestHandler isnt empty;
          openRequestHandler = nextOpenRequestHandler)
     {
        nextOpenRequestHandler = openRequestHandler->next;
        if (not openRequestHandler->inUse)
           openRequestHandler->completionRoutine(ATadspConnectionListenerDeleted,
                                                 openRequestHandler->userData,
                                                 source, listenerRefNum,
                                                 (long)0);
        Free(openRequestHandler);
     }

     /* Do we have any deferred connection events? */

     EnterCriticalSection();
     openRequestHandler = connectionListenerInfo->deferredConnectionEvents;
     connectionListenerInfo->deferredConnectionEvents = Empty;
     LeaveCriticalSection();
     while (openRequestHandler isnt Empty)
     {
        nextOpenRequestHandler = openRequestHandler->next;
        Free(openRequestHandler);
        openRequestHandler = nextOpenRequestHandler;
     }

     /* Free the listener and we're finished! */

     Free(connectionListenerInfo);
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);

  }

  /* Okay, find our connection listener. */

  if ((connectionListenerInfo = FindConnectionListenerByRefNum(listenerRefNum))
      is empty)
  {
     ErrorLog("AdspConnectionListenerPacketIn", ISevWarning, __LINE__, port,
              IErrAdspListenerMissing, IMsgAdspListenerMissing,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);
  }

  if (protocolType isnt DdpProtocolAdsp)
  {
     ErrorLog("AdspConnectionListenerPacketIn", ISevWarning, __LINE__, port,
              IErrAdspOddProtocol, IMsgAdspOddProtocol,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);
  }

  /* Decode the header, if it's long enough. */

  if (datagramLength < AdspDataOffset)
  {
     ErrorLog("AdspConnectionListenerPacketIn", ISevWarning, __LINE__, port,
              IErrAdspMissingHeader, IMsgAdspMissingHeader,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);
  }
  DecodeAdspHeader(datagram, &remoteConnectionId, &remoteFirstByteSeqNum,
                   &remoteNextReceiveSeqNum, &remoteReceiveWindowSize,
                   &descriptor);

  /* As a connection listener, we only care about open requests. */

  controlCode = (descriptor & AdspControlCodeMask);
  if (not (descriptor & AdspControlFlag) or
      controlCode isnt AdspOpenConnectionReqCode)
  {
     ErrorLog("AdspConnectionListenerPacketIn", ISevWarning, __LINE__, port,
              IErrAdspFunnyRequest, IMsgAdspFunnyRequest,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);
  }

  /* Get the rest of the Open header. */

  if (datagramLength < AdspNextAttentionSeqNumOffset + sizeof(long))
  {
     ErrorLog("AdspConnectionListenerPacketIn", ISevWarning, __LINE__, port,
              IErrAdspMissingHeader, IMsgAdspMissingHeader,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);
  }
  MoveShortWireToMachine(datagram + AdspVersionStampOffset,
                         adspVersionStamp);
  MoveShortWireToMachine(datagram + AdspDestConnectionIdOffset,
                         destinationConnectionId);
  MoveLongWireToMachine(datagram + AdspNextAttentionSeqNumOffset,
                        receiveAttentionSeqNum);

  /* Version okay? */

  if (adspVersionStamp isnt AdspVersionStamp)
  {
     ErrorLog("AdspConnectionListenerPacketIn", ISevWarning, __LINE__, port,
              IErrAdspBadVersion, IMsgAdspBadVersion,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);
  }

  /* Is this a duplicate openRequest (same remoteID and same remote address)?
     That is, have we already passed this guy up to the user to decide whether
     to accept or deny the request?  If so, ignore this open request. */

  for (openRequestHandler = connectionListenerInfo->openRequestHandlers;
       openRequestHandler isnt empty;
       openRequestHandler = openRequestHandler->next)
     if (openRequestHandler->inUse and
         openRequestHandler->remoteConnectionId is remoteConnectionId and
         AppleTalkAddressesEqual(&openRequestHandler->remoteAddress,
                                 &source))
     {
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        return((long)True);
     }

  /* Also go through the deferred events queue. */

  for (openRequestHandler = connectionListenerInfo->deferredConnectionEvents;
       openRequestHandler isnt empty;
       openRequestHandler = openRequestHandler->next)
     if (openRequestHandler->remoteConnectionId is remoteConnectionId and
         AppleTalkAddressesEqual(&openRequestHandler->remoteAddress,
                                 &source))
     {
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        return((long)True);
     }

  /* Okay we're not a duplicate, do we have a pending non-in-use handler?
     If not, ignore the request.  NOTE: For events indicated, inUse will
     always be true */

  for (openRequestHandler = connectionListenerInfo->openRequestHandlers;
       openRequestHandler isnt empty;
       openRequestHandler = openRequestHandler->next)
     if (not openRequestHandler->inUse)
        break;

  /* No GetConnection and no event handler? */

  connectionEventHandler = connectionListenerInfo->connectionEventHandler;
  connectionEventContext = connectionListenerInfo->connectionEventContext;
  if (openRequestHandler is empty and
      connectionEventHandler is empty)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);
  }

  /* Copy the fields that we'll later need to accept the request into the
     openRequest node.  Tag the guy as InUse too. */

  if ((newRefNum = GetNextRefNum()) < 0)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);
  }

  if (openRequestHandler is empty)
  {
     /* We need to indicate this incoming connection - make an event handler. */

     if ((openRequestHandler = Calloc(sizeof(*openRequestHandler), 1)) is empty)
     {
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        ErrorLog("AdspConnectionListenerPacketIn", ISevError, __LINE__, port,
                 IErrAdspOutOfMemory, IMsgAdspOutOfMemory,
                 Insert0());
        return((long)True);
     }
     openRequestHandler->eventHandler = True;
  }

  openRequestHandler->inUse = True;
  openRequestHandler->refNum = newRefNum;
  openRequestHandler->remoteAddress = source;
  openRequestHandler->remoteConnectionId = remoteConnectionId;
  openRequestHandler->remoteNextReceiveSeqNum = remoteNextReceiveSeqNum;
  openRequestHandler->remoteReceiveWindowSize = remoteReceiveWindowSize;
  openRequestHandler->receiveAttentionSeqNum = receiveAttentionSeqNum;

  if (openRequestHandler->eventHandler)
  {
     EnterCriticalSection();
     if (connectionListenerInfo->connectEventInProgress)
     {
        /* There is already an incoming connection event in progress. Queue
           this event into the deferred queue. */

        openRequestHandler->next =
             connectionListenerInfo->deferredConnectionEvents;
        connectionListenerInfo->deferredConnectionEvents = openRequestHandler;
        LeaveCriticalSection();
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        return((long)True);
     }
     connectionListenerInfo->connectEventInProgress = True;
     LeaveCriticalSection();

     /* Enter openRequestHandler into the connectionListener structure. */

     openRequestHandler->next = connectionListenerInfo->openRequestHandlers;
     connectionListenerInfo->openRequestHandlers = openRequestHandler;

     /* Indicate the event, the accept()/deny() will happen whenever and we
        don't have to worry about it as the thing is queued up in the
        openRequestHandler queue anyway. */

     while (True)
     {
        (*connectionEventHandler)(connectionListenerInfo->
                                             connectionListenerRefNum,
                                  connectionEventContext,
                                  openRequestHandler->remoteAddress,
                                  openRequestHandler->refNum);
        EnterCriticalSection();
        openRequestHandler = connectionListenerInfo->deferredConnectionEvents;
        if (openRequestHandler is empty)
        {
           LeaveCriticalSection();
           break;
        }

        /* Dequeue from deferred, put into the openRequest queue. */

        connectionListenerInfo->deferredConnectionEvents =
               openRequestHandler->next;
        openRequestHandler->next = connectionListenerInfo->openRequestHandlers;
        connectionListenerInfo->openRequestHandlers = openRequestHandler;
        LeaveCriticalSection();
     }

     connectionListenerInfo->connectEventInProgress = False;
     HandleAdspPackets();
     HandleDeferredTimerChecks();
  }
  else
  {
     /* Okay, set up to call the completion routine... let the user know there
        is a new open request to deal with. */

     completionRoutine = openRequestHandler->completionRoutine;
     userData = openRequestHandler->userData;
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     (*completionRoutine)(ATnoError, userData, source, listenerRefNum,
                          newRefNum);
  }

  return((long)True);

}  /* AdspConnectionListenerPacketIn */

ExternForVisibleFunction long far
         AdspPacketIn(AppleTalkErrorCode errorCode,
                      long unsigned userData,
                      int port,
                      AppleTalkAddress source,
                      long destinationSocket,
                      int protocolType,
                      char far *datagram,
                      int datagramLength,
                      AppleTalkAddress actualDestination)
{
  ConnectionEnd connectionEnd, nextConnectionEnd;
  long index;
  short unsigned remoteConnectionId;
  long unsigned remoteFirstByteSeqNum;
  long unsigned remoteNextReceiveSeqNum;
  long remoteReceiveWindowSize;
  int descriptor, controlCode;
  short unsigned destinationConnectionId;
  short unsigned adspVersionStamp;
  long unsigned receiveAttentionSeqNum;
  long unsigned newSendWindowSeqNum;
  AdspOpenCompleteHandler *openCompletionRoutine;
  AdspForwardResetAckHandler *forwardResetAckHandler;
  AdspIncomingAttentionRoutine *incomingAttentionHandler;
  AdspAttentionAckHandler *outgoingAttentionAckHandler = Empty;
  AdspGetAnythingHandler *getAnythingRoutine = Empty;
  AdspReceiveAttnEventHandler far *receiveAttentionEventHandler = Empty;
  long unsigned receiveAttentionEventContext;
  long unsigned outgoingAttentionAckUserData;
  long refNum, bytesAccepted = 0;
  #if not defined(DeferAdspPackets)
     DeferredPacket deferredPacket;
  #endif
  Boolean endOfMessage;
  long dataSize;
  BufferDescriptor packet;

  /* Do we like the error code? */

  if (errorCode isnt ATnoError and
      errorCode isnt ATsocketClosed)
  {
     ErrorLog("AdspPacketIn", ISevWarning, __LINE__, port,
              IErrAdspFunnyErrorCode, IMsgAdspFunnyErrorCode,
              Insert0());
     return((long)True);
  }

  #if not defined(DeferAdspPackets)
     /* Should we be defering incoming packets? */

     EnterCriticalSection();
     if (deferIncomingAdspPacketsCount > 0)
     {
        #if VerboseMessages & 0
           printf("AdspPacketIn: from %d:%d:%d to %d; Deferred.\n",
                  source.networkNumber, source.nodeNumber, source.socketNumber,
                  destinationSocket);
        #endif
        if (currentDeferredPacketCount is MaximumDeferredPackets)
        {
           LeaveCriticalSection();
           ErrorLog("AdspPacketIn", ISevWarning, __LINE__, port,
                    IErrAdspLosingData, IMsgAdspLosingData,
                    Insert0());
           return((long)True);
        }
        LeaveCriticalSection();
        deferredPacket = (DeferredPacket)Malloc(sizeof(*deferredPacket) +
                                              datagramLength);
        if (deferredPacket is empty)
        {
           ErrorLog("AdspPacketIn", ISevError, __LINE__, port,
                    IErrAdspOutOfMemory, IMsgAdspOutOfMemory,
                    Insert0());
           return((long)True);
        }

        /* Fill in the data strcuture, and place the packet at the end of the
           queue. */

        deferredPacket->forConnectionListener = False;
        deferredPacket->errorCode = errorCode;
        deferredPacket->userData = userData;
        deferredPacket->next = empty;
        deferredPacket->port = port;
        deferredPacket->source = source;
        deferredPacket->destinationSocket = destinationSocket;
        deferredPacket->datagramLength = (short)datagramLength;
        deferredPacket->actualDestination = actualDestination;
        if (datagram isnt empty)
           MoveMem(deferredPacket->datagram, datagram, datagramLength);

        EnterCriticalSection();
        if (tailOfDeferredPacketList is empty)
           tailOfDeferredPacketList = headOfDeferredPacketList = deferredPacket;
        else
        {
           tailOfDeferredPacketList->next = deferredPacket;
           tailOfDeferredPacketList = deferredPacket;
        }

        /* All set... return. */

        currentDeferredPacketCount += 1;
        LeaveCriticalSection();

        return((long)True);
     }
     else
        LeaveCriticalSection();
  #endif

  DeferTimerChecking();
  DeferAdspPackets();

  /* If we get a "socket closed", we should close all Adsp sessions operating
     on the incoming socket (regardless of connection ID). */

  if (errorCode is ATsocketClosed)
  {
     CheckMod(index, destinationSocket, NumberOfConnectionEndHashBkts,
              "AdspPacketIn");
     for (connectionEnd = connectionEndLocalHashBuckets[index];
          connectionEnd isnt empty;
          connectionEnd = nextConnectionEnd)
     {
        nextConnectionEnd = connectionEnd->nextByLocalInfo;
        if (connectionEnd->socket isnt destinationSocket)
           continue;
        connectionEnd->socket = -1;    /* Already closed, obviously */
        AdspCloseConnection(connectionEnd->refNum, True);
     }
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);
  }

  if (protocolType isnt DdpProtocolAdsp)
  {
     ErrorLog("AdspPacketIn", ISevWarning, __LINE__, port,
              IErrAdspOddProtocol, IMsgAdspOddProtocol,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);
  }

  /* Decode the header, if it's long enough. */

  if (datagramLength < AdspDataOffset)
  {
     ErrorLog("AdspPacketIn", ISevWarning, __LINE__, port,
              IErrAdspMissingHeader, IMsgAdspMissingHeader,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);
  }
  DecodeAdspHeader(datagram, &remoteConnectionId, &remoteFirstByteSeqNum,
                   &remoteNextReceiveSeqNum, &remoteReceiveWindowSize,
                   &descriptor);

  /* Process Open oriented requests. */

  controlCode = (descriptor & AdspControlCodeMask);
  if ((descriptor & AdspControlFlag) and
      controlCode >= AdspOpenConnectionReqCode and
      controlCode <= AdspOpenConnectionDenyCode)
  {
     /* Get the rest of the Open header. */

     if (datagramLength < AdspNextAttentionSeqNumOffset + sizeof(long))
     {
        ErrorLog("AdspPacketIn", ISevWarning, __LINE__, port,
                 IErrAdspMissingHeader, IMsgAdspMissingHeader,
                 Insert0());
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        return((long)True);
     }
     MoveShortWireToMachine(datagram + AdspVersionStampOffset,
                            adspVersionStamp);
     MoveShortWireToMachine(datagram + AdspDestConnectionIdOffset,
                            destinationConnectionId);
     MoveLongWireToMachine(datagram + AdspNextAttentionSeqNumOffset,
                           receiveAttentionSeqNum);

     /* Version okay? */

     if (adspVersionStamp isnt AdspVersionStamp)
     {
        ErrorLog("AdspPacketIn", ISevWarning, __LINE__, port,
                 IErrAdspBadVersion, IMsgAdspBadVersion,
                 Insert0());
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        return((long)True);
     }

     /* Find our connection ID; if just "Req" we must have a HalfOpen
        targetted at the source address; otherwise we can use the destination
        connectionId and the source address for a normal local lookup. */

     if (controlCode is AdspOpenConnectionReqCode)
     {
        CheckMod(index, destinationSocket, NumberOfConnectionEndHashBkts,
                 "AdspPacketIn");
        for (connectionEnd = connectionEndLocalHashBuckets[index];
             connectionEnd isnt empty;
             connectionEnd = connectionEnd->nextByLocalInfo)
           if (connectionEnd->socket is destinationSocket and
               connectionEnd->connectionState is AdspHalfOpen and
               not connectionEnd->seenRemoteOpenRequest and
               (connectionEnd->passiveOpen or
                AppleTalkAddressesEqual(&connectionEnd->remoteAddress,
                                        &source)))
              break;
     }
     else
        connectionEnd = FindConnectionEndByLocalInfo(destinationSocket,
                                                     destinationConnectionId);
     if (connectionEnd is empty)
     {
        /* Nobody to connect to. */

        #if VerboseMessages
           printf("Open control code = %d; no live target.\n", controlCode);
        #endif
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        return((long)True);
     }
     refNum = connectionEnd->refNum;
     #if VerboseMessages
        printf("Open control code %d to target RefNum = %d.\n", controlCode,
               connectionEnd->refNum);
     #endif

     /* Handle easy deny case. */

     if (controlCode is AdspOpenConnectionDenyCode)
     {
        if (connectionEnd->connectionState is AdspOpen)
        {
           /* The other end can't change its mind... let connection age
              away if the other guy is serious. */

           ErrorLog("AdspPacketIn", ISevError, __LINE__, port,
                    IErrAdspCantDeny, IMsgAdspCantDeny,
                    Insert0());
           HandleAdspPackets();
           HandleDeferredTimerChecks();
           return((long)True);
        }

        /* If a passive open was denied, just get back to the passive open
           state. */

        if (connectionEnd->passiveOpen)
        {
           connectionEnd->seenRemoteOpenRequest = False;
           CancelTimer(connectionEnd->openTimerId);
           HandleAdspPackets();
           HandleDeferredTimerChecks();
           return((long)True);
        }

        /* Otherwise, complete our Open request with the bad news. */

        CancelTimer(connectionEnd->openTimerId);
        openCompletionRoutine = connectionEnd->openCompletionRoutine;
        userData = connectionEnd->openUserData;

        RemoveConnectionEnd(connectionEnd);
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        (*openCompletionRoutine)(ATadspConnectionDenied, userData, refNum,
                                 (long)0, unknownAddress);
        return((long)True);
     }

     /* If we're HalfOpen, and we have a request, accept all the interesting
        fields from the header (assuming we have't already done that). */

     if (not connectionEnd->seenRemoteOpenRequest and
         (controlCode is AdspOpenConnectionReqCode or
          controlCode is AdspOpenConnectionReqAndAckCode))
     {
        connectionEnd->seenRemoteOpenRequest = True;

        connectionEnd->remoteConnectionId = remoteConnectionId;
        connectionEnd->remoteAddress = source;

        connectionEnd->sendSeqNum = remoteNextReceiveSeqNum;
        connectionEnd->retransmitSeqNum = remoteNextReceiveSeqNum;
        connectionEnd->sendWindowSeqNum =
                   remoteNextReceiveSeqNum +
                   (long unsigned)remoteReceiveWindowSize -
                   (long unsigned)1;

        connectionEnd->receiveAttentionSeqNum = receiveAttentionSeqNum;

        /* If we're a passive open, start the open timer now. */

        if (connectionEnd->passiveOpen)
           connectionEnd->openTimerId = StartTimer(OpenTimerExpired,
                                                   AdspOpenIntervalSeconds,
                                                   sizeof(connectionEnd->
                                                          refNum),
                                                   (char *)&connectionEnd->
                                                            refNum);
     }

     /* If we're any flavour of Ack, and we're not open yet, we can make
        the connection that way now. */

     if (connectionEnd->connectionState is AdspHalfOpen and
         (controlCode is AdspOpenConnectionAckCode or
          controlCode is AdspOpenConnectionReqAndAckCode))
     {
        connectionEnd->connectionState = AdspOpen;
        CancelTimer(connectionEnd->openTimerId);
        connectionEnd->lastContactTime = CurrentRelativeTime();
        connectionEnd->probeTimerId = StartTimer(ProbeTimerExpired,
                                                 ProbeTimerIntervalSeconds,
                                                 sizeof(refNum),
                                                 (char *)&refNum);
        connectionEnd->retransmitTimerId =
                                      StartTimer(RetransmitTimerExpired,
                                                 RetransmitTimerIntervalSeconds,
                                                 sizeof(refNum),
                                                 (char *)&refNum);

        /* We're now open, add the connection end to the remote info lookup
           table. */

        CheckMod(index, connectionEnd->remoteConnectionId,
                 NumberOfConnectionEndHashBkts, "AdspPacketIn");
        connectionEnd->nextByRemoteInfo = connectionEndRemoteHashBuckets[index];
        connectionEndRemoteHashBuckets[index] = connectionEnd;

        /* Is the other end still looking for an Ack? */

        if (controlCode is AdspOpenConnectionReqAndAckCode)
           SendOpenControl(connectionEnd);

        /* We're set, call the completion routine with the good news. */

        HandleAdspPackets();
        HandleDeferredTimerChecks();
        (*connectionEnd->openCompletionRoutine)(ATnoError,
                                                connectionEnd->openUserData,
                                                connectionEnd->refNum,
                                                destinationSocket,
                                                source);
        return((long)True);
     }

     /* Okay, we're basically set, if the other end is looking for an Ack,
        give it to him, else we just got a stale Ack from the other side,
        which we can ignore. */

     if (controlCode is AdspOpenConnectionReqCode or
         controlCode is AdspOpenConnectionReqAndAckCode)
        SendOpenControl(connectionEnd);
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);

  }  /* Open control packet handling */

  /* Find our connection end. */

  if ((connectionEnd = FindConnectionEndByRemoteInfo(source,
                                                     remoteConnectionId)) is
      empty)
  {
     #if VerboseMessages
        ErrorLog("AdspPacketIn", ISevVerbose, __LINE__, port,
                 IErrAdspDeadDest, IMsgAdspDeadDest,
                 Insert0());
     #endif
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);
  }
  connectionEnd->lastContactTime = CurrentRelativeTime();

  /* Handle Attention packets. */

  if (descriptor & AdspAttentionFlag)
  {
     long unsigned remoteAttentionSendSeq = remoteFirstByteSeqNum;
     long unsigned remoteAttentionReceiveSeq = remoteNextReceiveSeqNum;
     short unsigned attentionCode;
     int attentionDataSize;
     char far *attentionOpaqueBuffer;

     if (controlCode isnt 0)
     {
        ErrorLog("AdspPacketIn", ISevWarning, __LINE__, port,
                 IErrAdspFunnyValue, IMsgAdspFunnyValue,
                 Insert0());
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        return((long)True);
     }

     /* Validate sequence numbers. */

     if (remoteAttentionReceiveSeq is connectionEnd->sendAttentionSeqNum + 1)
        connectionEnd->sendAttentionSeqNum += 1;   /* Ack of our last Attn */
     if (remoteAttentionSendSeq isnt connectionEnd->receiveAttentionSeqNum)
     {
        #if VerboseMessages
           ErrorLog("AdspPacketIn", ISevVerbose, __LINE__, port,
                    IErrAdspAttnOutOfSeq, IMsgAdspAttnOutOfSeq,
                    Insert0());
        #endif
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        return((long)True);
     }

     /* If we're waiting for an attention acknowledgement, any attention
        packet will do. */

     if (connectionEnd->waitingForAttentionAck)
     {
        /* Save what we need to call completion routine when packets get
           undeferred. */

        outgoingAttentionAckHandler =
                   connectionEnd->outgoingAttentionAckHandler;
        outgoingAttentionAckUserData =
                   connectionEnd->outgoingAttentionAckUserData;
        refNum = connectionEnd->refNum;

        CancelTimer(connectionEnd->outgoingAttentionTimerId);
        connectionEnd->waitingForAttentionAck = False;
        if (connectionEnd->outgoingAttentionBuffer isnt empty)
           Free(connectionEnd->outgoingAttentionBuffer);
     }

     /* If the packet is a "Control", then all it can be is an Ack, so
        no data to handle. */

     if (descriptor & AdspControlFlag)
     {
        #if 0
        connectionEnd->receiveAttentionSeqNum += 1;   /* "accept" Ack */
        #endif
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        if (outgoingAttentionAckHandler isnt empty)
           (*outgoingAttentionAckHandler)(ATnoError,
                                          outgoingAttentionAckUserData,
                                          connectionEnd->refNum);
        return((long)True);
     }

     /* If we don't have a handler or if there's no attention code, don't
        accept the attention.  Note that we only try to use the event
        handler if we don't have an attention handler and there is no
        GetAnythingPending and we're not already doing an attention
        event. */

     EnterCriticalSection();
     if (connectionEnd->incomingAttentionHandler is Empty and
         not connectionEnd->getAnythingPending)
     {
        receiveAttentionEventHandler =
              connectionEnd->receiveAttentionEventHandler;
        receiveAttentionEventContext =
              connectionEnd->receiveAttentionEventContext;
     }
     if ((connectionEnd->incomingAttentionHandler is Empty and
          not connectionEnd->getAnythingPending and
          receiveAttentionEventHandler is Empty) or
         connectionEnd->attentionEventInProgress or
         datagramLength < AdspAttentionDataOffset or
         (connectionEnd->getAnythingPending and
          connectionEnd->getAnythingBufferSize <
              datagramLength - AdspAttentionCodeOffset))
     {
        LeaveCriticalSection();
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        if (outgoingAttentionAckHandler isnt empty)
           (*outgoingAttentionAckHandler)(ATnoError,
                                          outgoingAttentionAckUserData,
                                          connectionEnd->refNum);
        return((long)True);
     }
     if (receiveAttentionEventHandler isnt Empty)
        connectionEnd->attentionEventInProgress = True;
     LeaveCriticalSection();

     /* Okay, the following is a little tricky and it's after 11:00pm on
        a Sunday night and I'm not sure I should be thinking this hard.
        Anyhow, if we're using an attention handler we only want to "ack"
        the attention if it is accepted by the handler -- that is only if
        the handler returns that it accepted some data or if when we get
        back and there is a posted GetAttention or GetAnything.  In the latter
        case we'll go ahead and supply THIS attention message to the newly
        posted handler (or GetAnything).  If the event handler accepted
        any bytes, we don't supply this attention to a handler that may
        have been posted by the attention handler. */

     if (receiveAttentionEventHandler isnt Empty)
     {
        /* Note for indications, the attention code is just the first two
           bytes of the attention buffer. */

        bytesAccepted = (*receiveAttentionEventHandler)
                             (connectionEnd->refNum,
                              receiveAttentionEventContext,
                              datagram + AdspAttentionCodeOffset,
                              datagramLength - AdspAttentionCodeOffset,
                              datagramLength - AdspAttentionCodeOffset);
        if (bytesAccepted is 0 and
            connectionEnd->incomingAttentionHandler is Empty and
            (not connectionEnd->getAnythingPending or
             (connectionEnd->getAnythingPending and
              connectionEnd->getAnythingBufferSize < datagramLength -
                   AdspAttentionCodeOffset)))
        {
           /* No apparent interest, don't accept the attention. */

           connectionEnd->attentionEventInProgress = False;
           HandleAdspPackets();
           HandleDeferredTimerChecks();
           if (outgoingAttentionAckHandler isnt empty)
              (*outgoingAttentionAckHandler)(ATnoError,
                                             outgoingAttentionAckUserData,
                                             connectionEnd->refNum);
           return((long)True);

        }
     }

     /* Okay, Ack and accept the attention. */

     if ((packet = NewBufferDescriptor(AdspDataOffset)) is Empty)
     {
        ErrorLog("AdspPacketIn", ISevError, __LINE__, port,
                 IErrAdspOutOfMemory, IMsgAdspOutOfMemory,
                 Insert0());
        connectionEnd->attentionEventInProgress = False;
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        if (outgoingAttentionAckHandler isnt empty)
           (*outgoingAttentionAckHandler)(ATnoError,
                                          outgoingAttentionAckUserData,
                                          connectionEnd->refNum);
        return((long)True);
     }
     connectionEnd->receiveAttentionSeqNum += 1;
     BuildAdspHeader(connectionEnd, packet->data, AdspControlFlag +
                     AdspAttentionFlag);
     if (DeliverDdp(connectionEnd->socket,
                    connectionEnd->remoteAddress,
                    DdpProtocolAdsp,
                    packet,
                    AdspDataOffset,
                    Empty, Empty, 0) isnt ATnoError)
     {
        connectionEnd->receiveAttentionSeqNum -= 1;   /* Unaccept it. */
        ErrorLog("AdspPacketIn", ISevError, __LINE__, port,
                 IErrAdspBadAckSend, IMsgAdspBadAckSend,
                 Insert0());
        connectionEnd->attentionEventInProgress = False;
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        if (outgoingAttentionAckHandler isnt empty)
           (*outgoingAttentionAckHandler)(ATnoError,
                                          outgoingAttentionAckUserData,
                                          connectionEnd->refNum);
        return((long)True);
     }

     /* If the attention was already accepted by an event handler, we're
        finished now. */

     if (bytesAccepted isnt 0)
     {
        connectionEnd->attentionEventInProgress = False;
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        if (outgoingAttentionAckHandler isnt empty)
           (*outgoingAttentionAckHandler)(ATnoError,
                                          outgoingAttentionAckUserData,
                                          connectionEnd->refNum);
        return((long)True);
     }

     /* Move the data into user space, call the user's handler and we're
        set. */

     attentionDataSize = datagramLength - AdspAttentionDataOffset;
     if (connectionEnd->getAnythingPending)
     {
        /* For a GetAnything, the attention code is just the first two
           bytes of the data. */

        attentionOpaqueBuffer = connectionEnd->getAnythingOpaqueBuffer;
        MoveToOpaque(attentionOpaqueBuffer, 0,
                     datagram + AdspAttentionCodeOffset,
                     sizeof(short unsigned));
        MoveToOpaque(attentionOpaqueBuffer, sizeof(short unsigned),
                     datagram + AdspAttentionDataOffset,
                     attentionDataSize);
        attentionDataSize += sizeof(short unsigned);
        getAnythingRoutine = connectionEnd->getAnythingCompletionRoutine;
        userData = connectionEnd->getAnythingUserData;
        connectionEnd->getAnythingPending = False;
     }
     else
     {
        attentionOpaqueBuffer = connectionEnd->incomingAttentionOpaqueBuffer;
        MoveShortWireToMachine(datagram + AdspAttentionCodeOffset,
                               attentionCode);
        MoveToOpaque(attentionOpaqueBuffer, 0,
                     datagram + AdspAttentionDataOffset,
                     attentionDataSize);
        incomingAttentionHandler = connectionEnd->incomingAttentionHandler;
        userData = connectionEnd->incomingAttentionUserData;
        connectionEnd->incomingAttentionHandler = Empty;   /* Been used now. */
     }
     refNum = connectionEnd->refNum;
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     if (outgoingAttentionAckHandler isnt empty)
        (*outgoingAttentionAckHandler)(ATnoError,
                                       outgoingAttentionAckUserData,
                                       connectionEnd->refNum);
     if (getAnythingRoutine isnt Empty)
        (*getAnythingRoutine)(ATnoError, userData, refNum, True,
                              attentionOpaqueBuffer,
                              attentionDataSize, False);
     else
        (*incomingAttentionHandler)(ATnoError, userData, refNum,
                                    attentionCode, attentionOpaqueBuffer,
                                    attentionDataSize);
     return((long)True);

  }  /* Handle Attention packets */

  /* Can we use "remoteNextReceiveSeqNum" to "Ack" any pending data? */

  if (UnsignedBetweenWithWrap(connectionEnd->retransmitSeqNum,
                              connectionEnd->sendSeqNum,
                              remoteNextReceiveSeqNum))
  {
     long sendSize;

     #if VerboseMessages
        if (remoteNextReceiveSeqNum isnt connectionEnd->retransmitSeqNum)
           printf("AsdpPacketIn: have ack for %u to %u for refNum %d.\n",
                  connectionEnd->retransmitSeqNum,
                  remoteNextReceiveSeqNum - (long unsigned)1,
                  connectionEnd->refNum);
     #endif
     if (not DiscardFromBufferQueue(&connectionEnd->sendQueue,
                                    (long)(remoteNextReceiveSeqNum -
                                           connectionEnd->retransmitSeqNum),
                                    &connectionEnd->nextSendQueue))
        ErrorLog("AdspPacketIn", ISevError, __LINE__, port,
                 IErrAdspBufferQueueError, IMsgAdspBufferQueueError,
                 Insert0());
     connectionEnd->retransmitSeqNum = remoteNextReceiveSeqNum;

     /* If the send window opened up some and it was zero before indicate the
        "sendOkay" event.  */

     if ((sendSize = MaxSendSize(connectionEnd)) isnt 0 and
         connectionEnd->sendWindowHasClosed and
         connectionEnd->sendOkayEventHandler isnt Empty)
     {
        connectionEnd->sendWindowHasClosed = False;
        (*connectionEnd->sendOkayEventHandler)(connectionEnd->refNum,
                                               connectionEnd->
                                                      sendOkayEventContext,
                                               sendSize);
     }
  }

  /* We almost always can use the header values to update sendWindowSeqNum. */

  newSendWindowSeqNum = remoteNextReceiveSeqNum +
                        (long unsigned)remoteReceiveWindowSize -
                        (long unsigned)1;
  if (UnsignedGreaterWithWrap(newSendWindowSeqNum,
                              connectionEnd->sendWindowSeqNum))
     connectionEnd->sendWindowSeqNum = newSendWindowSeqNum;
  else
     if (connectionEnd->sendWindowSeqNum isnt newSendWindowSeqNum)
        #if VerboseMessages
           ErrorLog("AdspPacketIn", ISevWarning, __LINE__, port,
                    IErrAdspShrinkingWindow, IMsgAdspShrinkingWindow,
                    Insert0());
        #else
           ;
        #endif

  /* Don't handle "ack" requests now... do it after we've processed any
     data in the incoming packet, so we can ack that too. */

  /* Handle the remainder of the Control packets (we've already taken care
     of the various Opens). */

  if (descriptor & AdspControlFlag)
  {
     /* Ack if requested, send any data too. */

     if (descriptor & AdspAckRequestFlag)
        SendData(connectionEnd);

     switch(controlCode)
     {
        case AdspProbeOrAckCode:

           /* If it was a "probe" it would have had its AckRequest flag set,
              so we've handled it above; if it was an "ack", we've already set
              lastContactTime -- so, we're set.  Otherwise, maybe some data was
              really acked, and we can send some more data. */

           if (not (descriptor & AdspAckRequestFlag) and
               BufferQueueSize(&connectionEnd->nextSendQueue) isnt 0 and
               connectionEnd->sendSeqNum isnt
                   connectionEnd->sendWindowSeqNum + 1)
              SendData(connectionEnd);
           break;

        case AdspCloseConnectionCode:
           AdspCloseConnection(connectionEnd->refNum, True);
           break;

        case AdspForwardResetCode:

           /* Is the forward reset within range? */

           if (UnsignedBetweenWithWrap(connectionEnd->receiveSeqNum,
                                       connectionEnd->receiveSeqNum +
                                            (long unsigned)
                                             (connectionEnd->receiveWindowSize),
                                       remoteFirstByteSeqNum))
           {
              /* Yes, do the reset. */

              connectionEnd->receiveSeqNum = remoteFirstByteSeqNum;
              connectionEnd->receiveWindowSize = connectionEnd->receiveQueueMax;
              FreeBufferQueue(&connectionEnd->receiveQueue);
              connectionEnd->incomingForwardReset = True;
           }

           /* Ack regardless of whether we accepted the reset... */

           if ((packet = NewBufferDescriptor(AdspDataOffset)) is Empty)
           {
              ErrorLog("AdspPacketIn", ISevError, __LINE__, port,
                       IErrAdspOutOfMemory, IMsgAdspOutOfMemory,
                       Insert0());
              HandleAdspPackets();
              HandleDeferredTimerChecks();
              return((long)True);
           }
           BuildAdspHeader(connectionEnd, packet->data, AdspControlFlag +
                           AdspForwardResetAckCode);
           if (DeliverDdp(connectionEnd->socket,
                          connectionEnd->remoteAddress,
                          DdpProtocolAdsp,
                          packet,
                          AdspDataOffset,
                          Empty, Empty, 0) isnt ATnoError)
              ErrorLog("AdspPacketIn", ISevError, __LINE__, port,
                       IErrAdspBadFrwdResetAckSend, IMsgAdspBadFrwdResetAckSend,
                       Insert0());

           /* Complete a read if we can... ReadData will undefer. */

           ReadData(connectionEnd, False, Empty, Empty);
           return((long)True);

        case AdspForwardResetAckCode:

           /* Do we want an Ack and is it a "valid" Ack? */

           if (not connectionEnd->outgoingForwardReset)
              break;
           if (not UnsignedBetweenWithWrap(connectionEnd->sendSeqNum,
                                           connectionEnd->sendWindowSeqNum +
                                                 (long unsigned)1,
                                           remoteNextReceiveSeqNum))
              break;

           /* Okay, note the Ack -- if no completion routine, we're
              finished. */

           connectionEnd->outgoingForwardReset = False;
           CancelTimer(connectionEnd->forwardResetTimerId);
           if (connectionEnd->forwardResetAckHandler is empty)
              break;

           /* Let the user know that the forward reset took. */

           userData = connectionEnd->forwardResetAckUserData;
           forwardResetAckHandler = connectionEnd->forwardResetAckHandler;
           refNum = connectionEnd->refNum;
           HandleAdspPackets();
           HandleDeferredTimerChecks();
           (*forwardResetAckHandler)(ATnoError, userData, refNum);
           return((long)True);

        case AdspRetransmitCode:

           /* We've modified retransmitSeqNum to match remoteNextReceiveSeqNum
              if the remote side acked any data, now if remoteNextReceiveSeqNum
              is within range we should back up retransmitSeqNum and the
              nextSendQueue to the start of the retransmit queue and try again
              to send the data. */

           if (UnsignedBetweenWithWrap(connectionEnd->retransmitSeqNum,
                                       connectionEnd->sendSeqNum,
                                       remoteNextReceiveSeqNum))
           {
              connectionEnd->nextSendQueue = connectionEnd->sendQueue;
              connectionEnd->sendSeqNum = connectionEnd->retransmitSeqNum;
              SendData(connectionEnd);
           }
           break;

        default:
           ErrorLog("AdspPacketIn", ISevWarning, __LINE__, port,
                    IErrAdspFunnyValue, IMsgAdspFunnyValue,
                    Insert0());
           break;
     }

     /* All set. */

     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);

  }  /* The rest of the Control codes */

  /* Okay, lastly, we might have some data!  Check it out. */

  if (connectionEnd->receiveSeqNum isnt remoteFirstByteSeqNum)
  {
     #if VerboseMessages
        printf("Rejecting an out of seq packet for refNum %d.\n",
               connectionEnd->refNum);
     #endif
     if ((connectionEnd->outOfSequencePackets += 1) >= OutOfSequencePacketsMax)
     {
        #if VerboseMessages
           printf("Sending retransmit advice control packet for refNum %d.\n",
                  connectionEnd->refNum);
        #endif
        if ((packet = NewBufferDescriptor(AdspDataOffset)) is Empty)
        {
           ErrorLog("AdspPacketIn", ISevError, __LINE__, port,
                    IErrAdspOutOfMemory, IMsgAdspOutOfMemory,
                    Insert0());
           HandleAdspPackets();
           HandleDeferredTimerChecks();
           return((long)True);
        }
        BuildAdspHeader(connectionEnd, packet->data, AdspControlFlag +
                        AdspRetransmitCode);
        if (DeliverDdp(connectionEnd->socket,
                       connectionEnd->remoteAddress,
                       DdpProtocolAdsp,
                       packet,
                       AdspDataOffset,
                       Empty, Empty, 0) isnt ATnoError)
           ErrorLog("AdspPacketIn", ISevError, __LINE__, port,
                    IErrAdspBadRetranSend, IMsgAdspBadRetranSend,
                    Insert0());
        connectionEnd->outOfSequencePackets = 0;
     }

     /* Ack if requested, send any data too. */

     if (descriptor & AdspAckRequestFlag)
        SendData(connectionEnd);

     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);
  }
  connectionEnd->outOfSequencePackets = 0;
  endOfMessage = ((descriptor & AdspEndOfMessageFlag) isnt 0);
  dataSize = datagramLength - AdspDataOffset;
  if (dataSize + endOfMessage is 0)
  {
     #if VerboseMessages
        printf("Rejecting a no-data packet for refNum %d.\n",
               connectionEnd->refNum);
     #endif

     /* Ack if requested, send any data too. */

     if (descriptor & AdspAckRequestFlag)
        SendData(connectionEnd);

     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);
  }
  if (dataSize + endOfMessage > connectionEnd->receiveWindowSize)
  {
     #if VerboseMessages
        printf("Rejecting a packet due to too much data for refNum %d.\n",
               connectionEnd->refNum);
     #endif

     /* Ack if requested, send any data too. */

     if (descriptor & AdspAckRequestFlag)
        SendData(connectionEnd);

     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return((long)True);
  }

  /* Accept the data. */

  if (not AddToBufferQueue(&connectionEnd->receiveQueue,
                           datagram, AdspDataOffset, dataSize, False,
                           endOfMessage, empty))
     ErrorLog("AdspPacketIn", ISevError, __LINE__, port,
              IErrAdspBufferQueueError, IMsgAdspBufferQueueError,
              Insert0());
  #if VerboseMessages
     printf("Accepting %u to %u for refNum %d.\n",
            connectionEnd->receiveSeqNum,
            connectionEnd->receiveSeqNum + (long unsigned)dataSize +
                   (long unsigned)endOfMessage - (long unsigned)1,
            connectionEnd->refNum);
  #endif
  connectionEnd->receiveSeqNum += (long unsigned)(dataSize + endOfMessage);
  connectionEnd->receiveWindowSize -= (dataSize + endOfMessage);
  if (connectionEnd->receiveWindowSize < 0)
  {
     ErrorLog("AdspPacketIn", ISevError, __LINE__, port,
              IErrAdspBadWindowSize, IMsgAdspBadWindowSize,
              Insert0());
     connectionEnd->receiveWindowSize = 0;
  }

  /* Ack if requested, send any data too. */

  if (descriptor & AdspAckRequestFlag)
     SendData(connectionEnd);

  /* Lastly, try to handle any outstanding AdspRead().

              ** ReadData() will undefer **
  */

  ReadData(connectionEnd, False, Empty, Empty);
  return((long)True);

}  /* AdspPacketIn */

/* @Add new static routines here@ */

ExternForVisibleFunction ConnectionListenerInfo
              FindConnectionListenerByRefNum(long refNum)
{
  ConnectionListenerInfo connectionListenerInfo;

  /* Walk the list and find the guy. */

  for (connectionListenerInfo = connectionListenerList;
       connectionListenerInfo isnt empty;
       connectionListenerInfo = connectionListenerInfo->next)
     if (connectionListenerInfo->connectionListenerRefNum is refNum)
        break;

  return(connectionListenerInfo);

}  /* FindConnectionListenerByRefNum */

ExternForVisibleFunction Boolean UnsignedGreaterWithWrap(long unsigned high,
                                                         long unsigned low)
{
  /* Do we have a presumed wrap? */

  if (high < 0x80000 and low > 0x10000)
     return(True);
  else
     return(high > low);

}  /* UnsignedGreatWithWrap */

ExternForVisibleFunction Boolean UnsignedBetweenWithWrap(long unsigned low,
                                                         long unsigned high,
                                                         long unsigned target)
{

  if (low <= high)
     return(target >= low and target <= high);

  /* Otherwise, assume an unsigned wrap at zero lies between high and low. */

  return(target >= low or target <= high);

}  /* UnsignedBetweenWithWrap */

ExternForVisibleFunction long MaxSendSize(ConnectionEnd connectionEnd)
{
  long sendSize;

  /* The answer is the remaining available (to fill) space in the retransmit
     queue -- this includes data we're saving for possible retransmit as well
     as data we haven't sent yet.  Actually, this could go negative because
     BufferQueueSize counts EOMs and sendQueueMax doesn't -- answer with zero
     if this happens. */

  sendSize = connectionEnd->sendQueueMax -
                        BufferQueueSize(&connectionEnd->sendQueue);
  if (sendSize < 0)
     sendSize = (long)0;

  return(sendSize);

}  /* MaxSendSize */

ExternForVisibleFunction void
     DecodeAdspHeader(char far *datagram,
                      short unsigned far *connectionId,
                      long unsigned far *firstByteSeqNum,
                      long unsigned far *nextReceiveSeqNum,
                      long far *receiveWindowSize,
                      int far *descriptor)
{
  short unsigned windowSizeTemp;

  MoveShortWireToMachine(datagram + AdspSourceConnectionIdOffset,
                         *connectionId);
  MoveLongWireToMachine(datagram + AdspFirstByteSeqNumOffset,
                        *firstByteSeqNum);
  MoveLongWireToMachine(datagram + AdspNextReceiveByteSeqNumOffset,
                        *nextReceiveSeqNum);
  MoveShortWireToMachine(datagram + AdspReceiveWindowSizeOffset,
                         windowSizeTemp);
  *receiveWindowSize = windowSizeTemp;
  *descriptor = (unsigned char)datagram[AdspDescriptorOffset];
  return;

}  /* DecodeAdspHeader */

ExternForVisibleFunction void far
         ForwardResetTimerExpired(long unsigned timerId,
                                  int dataSize,
                                  char far *additionalData)
{
  long refNum;
  ConnectionEnd connectionEnd;
  BufferDescriptor datagram;

  /* Validate and verify our additional data. */

  if (dataSize isnt sizeof(refNum))
  {
     ErrorLog("ForwardResetTimerExpired", ISevError, __LINE__, UnknownPort,
              IErrAdspBadData, IMsgAdspBadData,
              Insert0());
     HandleDeferredTimerChecks();
     return;
  }
  DeferAdspPackets();
  refNum = *(long *)additionalData;
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     ErrorLog("RetransmitTimerExpired", ISevError, __LINE__, UnknownPort,
              IErrAdspConnectionLost, IMsgAdspConnectionLost,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return;
  }
  if (not connectionEnd->outgoingForwardReset)
  {
     ErrorLog("RetransmitTimerExpired", ISevError, __LINE__, UnknownPort,
              IErrAdspResetNotPending, IMsgAdspResetNotPending,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return;
  }

  /* Build the Forward reset packet and send it on its way. */

  if ((datagram = NewBufferDescriptor(AdspDataOffset)) is Empty)
  {
     ErrorLog("RetransmitTimerExpired", ISevError, __LINE__, UnknownPort,
              IErrAdspOutOfMemory, IMsgAdspOutOfMemory,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return;
  }
  BuildAdspHeader(connectionEnd, datagram->data, AdspControlFlag +
                  AdspForwardResetCode);
  if (DeliverDdp(connectionEnd->socket,
                 connectionEnd->remoteAddress,
                 DdpProtocolAdsp,
                 datagram,
                 AdspDataOffset,
                 Empty, Empty, 0) isnt ATnoError)
  {
     ErrorLog("ForwardResetTimerExpired", ISevError, __LINE__, UnknownPort,
              IErrAdspBadFrwdResetSend, IMsgAdspBadFrwdResetSend,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return;
  }

  /* Restart the retry timer. */

  connectionEnd->forwardResetTimerId =
              StartTimer(ForwardResetTimerExpired,
                         ForwardResetTimerIntenvalSecs,
                         sizeof(connectionEnd->refNum),
                         (char *)&connectionEnd->refNum);

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return;

}  /* ForwardResetTimerExpired */

ExternForVisibleFunction void far
         RetransmitTimerExpired(long unsigned timerId,
                                int dataSize,
                                char far *additionalData)
{
  long refNum;
  ConnectionEnd connectionEnd;
  BufferDescriptor datagram;

  /* Validate and verify our additional data. */

  if (dataSize isnt sizeof(refNum))
  {
     ErrorLog("RetransmitTimerExpired", ISevError, __LINE__, UnknownPort,
              IErrAdspBadData, IMsgAdspBadData,
              Insert0());
     HandleDeferredTimerChecks();
     return;
  }
  DeferAdspPackets();
  refNum = *(long *)additionalData;
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     ErrorLog("RetransmitTimerExpired", ISevError, __LINE__, UnknownPort,
              IErrAdspConnectionLost, IMsgAdspConnectionLost,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return;
  }

  /* We only have work to do if the remote side has not accepted any data
     since last time we were here AND we have previously sent but still
     un-acked data pending. */

  if (connectionEnd->retransmitSeqNum is
              connectionEnd->lastRetransmitSeqNum and
      BufferQueueSize(&connectionEnd->sendQueue) isnt
              BufferQueueSize(&connectionEnd->nextSendQueue))
  {
     /* Okay, we may have a problem, on the first time, just request an Ack --
        maybe the data was received but just not acked. */

     if (not connectionEnd->retransmitAckRequestSent)
     {
        #if VerboseMessages
           printf("RetransmitTimerExpired: requesting Ack for refNum %d.\n",
                  connectionEnd->refNum);
        #endif
        if ((datagram = NewBufferDescriptor(AdspDataOffset)) is Empty)
           ErrorLog("RetransmitTimerExpired", ISevError, __LINE__, UnknownPort,
                    IErrAdspOutOfMemory, IMsgAdspOutOfMemory,
                    Insert0());
        else
        {
           BuildAdspHeader(connectionEnd, datagram->data, AdspControlFlag +
                           AdspAckRequestFlag + AdspProbeOrAckCode);
           if (DeliverDdp(connectionEnd->socket,
                          connectionEnd->remoteAddress,
                          DdpProtocolAdsp,
                          datagram,
                          AdspDataOffset,
                          Empty, Empty, 0) isnt ATnoError)
              ErrorLog("RetransmitTimerExpired", ISevError, __LINE__,
                       UnknownPort, IErrAdspBadProbeSend, IMsgAdspBadProbeSend,
                       Insert0());
           connectionEnd->retransmitAckRequestSent = True;
        }
     }
     else
     {
        /* Otherwise, rewind sendSeqNum and try to resend. */

        #if VerboseMessages
           printf("RetransmitTimerExpired: rewinding send queue for "
                  "refNum %d.\n", connectionEnd->refNum);
        #endif
        connectionEnd->sendSeqNum = connectionEnd->retransmitSeqNum;
        connectionEnd->nextSendQueue = connectionEnd->sendQueue;
        SendData(connectionEnd);
        connectionEnd->retransmitAckRequestSent = False;
     }
  }
  else
  {
     /* Tag that all's well. */

     connectionEnd->lastRetransmitSeqNum = connectionEnd->retransmitSeqNum;
     connectionEnd->retransmitAckRequestSent = False;
  }

  /* Restart the retransmit timer. */

  connectionEnd->retransmitTimerId = StartTimer(RetransmitTimerExpired,
                                                RetransmitTimerIntervalSeconds,
                                                sizeof(refNum),
                                                (char *)&refNum);
  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return;

}  /* RetransmitTimerExpired */

ExternForVisibleFunction void far ProbeTimerExpired(long unsigned timerId,
                                                    int dataSize,
                                                    char far *additionalData)
{
  long refNum;
  ConnectionEnd connectionEnd;
  long unsigned now = CurrentRelativeTime();
  BufferDescriptor datagram;

  /* Validate and verify our additional data. */

  if (dataSize isnt sizeof(refNum))
  {
     ErrorLog("ProbeTimerExpired", ISevError, __LINE__, UnknownPort,
              IErrAdspBadData, IMsgAdspBadData,
              Insert0());
     HandleDeferredTimerChecks();
     return;
  }
  DeferAdspPackets();
  refNum = *(long *)additionalData;
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     ErrorLog("ProbeTimerExpired", ISevError, __LINE__, UnknownPort,
              IErrAdspConnectionLost, IMsgAdspConnectionLost,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return;
  }

  /* Has the connection died? */

  if (connectionEnd->lastContactTime + ConnectionDeadSeconds <= now)
  {
     #if VerboseMessages
        printf("Removing dead connection RefNum = %d.\n",
               connectionEnd->refNum);
     #endif
     AdspCloseConnection(refNum, True);
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return;
  }

  /* If we haven't heard from the other side recently, send out a probe. */

  if (connectionEnd->lastContactTime + ProbeTimerIntervalSeconds <= now)
  {
     #if VerboseMessages
        printf("Sending probe for RefNum = %d to %d.%d.%d.\n",
               connectionEnd->refNum,
               connectionEnd->remoteAddress.networkNumber,
               connectionEnd->remoteAddress.nodeNumber,
               connectionEnd->remoteAddress.socketNumber);
     #endif
     if ((datagram = NewBufferDescriptor(AdspDataOffset)) is Empty)
        ErrorLog("ProbeTimerExpired", ISevError, __LINE__, UnknownPort,
                 IErrAdspOutOfMemory, IMsgAdspOutOfMemory,
                 Insert0());
     else
     {
        BuildAdspHeader(connectionEnd, datagram->data, AdspControlFlag +
                        AdspAckRequestFlag + AdspProbeOrAckCode);
        if (DeliverDdp(connectionEnd->socket,
                       connectionEnd->remoteAddress,
                       DdpProtocolAdsp,
                       datagram,
                       AdspDataOffset,
                       Empty, Empty, 0) isnt ATnoError)
           ErrorLog("ProbeTimerExpired", ISevError, __LINE__, UnknownPort,
                    IErrAdspBadProbeSend, IMsgAdspBadProbeSend,
                    Insert0());
     }
  }

  /* Restart the probe timer. */

  connectionEnd->probeTimerId = StartTimer(ProbeTimerExpired,
                                           ProbeTimerIntervalSeconds,
                                           sizeof(refNum),
                                           (char *)&refNum);
  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return;

}  /* ProbeTimerExpired */

ExternForVisibleFunction void far
         SendAttentionTimerExpired(long unsigned timerId,
                                   int dataSize,
                                   char far *additionalData)
{
  long refNum;
  ConnectionEnd connectionEnd;

  /* Validate and verify our additional data. */

  if (dataSize isnt sizeof(refNum))
  {
     ErrorLog("SendAttentionTimerExpired", ISevError, __LINE__, UnknownPort,
              IErrAdspBadData, IMsgAdspBadData,
              Insert0());
     HandleDeferredTimerChecks();
     return;
  }
  DeferAdspPackets();
  refNum = *(long *)additionalData;
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     ErrorLog("SendAttentionTimerExpired", ISevError, __LINE__, UnknownPort,
              IErrAdspConnectionLost, IMsgAdspConnectionLost,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return;
  }

  /* Are we here for a good reason? */

  if (not connectionEnd->waitingForAttentionAck)
  {
     ErrorLog("SendAttentionTimerExpired", ISevError, __LINE__, UnknownPort,
              IErrAdspTimerNotCanceled, IMsgAdspTimerNotCanceled,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return;
  }

  /* Okay, send the attention and restart the timer. */

  SendAttention(connectionEnd);
  connectionEnd->outgoingAttentionTimerId =
                        StartTimer(SendAttentionTimerExpired,
                                   AttentionTimerIntervalSeconds,
                                   sizeof(connectionEnd->refNum),
                                   (char far *)&connectionEnd->refNum);

  /* All set! */

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return;

}  /* SendAttentionTimerExpired */

ExternForVisibleFunction void far OpenTimerExpired(long unsigned timerId,
                                                   int dataSize,
                                                   char far *additionalData)
{
  long refNum;
  ConnectionEnd connectionEnd;
  AdspOpenCompleteHandler *completionRoutine;
  long unsigned userData;

  /* Validate and verify our additional data. */

  if (dataSize isnt sizeof(refNum))
  {
     ErrorLog("OpenTimerExpired", ISevError, __LINE__, UnknownPort,
              IErrAdspBadData, IMsgAdspBadData,
              Insert0());
     HandleDeferredTimerChecks();
     return;
  }
  DeferAdspPackets();
  refNum = *(long *)additionalData;
  if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
  {
     ErrorLog("OpenTimerExpired", ISevError, __LINE__, UnknownPort,
              IErrAdspConnectionLost, IMsgAdspConnectionLost,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return;
  }

  /* Did we get fully open? */

  if (connectionEnd->connectionState is AdspOpen)
  {
     ErrorLog("OpenTimerExpired", ISevError, __LINE__, UnknownPort,
              IErrAdspTimerNotCanceled, IMsgAdspTimerNotCanceled,
              Insert0());
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return;
  }

  /* If we're not out of attemps, try it again. */

  connectionEnd->openAttemptsCount += 1;
  if (connectionEnd->openAttemptsCount < AdspMaxOpenAttempts)
  {
     SendOpenControl(connectionEnd);
     connectionEnd->openTimerId = StartTimer(OpenTimerExpired,
                                             AdspOpenIntervalSeconds,
                                             sizeof(connectionEnd->refNum),
                                             (char *)&connectionEnd->refNum);
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return;
  }

  /* Okay, we're out of tries... if passive open just get back to that
     state. */

  if (connectionEnd->passiveOpen)
  {
     connectionEnd->seenRemoteOpenRequest = False;
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return;
  }

  /* Otherwise, remove the connection end, call the completion routine and
     give it up. */

  completionRoutine = connectionEnd->openCompletionRoutine;
  userData = connectionEnd->openUserData;
  RemoveConnectionEnd(connectionEnd);
  HandleAdspPackets();
  HandleDeferredTimerChecks();
  (*completionRoutine)(ATadspOpenFailed, userData, refNum, (long)0,
                       unknownAddress);
  return;

}  /* OpenTimerExpired */

ExternForVisibleFunction void SendOpenControl(ConnectionEnd connectionEnd)
{
  BufferDescriptor datagram;
  int descriptor;

  /* Pick what kind of descriptor we want: */

  descriptor = AdspControlFlag;
  if (connectionEnd->connectionState is AdspClosed)
     descriptor += AdspOpenConnectionDenyCode;
  else if (connectionEnd->connectionState is AdspOpen)
     descriptor += AdspOpenConnectionAckCode;
  else if (connectionEnd->seenRemoteOpenRequest)
     descriptor += AdspOpenConnectionReqAndAckCode;
  else
     descriptor += AdspOpenConnectionReqCode;

  #if VerboseMessages
     printf("Sending open control 0x%x for RefNum = %d to %d.%d.%d.\n",
            descriptor, connectionEnd->refNum,
            connectionEnd->remoteAddress.networkNumber,
            connectionEnd->remoteAddress.nodeNumber,
            connectionEnd->remoteAddress.socketNumber);
  #endif

  /* Build an OpenRequest datagram and send it on its way. */

  if ((datagram = NewBufferDescriptor(AdspNextAttentionSeqNumOffset +
                                      sizeof(long))) is Empty)
  {
     ErrorLog("SendOpenControl", ISevError, __LINE__, UnknownPort,
              IErrAdspOutOfMemory, IMsgAdspOutOfMemory,
              Insert0());
     return;
  }
  BuildAdspHeader(connectionEnd, datagram->data, descriptor);
  MoveShortMachineToWire(datagram->data + AdspVersionStampOffset,
                         AdspVersionStamp);
  MoveShortMachineToWire(datagram->data + AdspDestConnectionIdOffset,
                         connectionEnd->remoteConnectionId);
  MoveLongMachineToWire(datagram->data + AdspNextAttentionSeqNumOffset,
                        connectionEnd->receiveAttentionSeqNum);

  if (DeliverDdp(connectionEnd->socket,
                 connectionEnd->remoteAddress,
                 DdpProtocolAdsp,
                 datagram,
                 AdspNextAttentionSeqNumOffset + sizeof(long),
                 Empty, Empty, 0) isnt ATnoError)
     ErrorLog("SendOpenControl", ISevError, __LINE__, UnknownPort,
              IErrAdspBadOpenSend, IMsgAdspBadOpenSend,
              Insert0());
  return;

}  /* SendOpenControl */

ExternForVisibleFunction void SendAttention(ConnectionEnd connectionEnd)
{
  BufferDescriptor datagram;

  #if VerboseMessages
     printf("Sending attention (0x%X) for RefNum = %d to %d.%d.%d.\n",
            connectionEnd->outgoingAttentionCode,
            connectionEnd->refNum,
            connectionEnd->remoteAddress.networkNumber,
            connectionEnd->remoteAddress.nodeNumber,
            connectionEnd->remoteAddress.socketNumber);
  #endif

  /* Build an Attention datagram and send it on its way. */

  if ((datagram = NewBufferDescriptor(AdspAttentionDataOffset +
                        connectionEnd->outgoingAttentionBufferSize)) is Empty)
  {
     ErrorLog("SendAttention", ISevError, __LINE__, UnknownPort,
              IErrAdspOutOfMemory, IMsgAdspOutOfMemory,
              Insert0());
     return;
  }
  BuildAdspHeader(connectionEnd, datagram->data, AdspAttentionFlag +
                  AdspAckRequestFlag);
  MoveShortMachineToWire(datagram->data + AdspAttentionCodeOffset,
                         connectionEnd->outgoingAttentionCode);
  if (connectionEnd->outgoingAttentionBuffer isnt empty)
     MoveMem(datagram->data + AdspAttentionDataOffset,
             connectionEnd->outgoingAttentionBuffer,
             connectionEnd->outgoingAttentionBufferSize);

  if (DeliverDdp(connectionEnd->socket,
                 connectionEnd->remoteAddress,
                 DdpProtocolAdsp,
                 datagram,
                 AdspAttentionDataOffset +
                        connectionEnd->outgoingAttentionBufferSize,
                 Empty, Empty, 0) isnt ATnoError)
     ErrorLog("SendAttention", ISevError, __LINE__, UnknownPort,
              IErrAdspBadAttnSend, IMsgAdspBadAttnSend,
              Insert0());
  return;

}  /* SendAttntion */

ExternForVisibleFunction void SendData(ConnectionEnd connectionEnd)
{
  BufferDescriptor datagram;
  long windowSize;
  long dataSize, tempDataSize;
  Boolean endOfMessage;
  int descriptor;

  /* If there is no data to send (or the remote can't handle any more data),
     just send an Ack. */

  dataSize = BufferQueueSize(&connectionEnd->nextSendQueue);
  windowSize = (long)(connectionEnd->sendWindowSeqNum -
                      connectionEnd->sendSeqNum +
                      (long unsigned)1);
  if (windowSize < 0)
  {
     ErrorLog("SendData", ISevError, __LINE__, UnknownPort,
              IErrAdspBufferQueueError, IMsgAdspBufferQueueError,
              Insert0());
     return;
  }

  if (dataSize is 0 or windowSize is 0)
  {
     #if VerboseMessages
        printf("Sending non-data Ack for RefNum = %d to %d.%d.%d.\n",
               connectionEnd->refNum,
               connectionEnd->remoteAddress.networkNumber,
               connectionEnd->remoteAddress.nodeNumber,
               connectionEnd->remoteAddress.socketNumber);
     #endif

     /* Build an Ack datagram and send it on its way. */

     if ((datagram = NewBufferDescriptor(AdspDataOffset)) is Empty)
     {
        ErrorLog("SendData", ISevError, __LINE__, UnknownPort,
                 IErrAdspOutOfMemory, IMsgAdspOutOfMemory,
                 Insert0());
        return;
     }
     descriptor = AdspControlFlag + AdspProbeOrAckCode +
                  ((windowSize is 0) ? AdspAckRequestFlag : 0);
     BuildAdspHeader(connectionEnd, datagram->data, descriptor);

     if (DeliverDdp(connectionEnd->socket,
                    connectionEnd->remoteAddress,
                    DdpProtocolAdsp,
                    datagram,
                    AdspDataOffset,
                    Empty, Empty, Empty) isnt ATnoError)
        ErrorLog("SendData", ISevError, __LINE__, UnknownPort,
                 IErrAdspBadNonDataAckSend, IMsgAdspBadNonDataAckSend,
                 Insert0());
     return;
  }

  /* Okay, we have some data to send. */

  windowSize = Min(windowSize, dataSize);
  while (windowSize > 0)
  {
     /* Compute how much data we can send. */

     dataSize = MaxNextReadSizeOfBufferQueue(&connectionEnd->nextSendQueue,
                                             &endOfMessage);
     if (dataSize > AdspMaxDataSize)
        dataSize = AdspMaxDataSize;
     if (dataSize > windowSize)
        dataSize = windowSize;

     /* Get a buffer descriptor for our write. */

     if ((datagram = NewBufferDescriptor(AdspDataOffset + dataSize)) is Empty)
     {
        ErrorLog("SendData", ISevError, __LINE__, UnknownPort,
                 IErrAdspOutOfMemory, IMsgAdspOutOfMemory,
                 Insert0());
        return;
     }

     /* Send either up to the next EOM, or all remaining data, or 572
        bytes... */

     tempDataSize = ReadFromBufferQueue(&connectionEnd->nextSendQueue,
                                        datagram->data, AdspDataOffset,
                                        dataSize, False, &endOfMessage);
     if (tempDataSize isnt dataSize or
         dataSize + endOfMessage is 0 or
         dataSize > AdspMaxDataSize)
     {
        ErrorLog("SendData", ISevError, __LINE__, UnknownPort,
                 IErrAdspBufferQueueError, IMsgAdspBufferQueueError,
                 Insert0());
        FreeBufferChain(datagram);
        return;
     }

     /* Send over EOM and our ack requirements, if any. */

     descriptor = endOfMessage ? AdspEndOfMessageFlag : 0;
     if (windowSize is (dataSize + endOfMessage))
        descriptor += AdspAckRequestFlag;
     BuildAdspHeader(connectionEnd, datagram->data, descriptor);

     #if VerboseMessages
         printf("SendData: sending %u to %u for refNum %d.\n",
                connectionEnd->sendSeqNum,
                connectionEnd->sendSeqNum +
                        (long unsigned)dataSize +
                        (long unsigned)endOfMessage -
                        (long unsigned)1,
                connectionEnd->refNum);
     #endif
     if (DeliverDdp(connectionEnd->socket,
                    connectionEnd->remoteAddress,
                    DdpProtocolAdsp,
                    datagram,
                    (int)(AdspDataOffset + dataSize),
                    Empty, Empty, 0) isnt ATnoError)
        ErrorLog("SendData", ISevError, __LINE__, UnknownPort,
                 IErrAdspBadNonDataAckSend, IMsgAdspBadNonDataAckSend,
                 Insert0());

     /* Move our send seqNum up. */

     connectionEnd->sendSeqNum += (long unsigned)(dataSize + endOfMessage);
     windowSize -= (dataSize + endOfMessage);
  }
  if (windowSize isnt 0)
     ErrorLog("SendData", ISevError, __LINE__, UnknownPort,
              IErrAdspWindowSizeErrorToo, IMsgAdspWindowSizeErrorToo,
              Insert0());

  /* The deed is done. */

  return;

}  /* SendData */

ExternForVisibleFunction AppleTalkErrorCode
                              ReadData(ConnectionEnd connectionEnd,
                                       Boolean peekOnly,
                                       long far *peekBytesReturned,
                                       Boolean far *peekEndOfMessage)
{
  Boolean endOfMessage;
  long readSize, lookaheadSize, bytesAccepted, bytesRead;
  char far *lookaheadData;
  AdspReceiveHandler *completionRoutine;
  AdspGetAnythingHandler *getAnythingRoutine = Empty;
  long unsigned userData;
  void far *opaqueBuffer;
  long bufferSize, queuedData;
  AdspReceiveEventHandler far *receiveEventHandler;
  long unsigned receiveEventContext;

  /* Move any data from the receive queue into user space.

              ** We undefer **
  */

  if (peekOnly)
  {
     *peekBytesReturned = 0;
     *peekEndOfMessage = False;
  }

  /* If we've gotten a forward reset, inform the user -- no data is passed up
     in this case. */

  if ((peekOnly or
       connectionEnd->receivePending or
       connectionEnd->getAnythingPending) and
      connectionEnd->incomingForwardReset)
  {
     if (connectionEnd->getAnythingPending)
     {
        getAnythingRoutine = connectionEnd->getAnythingCompletionRoutine;
        userData = connectionEnd->getAnythingUserData;
        connectionEnd->getAnythingPending = False;
     }
     else
     {
        completionRoutine = connectionEnd->receiveCompletionRoutine;
        userData = connectionEnd->receiveUserData;
        connectionEnd->receivePending = False;
     }
     connectionEnd->incomingForwardReset = False;

     HandleAdspPackets();
     HandleDeferredTimerChecks();
     if (peekOnly)
        return(ATadspForwardReset);
     else if (getAnythingRoutine isnt Empty)
        (*getAnythingRoutine)(ATadspForwardReset, userData,
                              connectionEnd->refNum, False,
                              Empty, 0, False);
     else
        (*completionRoutine)(ATadspForwardReset, userData,
                             connectionEnd->refNum, empty,
                             0, False);
     return(ATnoError);
  }

  /* Any read request pending?  Any data to supply?  We do this in a loop
     to handle the cases such as packets coming in during a read and
     additional reads or indications need to be done.  Also of a whole big
     blob of messages has just come in and our read completion routine
     posts a new read, we can process a number of messages here. */

  receiveEventHandler = connectionEnd->receiveEventHandler;
  receiveEventContext = connectionEnd->receiveEventContext;
  queuedData = BufferQueueSize(&connectionEnd->receiveQueue);

  if (peekOnly and queuedData is 0)
  {
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return(ATnoError);
  }

  bytesRead = 1;
  while (queuedData > 0 and bytesRead > 0)
  {
     bytesRead = 0;

     /* Check for no pending reads but an unblocked event handler. */

     if (not peekOnly and
         not connectionEnd->receivePending and
         not connectionEnd->getAnythingPending and
         receiveEventHandler isnt Empty and
         connectionEnd->previouslyIndicatedData is 0)
     {
        /* Okay, there is data to be received and an event handler is
           present and receive data indications are unblocked
           (previouslyIndicatedData is 0). */

        connectionEnd->previouslyIndicatedData = queuedData;

        /* Call the indication handler with pointer to buffer chunk with data,
           length of data in buffer chunk as the lookahead size, and the size
           of the buffer queue as available bytes.

           The lookahead size is the number of bytes that can be pulled from
           the "first" buffer chunk on the queue; it may be zero if there is
           just an endOfMessage.  The last argument is the "available data;"
           each EOM in the queue does count as a "byte."  If this routine
           accepts the data, it should indicate so by returning
           "lookaheadSize + endOfMessage."  The "+ endOfMessage" is critical
           in the case where there is only an EOM, and we need to know
           something was accepted (if it was)!

           Further, we are indicating that all remaining data is immediately
           available (we're at the last buffer chunk) if "queuedData" is
           equal to "lookaheadSize + endOfMessage."

           We set "dataEventInProgress" so that any Reads or GetAnythings
           posted during the indication will just be queued (not fullfiled)
           until we return, we'll process them imediately after the indication
           returns.  The allows the user to accept some data, and then read
           data beyond the accepted bytes. */

        lookaheadData = GetLookaheadPointer(&connectionEnd->receiveQueue,
                                            &lookaheadSize);
        connectionEnd->dataEventInProgress = True;
        bytesAccepted = (*receiveEventHandler)(connectionEnd->refNum,
                                               receiveEventContext,
                                               lookaheadData, lookaheadSize,
                                               endOfMessage, queuedData);
        connectionEnd->dataEventInProgress = False;

        /* The user can't accept more than the lookahead! */

        if (bytesAccepted > lookaheadSize + endOfMessage)
           bytesAccepted = lookaheadSize + endOfMessage;
        bytesRead = bytesAccepted;

        if (bytesAccepted isnt 0)
        {
           /* Adjust the buffer queue to account for any accepted data;
              note that if the user accepts some data and also places a
              read for more data, the read will complete with data that
              is logically "after" the accepted data. */

           /* Consume bytesAccepted amount of data.  Adjust our window
              size too, and SendData to notify the other side that we've
              consumed the data. */

           DiscardFromBufferQueue(&connectionEnd->receiveQueue,
                                  bytesAccepted, empty);
           connectionEnd->receiveWindowSize += bytesAccepted;
           if (connectionEnd->receiveWindowSize >
               connectionEnd->receiveQueueMax)
           {
              ErrorLog("ReadData", ISevError, __LINE__, UnknownPort,
                       IErrAdspWindowSizeOverMax, IMsgAdspWindowSizeOverMax,
                       Insert0());
              connectionEnd->receiveWindowSize = connectionEnd->receiveQueueMax;
           }
           SendData(connectionEnd);
        }
     }

     /* Now check for pending reads, either an originally posted read, or
        maybe one posted by the event handler.  */

     if (peekOnly or
         connectionEnd->getAnythingPending or
         connectionEnd->receivePending)
     {
        if (peekOnly or connectionEnd->receivePending)
        {
           opaqueBuffer = connectionEnd->receiveOpaqueBuffer;
           bufferSize = connectionEnd->receiveBufferSize;
        }
        else
        {
           opaqueBuffer = connectionEnd->getAnythingOpaqueBuffer;
           bufferSize = connectionEnd->getAnythingBufferSize;
        }

        /* Read the data into user space; if we're just peeking make sure
           we don't really alter the buffer queue. */

        if (peekOnly)
        {
           BufferQueue originalBufferQueue;

           originalBufferQueue = connectionEnd->receiveQueue;
           readSize = ReadFromBufferQueue(&originalBufferQueue,
                                          opaqueBuffer, 0,
                                          bufferSize, True,
                                          &endOfMessage);
        }
        else
           readSize = ReadAndDiscardFromBufferQueue(&connectionEnd->
                                                           receiveQueue,
                                                    opaqueBuffer, 0,
                                                    bufferSize, True,
                                                    &endOfMessage);
        if (readSize is 0 and not endOfMessage)
        {
           ErrorLog("ReadData", ISevError, __LINE__, UnknownPort,
                    IErrAdspNoData, IMsgAdspNoData,
                    Insert0());
           connectionEnd->previouslyIndicatedData = 0;
           HandleAdspPackets();
           HandleDeferredTimerChecks();
           break;
        }
        bytesRead += (readSize + endOfMessage);

        /* If we're jsut peeking, return what we know, and flee. */

        if (peekOnly)
        {
           *peekBytesReturned = readSize;
           *peekEndOfMessage = endOfMessage;
           HandleAdspPackets();
           HandleDeferredTimerChecks();
           return(ATnoError);
        }

        /* Increase our window size, and return the data to user space.  Do a
           SendData so that the other side knows about the change to our receive
           window size. */

        connectionEnd->receiveWindowSize += (readSize + endOfMessage);
        if (connectionEnd->receiveWindowSize >
            connectionEnd->receiveQueueMax)
        {
           ErrorLog("ReadData", ISevError, __LINE__, UnknownPort,
                    IErrAdspWindowSizeOverMax, IMsgAdspWindowSizeOverMax,
                    Insert0());
           connectionEnd->receiveWindowSize = connectionEnd->receiveQueueMax;
        }

        SendData(connectionEnd);
        if (connectionEnd->getAnythingPending)
        {
           getAnythingRoutine = connectionEnd->getAnythingCompletionRoutine;
           userData = connectionEnd->getAnythingUserData;
           connectionEnd->getAnythingPending = False;
        }
        else
        {
           completionRoutine = connectionEnd->receiveCompletionRoutine;
           userData = connectionEnd->receiveUserData;
           connectionEnd->receivePending = False;
        }

        /* Call the read completion routine. */

        if (getAnythingRoutine isnt Empty)
           (*getAnythingRoutine)(ATnoError, userData, connectionEnd->refNum,
                                 False, opaqueBuffer, readSize, endOfMessage);
        else
           (*completionRoutine)(ATnoError, userData, connectionEnd->refNum,
                                opaqueBuffer, readSize, endOfMessage);
     }

     /* Now we need to adjust the previouslyIndicatedBytes and see if we need
        to go again... */

     connectionEnd->previouslyIndicatedData -=
              Min(bytesRead, connectionEnd->previouslyIndicatedData);
     queuedData = BufferQueueSize(&connectionEnd->receiveQueue);
  }

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return(ATnoError);

}  /* ReadData */

ExternForVisibleFunction void BuildAdspHeader(ConnectionEnd connectionEnd,
                                              char *datagram,
                                              int descriptor)
{
  /* Format a standard ADSP header or an attention ADSP header (based on the
     flags in "descriptor". */

  MoveShortMachineToWire(datagram + AdspSourceConnectionIdOffset,
                         connectionEnd->connectionId);
  if (descriptor & AdspAttentionFlag)
     MoveLongMachineToWire(datagram + AdspThisAttentionSeqNumOffset,
                           connectionEnd->sendAttentionSeqNum);
  else
     MoveLongMachineToWire(datagram + AdspFirstByteSeqNumOffset,
                           connectionEnd->sendSeqNum);
  if (descriptor & AdspAttentionFlag)
     MoveLongMachineToWire(datagram + AdspNextReceiveAttnSeqNumOffset,
                           connectionEnd->receiveAttentionSeqNum);
  else
     MoveLongMachineToWire(datagram + AdspNextReceiveByteSeqNumOffset,
                           connectionEnd->receiveSeqNum);
  if (descriptor & AdspAttentionFlag)
     MoveShortMachineToWire(datagram + AdspReceiveAttentionSizeOffset, 0);
  else
     MoveShortMachineToWire(datagram + AdspReceiveWindowSizeOffset,
                            connectionEnd->receiveWindowSize);

  /* Lastly, move the descriptor too. */

  datagram[AdspDescriptorOffset] = (char)descriptor;
  return;

}  /* BuildAdspHeader */

ExternForVisibleFunction void RemoveConnectionEnd(ConnectionEnd target)
{
  ConnectionEnd connectionEnd, previousConnectionEnd;
  long index;

  #if VerboseMessages
     printf("Removing connection end for RefNum = %d.\n", target->refNum);
  #endif

  /* Remove the target from the RefNum lookup table. */

  if (target->connectionState is AdspOpen or
      target->connectionState is AdspHalfOpen)
  {
     CheckMod(index, target->refNum, NumberOfConnectionEndHashBkts,
              "RemoveConnectionEnd");
     for (previousConnectionEnd = empty,
              connectionEnd = connectionEndRefNumHashBuckets[index];
          connectionEnd isnt empty;
          previousConnectionEnd = connectionEnd,
              connectionEnd = connectionEnd->next)
        if (connectionEnd is target)
           break;
     if (connectionEnd is empty)
        ErrorLog("RemoveConnectionEnd", ISevError, __LINE__, UnknownPort,
                 IErrAdspNotOnRefNumList, IMsgAdspNotOnRefNumList,
                 Insert0());
     else if (previousConnectionEnd is empty)
        connectionEndRefNumHashBuckets[index] = target->next;
     else
        previousConnectionEnd->next = target->next;
  }

  /* Remove the target from the LocalInfo lookup table. */

  if (target->connectionState is AdspOpen or
      target->connectionState is AdspHalfOpen)
  {
     CheckMod(index, target->socket, NumberOfConnectionEndHashBkts,
              "RemoveConnectionEnd");
     for (previousConnectionEnd = empty,
              connectionEnd = connectionEndLocalHashBuckets[index];
          connectionEnd isnt empty;
          previousConnectionEnd = connectionEnd,
              connectionEnd = connectionEnd->nextByLocalInfo)
        if (connectionEnd is target)
           break;
     if (connectionEnd is empty)
        ErrorLog("RemoveConnectionEnd", ISevError, __LINE__, UnknownPort,
                 IErrAdspNotOnRefNumList, IMsgAdspNotOnRefNumList,
                 Insert0());
     else if (previousConnectionEnd is empty)
        connectionEndLocalHashBuckets[index] = target->nextByLocalInfo;
     else
        previousConnectionEnd->nextByLocalInfo = target->nextByLocalInfo;
  }

  /* Remove the target from the RemoteInfo lookup table. */

  if (target->connectionState is AdspOpen)
  {
     CheckMod(index, target->remoteConnectionId,
              NumberOfConnectionEndHashBkts, "RemoveConnectionEnd");
     for (previousConnectionEnd = empty,
              connectionEnd = connectionEndRemoteHashBuckets[index];
          connectionEnd isnt empty;
          previousConnectionEnd = connectionEnd,
              connectionEnd = connectionEnd->nextByRemoteInfo)
        if (connectionEnd is target)
           break;
     if (connectionEnd is empty)
        ErrorLog("RemoveConnectionEnd", ISevError, __LINE__, UnknownPort,
                 IErrAdspNotOnRefNumList, IMsgAdspNotOnRefNumList,
                 Insert0());
     else if (previousConnectionEnd is empty)
        connectionEndRemoteHashBuckets[index] = target->nextByRemoteInfo;
     else
        previousConnectionEnd->nextByRemoteInfo = target->nextByRemoteInfo;
  }

  /* Now we need to know if this was/is the last guy using a specified
     socket.  If so, close the socket.  Socket may have been tagged to "-1"
     if AdspPacketIn gets a ATsocketClosed errorcode. */

  if (target->socket >= 0 and target->closeSocket)
  {
     CheckMod(index, target->socket, NumberOfConnectionEndHashBkts,
              "RemoveConnectionEnd");
     for (connectionEnd = connectionEndLocalHashBuckets[index];
          connectionEnd isnt empty;
          connectionEnd = connectionEnd->nextByLocalInfo)
        if (connectionEnd->socket is target->socket)
           break;
     if (connectionEnd is empty)
     {
        #if VerboseMessages
           printf("Closing socket %d for RefNum = %d.\n", target->socket,
                  target->refNum);
        #endif
        if (CloseSocketOnNode(target->socket, Empty, (long)0) isnt ATnoError)
           ErrorLog("RemoveConnectionEnd", ISevError, __LINE__, UnknownPort,
                    IErrAdspBadSocketClose, IMsgAdspBadSocketClose,
                    Insert0());
     }
  }

  /* Okay, free the two buffer queues. */

  FreeBufferQueue(&target->sendQueue);
  FreeBufferQueue(&target->receiveQueue);

  /* Free the actual connection and and we're all set! */

  Free(target);
  return;

}  /* RemoveConnectionEnd */

ExternForVisibleFunction short unsigned
              GetNextConnectionIdForSocket(long socket)
{
  short unsigned newConnectionId;
  ConnectionEnd connectionEnd;
  long index;
  Boolean wrapped = False;

  /* Pick a new connection ID - make sure it's not in use on the given
     socket. */

  while(True)
  {
     newConnectionId = nextConnectionId;
     if ((nextConnectionId += 1) is 0)
     {
        nextConnectionId = 1;
        if (wrapped)
        {
           ErrorLog("GetNextConnectionIdForSocket", ISevError, __LINE__,
                    UnknownPort, IErrAdspOutOfIds, IMsgAdspOutOfIds,
                    Insert0());
           return(0);
        }
        wrapped = True;
     }
     CheckMod(index, socket, NumberOfConnectionEndHashBkts,
              "GetNextConnectionIdForSocket");
     for (connectionEnd = connectionEndLocalHashBuckets[index];
          connectionEnd isnt empty;
          connectionEnd = connectionEnd->nextByLocalInfo)
        if (connectionEnd->socket is socket and
            connectionEnd->connectionId is newConnectionId)
           break;
     if (connectionEnd is empty)
        return(newConnectionId);
  }

}  /* GetNextConnectionIdForSocket */

ExternForVisibleFunction long GetNextRefNum(void)
{
  long newRefNum;
  long index;
  ConnectionEnd connectionEnd;
  Boolean wrapped = False;
  ConnectionListenerInfo connectionListenerInfo;
  OpenRequestHandler openRequestHandler;

  /* Pick out a new refNum; make sure it's not in use. */

  while(True)
  {
     newRefNum = nextConnectionRefNum;
     if ((nextConnectionRefNum += 1) < 0)
     {
        nextConnectionRefNum = (long)0;
        if (wrapped)
        {
           ErrorLog("GetNextRefNum", ISevError, __LINE__, UnknownPort,
                    IErrAdspOutOfRefNums, IMsgAdspOutOfRefNums,
                    Insert0());
           return(-1);
        }
        wrapped = True;
     }

     /* Check the open connection ends: */

     CheckMod(index, newRefNum, NumberOfConnectionEndHashBkts,
              "GetNextRefNum");
     for (connectionEnd = connectionEndRefNumHashBuckets[index];
          connectionEnd isnt empty;
          connectionEnd = connectionEnd->next)
        if (connectionEnd->refNum is newRefNum)
           break;
     if (connectionEnd isnt empty)
        continue;

     /* Check the inUse connection open request handlers (the've got a refNum
        reserved in them). */

     for (connectionListenerInfo = connectionListenerList,
              openRequestHandler = empty;
          connectionListenerInfo isnt empty;
          connectionListenerInfo = connectionListenerInfo->next)
     {
        for (openRequestHandler = connectionListenerInfo->openRequestHandlers;
             openRequestHandler isnt empty;
             openRequestHandler = openRequestHandler->next)
           if (openRequestHandler->inUse and
               openRequestHandler->refNum is newRefNum)
              break;
        if (openRequestHandler isnt empty)
           break;
     }
     if (openRequestHandler is empty)
        return(newRefNum);
  }

}  /* GetNextRefNum */

ExternForVisibleFunction ConnectionEnd
              FindConnectionEndByLocalInfo(long socket,
                                           short unsigned connectionId)
{
  long index;
  ConnectionEnd connectionEnd;

  /* Hash connectionId and walk the list matching both it and the socket
     handle. */

  CheckMod(index, socket, NumberOfConnectionEndHashBkts,
           "FindConnectionEndByLocalInfo");
  for (connectionEnd = connectionEndLocalHashBuckets[index];
       connectionEnd isnt empty;
       connectionEnd = connectionEnd->nextByLocalInfo)
     if (connectionEnd->socket is socket and
         connectionEnd->connectionId is connectionId)
        return(connectionEnd);

  return(empty);

}  /* FindConnectionEndByLocalInfo */

ExternForVisibleFunction ConnectionEnd
              FindConnectionEndByRemoteInfo(AppleTalkAddress remoteAddress,
                                            short unsigned remoteConnectionId)
{
  long index;
  ConnectionEnd connectionEnd;

  /* Hash remote connectionId and walk list matching both it and the remote
     AppleTalk address. */

  CheckMod(index, remoteConnectionId, NumberOfConnectionEndHashBkts,
           "FindConnectionEndByRemoteInfo");
  for (connectionEnd = connectionEndRemoteHashBuckets[index];
       connectionEnd isnt empty;
       connectionEnd = connectionEnd->nextByRemoteInfo)
     if (connectionEnd->remoteConnectionId is remoteConnectionId and
         AppleTalkAddressesEqual(&connectionEnd->remoteAddress,
                                 &remoteAddress))
        return(connectionEnd);

  return(empty);

}  /* FindConnectionEndByRemoteInfo */

ExternForVisibleFunction ConnectionEnd FindConnectionEndByRefNum(long refNum)
{
  long index;
  ConnectionEnd connectionEnd;

  /* Hash refNum and walk the list looking for it. */

  CheckMod(index, refNum, NumberOfConnectionEndHashBkts,
           "FindConnectionEndByRefNum");
  for (connectionEnd = connectionEndRefNumHashBuckets[index];
       connectionEnd isnt empty;
       connectionEnd = connectionEnd->next)
     if (connectionEnd->refNum is refNum)
        return(connectionEnd);

  return(empty);

}  /* FindConnectionEndByRefNum */

#if not defined(DeferAdspPackets)
  ExternForVisibleFunction void DeferAdspPackets(void)
  {

    EnterCriticalSection();
    deferIncomingAdspPacketsCount += 1;
    LeaveCriticalSection();

    return;

  }  /* DeferAdspPackets */

  ExternForVisibleFunction void HandleAdspPackets(void)
  {
    DeferredPacket deferredPacket;
    IncomingDdpHandler *handler;

    if (deferIncomingAdspPacketsCount is 0)
    {
       ErrorLog("HandleAdspPackets", ISevError, __LINE__, UnknownPort,
                IErrAdspZeroDeferCount, IMsgAdspZeroDeferCount,
                Insert0());
       return;
    }

    /* Decrement defer count. */

    EnterCriticalSection();
    deferIncomingAdspPacketsCount -= 1;

    /* This routine can be called indirectly recursively via the call to
       AdspPacketIn... we don't want to let our stack frame get too big, so
       if we're already trying to handle deferred packets higher on the
       stack, just ignore it here. */

    if (handleAdspPacketsNesting isnt 0)
    {
       LeaveCriticalSection();
       return;
    }
    handleAdspPacketsNesting += 1;

    /* If we're no longer defering packets, handle any queued ones. */

    if (deferIncomingAdspPacketsCount is 0)
    {
       while(headOfDeferredPacketList isnt empty)
       {
           deferredPacket = headOfDeferredPacketList;
           headOfDeferredPacketList = headOfDeferredPacketList->next;
           if (headOfDeferredPacketList is empty)
              tailOfDeferredPacketList = empty;
           if ((currentDeferredPacketCount -= 1) < 0)
           {
              ErrorLog("HandleAdspPackets", ISevError, __LINE__, UnknownPort,
                       IErrAdspBadDeferCount, IMsgAdspBadDeferCount,
                       Insert0());
              currentDeferredPacketCount = 0;
           }
           LeaveCriticalSection();
           if (deferredPacket->forConnectionListener)
              handler = AdspConnectionListenerPacketIn;
           else
              handler = AdspPacketIn;
           (*handler)(deferredPacket->errorCode, deferredPacket->userData,
                      deferredPacket->port, deferredPacket->source,
                      deferredPacket->destinationSocket, DdpProtocolAdsp,
                      deferredPacket->datagram, deferredPacket->datagramLength,
                      deferredPacket->actualDestination);
           Free(deferredPacket);
           EnterCriticalSection();
       }
    }

    handleAdspPacketsNesting -= 1;
    LeaveCriticalSection();
    return;

  }  /* HandleAdspPackets */
#endif

              /* **************************************** */
              /* *** Buffer queue management routines *** */
              /* **************************************** */

ExternForVisibleFunction Boolean
        AddToBufferQueue(BufferQueue far *bufferQueue, void far *data,
                         long offset, long dataSize, Boolean dataIsOpaque,
                         Boolean endOfMessage, BufferQueue *auxBufferQueue)
{
  BufferChunk newChunk;

  /* "auxBufferQueue" is optional, if present and bufferQueue was initially
     empty, "auxBufferQueue" will be set to "bufferQueue" before returning. */

  /* Make sure the queue looks basically okay. */

  if ((bufferQueue->head is empty and bufferQueue->tail isnt empty) or
      (bufferQueue->tail is empty and bufferQueue->head isnt empty))
  {
     ErrorLog("AddToBufferQueue", ISevError, __LINE__, UnknownPort,
              IErrAdspBufferQueueError, IMsgAdspBufferQueueError,
              Insert0());
     return(False);
  }
  if (dataSize is 0 and not endOfMessage)
     return(True);

  /* If we just adding an EOM and the last chunk in the queue does not have
     its EOM bit set, we can just set it, rather that adding a new "empty"
     chunk.  If we're here and dataSize is zero, we can assume an EOM, due to
     the above test. */

  if (dataSize is 0 and
      bufferQueue->tail isnt empty and
      not bufferQueue->tail->endOfMessage)
  {
     bufferQueue->tail->endOfMessage = True;
     return(True);
  }

  /* Build a new buffer chunk and fill it in. */

  if ((newChunk = Malloc(sizeof(*newChunk) + dataSize)) is empty)
  {
     ErrorLog("AddToBufferQueue", ISevError, __LINE__, UnknownPort,
              IErrAdspOutOfMemory, IMsgAdspOutOfMemory,
              Insert0());
     return(False);
  }
  if (dataSize isnt 0)
     if (dataIsOpaque)
        MoveFromOpaque(newChunk->data, data, offset, dataSize);
     else
        MoveMem(newChunk->data, (char far *)data + offset, dataSize);
  newChunk->dataSize = dataSize;
  newChunk->endOfMessage = endOfMessage;

  /* Link 'er up (at the end of the chunk list). */

  if (auxBufferQueue isnt empty and
      bufferQueue->head is empty)
  {
     auxBufferQueue->head = auxBufferQueue->tail = newChunk;
     auxBufferQueue->startIndex = (long)0;
  }
  newChunk->next = empty;
  if (bufferQueue->tail isnt empty)
     bufferQueue->tail->next = newChunk;
  bufferQueue->tail = newChunk;
  if (bufferQueue->head is empty)
  {
     bufferQueue->head = newChunk;
     bufferQueue->startIndex = (long)0;
  }

  return(True);

}  /* AddToBufferQueue */

ExternForVisibleFunction long
     ReadFromBufferQueue(BufferQueue far *bufferQueue, void far *data,
                         long offset, long dataSize, Boolean dataIsOpaque,
                         Boolean far *endOfMessage)
{
  /* Read from a buffer queue; return the total bytes read; stop at:
     end of queue; buffer filled; end of message.  Return the total
     bytes placed in the buffer; the EOM does NOT count as a byte.
     Move the queue along, but do NOT free any buffer chunks that have
     been read. */

  BufferChunk currentChunk, previousChunk;
  long startIndex = bufferQueue->startIndex;
  long copyChunkSize;
  long lastReadIndex;
  long dataIndex = 0;

  /* Does the queue look okay. */

  if ((bufferQueue->head is empty and bufferQueue->tail isnt empty) or
      (bufferQueue->tail is empty and bufferQueue->head isnt empty))
  {
     ErrorLog("ReadFromBufferQueue", ISevError, __LINE__, UnknownPort,
              IErrAdspBufferQueueError, IMsgAdspBufferQueueError,
              Insert0());
     return((long)0);
  }

  /* Check for the two "no data to read" cases. */

  *endOfMessage = False;
  if (bufferQueue->head is empty or
      (bufferQueue->head->next is empty and
       startIndex is bufferQueue->head->dataSize +
                     bufferQueue->head->endOfMessage))
     return((long)0);

  /* Copy chunks of data until either we exhust the target buffer or we hit
     an end of message. */

  for (previousChunk = empty, currentChunk = bufferQueue->head;
       currentChunk isnt empty;
       previousChunk = currentChunk, currentChunk = currentChunk->next)
  {
     /* Check for nothing to read in current chunk. */

     if (startIndex is currentChunk->dataSize +
                       currentChunk->endOfMessage)
     {
        startIndex = 0;
        continue;
     }

     /* Copy as much as we can; either whole chunk or full buffer. */

     copyChunkSize = Min(currentChunk->dataSize - startIndex, dataSize);
     if (copyChunkSize < 0)
     {
        ErrorLog("ReadFromBufferQueue", ISevError, __LINE__, UnknownPort,
                 IErrAdspBufferQueueError, IMsgAdspBufferQueueError,
                 Insert0());
        return((long)0);
     }
     if (dataIsOpaque)
        MoveToOpaque(data, offset + dataIndex, currentChunk->data + startIndex,
                     copyChunkSize);
     else
        MoveMem((char far *)data + offset + dataIndex, (char far *)currentChunk->data + startIndex,
                copyChunkSize);
     dataIndex += copyChunkSize;
     dataSize -= copyChunkSize;
     lastReadIndex = startIndex + copyChunkSize;

     /* Check terminating conditions. */

     startIndex = 0;   /* startIndex only counts for the first chunk */
     if (dataSize is 0)    /* Buffer full? */
     {
        if (lastReadIndex is currentChunk->dataSize and
            currentChunk->endOfMessage)
           *endOfMessage = True;   /* Are we really returning the eom? */
        break;
     }
     if (currentChunk->endOfMessage)   /* Hit eom? */
     {
        *endOfMessage = True;
        break;
     }
  }

  /* Update the head of the list. */

  if (currentChunk is empty)
  {
     /* We've really run out of data (no EOM). */

     bufferQueue->head = previousChunk;
     bufferQueue->startIndex = previousChunk->dataSize +
                               previousChunk->endOfMessage;
  }
  else if (*endOfMessage and currentChunk->next isnt empty)
  {
     /* We've hit EOM but there are additional chunks, move to start of
        next one. */

     bufferQueue->head = currentChunk->next;
     bufferQueue->startIndex = (long)0;
  }
  else if (*endOfMessage)
  {
     /* We've hit EOM of the last chunk, signify by moving to the end
        of the current chunk (data plus eom size) -- we've aways logically
        read the EOM. */

     bufferQueue->head = currentChunk;
     bufferQueue->startIndex = currentChunk->dataSize + 1;
  }
  else if (lastReadIndex is currentChunk->dataSize and
           currentChunk->next isnt empty)
  {
     /* Filled buffer at end of current chunk, more chunks remain. */

     bufferQueue->head = currentChunk->next;
     bufferQueue->startIndex = (long)0;
  }
  else if (lastReadIndex is currentChunk->dataSize)
  {
     /* Filled buffer at end of current chunk, no more chunks. */

     bufferQueue->head = currentChunk;
     bufferQueue->startIndex = currentChunk->dataSize;
  }
  else
  {
     /* We've filled the buffer in the middle of a chunk. */

     bufferQueue->head = currentChunk;
     bufferQueue->startIndex = lastReadIndex;
  }

  /* All set. */

  return(dataIndex);

}  /* ReadFromBufferQueue */

ExternForVisibleFunction long
     ReadAndDiscardFromBufferQueue(BufferQueue far *bufferQueue,
                                   void far *data,
                                   long offset, long dataSize,
                                   Boolean dataIsOpaque,
                                   Boolean far *endOfMessage)
{
  /* Read from a buffer queue; return the total bytes read; stop at:
     end of queue; buffer filled; end of message.  Return the total
     bytes placed in the buffer; the EOM does NOT count as a byte.
     Move the queue along, free any complete buffer chunks that have
     been read. */

  long totalBytesRead;
  BufferChunk currentChunk, nextChunk;
  BufferChunk originalHead = bufferQueue->head;

  /* Okay, first do the read. */

  if ((totalBytesRead = ReadFromBufferQueue(bufferQueue, data, offset, dataSize,
                                            dataIsOpaque, endOfMessage))
       is (long)0 and not *endOfMessage)
     return((long)0);

  /* If we've completely processed any chunks, free them. */

  for (currentChunk = originalHead;
       currentChunk isnt bufferQueue->head;
       currentChunk = nextChunk)
  {
     nextChunk = currentChunk->next;
     Free(currentChunk);
  }

  /* Okay, if we're at the end of the last chunk, free it and set the
     queue to empty. */

  if (bufferQueue->startIndex is bufferQueue->head->dataSize +
                                 bufferQueue->head->endOfMessage)
  {
     if (bufferQueue->head->next isnt empty)
     {
        /* ReadFromBufferQueue should never leave us in this state, it should
           move head up to the next chunk... but check anyway. */

        ErrorLog("ReadAndDiscardFromBufferQueue", ISevError, __LINE__,
                 UnknownPort, IErrAdspBadStartIndex, IMsgAdspBadStartIndex,
                 Insert0());
        bufferQueue->head = bufferQueue->head->next;
        bufferQueue->startIndex = (long)0;
        Free(currentChunk);
        return(totalBytesRead);
     }

     /* Free the last chunk and set the queue to empty. */

     Free(bufferQueue->head);
     bufferQueue->head = bufferQueue->tail = empty;
     bufferQueue->startIndex = 0;
     return(totalBytesRead);
  }

  /* More data ramaining... */

  return(totalBytesRead);

}  /* ReadAndDiscardFromBufferQueue */

ExternForVisibleFunction Boolean
        DiscardFromBufferQueue(BufferQueue far *bufferQueue, long dataSize,
                               BufferQueue far *auxBufferQueue)
{
  /* Discard data from a buffer queue.  "dataSize" is the amount of data
     to discard; each EOM DOES count a single byte.  "auxBufferQueue" is
     optional; if present it points somewhere within "bufferQueue" -- it
     is invalid to discard beyond this point.  If "auxBufferQueue" is
     present AND points to the end of "bufferQueue" AND all data in
     "bufferQueue" is discarded, "auxBufferQueue" will also be set to empty
     at completion.  For managing the "ADSP sendQueue" the values for
     "bufferQueue" and "auxBufferQueue" would be "sendQueue" and
     "nextSendQueue" respectively. */

  BufferChunk currentChunk, nextChunk;
  long startIndex = bufferQueue->startIndex;
  long chunkSize;

  /* A little error checking. */

  if (dataSize > 0 and bufferQueue->head is empty)
  {
     ErrorLog("DiscardFromBufferQueue", ISevError, __LINE__, UnknownPort,
              IErrAdspQueueIsEmpty, IMsgAdspQueueIsEmpty,
              Insert0());
     return(False);
  }
  if ((bufferQueue->head is empty and bufferQueue->tail isnt empty) or
      (bufferQueue->tail is empty and bufferQueue->head isnt empty) or
      dataSize < 0)
  {
     ErrorLog("DiscardFromBufferQueue", ISevError, __LINE__, UnknownPort,
              IErrAdspBufferQueueError, IMsgAdspBufferQueueError,
              Insert0());
     return(False);
  }
  if (auxBufferQueue isnt empty)
     if ((auxBufferQueue->head is empty and auxBufferQueue->tail isnt empty) or
         (auxBufferQueue->tail is empty and auxBufferQueue->head isnt empty))
     {
        ErrorLog("DiscardFromBufferQueue", ISevError, __LINE__, UnknownPort,
                 IErrAdspAuxQueueError, IMsgAdspAuxQueueError,
                 Insert0());
        return(False);
     }

  /* Walk along our buffer queue discarding what we can. */

  for (currentChunk = bufferQueue->head;
       currentChunk isnt empty;
       currentChunk = nextChunk)
  {
     nextChunk = currentChunk->next;

     /* How much data is in the current chunk? */

     chunkSize = currentChunk->dataSize - startIndex;
     if (currentChunk->endOfMessage)
        chunkSize += 1;

     /* Are we finished discarding while data still remains in the current
        chunk?  If so, reset the start index and we're finished. */

     if (dataSize < chunkSize)
     {
        bufferQueue->head = currentChunk;
        bufferQueue->startIndex = startIndex + dataSize;
        if (auxBufferQueue isnt empty and
            bufferQueue->head is auxBufferQueue->head and
            bufferQueue->startIndex > auxBufferQueue->startIndex)
        {
           ErrorLog("DiscardFromBufferQueue", ISevError, __LINE__, UnknownPort,
                    IErrAdspTooMuchDiscard, IMsgAdspTooMuchDiscard,
                    Insert0());
           return(False);
        }
        return(True);
     }

     /* Otherwise, we discarded a whole chunk? */

     if (auxBufferQueue isnt empty and
         auxBufferQueue->head is currentChunk and
         (auxBufferQueue->head->next isnt empty or
          auxBufferQueue->startIndex < auxBufferQueue->head->dataSize +
                                       auxBufferQueue->head->endOfMessage))
     {
        ErrorLog("DiscardFromBufferQueue", ISevError, __LINE__, UnknownPort,
                 IErrAdspTooMuchFree, IMsgAdspTooMuchFree,
                 Insert0());
        auxBufferQueue->head = auxBufferQueue->tail = empty;
        auxBufferQueue->startIndex = (long)0;
     }
     Free(currentChunk);

     /* Move on to the next chunk. */

     dataSize -= chunkSize;
     startIndex = 0;   /* StartIndex only counts for first chunk */
  }

  /* If we exit out this way, the whole queue has been discarded -- we should
     mark it as empty. */

  if (dataSize isnt 0)
     ErrorLog("DiscardFromBufferQueue", ISevError, __LINE__, UnknownPort,
              IErrAdspCantDiscard, IMsgAdspCantDiscard,
              Insert0());
  bufferQueue->head = bufferQueue->tail = empty;
  bufferQueue->startIndex = (long)0;
  if (auxBufferQueue isnt empty)
  {
     auxBufferQueue->head = auxBufferQueue->tail = empty;
     auxBufferQueue->startIndex = (long)0;
  }

  /* All set. */

  return(True);

}  /* DiscardFromBufferQueue */

ExternForVisibleFunction long
     MaxNextReadSizeOfBufferQueue(BufferQueue far *bufferQueue,
                                  Boolean far *endOfMessage)
{
  /* Return the data size in a buffer queue up to the end or next EOM.  The
     EOM is NOT counted in the return value. */

  BufferChunk currentChunk;
  long nextReadSize = (long)0;
  long startIndex = bufferQueue->startIndex;

  /* Error check. */

  *endOfMessage = False;
  if ((bufferQueue->head is empty and bufferQueue->tail isnt empty) or
      (bufferQueue->tail is empty and bufferQueue->head isnt empty))
  {
     ErrorLog("MaxNextReadSizeOfBufferQueue", ISevError, __LINE__, UnknownPort,
              IErrAdspBufferQueueError, IMsgAdspBufferQueueError,
              Insert0());
     return((long)0);
  }

  /* Walk the queue. */

  for (currentChunk = bufferQueue->head;
       currentChunk isnt empty;
       currentChunk = currentChunk->next)
  {
     /* Check for nothing in current chunk. */

     if (startIndex is currentChunk->dataSize +
                       currentChunk->endOfMessage)
     {
        startIndex = 0;
        continue;
     }

     nextReadSize += currentChunk->dataSize - startIndex;
     if (currentChunk->endOfMessage)
     {
        *endOfMessage = True;
        return(nextReadSize);
     }
     startIndex = 0;   /* StartIndex only counts in first chunk */
  }

  /* Well, we could read the whole thing -- return that length. */

  return(nextReadSize);

}  /* MaxNextReadSizeOfBufferQueue */

ExternForVisibleFunction long BufferQueueSize(BufferQueue far *bufferQueue)
{
  /* Return the total size of a buffer queue; each EOM counts as a single
     byte. */

  BufferChunk currentChunk;
  long queueSize = (long)0;
  long startIndex = bufferQueue->startIndex;

  /* Error check. */

  if ((bufferQueue->head is empty and bufferQueue->tail isnt empty) or
      (bufferQueue->tail is empty and bufferQueue->head isnt empty))
  {
     ErrorLog("BufferQueueSize", ISevError, __LINE__, UnknownPort,
              IErrAdspBufferQueueError, IMsgAdspBufferQueueError,
              Insert0());
     return((long)0);
  }

  /* Walk the queue. */

  for (currentChunk = bufferQueue->head;
       currentChunk isnt empty;
       currentChunk = currentChunk->next)
  {
     /* Check for nothing in current chunk. */

     if (startIndex is currentChunk->dataSize +
                       currentChunk->endOfMessage)
     {
        startIndex = 0;
        continue;
     }

     queueSize += currentChunk->dataSize - startIndex;
     if (currentChunk->endOfMessage)
        queueSize += 1;
     startIndex = 0;   /* StartIndex only counts in first chunk */
  }

  /* Return the size. */

  return(queueSize);

}  /* BufferQueueSize */

ExternForVisibleFunction void FreeBufferQueue(BufferQueue far *bufferQueue)
{
  BufferChunk currentChunk, nextChunk;

  if ((bufferQueue->head is empty and bufferQueue->tail isnt empty) or
      (bufferQueue->tail is empty and bufferQueue->head isnt empty))
     ErrorLog("FreeBufferQueue", ISevError, __LINE__, UnknownPort,
              IErrAdspBufferQueueError, IMsgAdspBufferQueueError,
              Insert0());

  for (currentChunk = bufferQueue->head;
       currentChunk isnt empty;
       currentChunk = nextChunk)
  {
     nextChunk = currentChunk->next;
     Free(currentChunk);
  }

  bufferQueue->head = bufferQueue->tail = empty;
  bufferQueue->startIndex = (long)0;

  return;

}  /* FreeBufferQueue */

ExternForVisibleFunction char far
         *GetLookaheadPointer(BufferQueue far *bufferQueue, long far *size)
{
  /* Return the number of bytes that can be read from the next buffer chunk
     to be processed without crossing buffer chunk boundries.  An available
     EOM flag does NOT count as a byte. */

  BufferChunk firstChunk;
  long startIndex = bufferQueue->startIndex;

  if ((bufferQueue->head is empty and bufferQueue->tail isnt empty) or
      (bufferQueue->tail is empty and bufferQueue->head isnt empty))
     ErrorLog("GetLookaheadPointer", ISevError, __LINE__, UnknownPort,
              IErrAdspBufferQueueError, IMsgAdspBufferQueueError,
              Insert0());

  /* Queue empty? */

  if (bufferQueue->head is Empty)
  {
     *size = 0;
     return(Empty);
  }

  /* "Past" end of current chunk? */

  firstChunk = bufferQueue->head;
  if (startIndex is firstChunk->dataSize + firstChunk->endOfMessage)
  {
     firstChunk = firstChunk->next;
     startIndex = 0;
  }
  if (firstChunk is Empty)
  {
     *size = 0;
     return(Empty);
  }

  /* Okay, there is some data, return the information. */

  *size = firstChunk->dataSize - startIndex;
  return((char far *)(firstChunk->data + startIndex));

}  /* GetLookaheadPointer */

                        /* ****************** */
                        /* *** Debug code *** */
                        /* ****************** */

#if DebugCode

void DumpAdspConnectionEnd(ConnectionEnd connectionEnd)
{
  printf("\n**** Start connection end dump; RefNum = %d.\n",
         connectionEnd->refNum);
  printf("     Connection state = ");
  switch(connectionEnd->connectionState)
  {
     case AdspClosed:
        printf("Closed.\n");
        break;
     case AdspHalfOpen:
        printf("HalfOpen (%s; %s seen remote OpenReq).\n",
               (connectionEnd->passiveOpen ? "passive" : "active"),
               (connectionEnd->seenRemoteOpenRequest ? "have" : "haven't"));
        break;
     case AdspOpen:
        printf("Open.\n");
        break;
     default:
        printf("Unknown.\n");
        break;
  }
  printf("     socket = %d, localConnId = 0%o, sendMax = %d, receiveMax = %d,\n",
         connectionEnd->socket, connectionEnd->connectionId,
         connectionEnd->sendQueueMax, connectionEnd->receiveQueueMax);
  printf("     toAddr = %d.%d.%d, remConnId = 0%o, sendASN = %d, recvASN = %d,\n",
         connectionEnd->remoteAddress.networkNumber,
         connectionEnd->remoteAddress.nodeNumber,
         connectionEnd->remoteAddress.socketNumber,
         connectionEnd->remoteConnectionId,
         connectionEnd->sendAttentionSeqNum,
         connectionEnd->receiveAttentionSeqNum);
  printf("     sendSQ = %d, sendWSN = %d, resendSN = %d, tosend = %d.\n",
         connectionEnd->sendSeqNum, connectionEnd->sendWindowSeqNum,
         connectionEnd->retransmitSeqNum,
         BufferQueueSize(&connectionEnd->nextSendQueue));
  printf("     recvSN = %d, recvWN = %d, undeliveredWN = %d; recvP = %d.\n",
         connectionEnd->receiveSeqNum, connectionEnd->receiveWindowSize,
         BufferQueueSize(&connectionEnd->receiveQueue),
         connectionEnd->receivePending);
  printf("**** End connection end dump.\n");

}  /* DumpAdspConnectionEnd */

void DumpAdspInfo(long refNum)
{
  int index;
  ConnectionEnd connectionEnd;

  /* If RefNum specified, dump that one, else dump 'em all. */

  DeferTimerChecking();
  DeferAdspPackets();
  if (refNum >= 0)
  {
     if ((connectionEnd = FindConnectionEndByRefNum(refNum)) is empty)
     {
        printf("**** Could not find RefNum %d.\n", refNum);
        HandleAdspPackets();
        HandleDeferredTimerChecks();
        return;
     }
     DumpAdspConnectionEnd(connectionEnd);
     HandleAdspPackets();
     HandleDeferredTimerChecks();
     return;
  }

  for (index = 0; index < NumberOfConnectionEndHashBkts; index += 1)
     for (connectionEnd = connectionEndRefNumHashBuckets[index];
          connectionEnd isnt empty;
          connectionEnd = connectionEnd->next)
        DumpAdspConnectionEnd(connectionEnd);

  HandleAdspPackets();
  HandleDeferredTimerChecks();
  return;

}  /* DumpAdspInfo */

#endif
