/* PupImpl.c -- Pup level 1;  'main line' code. 
   C Version modified July 14, 1982  11:49 AM by Swinehart
   L. Stewart October 26, 1982  2:41 PM
   L. Stewart November 13, 1982  5:21 PM
   L. Stewart November 23, 1982  9:55 AM restore statics
   L. Stewart November 28, 1982  6:51 PM cleanup
   L. Stewart December 27, 1982  2:51 PM
    flush nested declarations
   L. Stewart February 11, 1983  9:55 AM Routing table changed
   L. Stewart March 6, 1983  4:25 PM, flush Alloc
   L. Stewart March 9, 1983  8:10 PM, bug in NextNet
   L. Stewart March 22, 1983  9:01 AM, add WDT
   L. Stewart April 4, 1983  6:56 PM, Try for 2 seconds in GetPBI
   L. Stewart May 24, 1983  3:43 PM, clientWord holds owner of PBI
  July 7, 1983  10:52 AM by Stewart, Route changed
  July 9, 1983 by Stewart, add CheckCheckSum
  July 11, 1983  9:26 AM by Stewart, fix routing
  July 15, 1983  3:34 PM by Stewart, current vs localHost
  August 15, 1983  6:10 PM by Stewart, re-init encapsul.
 */

/* Major differences from Alto Pup package:
  Incoming Pups are directed to destination sockets
    registered via OpenLevel1Socket.
  Pups are dispatched to procedures associated with
    the sockets; other packets are discarded.
  Outgoing packets are assumed complete; no defaulting.
  SendPup replaces CompletePup.
  GetPBI targets the pbi to the pbiFreeQ after transmission,
    but the client can, as usual, override it.
  Facilities for broadcasting to all connected networks,
    forwarding packets, etc., are absent.
 */

#include <Env.h>
#include <Queue.h>
#include <Pup.h>
#include <ec.h>

/* Memory Management  */
extern int *GetFixed();
extern Zero();
extern MoveBlock();

/* Queues and Contexts */
extern Enqueue();
extern int *InitNContext();
extern Block();
extern int *Dequeue();
extern Unqueue();
extern InitQueue();

/* Pup level 0 */
extern EtherHost();
extern InitEther();

/* PupMisc */
extern RequestRoute();

/* OS */
extern CallSwat();
extern Swab();
extern MultEq();
extern ReadTmr();
extern Call1();

/* Level 1 Statics */
static struct Queue pbiIQ[1];
int lenPBI;
int lenPup;
int maxPupDataBytes;
int localNet;  /* 0173: known only 1.5MB network, for now. */
int localHost;
/* == localHost or designated conference call host number */
int currentHost;
/* max hop count to accept when enumerating nets in hop count order */
int maxHops;

static struct Queue pbiFreeQ[1];
static int *pupLevel1Ctx;
struct SocketEntry pupSockets[maxPupSockets+1];
struct Route routingTable[maxNetworks];

static int ageNet;
static struct Route *ageRoute;

/*----------------------------------------------------------------------- */
static PupLevel1() {
/*----------------------------------------------------------------------- */
  struct PBI *pbi;
  /* Process to distribute input Pups. */
  for (;;) {
    if ((ReadTmr() & 3) == 0) {
      /* age routing table entries */
      if (ageNet < 0) ageNet = 0;
      if (ageNet >= maxNetworks) ageNet = 0;
      ageRoute = &routingTable[ageNet];
      if (ageRoute->age & 0x0080) ageRoute->age = 0x00ff;
      else ageRoute->age += 1;
      ageNet += 1;
      };
    /* Wait for a Pup to arrive */
    Block();
    pbi = (struct PBI *) Dequeue(pbiIQ);
    if (pbi) {
      if (CheckCheckSum(pbi->pup)) Call1(pbi->clientWord, pbi);
      else ReleasePBI(pbi);
      };
    PokeWDTD();
    };
  };

/*----------------------------------------------------------------------- */
/* not used anywhere
static PupError(pbi, subtype)
  struct PBI *pbi;
  int subtype;
  {
  ReleasePBI(pbi);
  };
 */

/*----------------------------------------------------------------------- */
SendPup(pbi)
/*----------------------------------------------------------------------- */
  struct PBI *pbi;
  {
  int pdh;
  int *tmp;
  struct Pup *pup;
  struct EtherEncapsulation *enc;

  pup = pbi->pup;
  pup->transport = 0;

  /* Set Pup checksum */
  /* Here could check if real checksums are desirable,
    and produce one.  Off for now. */
  pup->data.words[(Swab(pup->length)-pupOvBytes+1)>>1] = -1;
/*
  ReallySetCheckSum(pup);
*/
  /* Route, and transmit */
  if ((pdh = RoutePup(pup)) < 0) Enqueue(pbi->queue, pbi);
  else {
    /* encapsulate */
    tmp = ((int *) pup) - lenEtherEncapsulation;
    enc = (struct EtherEncapsulation *) tmp;
    enc->src = localHost;
    enc->dst = pdh;
    enc->type = typePup;
    TransmitPacket(pbi);
    };
  };

