/*
** PVM/PC interface for the XANT program
** Written by: Edie Gunter
** Modified by: Tom Engelsiepen
*/
 
#ifdef PVM_SUPPORT
#include <stdio.h>
#include <memory.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/file.h>
#include <fcntl.h>
#ifdef _IBMR2
#include <sys/pcr01.h>
#include <sys/pcr03.h>
#else
#include <pcr01.h>
#include <pcr03.h>
#endif
 
#ifndef NULL
#define NULL 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
 
typedef struct pcr__ncb NCB;
typedef char Boolean;
 
typedef unsigned char BYTE;
typedef unsigned short BIT16;
typedef unsigned long BIT32;

/*
**      PVM BLOCK definition
*/
#define BLKHSIZE 8		/* Size of BLOCK header */
typedef struct  dvmblk  {
        BIT16   blkstx;		/* DLE-STX unsigned character */
        BIT16   blkseq;		/* Block sequence number */
        BIT16   blklen;		/* Length of block */
        BIT16   blkrnum;	/* Number of packets in block */
        } PVMBLK;
 
/*
**      PVM BUFFER (packet) definition
*/
#define BUFHSIZE 44		/* Size of PACKET header */
#define BUFDSIZE 4044		/* Size of PACKET data */
#define BUFSSIZE 4011		/* Size of SEND PACKET data */
typedef struct  dvmbuf  {
        BYTE    buftsk[4];	/* Unused pvm */
        BYTE    buftype;	/* Packet type */
        BYTE    bufstyp;	/* Packet subtype */
        BIT16   buflen;		/* Length of data in packet */
        BYTE    buforig[8];	/* Origin node of packet */
        BYTE    bufouid[8];	/* Origin userid */
        BYTE    bufdest[8];	/* Destination node id */
        BYTE    bufduid[8];	/* Destination user id */
        BYTE    bufflg1;	/* Flag byte */
        BYTE    bufflg2;	/* Flag byte */
        BYTE    bufspec[2];	/* Special definition */
        } PVMBUF;
 
/*
**      PVM session control definition
*/
#define SCTHSIZE 32		/* Size of SCT header */
typedef struct  dvmsct {
        BYTE    sctlport[2];	/* Port address */
        BIT16   sctlreal;	/* Terminal real address */
        BIT16   sctllnam;	/* Link type */
        BYTE    sctlclas[2];	/* Terminal device type */
        BYTE    sctlmdl;	/* Term model number */
        BYTE    sctltype;	/* DVMAUT flag bits */
        BIT16   sctlssiz;	/* Max data segment size */
        BYTE    sctlcmax;	/* Pacing count max */
        BYTE    sctlnmax;	/* Pacing number max */
        BYTE    sctlrfea;	/* Extended features */
        BYTE    sctlflag;	/* Flags */
        BYTE    sctlrout[8];	/* Secondary routing */
        BYTE    sctlvmid[8];	/* Vmid of origin (iucv) */
        } PVMSCT;
 
/* Bits defined in buftype */
#define BUFTCTL    0x80		/* Network control record */
#define BUFTIMER   0x40		/* Link timing record */
#define BUFTGDAT   0x20		/* Graphic data record */
#define BUFTDATA   0x10		/* Non-graphic data record */
#define BUFTCMD    0x4		/* Local command buffer */
 
/* Bits defined in bufstyp */
#define BUFSIGN    0x10		/* Control - request for signon */
#define BUFSGNR    0x8		/* Control - response to signon */
#define BUFSOFF    0x4		/* Control - session termination */
#define BUFCMSG    0x2		/* Control - 'cmd', 'msg', and replys */
#define BUFSMISC   0x1		/* Control - application priority post */
#define BUFRETRN   0x40		/* Timing  - this is the return buffer */
#define GRAFOFF    0x40		/* Graphic - session termination */
#define GRAFRDBF   0x20		/* Graphic - data from a read-buffer */
#define GRAFMSG    0x10		/* Graphic - message text from PVM */
#define GPRTSTAT   0x8		/* Graphic - status from printer */
#define GSRDBUF    0x4		/* Graphic - short read-buffer request */
#define BUFRECV    0x20		/* Data    - iucv reply to 2-way send */
 
