/*--------------------------------------------------------------------*/
/*       d c p g p k t . c                                            */
/*                                                                    */
/*       UUCP 'g' protocol module for UUPC/extended.  Supports 7      */
/*       window variable length packets of up yo 512 bytes in         */
/*       length.                                                      */
/*--------------------------------------------------------------------*/

/*--------------------------------------------------------------------*/
/*       Stuart Lynne May/87                                          */
/*                                                                    */
/*       Copyright (c) Richard H. Lamb 1985, 1986, 1987               */
/*       Changes Copyright (c) Stuart Lynne 1987                      */
/*                                                                    */
/*    Changes Copyright (c) 1989-1993 by Kendra Electronic            */
/*    Wonderworks.                                                    */
/*                                                                    */
/*    All rights reserved except those explicitly granted by the      */
/*    UUPC/extended license agreement.                                */
/*--------------------------------------------------------------------*/

/*--------------------------------------------------------------------*/
/*                          RCS Information                           */
/*--------------------------------------------------------------------*/

/*
 *      $Id: dcpgpkt.c 1.18 1993/10/04 03:57:20 ahd Exp $
 *
 *      $Log: dcpgpkt.c $
 * Revision 1.18  1993/10/04  03:57:20  ahd
 * Drop new lines from start up message
 *
 * Revision 1.17  1993/10/02  22:56:59  ahd
 * Suppress compile warning
 *
 * Revision 1.16  1993/10/02  19:07:49  ahd
 * Treat Windows 3.x ala DOS in handling packets in far memory
 *
 * Revision 1.15  1993/09/20  04:41:54  ahd
 * OS/2 2.x support
 *
 * Revision 1.14  1993/07/22  23:22:27  ahd
 * First pass at changes for Robert Denny's Windows 3.1 support
 *
 * Revision 1.13  1993/05/30  00:01:47  ahd
 * Move UUFAR into header file
 *
 * Revision 1.12  1993/04/13  03:19:45  ahd
 * Only perform copy to gspkt if input to gsendpkt is non-null
 *
 * Revision 1.11  1993/04/13  03:00:05  ahd
 * Correct gspkt declare
 *
 * Revision 1.10  1993/04/13  02:26:30  ahd
 * Move buffers to FAR memory
 *
 * Revision 1.9  1993/04/05  04:32:19  ahd
 * Allow unique send and receive packet sizes
 *
 * Revision 1.8  1993/03/06  23:04:54  ahd
 * Make state names more descriptive
 *
 * Revision 1.7  1992/11/21  05:55:11  ahd
 * Use single bit field for gopenpk flag bits, add debugging info
 *
 * Revision 1.6  1992/11/20  12:38:39  ahd
 * Add additional flags to avoid prematurely ending init sequence
 *
 * Revision 1.5  1992/11/19  03:00:29  ahd
 * drop rcsid
 *
 * Revision 1.4  1992/11/17  13:45:37  ahd
 * Add comments from Ian Talyor
 *
 * Revision 1.3  1992/11/16  02:10:27  ahd
 * Rewrite protocol initialize to insure full exchange of packets
 *
 *          25Aug87 - Allow for up to 7 windows - Jal
 *
 *          01Nov87 - those strncpy's should really be memcpy's! -
 *                    Jal
 *
 *          11Sep89 - Raise TimeOut to 15 - ahd
 *
 *          30Apr90 - Add Jordon Brown's fix for short packet
 *                    retries. Reduce retry limit to 20
 *
 *          22Jul90 - Change error retry limit from per host to per
 *                    packet.
 *
 *          22Jul90 - Add error message for number of retries
 *                    exceeded
 *
 *          08Sep90 - Drop memmove to memcpy change supplied by
 *                    Jordan Brown, MS 6.0 and Turbo C++ agree
 *                    memmove insures no overlap
 */

/*--------------------------------------------------------------------*/
/*    Thanks goes to John Gilmore for sending me a copy of Greg       */
/*    Chesson's UUCP protocol description -- Obviously invaluable.    */
/*    Thanks also go to Andrew Tannenbaum for the section on          */
/*    Siding window protocols with a program example in his           */
/*    "Computer Networks" book.                                       */
/*--------------------------------------------------------------------*/

/*--------------------------------------------------------------------*/
/*                        System include files                        */
/*--------------------------------------------------------------------*/

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <ctype.h>

#ifdef __TURBOC__
#include <mem.h>
#include <alloc.h>
#else
#include <malloc.h>
#endif

/*--------------------------------------------------------------------*/
/*                    UUPC/extended include files                     */
/*--------------------------------------------------------------------*/

#include "lib.h"
#include "dcp.h"
#include "dcpsys.h"
#include "dcpgpkt.h"
#include "hostable.h"
#include "security.h"
#include "commlib.h"
#include "modem.h"
#include "catcher.h"

/*--------------------------------------------------------------------*/
/*                           Local defines                            */
/*--------------------------------------------------------------------*/

#define PKTSIZE   MAXPACK
#define MINPKT    32

#define HDRSIZE   6
#define MAXTRY 4

#ifndef GDEBUG
#define GDEBUG 4
#endif

#ifdef __OS2__
#pragma warn -sig
#endif

/*--------------------------------------------------------------------*/
/*    Control whether some buffers are placed outside the default     */
/*    data segment                                                    */
/*--------------------------------------------------------------------*/

#ifdef __TURBOC__
#define memavail  coreleft
#else
#define memavail  stackavail
#endif

/*--------------------------------------------------------------------*/
/*                     g-packet type definitions                      */
/*--------------------------------------------------------------------*/

#define DATA   0
#define CLOSE  1
#define NAK    2
#define SRJ    3
#define ACK    4
#define INITC  5
#define INITB  6
#define INITA  7

#define POK    -1

#define MAXWINDOW 7
#define NBUF   8              /* always SAME as MAXSEQ ? */
#define MAXSEQ 8

typedef enum {
      I_EMPTY,
      I_ERROR,
      I_RESTART,
      I_CALLEE,
      I_CALLER,
      I_GRPACK,
      I_INITA_RECV,
      I_INITA_SEND,
      I_INITB_RECV,
      I_INITB_SEND,
      I_INITC_RECV,
      I_INITC_SEND,
      I_COMPLETE
      } I_STATE;

#define between(a,b,c) ((a<=b && b<c) || \
                        (c<a && a<=b) || \
                        (b<c && c<a))

#define nextpkt(x)    ((x + 1) % MAXSEQ)
#define nextbuf(x)    ((x + 1) % (nwindows+1))

/*--------------------------------------------------------------------*/
/*                 Handle 16 bit vs. 32 bit compilers                 */
/*--------------------------------------------------------------------*/