/*----------------------------------------------------------------------- */
int WaitUntilSent(pbi)
/*----------------------------------------------------------------------- */
/* Returns number of milliseconds until pbi transmitted */
  struct PBI *pbi;
  {
  int i;
  i = ReadTmr();
  while (Unqueue(pbi->queue, pbi) == 0) {
    Block();
    };
  return(ReadTmr()-i);
  };

/*----------------------------------------------------------------------- */
static int RoutePup(pup)
/*----------------------------------------------------------------------- */
  struct Pup *pup;
  {
  /* returns directly connected or gateway host
     to reach this network; -1 if none found. */
  /* If none found, launch a request for one. */
  int net;
  struct Route *route;
  if ((net = pup->dPort.net) == localNet) return pup->dPort.host;
  route = &routingTable[net & 0x00ff];
  if (route->hops < 100) return(route->host);
  RequestRoute(net);
  return(-1);
  };

/*----------------------------------------------------------------------- */
struct Route *LookupNet(net)
/*----------------------------------------------------------------------- */
  int net;
  {
  struct Route *route;
  route = &routingTable[net & 0x00ff];
  if (route->hops < 100) return(route);
  else return(0);
  };

struct WhereAmI {
  int hops, index;
  };
#define lenWhereAmI (sizeof(struct WhereAmI)/2)

/*----------------------------------------------------------------------- */
GenerateNets(handle)
/*----------------------------------------------------------------------- */
  struct WhereAmI *handle;
  {
  /* Initializes a handle for NextNet -- enumerates networks,
     from nearest to farthest. For nets reachable in the same
     number of hops, presents them in no particular order.  Handle
     should be a two-word vector.
   */
  handle->index = -1;
  handle->hops = 0;
  };
  
/*----------------------------------------------------------------------- */
int NextNet(handle)
/*----------------------------------------------------------------------- */
  struct WhereAmI *handle;
  {
  if (handle->hops == 0) {
    handle->index = -1;
    handle->hops = 1;
    return(localNet);
    };
  for (;;) {
    handle->index += 1;
    if (handle->index >= maxNetworks) {
      handle->index = 0;
      handle->hops += 1;
      if (handle->hops >= maxHops) return(0);
      };
    if (routingTable[handle->index].hops == handle->hops) return(handle->index);
    };
  };

/* ----------------------------------------------------------------------- */
struct PBI *GetPBI(soc)
/* ----------------------------------------------------------------------- */ 
  int soc;
  {
  int pbiTimer;
  /* Soc argument is ignored.  Allocates a PBI for input or output */
  struct PBI *pbi;
  pbi = (struct PBI *) MaybeGetPBI(soc);
  if (pbi != 0) goto gotOne;
  /* we are very patient, we will wait up to two seconds */
  SetTmr(2000, &pbiTimer);
  for (;;) {
    Block();
    pbi = (struct PBI *) MaybeGetPBI(soc);
    if (pbi != 0) goto gotOne;
    if (TmrExp(&pbiTimer)) CallSwat(ecPup1+1); 
    };
  gotOne:
  pbi->clientWord = ReturnLoc(MyFrame());
  return (pbi);
  };

/* ----------------------------------------------------------------------- */
struct PBI *MaybeGetPBI(soc)
/* ----------------------------------------------------------------------- */ 
  int soc;
  {
  /* Soc argument is ignored.  Allocates a PBI for input or output */
  struct PBI *pbi;
  pbi = (struct PBI *) Dequeue(pbiFreeQ);
  if (pbi != 0) { 
    pbi->queue = pbiFreeQ;
    Zero(pbi->pup, 10);
    };
  return (pbi);
  };

/*----------------------------------------------------------------------- */
ReleasePBI(pbi)
/*----------------------------------------------------------------------- */
  struct PBI *pbi;
  {
  Enqueue(pbiFreeQ, pbi);
  };
  
/*----------------------------------------------------------------------- */
AppendStringToPup(pbi, firstByte, string)
/*----------------------------------------------------------------------- */
  struct PBI *pbi;
  int firstByte;
  struct ShortSTRING *string;
  {
  int i;
  struct Pup *pup;
  pup = pbi->pup;
  for (i = 0;i < string->length; ++i) pup->data.bytes[firstByte+i] = string->text[i];
  pup->length = Swab(pupOvBytes + firstByte + string->length);
  };