/* Bits defined in bufspec */
#define CMDLOCAL   0x40		/* Lcommand - command from console */
#define CMDRPLY    0x20		/* Lcommand - reply to command */
#define CMDCP      0x10		/* Lcommand - CP command */
#define CMDAUTH    0x8		/* Lcommand - authorized user */
#define CMDSMSG    0x4		/* Lcommand - reply by smsg */
 
/* Bits defined in bufflg1 */
#define BUFPACE    0x20		/* Pacing flag */
#define BUFAPAC    0x10		/* Response to pacing */
#define BUFSEGF    0x0F		/* Number of 256 byte segments in buffer */
 
/* Bits defined in bufflg2 */
#define BUFSFLG    0x80		/* This is a spanned record */
#define BUFSPFE    0x40		/* This is the last record segment */
#define BUFSCNT    0x3F		/* Spanned record number 0-63 */
 
/* Bits defined in sctlflag */
#define SCTLSFLG   0x80		/* Secondary routing provided */
 
/* Sctl types */
#define SCTL_EDS   0x08		/* Terminal supports EDS */

/* 3270 data packet types */
#define PVMWRITE   0x01
#define PVMERWRT   0x05
#define PVMERWRTA  0x0D
#define PVMEREAU   0x0F
#define PVMWSF     0x11
#define PVMRB      0x02
#define PVMRM      0x06

/* Format of a send or receive buffer */
typedef struct pvmcb {
	PVMBLK blkhdr;
	PVMBUF bufhdr;
	BYTE data[BUFDSIZE];
} PVMCB;

/* Macro to return bytes swapped */
#define SWAP(n) (((BIT16)(n) & 0x00FF) << 8) | (((BIT16)(n) % 0xFF00) >> 8)

/* 3270 Model number screen sizes */
#define MODEL2SIZE (80 * 24)
#define MODEL3SIZE (80 * 32)
#define MODEL4SIZE (80 * 43)
#define MODEL5SIZE (132 * 27)
 
/* Netbios variables */
static int fd;			/* File descriptor for /dev/netbios */
static BYTE lsn;		/* Local session number */
static BYTE lana;		/* LAN adapter number */

/* Variables for sending */
static NCB sendncb;		/* NCB for sending */
static PVMCB *sendbuff;		/* Output buffer */
static BYTE *sendp_cur;		/* Current send pointer */
static BYTE spancount;		/* Count spanned segments */

/* Variables for receiving */
enum rflag { NCB_PROCESSED, NCB_COMPLETED, NCB_INPROGRESS };
static NCB recvncb[2];		/* NCBs for double-buffered receiving */
static enum rflag recvflags[2];	/* Receive state */
static PVMCB *recvdata1, *recvdata2, *recvdata3; /* Data buffers */

/* Miscellaneous global variables */
static char hostnode[9] = "        ";
static char secroute[9] = "        ";
static Boolean support_eds;
static BYTE readtype = 0;	/* Read modified or read buffer */
static int thisrecv;
static Boolean sessionclosed = FALSE;
static Boolean firstime = TRUE;

static void ncbrecv(), sendit(), upcopy();
static int long ncbpost();
static char *getmsg();
extern char *malloc();

