/***************************************************************************
 *                                                                         *
 *   SAPUTL.C                                                              *
 *                                                                         *
 *   Copyright (c) 1991-1997 Galacticomm, Inc.     All Rights Reserved.    *
 *                                                                         *
 *   Service Advertising Protocol utilities for Novell Netware based on    *
 *   GSBL/LAN.                                                             *
 *                                                                         *
 *                                             - R. Stein 12/04/90         *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "ipx.h"
#include "saputl.h"
#include "brkthu.h"

#define SAPEXP 5   /* # minutes to expire remote server, after last brdcast */

INT sapecb=16;                 /* Number of listen ECB's to post for S.A.P. */
INT sapisiz=1024;                            /* Size of S.A.P. input buffer */
INT saposiz=1024;                           /* Size of S.A.P. output buffer */
struct sapinf sitabl[MAXSAP];                   /* Server Information Table */
INT sapchg=0;               /* set to 1 whenever sitabl[] changed by remote */
struct sip sipbuf;                                 /* Server broadcast data */
struct netnod thisad;                      /* network-node of this computer */
INT sapchn=-1;                        /* GSBL/LAN channel to use for S.A.P. */
UINT saptyp;                      /* Server type, (lo-hi order) 0xFFFF=wild */
USHORT minctr;                                            /* counts minutes */

static struct {                   /* Structure of data returned by btuhdr() */
     struct ecbgsbl lecb;               /* most recent completed listen ECB */
     struct ipxhdr lhdr;               /* most recently received IPX header */
} hdrbuf;

INT srcidx;                         /* global used by sapprp() and saprep() */
INT sancnt;                  /* global used by sapan1(), sapann(), sapane() */

CHAR allsap[]="\000\000\000\000\377\377\377\377\377\377\004\122";
            /* network-node-socket for all server query's, gen'l broadcasts */


#define SAPQRY (sizeof(struct sqp))                   /* # bytes in a query */
#define SAPQOB (SAPQRY-2)             /* bytes in either query or broadcast */
                                     /* (that both messages have in common) */

/*

S.A.P. Broadcast Routines - sapan1(), sapann(), sapane()  (for local use only)
--------------------------------------------------------
These routines are for broadcasting server information.  Call like this:

     sapan1(rsptyp,nns);
     sapann(&svrinf);
     sapann(&svrinf);
     sapann(&svrinf);
     :
     sapane();

where:

     rsptyp  is 2 for a general server broadcast or 4 for a nearest
             server broadcast
     nns     is a pointer to the 12-byte network-node-socket address to
             send to
     svrinf  is information on each server (see the svrinf structure in
             IPXCALL.H)

sapan1() can also be used to construct a server query (vs. broadcast) packet
(as in sapitz() routine).  In that case, rsptyp == 1 for general server query.

*/

VOID sapan1(INT rsptyp,CHAR *nns);
VOID sapane(VOID);
VOID sapann(struct svrinf *siptr);
INT sapadv(CHAR *name,INT type,INT socket);
INT sapdwn(CHAR *name);
VOID sapprp(VOID);
struct sapinf *saprep(INT mask);
struct sapinf *sapfnd(CHAR *name,INT mask);
INT gobble(INT chunk,VOID *where);
VOID wastin(INT n);
VOID sapprc(VOID);
INT saplog(struct svrinf *siptr);
VOID sapmin(VOID);
VOID sapsts(INT status);
VOID sapfin(VOID);
INT sapfdn(VOID);

VOID
sapan1(             /* Prepare to announce Server(s) (via broadcast output) */
INT rsptyp,                                  /* response type (lo-hi order) */
CHAR *nns)                             /* announce to this net-node-socket */
{                                /* (also used to prepare for query output) */
     setmem(&sipbuf,sizeof(sipbuf),0);
     sipbuf.ipx.paktyp=4;
     movmem(nns,sipbuf.ipx.dstnet,12);
     sipbuf.rsptyp=hilo(rsptyp);
     sancnt=0;
}

VOID
sapane(VOID)                       /* Finish (flush) Server announcement(s) */
{
     INT n;

     if (sancnt > 0) {
          n=SAPQOB+(sancnt*sizeof(struct svrinf));
          sipbuf.ipx.length=hilo(n);
          btuxct(sapchn,n,(CHAR *)&sipbuf);
     }
}