/*----------------------------------------------------------------------- */
int GetPupHost()
/*----------------------------------------------------------------------- */
  {
  return ((EtherHost()<<8) + localNet);
  };

/*----------------------------------------------------------------------- */
InitPupLevel1(pctxQ, numPBI, pupDataBytes) 
/*----------------------------------------------------------------------- */
  struct Queue *pctxQ;
  int numPBI, pupDataBytes;
  {
  int i, pbiAlloc;
  int len;
  struct PBI *pbi;
  struct EtherEncapsulation *enc;
  struct Pup *pup;
  maxHops = 2;
  if (pupDataBytes==0) pupDataBytes = defaultPupDataBytes;
  /* define some defaults and other constants */
  maxPupDataBytes = pupDataBytes;
  lenPup = (pupOvBytes+pupDataBytes)/2;
  lenPBI = lenPBIOverhead;

  InitQueue(pbiIQ);  /* level 1 raw pup input queue */
  InitQueue(pbiFreeQ);  /* empty pbi queue */
  
  /* Set up socket array */
  Zero(pupSockets, ((maxPupSockets+1)*lenSocketEntry));

  /* free packet buffer queue */
  len = lenPup + lenEtherEncapsulation;
  for (i = 1; i <= numPBI; ++i) {
    enc = (struct EtherEncapsulation *) GetFixed(len);
    Zero(enc, len);
    enc->type = typePup;
    pbi = (struct PBI *) GetFixed(lenPBI);
    Zero(pbi, lenPBI);
    pbi->pup = &enc->pup;
    ReleasePBI(pbi);
    };
  ageNet = 0;
  
  /* Set up Default network values */
  localNet = 0; /* Router will fill in proper value */
  /* Level 0 function, read hardware ID */
  localHost = currentHost = EtherHost();

  /* Initialize all connected network interfaces */
  InitEther(pctxQ, 0);

  /* Fire up input process */
  pupLevel1Ctx = InitNContext("PupLevel1", GetFixed(175), 175, &PupLevel1);
  Enqueue(pctxQ, pupLevel1Ctx);
  /* initialize all routing table to empty */
  for (i = 0; i < maxNetworks; i += 1) {
    routingTable[i].host = 0;
    routingTable[i].hops = 100;
    routingTable[i].age = 0x00ff;
    };
  };

/*----------------------------------------------------------------------- */
int /*BOOL*/ OpenLevel1Socket(lclPort, PortProc, Q)
/*----------------------------------------------------------------------- */
  struct Port *lclPort;
  int *PortProc /* PROC[pbi: ->PBI] */;
  struct Queue *Q;  /* if true, PortProc is a queue pointer */
  {

/*
  lclPort points at a port structure indicating the Port (socket)
  to be registered.  It is assumed in this implementation that some
  higher-level application invents the socket numbers for ports.  We
  fill in the host and net numbers, if they're missing, with the hardware
  ID of this system (!!)  When Pups addressed to this port arrive,
  they're dispatched by calling PortProc(pbi). It is an error
  (return false) to register a  Port that's already registered,
  or to register too many ports.  If a client wishes to avoid the
  PupLevel1 dispatch, Q can be passed in, and the Ether driver will
  enqueue arriving packets there rather than on pbiIQ
 */

  int i;   
  struct SocketEntry *entry;
  if (lclPort->net == 0) lclPort->net = localNet;
  if (lclPort->host == 0) lclPort->host = localHost;
  for (i = 1; i <= maxPupSockets; i += 1) {
    entry = &pupSockets[i];
    if (MultEq(lclPort, &entry->port, lenPort)) CallSwat(ecPup1+10);
    if (entry->q == 0) {
      MoveBlock(&entry->port, lclPort, lenPort);
      entry->PortProc = PortProc;
      if (Q == 0) Q = pbiIQ;
      entry->q = Q;
      return(i);
      };
    };
  CallSwat(ecPup1+11);
  return(false); /* Table full! */
  };

/*----------------------------------------------------------------------- */
SetLocalNet(net) 
/*----------------------------------------------------------------------- */
  int net;
  {
  int i;
  localNet = net;
  for (i = 1; i <= maxPupSockets; ++i) {
/*  why bother checking ?
    if ((pupSockets[i].PortProc != 0) && (pupSockets[i].port.net == 0))
 */
      pupSockets[i].port.net = net;
    };
  };
  
/*----------------------------------------------------------------------- */
CloseLevel1Socket(soc)
/*----------------------------------------------------------------------- */
  int soc;
  {
  Zero(&pupSockets[soc], lenSocketEntry);
  };