/* Externally defined variables */
extern Boolean trace;		/* Tracing flag */
extern Boolean debug;		/* Debugging flag */
extern unsigned char atoe[];	/* ASCII to EBCDIC translation table */
 
 
/* Establish Netbios session with gateway */
int pvm_init(hostname, extended, gate, secondary, workstation, adapter)
     char *hostname;
     Boolean extended;
     char *gate, *secondary, *workstation;
     int adapter;
{
  static char gateway[17] = "               ";
  static char ws_name[17] = "               ";
  char localname[256];
  NCB ncb;
 
  /* Get the command options */
  upcopy(hostnode, hostname, sizeof hostnode);
  support_eds = extended;
  upcopy(gateway, gate, sizeof gateway);
  if (gateway[0] == ' ') error("A PVM/PC gateway name must be supplied");
  upcopy(secroute, secondary, sizeof secroute);
  upcopy(ws_name, workstation, sizeof ws_name);
  lana = adapter;
  if (lana > 3) error("Adapter number must be between 0 and 3");
 
  /* If a workstation name was supplied, then use it.  Otherwise, use
     the name of the current host (minus any domain part).  If that
     can't be obtained, then use "XANT".  The workstation name is up
     to fifteen characters long, with 0x00 as the sixteenth character.  */

  if (ws_name[0] == ' ')
    {
      if (gethostname(localname, sizeof localname) >= 0)
	{
	  /* Strip off any domain part from the host name */
	  int len;
	  char *dot = strchr(localname, '.');
	  if (!dot) len = strlen(localname);
	  else len = dot - localname;
	  if (len > sizeof ws_name) len = sizeof ws_name;
	  memcpy(ws_name, localname, len);
	}
      else memcpy(ws_name, "XANT", 4);
    }
  ws_name[15] = 0;

  /* Open the Netbios device */
  if ((fd = open("/dev/netbios", O_RDWR)) < 0)
    errorm("Can't open /dev/netbios");
  
  /* Do an ADD NAME for the workstation name */
  ncb.ncb_command = PCR_WAN;	/* Should we use PCR_WAG instead? */
  ncb.ncb_lana_num = lana;
  memcpy(ncb.ncb_name, ws_name, 16);
  ncb.ncb_post = 0;
  pcrbios(&ncb, fd);
  if (ncb.ncb_retcode && ncb.ncb_retcode != PCR_DNAM)
    error("Netbios initialization failed: %s", getmsg(ncb.ncb_retcode));
 
  /* Issue CALL command to gateway */
  ncb.ncb_command = PCR_WCL;	/* Adapter status command */
  ncb.ncb_lana_num = lana;
  ncb.ncb_buffer = 0;     
  ncb.ncb_length = 0; 
  ncb.ncb_post = 0;
  ncb.ncb_rto = 0;
  ncb.ncb_sto = 0;
  memcpy(ncb.ncb_callname, gateway, 16);
  memcpy(ncb.ncb_name, ws_name, 16);
  pcrbios(&ncb, fd);
  if (ncb.ncb_retcode)
    error("CALL to gateway failed: %s", getmsg(ncb.ncb_retcode));
  lsn = ncb.ncb_lsn;
 
  /* Get send/receive buffers */
  sendbuff  = (PVMCB *) malloc(sizeof(PVMCB));
  recvdata1 = (PVMCB *) malloc(sizeof(PVMCB));
  recvdata2 = (PVMCB *) malloc(sizeof(PVMCB));
  recvdata3 = (PVMCB *) malloc(sizeof(PVMCB));
  if (!(sendbuff && recvdata1 && recvdata2 && recvdata3))
    error("Not enough memory");

  /* Initialize send buffer */
  memset(sendbuff, 0x00, sizeof(PVMCB));
  sendncb.ncb_command = PCR_NSD;
  sendncb.ncb_buffer = (char *) sendbuff;
  sendncb.ncb_lsn = lsn;
  sendncb.ncb_post = ncbpost;
  sendncb.ncb_lana_num = lana;
  sendbuff->blkhdr.blkrnum = SWAP(1);
  sendp_cur = sendbuff->data;
  spancount = 0;
 
  /* Initialize receive buffers */
  memset(recvdata1, 0x00, sizeof(PVMCB));
  memset(recvdata2, 0x00, sizeof(PVMCB));
  memset(recvdata3, 0x00, sizeof(PVMCB));
 
  recvncb[0].ncb_command = PCR_NRC;
  recvncb[0].ncb_buffer = (char *) recvdata1;
  recvncb[0].ncb_lsn = lsn;
  recvncb[0].ncb_post = ncbpost;
  recvncb[0].ncb_lana_num = lana;
  recvflags[0] = NCB_PROCESSED;
 
  recvncb[1].ncb_command = PCR_NRC;
  recvncb[1].ncb_buffer = (char *) recvdata2;
  recvncb[1].ncb_lsn = lsn;
  recvncb[1].ncb_post = ncbpost;
  recvncb[1].ncb_lana_num = lana;
  recvflags[1] = NCB_PROCESSED;

  return 0;
}
 
 
/* Establish PVM Session with host */
pvm_setup(cols, rows, newcols, newrows)
     int cols, rows;
     int *newcols, *newrows;
{
  int screensize;
  PVMSCT *p;

  *newcols = cols;
  *newrows = rows;
  screensize = rows * cols;

  /* Send PVM Session Init packet */
  p = (PVMSCT *) sendbuff->data;
  memset(p, 0x00, sizeof(PVMSCT));
  p->sctltype = support_eds ? SCTL_EDS : 0;
  p->sctlport[0] = 0xff;
  p->sctlclas[0] = 0x40;
  p->sctlclas[1] = 0x01;
  p->sctlmdl = (support_eds ? 0x92 :
		((screensize == MODEL5SIZE) ? 0x05 :
		 (screensize == MODEL4SIZE) ? 0x04 :
		 (screensize == MODEL3SIZE) ? 0x03 :
		 0x02));
  p->sctlcmax = 4;
  p->sctlnmax = 3;
  if (secroute[0] != ' ') 
    {
      int i;
      p->sctlflag = SCTLSFLG;
      for (i = 0; i < 8; i++)
	p->sctlrout[i] = atoe[secroute[i] & 0177]; /* Change ASCII to EBCDIC */
    }
  memcpy(sendbuff->bufhdr.bufdest, hostnode, 8);
  memset(sendbuff->bufhdr.bufduid, 0x40, 8);
  sendbuff->bufhdr.buftype = BUFTCTL;
  sendbuff->bufhdr.bufstyp = BUFSIGN;
  sendbuff->bufhdr.buflen = sizeof(PVMSCT);
  sendncb.ncb_length = BLKHSIZE + BUFHSIZE + sendbuff->bufhdr.buflen;
  sendbuff->blkhdr.blklen = SWAP(sendncb.ncb_length);
  sendbuff->bufhdr.buflen = SWAP(sendbuff->bufhdr.buflen);
  if (trace) 
    {
      printf("Sending %d bytes:\n", sendncb.ncb_length);
      dumpbuff((unsigned char *) sendbuff, sendncb.ncb_length);
      putchar('\n');
    }
  if (pcrbios(&sendncb, fd)) sessionclosed = TRUE;
}