#if defined(BIT32ENV)
#define MEMSET(p,c,l)  memset(p,c,l)
#define MEMCPY(t,s,l)  memcpy(t,s,l)
#define MEMMOVE(t,s,l) memmove(t,s,l)
#else
#define MEMSET(p,c,l)  _fmemset(p,c,l)
#define MEMCPY(t,s,l)  _fmemcpy(t,s,l)
#define MEMMOVE(t,s,l) _fmemmove(t,s,l)
#endif

/*--------------------------------------------------------------------*/
/*              Global variables for packet definitions               */
/*--------------------------------------------------------------------*/

currentfile();

static short rwl, swl, swu, rwu, irec, lazynak;
static unsigned short nbuffers;
static short rbl, sbl, sbu;
static KEWSHORT nerr;
static unsigned short outlen[NBUF], inlen[NBUF], xmitlen[NBUF];
static boolean arrived[NBUF];
static size_t nwindows;

static char UUFAR outbuf[NBUF][MAXPACK];
static char UUFAR inbuf[NBUF][MAXPACK];
static time_t ftimer[NBUF];
static short timeouts, outsequence, naksin, naksout, screwups;
static short reinit, shifts, badhdr, resends;
static unsigned char *grpkt = NULL;

#if !defined(BIT32ENV)
static char *gspkt = NULL;       // Local buffer dir
#endif

static boolean variablepacket;  /* "v" or in modem file              */

/*--------------------------------------------------------------------*/
/*                    Internal function prototypes                    */
/*--------------------------------------------------------------------*/

static short initialize(const boolean caller, const char protocol );

static short  gmachine(const short timeout);

static void gspack(short  type,
                   short  yyy,
                   short  xxx,
                   short  len,
                   unsigned short xmit,
                   char UUFAR *data);

static short  grpack(short  *yyy,
                   short  *xxx,
                   short  *len,
                   char UUFAR *data,
                   const short timeout);

static void gstats( void );

static unsigned short checksum(char *data, short len);

/****************** SUB SUB SUB PACKET HANDLER ************/

/*--------------------------------------------------------------------*/
/*    g o p e n p k                                                   */
/*                                                                    */
/*    Initialize processing for protocol                              */
/*--------------------------------------------------------------------*/

short Gopenpk(const boolean caller)
{
   return initialize(caller , 'G');
} /* Gopenpk */

/*--------------------------------------------------------------------*/
/*    v o p e n p k                                                   */
/*                                                                    */
/*    Initialize processing for protocol                              */
/*--------------------------------------------------------------------*/

short vopenpk(const boolean caller)
{
   return initialize(caller, 'v');
} /* vopenpk */

/*--------------------------------------------------------------------*/
/*    g o p e n p k                                                   */
/*                                                                    */
/*    Initialize processing for protocol                              */
/*--------------------------------------------------------------------*/

short gopenpk(const boolean caller)
{
   return initialize(caller, 'g');
} /* vopenpk */

/*--------------------------------------------------------------------*/
/*    i n i t i a l i z e                                             */
/*                                                                    */
/*    Initialize processing for protocol                              */
/*--------------------------------------------------------------------*/