VOID
sapann(                                         /* Announce the next Server */
struct svrinf *siptr)
{
     movmem(siptr,&sipbuf.svr[sancnt],sizeof(struct svrinf));
     sancnt++;
     if (sancnt >= MAXSBC) {
          sapane();
          sancnt=0;
     }
}

GBOOL                              /*   TRUE=success FALSE=GSBL/LAN not avl*/
sapitz(                            /* Initialize S.A.P.                    */
INT chan,                          /*   GSBL channel to use for S.A.P.     */
INT type)                          /*   Server types lo-hi order,0xFFFF wild*/
{
     INT i,n;

     if ((btulan&BTLI7A) == 0
      || btusdf(chan,1,6,SAPSOC,sapecb) != 0
      || bturst(chan) != 3
      || btubsz(chan,sapisiz,saposiz) != 0) {
          return(FALSE);
     }
     sapchn=chan;
     btutrg(sapchn,32767);
     btuhdr(sapchn,sizeof(hdrbuf),(VOID *)&hdrbuf);
     movmem(hdrbuf.lhdr.dstnet,&thisad,sizeof(thisad));
     if ((saptyp=type) != 0x0000) {
          sapan1(1,allsap);
          sipbuf.svr[0].typ=hilo(saptyp);
          n=SAPQRY;
          sipbuf.ipx.length=hilo(n);
          btuxct(sapchn,n,(CHAR *)&sipbuf);
     }
     for (i=0 ; i < MAXSAP ; i++) {
          sitabl[i].state=SISNON;
     }
     return(TRUE);
}

INT
sapadv(                                         /* Advertise a local Server */
CHAR *name,   /* Name, NUL-terminated ASCII string, up to 48 bytes incl NUL */
INT type,         /* Object type (don't use 0x0000 or 0xFFFF) (lo-hi order) */
INT socket)         /* Local socket where service will reside (lo-hi order) */
{   /* returns 1=logged, 2=overwrite dup name, 3=bumped remote, 0=can't fit */
     struct sapinf *saptr;
     INT rc=0;

     if (sapchn == -1) {
          return(0);
     }
     if (strlen(name) > SNMLEN) {
          name[SNMLEN]='\0';
     }
     if ((saptr=sapfnd(name,SISLCL+SISRMT)) != NULL) {    /* Already logged */
          rc=2;                      /* with same name?  Use existing entry */
     }                       /* (will overwrite remote server if same name) */
     else {
          sapprp();                   /* Any empty table entries available? */
          if ((saptr=saprep(SISNON)) != NULL) {
               rc=1;                               /* Add new local server. */
          }
          else {
               sapprp();
               if ((saptr=saprep(SISRMT)) != NULL) {
                    rc=3;
               }
          }
     }
     if (rc) {
          saptr->inf.typ=hilo(type);
          setmem(saptr->inf.nam,SNMLEN+1,0);
          strcpy(saptr->inf.nam,name);
          movmem(&thisad,saptr->inf.net,sizeof(thisad));
          saptr->inf.soc=hilo(socket);
          saptr->inf.imnets=hilo(1);
          saptr->state=SISLCL;
          sapan1(2,allsap);
          sapann(&saptr->inf);
          sapane();
     }
     return(rc);
}

INT
sapdwn(                                    /* Announce shutdown of a server */
CHAR *name)                                               /* name of server */
{                                   /* returns -1=removed 0=not here anyway */
     struct sapinf *saptr;

     if (sapchn == -1) {
          return(0);
     }
     if ((saptr=sapfnd(name,SISLCL)) != NULL) {
          saptr->inf.imnets=hilo(16);
          sapan1(2,allsap);
          sapann(&saptr->inf);
          sapane();
          return(1);                /* entry deleted eventually by sapmin() */
     }                        /* after notifying of shutdown one more time) */
     return(0);
}


/*

S.A.P. Reporting routines - sapprp() and saprep()
-------------------------------------------------
sapprp() and saprep() are used for scanning through all servers in the server
information table.  Use like this:

     struct sapinf *saptr;

     *apprp();
     while ((saptr=saprep(SISXXX)) != NULL) {
          ...
     }

where SISXXX is one of:

     0=SISNON          Look for empty entries
     1=SISRMT          Look for remote server entries
     2=SISLCL          Look for local server entries
     3=SISLCL+SISRMT   Look for either remote or local server entries

NO NESTING OF sapprp()/saprep() LOOPS ALLOWED.

Most calls to sapprp()/saprep() from outside of SAPUTL.C will use 1=SISRMT,
looking for info about remote servers.

Be careful about calling any other sapxxx() routines in this reporting loop
(e.g. sapprc()) -- many of them use sapprp()/saprep() themselves.

*/