/* Process any input from remote host.  Returns -1 if remote system
   closed the connection, 1 if data was found, and 0 otherwise.  */
int pvm_check()
{
  int count, nextrecv;
  PVMCB *p;
 
  if (sessionclosed) return -1;
  if (firstime)
    {
      thisrecv = 0;
      ncbrecv();
      firstime = FALSE;
    }
  if (recvflags[thisrecv] != NCB_COMPLETED) return 0;
  if (recvncb[thisrecv].ncb_retcode) return -1;

  if (trace) 
    {
      printf("Received %d bytes:\n", recvncb[thisrecv].ncb_length);
      dumpbuff(recvncb[thisrecv].ncb_buffer, recvncb[thisrecv].ncb_length);
      putchar('\n');
    }

  /* Find a free buffer and start a new receive */
  nextrecv = thisrecv ? 0 : 1;
  p = (PVMCB *) recvncb[thisrecv].ncb_buffer;
  recvncb[thisrecv].ncb_buffer = (char *)
    (((recvdata1 != p) &&
      ((char *) recvdata1 != (char *) recvncb[nextrecv].ncb_buffer))
     ? recvdata1
     : ((recvdata2 != p) &&
	((char *) recvdata2 != (char *) recvncb[nextrecv].ncb_buffer))
     ? recvdata2
     : recvdata3);
  recvflags[thisrecv] = NCB_PROCESSED;
  ncbrecv();
  thisrecv = nextrecv;
 
  /* Handle a graphic data record */
  if (p->bufhdr.buftype & BUFTGDAT)
    {
      if ((!p->bufhdr.bufstyp) ||
	  (p->bufhdr.bufstyp & GRAFRDBF) ||
	  (p->bufhdr.bufstyp & GSRDBUF))
	{
	  readtype = (p->data[0] == PVMRB) ? PVMRB : 0;
	  count = SWAP(p->bufhdr.buflen);
	  s3270_accept(p->data, count);
	  if (!(p->bufhdr.bufflg2 & BUFSFLG) || (p->bufhdr.bufflg2 & BUFSPFE))
	    s3270_eor();
	}
      else
	if (debug) printf("Unknown bufstyp %02x\n", p->bufhdr.bufstyp);
      return 1;
    }

  /* Handle a network control record */
  if (p->bufhdr.buftype & BUFTCTL)
    {
      if (p->bufhdr.bufstyp & BUFSGNR) 
	{
	  /* Signon complete */
	  memcpy(sendbuff->bufhdr.bufdest, p->bufhdr.buforig, 8);
	  memcpy(sendbuff->bufhdr.bufduid, p->bufhdr.bufouid, 8);
	}
      else if (p->bufhdr.bufstyp & BUFSOFF)
	{
	  /* PVM Terminated the session */
	  printf("%-48.48s\n", p->data);
	  return -1;
	}
      else
	if (debug) printf("Unknown bufstyp %02x\n", p->bufhdr.bufstyp);
      return 1;
    }

  /* We don't know how to handle any other record types */
  if (debug) printf("Unknown buftype %02x\n", p->bufhdr.buftype);
  return 1;
}
 
 
/* Issue Netbios receives */
static void ncbrecv()
{
  if (recvflags[0] == NCB_PROCESSED)
    {
      recvncb[0].ncb_length = BLKHSIZE + BUFHSIZE + BUFDSIZE;
      recvflags[0] = NCB_INPROGRESS;
      pcrbios(&recvncb[0], fd);
    }
 
  if (recvflags[1] == NCB_PROCESSED)
    {
      recvncb[1].ncb_length = BLKHSIZE + BUFHSIZE + BUFDSIZE;
      recvflags[1] = NCB_INPROGRESS;
      pcrbios(&recvncb[1], fd);
    }
}