static short initialize(const boolean caller, const char protocol )
{
   short i, xxx, yyy, len, maxwindows;

#define B_SENT_INITA 0x01
#define B_SENT_INITB 0x02
#define B_SENT_INITC 0x04
#define B_RECV_INITA 0x10
#define B_RECV_INITB 0x20
#define B_RECV_INITC 0x40
#define B_INITA (B_SENT_INITA | B_RECV_INITA)
#define B_INITB (B_SENT_INITB | B_RECV_INITB)
#define B_INITC (B_SENT_INITC | B_RECV_INITC)

   short  flags = 0x00;   /* Init state flags, as defined above  */

   I_STATE state;

/*--------------------------------------------------------------------*/
/*    Read modem file values for the number of windows and packet     */
/*    sizes                                                           */
/*--------------------------------------------------------------------*/

   r_pktsize = s_pktsize = GetGPacket( MAXPACK, protocol );
   maxwindows = GetGWindow(
                     min( MAXWINDOW, RECV_BUF / (s_pktsize+HDRSIZE)),
                     protocol);

   variablepacket = bmodemflag[MODEM_VARIABLEPACKET] || (protocol == 'v');

   grpkt = malloc( r_pktsize + HDRSIZE );

/*--------------------------------------------------------------------*/
/*                     Initialize error counters                      */
/*--------------------------------------------------------------------*/

   timeouts = outsequence = naksin = naksout = screwups =
      shifts = badhdr = resends = reinit = 0;

/*--------------------------------------------------------------------*/
/*                    Initialize proto parameters                     */
/*--------------------------------------------------------------------*/

   nerr = nbuffers = 0;
   sbl = swl = swu = sbu = 1;
   rbl = rwl = 0;
   nwindows = maxwindows;
   rwu = nwindows - 1;

   for (i = 0; i < NBUF; i++)
   {
      ftimer[i] = 0;
      arrived[i] = FALSE;
   }

/*--------------------------------------------------------------------*/
/*                          3-way handshake                           */
/*--------------------------------------------------------------------*/

/*--------------------------------------------------------------------*/
/*    The three-way handshake should be independent of who            */
/*    initializes it, but it seems that some versions of uucico       */
/*    assume that the caller sends first and the callee responds.     */
/*    This only matters if we are the callee and the first packet     */
/*    is garbled.  If we send a packet, the other side will assume    */
/*    that we must have seen the packet they sent and will never      */
/*    time out and send it again.  Therefore, if we are the callee    */
/*    we don't send a packet the first time through the loop.         */
/*    This can still fail, but should usually work, and, after        */
/*    all, if the initialization packets are received correctly       */
/*    there will be no problem no matter what we do.                  */
/*                                                                    */
/*    (The above quoted verbatim from Ian Taylor)                     */
/*--------------------------------------------------------------------*/

   state = caller ? I_CALLER : I_CALLEE;

/*--------------------------------------------------------------------*/
/*    Exchange initialization messages with the other system.         */
/*                                                                    */
/*    A problem:                                                      */
/*                                                                    */
/*    We send INITA; it gets received                                 */
/*    We receive INITA                                                */
/*    We send INITB; it gets garbled                                  */
/*    We receive INITB                                                */
/*                                                                    */
/*    We have seen and sent INITB, so we start to send INITC.  The    */
/*    other side as sent INITB but not seen it, so it times out       */
/*    and resends INITB.  We will continue sending INITC and the      */
/*    other side will continue sending INITB until both sides give    */
/*    up and start again with INITA.                                  */
/*                                                                    */
/*    It might seem as though if we are sending INITC and receive     */
/*    INITB, we should resend our INITB, but this could cause         */
/*    infinite echoing of INITB on a long-latency line.  Rather       */
/*    than risk that, I have implemented a fast drop-back             */
/*    procedure.  If we are sending INITB and receive INITC, the      */
/*    other side has gotten ahead of us.  We immediately fail and     */
/*    begin again with INITA.  For the other side, if we are          */
/*    sending INITC and see INITA, we also immediately fail back      */
/*    to INITA.                                                       */
/*                                                                    */
/*    Unfortunately, this doesn't work for the other case, in         */
/*    which we are sending INITB but the other side has not yet       */
/*    seen INITA.  As far as I can see, if this happens we just       */
/*    have to wait until we time out and resend INITA.                */
/*                                                                    */
/*    (The above also quoted verbatim from Ian Taylor; however, the   */
/*    code and associated bugs are all Drew's)                        */
/*--------------------------------------------------------------------*/

/*--------------------------------------------------------------------*/
/*    A note about the games with the variable "flags", which is      */
/*    bit twiddled alot below.  The statement below:                  */
/*                                                                    */
/*          flags = (flags & B_SENT_INITA) | B_RECV_INITA;            */
/*                                                                    */
/*    works to turn off all the bits in flags except B_SENT_INITA     */
/*    if it was already on, and then turns on flag B_RECV_INITA.      */
/*    We use statements like this to reset most of the flags at       */
/*    once, leaving one or two bits on; this in turn allows us to     */
/*    check in later states that the previous two states were the     */
/*    expected ones.                                                  */
/*                                                                    */
/*    Likewise, the following statement:                              */
/*                                                                    */
/*          state = (flags & B_SENT_INITA) ?                          */
/*                         I_INITB_SEND : I_INITA_SEND;               */
/*                                                                    */
/*    tests to see if B_SENT_INITA was already set, and return the    */
/*    "true" condition (I_INITB_SEND) otherwise return the "false"    */
/*    (I_INITA_SEND).                                                 */
/*--------------------------------------------------------------------*/


   while( state != I_COMPLETE )
   {
      printmsg(4, "gopenpk: I State = %2d, flag = 0x%02x",
               (int) state, (int) flags);

      switch( state )
      {

/*--------------------------------------------------------------------*/
/*                          Receive a packet                          */
/*--------------------------------------------------------------------*/

         case I_GRPACK:
            switch (grpack(&yyy, &xxx, &len, NULL, M_gPacketTimeout ))
            {

               case INITA:
                  printmsg(5, "**got INITA");
                  state = I_INITA_RECV;
                  break;

               case INITB:
                  printmsg(5, "**got INITB");
                  state = I_INITB_RECV;
                  break;

               case INITC:
                  printmsg(5, "**got INITC");
                  state = I_INITC_RECV;
                  break;

               case DCP_EMPTY:
                  printmsg(GDEBUG, "**got EMPTY");
                  state = I_EMPTY;

                  if (bmodemflag[MODEM_CD] && !CD())
                  {
                     printmsg(0,"gopenpk: Modem carrier lost");
                     return DCP_FAILED;
                  }
                  break;

               case CLOSE:
                  printmsg(GDEBUG, "**got CLOSE");
                  gspack(CLOSE, 0, 0, 0, 0, NULL);
                  return DCP_FAILED;

               default:
                  printmsg(GDEBUG, "**got SCREW UP");
                  state = I_ERROR;
                  break;
            }

            if (bmodemflag[MODEM_CD] && !CD())
            {
               printmsg(0,"gopenpk: Modem carrier lost");
               return DCP_FAILED;
            }
            break;

/*--------------------------------------------------------------------*/
/*                         Initialize states                          */
/*--------------------------------------------------------------------*/

         case I_CALLER:
            state = I_INITA_SEND;
            break;

         case I_CALLEE:
            state = I_GRPACK;
            break;

/*--------------------------------------------------------------------*/
/*                  Process received or sent packets                  */
/*--------------------------------------------------------------------*/

         case I_INITA_RECV:
            if (yyy < (short) nwindows)
            {
               nwindows = yyy;
               rwu = nwindows - 1;
            }
            flags = (flags & B_SENT_INITA) | B_RECV_INITA;
            state = (flags & B_SENT_INITA) ? I_INITB_SEND : I_INITA_SEND;
            break;

         case I_INITA_SEND:
            gspack(INITA, 0, 0, 0, nwindows, NULL);
            flags = (flags & B_RECV_INITA) | B_SENT_INITA;
            state = I_GRPACK;
            break;

         case I_INITB_RECV:
            if ((flags & (B_RECV_INITA | B_SENT_INITA)) ==
                         (B_RECV_INITA | B_SENT_INITA))
            {
               i = (short) 8 * (2 << (yyy+1));
               if (i < (short) s_pktsize)
                  s_pktsize = i;
               flags = (flags & B_SENT_INITB) | B_RECV_INITB;
               state = (flags & B_SENT_INITB) ? I_INITC_SEND : I_INITB_SEND;
            } /* if */
            else
               state = I_RESTART;
            break;

         case I_INITB_SEND:
            gspack(INITB, 0, 0, 0, r_pktsize, NULL);
                                       /* Data segment (packet) size    */
            flags = (flags & (B_INITA | B_RECV_INITB)) | B_SENT_INITB;
            state = I_GRPACK;
            break;

         case I_INITC_RECV:
            if ((flags & (B_RECV_INITB | B_SENT_INITB)) ==
                           (B_RECV_INITB | B_SENT_INITB))
            {
               if (yyy < (short) nwindows)
               {
                  printmsg(0,"Unexpected INITC window size of %d",
                             nwindows );
                  nwindows = yyy;
                  rwu = nwindows - 1;
               }
               flags = (flags & B_SENT_INITC) | B_RECV_INITC;
               state = (flags & B_SENT_INITC) ? I_COMPLETE : I_INITC_SEND;
            }
            else
               state = I_RESTART;
            break;

         case I_INITC_SEND:
            gspack(INITC, 0, 0, 0, nwindows, NULL);
            flags = (flags & (B_INITB | B_RECV_INITC)) | B_SENT_INITC;
            state = (flags & B_RECV_INITC) ? I_COMPLETE : I_GRPACK;
            break;

/*--------------------------------------------------------------------*/
/*                            Error states                            */
/*--------------------------------------------------------------------*/

         case I_EMPTY:
            timeouts++;
            state = I_RESTART;
            break;

         case I_ERROR:
            screwups++;
            state = I_RESTART;
            break;

         case I_RESTART:
            printmsg(2,"gopenpk: Restarting initialize sequence");
            nerr++;
            flags = 0x00;
            state = I_INITA_SEND;
            break;

      } /* switch */

      if ( terminate_processing )
      {
         printmsg(0,"gopenpk: Terminated by user");
         return DCP_FAILED;
      }

      if (nerr >= M_MaxErr)
      {
         remote_stats.errors += nerr;
         nerr = 0;
         printmsg(0,
            "gopenpk: Consecutive error limit of %ld exceeded, "
                     "%ld total errors",
             (long) M_MaxErr, remote_stats.errors);
         return(DCP_FAILED);
      }
   } /* while */

/*--------------------------------------------------------------------*/
/*                    Allocate the needed buffers                     */
/*--------------------------------------------------------------------*/

   grpkt = realloc( grpkt, r_pktsize + HDRSIZE );
   checkref( grpkt );

#if !defined(BIT32ENV)
   gspkt = malloc( s_pktsize );
   checkref( gspkt );
#endif

   nerr = 0;
   lazynak = 0;


#if defined(BIT32ENV) || defined(_Windows)
   printmsg(2,"%s packets, "
              "Window size %d, "
              "Receive packet %d, "
              "Send packet %d",
            variablepacket ? "Variable" : "Fixed",
            nwindows,
            r_pktsize,
            s_pktsize );
#else
   printmsg(2,"%s packets, "
              "Window size %d, "
              "Receive packet %d, "
              "Send packet %d, "
              "Memory avail %u",
            variablepacket ? "Variable" : "Fixed",
            nwindows,
            r_pktsize,
            s_pktsize,
            memavail());
#endif

   return(DCP_OK); /* channel open */

} /*initialize*/