VOID
sapprp(VOID)                         /* Prepare to report on S.A.P. servers */
{
     srcidx=0;
}

struct sapinf *
saprep(                            /* Report on one S.A.P. server at a time */
INT mask)          /* 1=report local servers 2=remote 3=both 0=report empty */
{         /* returns address of next svrinf structure, or NULL if none more */
     struct sapinf *saptr;

     if (sapchn == -1) {
          return(NULL);
     }
     for (saptr=NULL ; srcidx < MAXSAP && saptr == NULL ; srcidx++) {
          if ((sitabl[srcidx].state&mask) != 0
           || (mask == SISNON && sitabl[srcidx].state == SISNON)) {
               saptr=&sitabl[srcidx];
          }
     }
     return(saptr);
}

struct sapinf *
sapfnd(                       /* Find server information, given server name */
CHAR *name,         /* NUL-terminated ASCII string, up to 48 bytes incl NUL */
INT mask)                  /* 1=search for a local server 2=remote 3=either */
{                /* Returns pointer to entry in sitabl[] or NULL=can't find */
     struct sapinf *saptr;

     sapprp();
     while ((saptr=saprep(mask)) != NULL) {
          if (strcmp(name,saptr->inf.nam) == 0) {
               return(saptr);
          }
     }
     return(NULL);
}

INT
gobble(                          /* Retrieve input data from S.A.P. channel */
INT chunk,                                                    /* # of bytes */
VOID *where)                                              /* where to store */
{                                             /* returns 1=got 0=not enough */
     INT rc=0;

     if (chunk > 0) {
          rc=btuica(sapchn,where,chunk);
     }
     return(rc > 0);
}

VOID
wastin(INT n)                  /* remove n characters from the input stream */
{
     CHAR *trash[32];

     while (n >= 32) {
          gobble(32,trash);
          n-=32;
     }
     gobble(n,trash);
}

VOID
sapprc(VOID)                                   /* routine S.A.P. processing */
{
     struct sapinf *saptr;
     INT i,n,narray;
     UINT rsvtyp;                       /* remote server type (hi-lo order) */
     CHAR nns[12];
     INT rsptyp;

     if (sapchn == -1 || btuibw(sapchn) == 0) {
          return;
     }
     if (!gobble(SAPQOB,&sipbuf)) {
          btucli(sapchn);
          return;
     }
     n=hilo(sipbuf.ipx.length);
     if (memcmp(sipbuf.ipx.srcnet,&thisad,10) == 0
      && sipbuf.ipx.srcsoc == hilo(SAPSOC)) {
          wastin(n-SAPQOB);                         /* (ignore reflections) */
          return;
     }
     switch (rsptyp=hilo(sipbuf.rsptyp)) {
     case 1:                                      /* Received General Query */
     case 3:                                      /* Received Nearest Query */
          if (n != SAPQRY) {
               btucli(sapchn);
               break;
          }
          gobble (2,&rsvtyp);
          sapprp();
          movmem(sipbuf.ipx.srcnet,nns,sizeof(nns));
          sapan1(rsptyp+1,nns);
          while ((saptr=saprep(SISLCL)) != NULL) {
               if (rsvtyp == hilo(0xFFFF)
                || rsvtyp == saptr->inf.typ) {
                    sapann(&saptr->inf);               /* send broadcast(s) */
                    if (rsptyp == 3) {
                         break;       /* broadcast just 1 for query-nearest */
                    }
               }
          }
          sapane();
          break;
     case 2:                                  /* Received General Broadcast */
     case 4:                                  /* Received Nearest Broadcast */
          narray=hilo(sipbuf.ipx.length)-SAPQOB;
          n=narray/sizeof(struct svrinf);
          if (n*sizeof(struct svrinf) != narray) {
               btucli(sapchn);
               break;
          }
          for (i=0 ; i < n ; i++) {
               gobble(sizeof(struct svrinf),&sipbuf.svr[0]);
               if (saptyp != 0x0000
                && (saptyp == 0xFFFF
                 || saptyp == hilo(sipbuf.svr[0].typ))) {
                    saplog(&sipbuf.svr[0]);           /* log in server info */
               }
          }
          break;
     default:
          btucli(sapchn);
          break;
     }
}