/* Send data to remote system */
pvm_put(buff, length)
     unsigned char *buff;
     int length;
{
  int spaceleft, copylength;
 
  while (TRUE)
    {
      /* Copy as much into the buffer as will fit */
      spaceleft = &sendbuff->data[BUFSSIZE] - sendp_cur;
      copylength = length;
      if (copylength > spaceleft) copylength = spaceleft;
      memcpy(sendp_cur, buff, copylength);
      sendp_cur += copylength;
      buff += copylength;
      length -= copylength;
      if (!length) break;

      /* If all the data didn't fit, then send what we've got as a
	 spanned record, and loop to process the rest.  */

      sendbuff->bufhdr.bufflg2 = BUFSFLG | spancount;
      spancount++;
      sendit();
    }
}
 

/* Force Netbios send for data collected.  If data is segmented, this 
   is the last segment of the spanned records.  */
pvm_eor()
{
  if (sendbuff->bufhdr.bufflg2 & BUFSFLG)
    sendbuff->bufhdr.bufflg2 = BUFSPFE | (sendbuff->bufhdr.bufflg2 + 1);
  sendit();

  /* Reset values pvm_put will use */
  readtype = 0;		/* Reset so we get fresh read type next pvm_put */
  sendbuff->bufhdr.bufflg2 = 0;
  spancount = 0;
}

 
/* Send break */
pvm_break()
{
  /* Not supported by PVM/PC */
}


/* Issue Netbios send for data collected thus far */
static void sendit()
{
  sendncb.ncb_length = sendp_cur - (BYTE *) sendbuff;
  sendbuff->blkhdr.blklen = SWAP(sendncb.ncb_length);
  sendbuff->bufhdr.buftype = BUFTGDAT;
  sendbuff->bufhdr.bufstyp = (readtype == 0x00) ? 0 : GRAFRDBF;
  sendbuff->bufhdr.buflen = SWAP(sendp_cur - sendbuff->data);
  if (trace) 
    {
      printf("Sending %d bytes:\n", sendncb.ncb_length);
      dumpbuff((unsigned char *) sendbuff, sendncb.ncb_length);
      putchar('\n');
    }
  if (pcrbios(&sendncb, fd)) sessionclosed = TRUE;

  /* Reset the output buffer pointer */
  sendp_cur = sendbuff->data;
}
 
 
/* Hangup the Netbios session with the gateway.  The gateway will
   forward PVM Session Terminate to host, though we probably ought
   to send it ourself, here.  */