/*--------------------------------------------------------------------*/
/*    g f i l e p k t                                                 */
/*                                                                    */
/*    Begin a file transfer (not used by "g" protocol)                */
/*--------------------------------------------------------------------*/

short gfilepkt( void )
{

   return DCP_OK;

} /* gfilepkt */

/*--------------------------------------------------------------------*/
/*    g c l o s e p k                                                 */
/*                                                                    */
/*    Close packet machine                                            */
/*--------------------------------------------------------------------*/

short gclosepk()
{
   unsigned short i;

   for (i = 0; i < MAXTRY; i++)
   {
      gspack(CLOSE, 0, 0, 0, 0, NULL);
      if (gmachine(M_gPacketTimeout) == CLOSE)
         break;
   } /* for (i = 0; i < MAXTRY; i++) */

/*--------------------------------------------------------------------*/
/*                        Release our buffers                         */
/*--------------------------------------------------------------------*/

   free( grpkt );
   grpkt = NULL;

#if !defined(BIT32ENV)
   free( gspkt );
   gspkt = NULL;
#endif

/*--------------------------------------------------------------------*/
/*                Report the results of our adventures                */
/*--------------------------------------------------------------------*/

   gstats();

/*--------------------------------------------------------------------*/
/*                          Return to caller                          */
/*--------------------------------------------------------------------*/

   return(0);

} /*gclosepk*/

/*--------------------------------------------------------------------*/
/*    g s t a t s                                                     */
/*                                                                    */
/*    Report summary of errors for processing                         */
/*--------------------------------------------------------------------*/

static void gstats( void )
{
   remote_stats.errors += nerr;
   nerr = 0;
   if ( remote_stats.errors || badhdr )
   {
      printmsg(0,
         "%d time outs, %d port reinits, %d out of seq pkts, "
         "%d NAKs rec, %d NAKs sent",
            timeouts, reinit, outsequence, naksin, naksout);
      printmsg(0,
         "%d invalid pkt types, %d re-syncs, %d bad pkt hdrs, %d pkts resent",
            screwups, shifts, badhdr, resends);
   } /* if ( remote_stats.errors || shifts || badhdr ) */
} /* gstats */

/*--------------------------------------------------------------------*/
/*    g g e t p k t                                                   */
/*                                                                    */
/*    Gets no more than a packet's worth of data from                 */
/*    the "packet I/O state machine".  May have to                    */
/*    periodically run the packet machine to get some packets.        */
/*                                                                    */
/*    on input:   don't care                                          */
/*    on return:  data+\0 and length in len.                          */
/*                                                                    */
/*    ret(0)   if all's well                                          */
/*    ret(-1) if problems (failed)                                    */
/*--------------------------------------------------------------------*/

short ggetpkt(char *data, short *len)
{
   short   retry = M_MaxErr;
   time_t start;
#ifdef _DEBUG
   short savedebug = debuglevel;
#endif

   irec = 1;
   checkref( data );

/*--------------------------------------------------------------------*/
/*                Loop to wait for the desired packet                 */
/*--------------------------------------------------------------------*/

   time( &start );
   while (!arrived[rbl] && retry)
   {
      if (gmachine(M_gPacketTimeout) != POK)
         return(-1);

      if (!arrived[rbl] )
      {
         time_t now;
         if (time( &now ) > (start + M_gPacketTimeout) )
         {
#ifdef _DEBUG
            if ( debuglevel < 6 )
               debuglevel = 6;
#endif
            printmsg(GDEBUG,
                     "ggetpkt: Timeout %d waiting for inbound packet %d",
                     M_MaxErr - --retry, remote_stats.packets + 1);
            timeouts++;
            start = now;
         } /* if (time( now ) > (start + M_gPacketTimeout) ) */
      } /* if (!arrived[rbl] ) */
   } /* while (!arrived[rbl] && i) */

#ifdef _DEBUG
   debuglevel = savedebug;
#endif

   if (!arrived[rbl])
   {
      printmsg(0,"ggetpkt: Remote host failed to respond after %ld seconds",
               (long) M_gPacketTimeout * M_MaxErr);
      gclosepk();
      return -1;
   }

/*--------------------------------------------------------------------*/
/*                           Got a packet!                            */
/*--------------------------------------------------------------------*/

   *len = inlen[rbl];
   MEMCPY(data, inbuf[rbl], *len);

   arrived[rbl] = FALSE;      /* Buffer is now emptied               */
   rwu = nextpkt(rwu);        /* bump receive window                 */

   return(0);

} /*ggetpkt*/


/*
   g s e n d p k t

   Put at most a packet's worth of data in the packet state
   machine for transmission.
   May have to run the packet machine a few times to get
   an available output slot.

   on input: data=*data; len=length of data in data.

   return:
    0 if all's well
   -1 if problems (failed)
*/