INT saplog(         /* Log S.A.P. information from remote server into table */
struct svrinf *siptr)                                               /* info */
{     /* 1=logged 2=re-logged 3=dup of lcl 4=removed 5=re-removed 0=no room */
         /* 6=ignored report of server shutdown when details are mismatched */
     struct sapinf *saptr;

     if ((saptr=sapfnd(siptr->nam,SISLCL+SISRMT)) != NULL) {
          if (saptr->state == SISRMT) {
               if (siptr->imnets == hilo(16)) {
                    if (memcmp(siptr,&saptr->inf,
                      sizeof(struct svrinf)-2) != 0) {
                         return(6);             /* ignore mismatch shutdown */
                    }
                    saptr->state=SISNON;
                    sapchg=1;
                    return(4);                        /* removed from table */
               }
               if (memcmp(siptr,&saptr->inf,sizeof(struct svrinf)) != 0) {
                    sapchg=1;
                    movmem(siptr,&saptr->inf,sizeof(struct svrinf));
               }
               saptr->minseen=minctr;
               return(2);                          /* re-logged into table */
          }
          else {
               if (siptr->imnets != hilo(16)
                && saptr->inf.imnets == hilo(16)) {    /* (live remote will */
                    saptr->state=SISNON;            /* override dying local */
               }                                 /* that has the same name) */
               else {
                    return(3);   /* ignore remote duplicate of local server */
               }
          }
     }
     if (siptr->imnets == hilo(16)) {
          return(5);            /* remote already duplicate of local server */
     }
     sapprp();
     if ((saptr=saprep(SISNON)) != NULL) {          /* Look for empty entry */
          movmem(siptr,&saptr->inf,sizeof(struct svrinf));
          saptr->state=SISRMT;
          saptr->minseen=minctr;
          sapchg=1;
          return(1);                                /* logged in new remote */
     }
     return(0);                              /* no room for remote in table */
}

VOID
sapmin(VOID)                             /* Every minute handling of S.A.P. */
{
     struct sapinf *saptr;

     if (sapchn == -1) {
          return;
     }
     minctr++;
     sapprp();
     sapan1(2,allsap);
     while ((saptr=saprep(SISLCL)) != NULL) {         /* Scan local servers */
          sapann(&saptr->inf);                            /* Broadcast info */
          if (saptr->inf.imnets == hilo(16)) {
               saptr->state=SISNON;      /* (remove shut-down local servers */
          }
     }
     sapane();
     sapprp();
     while ((saptr=saprep(SISRMT)) != NULL) {        /* Scan remote servers */
          if (minctr-saptr->minseen > SAPEXP) {
               saptr->state=SISNON;          /* remove if no recent notices */
          }
     }
}

VOID
sapsts(INT status)                         /* S.A.P. channel status handler */
{                           /* passed "status" code parameter from btusts() */
     switch (status) {
     case 251:
          btucli(sapchn);
          break;
     }
}

VOID
sapfin(VOID)                 /* End of S.A.P. (shut down all local servers) */
{
     struct sapinf *saptr;

     if (sapchn == -1) {
          return;
     }
     sapprp();
     sapan1(2,allsap);
     while ((saptr=saprep(SISLCL)) != NULL) {
          saptr->inf.imnets=hilo(16);
          sapann(&saptr->inf);
     }
     sapane();
     btuxct(sapchn,1,"\0");    /* used by sapfdn() to detect shut-down done */
}

INT
sapfdn(VOID)                  /* Find out when sapfin() has really finished */
  /* (Since the S.A.P. channel is in raw-packet mode, the NUL in sapfin()'s */
  /* call to btuxct() never gets transmitted, but we find out when the      */
  /* broadcast notices are completely transmitted)                          */
                       /* Note: timeout should be used to override sapfdn() */
{
     if (sapchn == -1) {
          return(1);
     }
     btuscn();                          /* continue LAN channel processing! */
     return(btuoba(sapchn) == saposiz-1);
}