pvm_close()
{
  NCB ncb;
  ncb.ncb_command = PCR_WHG;
  ncb.ncb_lsn = lsn;
  ncb.ncb_lana_num = lana;
  pcrbios(&ncb, fd);
  close(fd);
}
 

/* Interrupt service routine to process completed NCBs */
static int long ncbpost(ncb_addr)
     NCB *ncb_addr;
{
  if ((char *) ncb_addr == (char *) &recvncb[0])
    recvflags[0] = NCB_COMPLETED;
  else if ((char *) ncb_addr == (char *) &recvncb[1])
    recvflags[1] = NCB_COMPLETED;
  else if ((char *) ncb_addr == (char *) &sendncb && sendncb.ncb_retcode)
    sessionclosed = TRUE;
  return 0;
}
 
 
/* Copy a string, converting it to upper case */
static void upcopy(s1, s2, n)
     char *s1;			/* Destination */
     char *s2;			/* Source */
     int n;			/* Maximum length */
{
  int i;
  for (i = 0; i < n && *s2; i++, s2++)
    s1[i] = toupper((int) *s2);
}


/* Convert a Netbios return code into a printable error message */
static char *getmsg(code)
     BYTE code;
{
  register int i;

  static struct errtype
    {
      BYTE code;
      char *msg;
    }
  errtab[] = 
    {
      PCR_ILEN, "Invalid buffer length",
      PCR_ICMD, "Invalid command",
      PCR_TIME, "Command timed out",
      PCR_IMSG, "Message incomplete",
      PCR_ILSN, "Invalid local session number",
      PCR_NRES, "No resource available",
      PCR_NSES, "Session closed",
      PCR_NCMD, "Command canceled",
      PCR_DNAM, "Duplicate name in local name table",
      PCR_FTAB, "Name table full",
      PCR_NDEL, "Command completed, name has active sessions and is now de-registered",
      PCR_LTAB, "Local session table full",
      PCR_SREJ, "Session open rejected",
      PCR_INAM, "Invalid name number",
      PCR_NNAM, "Cannot find name called",
      PCR_SNAM, "Name not found or cannot specify an * or 0x00",
      PCR_ANAM, "Name in use on remote adapter",
      PCR_NONM, "Name was deleted",
      PCR_ASES, "Session ended abnormally",
      PCR_CNAM, "Name conflict detected",
      PCR_ICPD, "Incompatible remote device",
      PCR_BLAN, "Invalid adapter number",
      PCR_CCOM, "Command completed while cancel occuring",
      PCR_RNAM, "Reserved name specified",
      PCR_NCAN, "Command not valid to cancel",
      PCR_BNET, "Unusual network condition",
      PCR_ERCC, "Cont. carrier detect (not this adapter)",
      PCR_ELCC, "Cont. carrier detect (this adapter)",
      PCR_NCAR, "No carrier detected",
      PCR_IFAC, "Interface failure",
      PCR_IROM, "ROM checksum failed",
      PCR_IUID, "Unit ID prom test failed",
      PCR_IRAM, "RAM test failed",
      PCR_IHST, "Host interface test failed",
      PCR_IVLT, "+-12 volt test failed",
      PCR_IDLP, "Digital loopback test failed",
      PCR_IALP, "Analog loopback test failed",
    };
#define NMSG (sizeof errtab / (sizeof(struct errtype)))

  for (i = 0; i < NMSG; i++)
    if (errtab[i].code == code) return errtab[i].msg;
  return "Unknown error";
}


#else

/* Dummy routines for systems without Netbios support */

typedef char Boolean;

/*NOTUSED*/
int pvm_init(hostname, extended, gate, secondary, workstation, adapter)
     char *hostname;
     Boolean extended;
     char *gate, *secondary, *workstation;
     int adapter;
{ error("PVM is not supported on this system"); return 0; }

pvm_setup(cols, rows, newcols, newrows)
     int cols, rows;
     int *newcols, *newrows;
{ }

int pvm_check()
{ return 0; }

pvm_put(buff, length)
     unsigned char *buff;
     int length;
{ }

pvm_eor()
{ }

pvm_break()
{ }

pvm_close()
{ }

#endif