short gsendpkt(char *data, short len)
{
   short delta;
#ifdef _DEBUG
   short savedebug = debuglevel;
#endif

   checkref( data );
   irec = 0;

/*--------------------------------------------------------------------*/
/*       WAIT FOR INPUT i.e. if we have sent SWINDOW pkts and none    */
/*       have been acked, wait for acks.  Note that we always go      */
/*       through the machine at least once to keep caught up with     */
/*       ACK's sent by remote machine.                                */
/*--------------------------------------------------------------------*/

   do {
      if (gmachine(0) != POK)    /* Spin with no timeout             */
         return(-1);
   } while (nbuffers >= nwindows);

/*--------------------------------------------------------------------*/
/*               Place packet in table and mark unacked               */
/*--------------------------------------------------------------------*/

   MEMCPY(outbuf[sbu], data, len);

/*--------------------------------------------------------------------*/
/*                       Handle short packets.                        */
/*--------------------------------------------------------------------*/

   xmitlen[sbu] = s_pktsize;
   if (variablepacket)
      while ( ((len * 2) < (short) xmitlen[sbu]) && (xmitlen[sbu] > MINPKT) )
         xmitlen[sbu] /= 2;

   if ( xmitlen[sbu] < MINPKT )
   {
      printmsg(0,"gsendpkt: Bad packet size %d, "
               "data length %d",
               xmitlen[sbu], len);
      xmitlen[sbu] = MINPKT;
   }

   delta = xmitlen[sbu] - len;
   if (delta > 127)
   {
      MEMMOVE(outbuf[sbu] + 2, outbuf[sbu], len);
      MEMSET(outbuf[sbu]+len+2, 0, delta - 2);
                              /* Pad with nulls.  Ugh.               */
      outbuf[sbu][0] = (unsigned char) ((delta & 0x7f) | 0x80);
      outbuf[sbu][1] = (unsigned char) (delta >> 7);
   } /* if (delta > 127) */
   else if (delta > 0 )
   {
      MEMMOVE(outbuf[sbu] + 1, outbuf[sbu], len);
      outbuf[sbu][0] = (unsigned char) delta;
      MEMSET(outbuf[sbu]+len+1, 0, delta - 1);
                              /* Pad with nulls.  Ugh.               */
   } /* else if (delta > 0 )  */

/*--------------------------------------------------------------------*/
/*                            Mark packet                             */
/*--------------------------------------------------------------------*/

   outlen[sbu] = len;
   ftimer[sbu] = time(nil(long));
   nbuffers++;

/*--------------------------------------------------------------------*/
/*                              send it                               */
/*--------------------------------------------------------------------*/

   gspack(DATA, rwl, swu, outlen[sbu], xmitlen[sbu], outbuf[sbu]);

   swu = nextpkt(swu);        /* Bump send window                    */
   sbu = nextbuf( sbu );      /* Bump to next send buffer            */

#ifdef _DEBUG
   debuglevel = savedebug;
#endif

   return(0);

} /*gsendpkt*/


/*--------------------------------------------------------------------*/
/*    g e o f p k t                                                   */
/*                                                                    */
/*    Transmit EOF to the other system                                */
/*--------------------------------------------------------------------*/

short geofpkt( void )
{
   if ((*sendpkt)("", 0))          /* Empty packet == EOF              */
      return DCP_FAILED;
   else
      return DCP_OK;
} /* geofpkt */

/*--------------------------------------------------------------------*/
/*    g w r m s g                                                     */
/*                                                                    */
/*    Send a message to remote system                                 */
/*--------------------------------------------------------------------*/

short gwrmsg( char *s )
{
   for( ; strlen(s) >= s_pktsize; s += s_pktsize)
   {
      short result = (*sendpkt)(s, (short) s_pktsize);
      if (result)
         return result;
   }

   return (*sendpkt)(s, (short) (strlen(s) + 1));
} /* gwrmsg */

/*--------------------------------------------------------------------*/
/*    g r d m s g                                                     */
/*                                                                    */
/*    Read a message from the remote system                           */
/*--------------------------------------------------------------------*/

short grdmsg( char *s)
{
   for ( ;; )
   {
      short len;
      short result = (*getpkt)( s, &len );
      if (result || (s[len-1] == '\0'))
         return result;
      s += len;
   } /* for */

} /* grdmsg */

/**********  Packet Machine  ********** RH Lamb 3/87 */

/*--------------------------------------------------------------------*/
/*    g m a c h i n e                                                 */
/*                                                                    */
/*    Ideally we would like to fork this process off in an            */
/*    infinite loop and send and receive packets through "inbuf"      */
/*    and "outbuf".  Can't do this in MS-DOS so we setup "getpkt"     */
/*    and "sendpkt" to call this routine often and return only        */
/*    when the input buffer is empty thus "blocking" the packet-      */
/*    machine task.                                                   */
/*--------------------------------------------------------------------*/

static short gmachine(const short timeout )
{
   static time_t idletimer = 0;

   boolean done   = FALSE;    /* True = drop out of machine loop  */
   boolean close  = FALSE;    /* True = terminate connection upon
                                        exit                      */
   boolean inseq  = TRUE;     /* True = Count next out of sequence
                                        packet as an error           */
   while ( !done )
   {
      boolean resend = FALSE;    /* True = resend data packets       */
      boolean donak  = FALSE;    /* True = NAK the other system      */
      unsigned long packet_no = remote_stats.packets;

      short pkttype, rack, rseq, rlen, rbuf, i1;
      time_t now;

#ifdef UDEBUG
      if ( debuglevel >= 7 )     /* Optimize processing a little bit */
      {

         printmsg(10, "* send %d %d < W < %d %d, "
                      "receive %d %d < W < %d, "
                      "error %d, packet %d",
            swl, sbl, swu, sbu, rwl, rbl, rwu, nerr,
            (long) remote_stats.packets);

/*--------------------------------------------------------------------*/
/*    Waiting for ACKs for swl to swu-1.  Next pkt to send=swu        */
/*    rwl=expected pkt                                                */
/*--------------------------------------------------------------------*/

      }
#endif

/*--------------------------------------------------------------------*/
/*             Attempt to retrieve a packet and handle it             */
/*--------------------------------------------------------------------*/

      pkttype = grpack(&rack, &rseq, &rlen, inbuf[nextbuf(rbl)], timeout);
      time(&now);
      switch (pkttype) {

         case CLOSE:
            remote_stats.packets++;
            printmsg(GDEBUG, "**got CLOSE");
            close = done = TRUE;
            break;

         case DCP_EMPTY:
            printmsg(timeout ? GDEBUG : 8, "**got EMPTY");

            if (bmodemflag[MODEM_CD] && !CD())
            {
               printmsg(0,"gmachine: Modem carrier lost");
               nerr++;
               close = TRUE;
            }

            if ( terminate_processing )
            {
               printmsg(0,"gmachine: User aborted processing");
               close = TRUE;
            }

            if (ftimer[sbl])
            {
#ifdef UDEBUG
               printmsg(6, "---> seq, elapst %d %ld", sbl,
                    ftimer[sbl] - now);
#endif
               if ( ftimer[sbl] <= (now - M_gPacketTimeout))
               {
                   printmsg(4, "*** timeout %d (%ld)",
                               sbl, (long) remote_stats.packets);
                       /* Since "g" is "go-back-N", when we time out we
                          must send the last N pkts in order.  The generalized
                          sliding window scheme relaxes this reqirment. */
                   nerr++;
                   timeouts++;
                   resend = TRUE;
               } /* if */
            } /* if */

            done = TRUE;
            break;

         case DATA:
            printmsg(5, "**got DATA %d %d", rack, rseq);
            i1 = nextpkt(rwl);   /* (R+1)%8 <-- -->(R+W)%8 */
            if (i1 == rseq) {
               lazynak--;
               remote_stats.packets++;
               idletimer = now;
               rwl = i1;
               rbl = nextbuf( rbl );
               inseq = arrived[rbl] = TRUE;
               inlen[rbl] = rlen;
               printmsg(5, "*** ACK d %d %d", rwl, rbl);
               gspack(ACK, rwl, 0, 0, 0, NULL);
               done = TRUE;   /* return to caller when finished      */
                              /* in a mtask system, unneccesary      */
            } else {
               if (inseq || ( now > (idletimer + M_gPacketTimeout)))
               {
                  donak = TRUE;  /* Only flag first out of sequence
                                    packet as error, since we know
                                    following ones also bad             */
                  outsequence++;
                  inseq = FALSE;
               }
               printmsg(GDEBUG, "*** unexpect %d ne %d (%d - %d)",
                                       rseq, i1, rwl, rwu);
            } /* else */

            if ( swl == swu )       /* We waiting for an ACK?     */
               break;               /* No --> Skip ACK processing */
            /* else Fall through to ACK case */

         case NAK:
         case ACK:
            if (pkttype == NAK)
            {
               nerr++;
               naksin++;
               printmsg(5, "**got NAK %d", rack);
               resend = TRUE;
            }
            else if (pkttype == ACK)
               printmsg(5, "**got ACK %d", rack);

            while(between(swl, rack, swu))
            {                             /* S<-- -->(S+W-1)%8 */
               remote_stats.packets++;
               printmsg(5, "*** ACK %d", swl);
               ftimer[sbl] = 0;
               idletimer = now;
               nbuffers--;
               done = TRUE;            /* Get more data for input */
               swl = nextpkt(swl);
               sbl = nextbuf(sbl);
            } /* while */

            if (!done && (pkttype == ACK)) /* Find packet?         */
            {
               printmsg(GDEBUG,"*** ACK for bad packet %d (%d - %d)",
                           rack, swl, swu);
            } /* if */
            break;

         case DCP_ERROR:
            printmsg(GDEBUG, "*** got BAD CHK");
            naksout++;
            donak = TRUE;
            lazynak = 0;               /* Always NAK bad checksum */
            break;

         default:
            screwups++;
            nerr++;
            printmsg(GDEBUG, "*** got SCREW UP");
            break;

      } /* switch */

/*--------------------------------------------------------------------*/
/*      If we received an NAK or timed out, resend data packets       */
/*--------------------------------------------------------------------*/

      if ( resend )
      for (rack = swl,
           rbuf = sbl;
           between(swl, rack, swu);
           rack = nextpkt(rack), rbuf = nextbuf( rbuf ))
      {                          /* resend rack->(swu-1)             */
         resends++;

         if ( outbuf[rbuf] == NULL )
         {
            printmsg(0,"gmachine: Transmit of NULL packet (%d %d)",
                     rwl, rbuf);
            panic();
         }

         if ( xmitlen[rbuf] == 0 )
         {
            printmsg(0,"gmachine: Transmit of 0 length packet (%d %d)",
                     rwl, rbuf);
            panic();
         }

         gspack(DATA, rwl, rack, outlen[rbuf], xmitlen[rbuf], outbuf[rbuf]);
         printmsg(5, "*** resent %d", rack);
         idletimer = ftimer[rbuf] = now;
      } /* for */

/*--------------------------------------------------------------------*/
/*  If we have an error and have not recently sent a NAK, do so now.  */
/*  We then reset our counter so we receive at least a window full of */
/*                 packets before sending another NAK                 */
/*--------------------------------------------------------------------*/

      if ( donak )
      {
         nerr++;
         if ( (lazynak < 1) || (now > (idletimer + M_gPacketTimeout)))
         {
            printmsg(5, "*** NAK d %d", rwl);
            gspack(NAK, rwl, 0, 0, 0, NULL);
            naksout++;
            idletimer = now;
            lazynak = nwindows + 1;
         } /* if ( lazynak < 1 ) */
      } /* if ( donak ) */

/*--------------------------------------------------------------------*/
/*                   Update error counter if needed                   */
/*--------------------------------------------------------------------*/

      if ((close || (packet_no != remote_stats.packets)) && (nerr > 0))
      {
         printmsg(GDEBUG,"gmachine: Packet %ld had %ld errors during transfer",
                     remote_stats.packets, (long) nerr);
         remote_stats.errors += nerr;
         nerr = 0;
      }

/*--------------------------------------------------------------------*/
/*    If we have an excessive number of errors, drop out of the       */
/*    loop                                                            */
/*--------------------------------------------------------------------*/

      if (nerr >= M_MaxErr)
      {
         printmsg(0,
            "gmachine: Consecutive error limit of %d exceeded, %d total errors",
            M_MaxErr, nerr + remote_stats.errors);
         done = close = TRUE;
         gstats();
      }
   } /* while */

/*--------------------------------------------------------------------*/
/*    Return to caller, gracefully terminating packet machine if      */
/*    requested                                                       */
/*--------------------------------------------------------------------*/

   if ( close )
   {
      gspack(CLOSE, 0, 0, 0, 0, NULL);
      return CLOSE;
   }
   else
      return POK;

} /*gmachine*/


/*************** FRAMING *****************************/

/*
   g s p a c k

   Send a packet

   type=type yyy=pkrec xxx=timesent len=length<=s_pktsize data=*data
   ret(0) always
*/

static void gspack(short type,
                   short yyy,
                   short xxx,
                   short len,
                   unsigned short xmit,
#if defined(BIT32ENV)
                   char *data)
#else
                   char UUFAR *input)
#endif
{
   unsigned short check, i;
   unsigned char header[HDRSIZE];

#if !defined(BIT32ENV)
   char *data;                   // Local data buffer address
   if ( input == NULL )
      data = NULL;               // Make consistent with real buffer
   else {                        // Only copy if non-NULL
      data = gspkt;
      MEMCPY( data, input, xmit );
   }
#endif

#ifdef   LINKTEST
   /***** Link Testing Mods *****/
   unsigned char dpkerr[10];
   /***** End Link Testing Mods *****/
#endif   /* LINKTEST */

#ifdef   LINKTEST
   /***** Link Testing Mods - create artificial errors *****/
   printf("**n:normal,e:error,l:lost,p:partial,h:bad header,s:new seq--> ");
   gets(dpkerr);
   if (dpkerr[0] == 's')
      sscanf(&dpkerr[1], "%d", &xxx);
   /***** End Link Testing Mods *****/
#endif   /* LINKTEST */

   if ( debuglevel > 4 )
      printmsg(5, "send packet type %d, yyy=%d, xxx=%d, len=%d, buf = %d",
               type, yyy, xxx, len, xmit);

   header[0] = '\020';
   header[4] = (unsigned char) (type << 3);

   switch (type) {

      case CLOSE:
         break;   /* stop protocol */

      case NAK:
         header[4] += yyy;
         break;   /* reject */

      case SRJ:
         break;

      case ACK:
         header[4] += yyy;
         break;   /* ack */

      case INITA:
      case INITC:
         header[4] += xmit;
         break;

      case INITB:
         i = MINPKT;
         while( i < xmit )
         {
            header[4] ++;
            i *= 2;
         }
         break;

      case DATA:
         header[4] = (unsigned char) (0x80 + (xxx << 3) + yyy);
         if (len < (short) xmit)      /* Short packet?              */
            header[4] |= 0x40;/* Count byte handled at higher level */

#ifdef UDEBUG
            printmsg(7, "data=|%.*s|", len, data);
#endif
         break;

      default:
         printmsg(0,"gspack: Invalid packet type %d",type);
         panic();
   } /* switch */

/*--------------------------------------------------------------------*/
/*    Now we finish up the header.  For data packets, determine       */
/*    the K number in header[1], which specifies the number of        */
/*    actual data bytes transmitted as a power of 2; we also          */
/*    compute a checksum on the data.                                 */
/*--------------------------------------------------------------------*/

   if (type == DATA)
   {
      header[1] = 1;
      i = MINPKT;
      while( i < xmit )
      {
         header[1] ++;
         i *= 2;
      }

      if ( i != xmit )        /* Did it come out exact power of 2?   */
      {
         printmsg(0,"Packet length error ... %d != %d for K = %d",
               i, xmit, (int) header[1]);
         panic();             /* No --> Well, we blew THAT math      */
      } /* if ( i != xmit ) */

/*--------------------------------------------------------------------*/
/*                        Compute the checksum                        */
/*--------------------------------------------------------------------*/

      check = checksum(data, xmit);
      i = header[4]; /* got to do this on PC for ex-or high bits */
      i &= 0xff;
      check = (check ^ i) & 0xffff;
      check = (0xaaaa - check) & 0xffff;
   }
   else {
      header[1] = 9;          /* Control packet size K number (9)    */
      check = (0xaaaa - header[4]) & 0xffff;
                              /* Simple checksum for control         */
   } /* else */

   header[2] = (unsigned char) (check & 0xff);
   header[3] = (unsigned char) ((check >> 8) & 0xff);
   header[5] = (unsigned char)
            ((header[1] ^ header[2] ^ header[3] ^ header[4]) & 0xff) ;

#ifdef   LINKTEST
   /***** More Link Testing Mods *****/
   switch(dpkerr[0]) {
   case 'e':
      data[10] = - data[10];
      break;
   case 'h':
      header[5] = - header[5];
      break;
   case 'l':
      return;
   case 'p':
      swrite((char *) header, HDRSIZE);
      if (header[1] != 9)
         swrite(data, xmit - 3);
      return;
   default:
      break;
   }
   /***** End Link Testing Mods *****/
#endif   /* LINKTEST */

   swrite((char *) header, HDRSIZE);      /* header is 6-bytes long */
   if (header[1] != 9)
      swrite(data, xmit);

} /*gspack*/


/*
   g r p a c k

   Read packet

   on return: yyy=pkrec xxx=pksent len=length<=PKTSIZE  data=*data

   ret(type)       ok
   ret(DCP_EMPTY)  input buf empty
   ret(DCP_ERROR)  bad header

   ret(DCP_EMPTY)  lost packet timeout
   ret(DCP_ERROR)  checksum error

   NOTE (specifications for sread()):

   sread(buf, n, timeout)
      while(TRUE) {
         if (# of chars available >= n) (without dec internal counter)
            read n chars into buf (decrement internal char counter)
            break
    else
       if (time > timeout)
          break;
      }
      return(# of chars available)

*/

static short grpack(short *yyy,
                  short *xxx,
                  short *len,
                  char UUFAR *data,
                  const short timeout)
{
   static short got_hdr  = FALSE;
   static short received = 0;     /* Bytes already read into buffer */
   short needed;

   unsigned short type, check, checkchk, i, total = 0;
   unsigned char c, c2;

   time_t start;

   if (got_hdr)
      goto get_data;

/*--------------------------------------------------------------------*/
/*   Spin up to timeout waiting for a Control-P, our sync character   */
/*--------------------------------------------------------------------*/

   start = 0;
   while (!got_hdr)
   {
      unsigned char *psync;

      needed = HDRSIZE - received;
      if ( needed > 0 )       /* Have enough bytes for header?       */
      {                       /* No --> Read as much as we need      */
         short wait;

         if ( start == 0 )    /* First pass through data?            */
         {                    /* Yes --> Set timers up               */
            start = time(nil(time_t));
            wait = timeout;
         } /* if ( start == 0 ) */
         else {
            wait = (short) (time(NULL) - start) - timeout;
            if (wait < 0)     /* Negative timeout?                   */
               wait = 0;      /* Make it no time out                 */
         } /* else */

         if (sread((char *) &grpkt[received], needed, wait ) <
             (unsigned short) needed )
                              /* Did we get the needed data?         */
            return DCP_EMPTY; /* No --> Return to caller             */

         received += needed;
      } /* if ( needed < received ) */

/*--------------------------------------------------------------------*/
/*            Search for sync character in newly read data            */
/*--------------------------------------------------------------------*/

#ifdef UDEBUG
      printmsg(10,"grpack: Have %d characters after reading %d",
               received, needed);
#endif

      psync = memchr( grpkt, '\020', received );
      if ( psync == NULL )    /* Did we find the sync character?     */
         received = 0;        /* No --> Reset to empty buffer        */
      else if ( psync != grpkt ) /* First character in buffer?       */
      {                       /* No --> Make it first character      */
         received -= psync - grpkt;
         shifts++;
         memmove( grpkt, psync, received );
                              /* Shift buffer over                   */
      } /* else */

/*--------------------------------------------------------------------*/
/*    If we have read an entire packet header, then perform a         */
/*    simple XOR checksum to determine if it is valid.  If we have    */
/*    a valid checksum, drop out of this search, else drop the        */
/*    sync character and restart the scan.                            */
/*--------------------------------------------------------------------*/

      if ( received >= HDRSIZE )
      {
         i = (unsigned short) (grpkt[1] ^ grpkt[2] ^ grpkt[3] ^
                        grpkt[4] ^ grpkt[5]);
         i &= 0xff;
         printmsg(i ? 2 : 10, "prpkt %02x %02x %02x %02x %02x .. %02x ",
            grpkt[1], grpkt[2], grpkt[3], grpkt[4], grpkt[5], i);

         if (i == 0)          /* Good header?                        */
            got_hdr = TRUE;   /* Yes --> Drop out of loop            */
         else {               /* No  --> Flag it, continue loop      */
            badhdr++;
            printmsg(GDEBUG, "*** bad pkt header ***");
            memmove( grpkt, &grpkt[ 1 ], --received );
                              /* Begin scanning for sync character
                                 with next byte                      */
         } /* else */
      } /* if ( received > HDRSIZE ) */
   } /* while */

/*--------------------------------------------------------------------*/
/*                       Handle control packets                       */
/*--------------------------------------------------------------------*/

   if (grpkt[1] == 9)
   {
      if ( data != NULL )
         *data = '\0';
      *len = 0;
      c = grpkt[4];
      type = c >> 3;
      *yyy = c & 0x07;
      *xxx = 0;
      check = 0;
      checkchk = 0;
      got_hdr = FALSE;
   }

/*--------------------------------------------------------------------*/
/*                        Handle data packets                         */
/*--------------------------------------------------------------------*/
   else {
get_data:
      if ( data == NULL )
      {
         printmsg(0,"grpack: Unexpected data packet!");
         received = 0;
         return(DCP_ERROR);
      }

/*--------------------------------------------------------------------*/
/*             Compute the size of the data block desired             */
/*--------------------------------------------------------------------*/

      total = 8 * (2 << grpkt[1]);
      if (total > r_pktsize)  /* Within the defined limits?          */
      {                       /* No --> Other system has bad header,
                                 or the header got corrupted         */
         printmsg(0,"grpack: Invalid packet size %d (%d)",
            total, (int) grpkt[1]);
         received = 0;
         got_hdr = FALSE;
         return(DCP_ERROR);
      }

      needed = total + HDRSIZE - received;
                                 /* Compute byte required to fill
                                    data buffer                      */

/*--------------------------------------------------------------------*/
/*     If we don't have enough data in the buffer, read some more     */
/*--------------------------------------------------------------------*/

      if ((needed > 0) &&
          (sread((char *) &grpkt[HDRSIZE+total-needed], needed, timeout) <
           (unsigned short)needed))
         return(DCP_EMPTY);

      got_hdr = FALSE;           /* Must re-process header next pass */

/*--------------------------------------------------------------------*/
/*              Break packet header into various values               */
/*--------------------------------------------------------------------*/

      type = 0;
      c2 = grpkt[4];
      c = (unsigned char) (c2 & 0x3f);
      *xxx = c >> 3;
      *yyy = c & 0x07;
      i = grpkt[3];
      i = (i << 8) & 0xff00;
      check = grpkt[2];
      check = i | (check & 0xff);
      checkchk = checksum( (char *) grpkt + HDRSIZE , total);
      i = grpkt[4] | 0x80;
      i &= 0xff;
      checkchk = 0xaaaa - (checkchk ^ i);
      checkchk &= 0xffff;
      if (checkchk != check)
      {
         printmsg(4, "*** checksum error ***");
         memmove( grpkt, grpkt + HDRSIZE, total );
                              /* Save data so we can scan for sync   */
         received = total;    /* Note the amount of the data in buf  */
         return(DCP_ERROR);   /* Return to caller with error         */
      }

/*--------------------------------------------------------------------*/
/*    The checksum is correct, now determine the length of the        */
/*    data to return.                                                 */
/*--------------------------------------------------------------------*/

      *len = total;

      if (c2 & 0x40)
      {
         short ii;
         if ( grpkt[HDRSIZE] & 0x80 )
         {
            ii = (grpkt[HDRSIZE] & 0x7f) + ((grpkt[HDRSIZE+1] & 0xff) << 7);
            *len -= ii;
            MEMCPY(data, grpkt + HDRSIZE + 2, *len);
         }
         else {
            ii = (grpkt[HDRSIZE] & 0xff);
            *len -= ii;
            MEMCPY(data, grpkt + HDRSIZE + 1, *len);
         } /* else */
      }
      else
         MEMCPY( data, grpkt + HDRSIZE, *len);
   } /* else */

/*--------------------------------------------------------------------*/
/*           Announce what we got and return to the caller            */
/*--------------------------------------------------------------------*/

   received = 0;              /* Returning entire buffer, reset count */
   printmsg(5, "receive packet type %d, yyy=%d, xxx=%d, len=%d",
      type, *yyy, *xxx, *len);

#ifdef UDEBUG
   printmsg(13, " checksum rec=%04x comp=%04x\ndata=|%.*s|",
      check, checkchk, total, grpkt + HDRSIZE);
#endif

   return(type);

} /*grpack*/


/*
   c h e c k s u m
*/

static unsigned short checksum(char *data, short len)
{
   short i, j;
   unsigned short tmp, chk1, chk2;
   chk1 = 0xffff;
   chk2 = 0;
   j = len;
   for (i = 0; i < len; i++) {
      if (chk1 & 0x8000) {
         chk1 <<= 1;
         chk1++;
      } else {
         chk1 <<= 1;
      }
      tmp = chk1;
      chk1 += (data[i] & 0xff);
      chk2 += chk1 ^ j;
      if ((chk1 & 0xffff) <= (tmp & 0xffff))
         chk1 ^= chk2;
      j--;
   }
   return(chk1 & 0xffff);

} /*checksum*/
