#include <stdio.h>		/* XXX NULL... */
#include "server.h"
#include "protocol.h"
#include "licenses.h"

/*
 * serverInt.c: contains code for server network interactions
 */

#define DEBUG

char *wakeupEvents[7] = {
  "huh?",
  "multicast",
  "retry",
  "disconnection",
  "death",
  "clue",
  "connect"
  };

server_init(s)
     srvinfo *s;
{
  License *l;
  Pool *p;
  int i;
  static P0Block P0multi;

  /*
   * Cached multicast retries
   */
  P0_InitBlock("static packet", &P0multi, NULL, 1024);
  s->multi = &P0multi;

  /*
   * Initialize remote server states (and some local state). We
   * consider the remote servers timed-out until we hear from them.
   */
  for (i = 0; i < s->numServers; i++)
    {
      s->servers[i].connection = s->toConnections[s->serverConns[i]];
      s->servers[i].activitySource = s->fromConnections[s->serverConns[i]];

      s->servers[i].last_handshake = (time_t)0;
      s->servers[i].contact = SERVER_DISCONNECTED;
      s->servers[i].poolState = NULL;

      s->servers[i].last_contact = (time_t)0;
      s->servers[i].state = MASTER;
      s->servers[i].custodian = i;
      s->servers[i].clue = CLUELESS;

      s->servers[i].retryCount = 0;
    }

  /*
   * Initialize local server state
   */
  s->startup = time((time_t *)0);
  s->nextMulticast = s->startup;
  s->lastFreeze = (time_t)0;

  /*
   * Init totalPools. This probably isn't the best place to
   * do it, but it's convenient for now.
   */
  for (l = s->firstLicense, i = 0; l != NULL; l = l->next)
    for (p = l->firstPool; p != NULL; p = p->next, i++);
  s->totalPools = i;

  /* XXX
     XXX Load pool state from disk.
     XXX */

  s->clueTime = s->startup + T_clueless;

  s->sendCount = 0;
  s->acked = 1;
  s->last_reduce = (time_t)0;

  /*
   * We'll wake up right away and send out our first multicast as a
   * clueless master. (Could just call compute_wakeup() here.)
   */
  s->nextWakeup = s->nextMulticast;
  s->reason = MULTICAST;
}

/*
 * Forget everything about all of the clients, and shut down their
 * connections. This is done on MASTER->BACKUP transition, and when
 * they come crying to us afterward we refer them to our new master.
 */
TrashClients(s)
     srvinfo *s;
{
  Context *c;

  while ((c = s->mostStale) != NULL)
    Obliterate(s, c);
}

/*
 * ERRORS:
 *
 * APP_NEWBACKUP: INFO LOG
 * Server has been demoted to a backup.
 */
Trap compute_wakeup(s)
     srvinfo *s;
{
  int i;
  int last_multicast_done;
  Trap retval = OK;

  switch(local(state))
    {
    case MASTER:
    case MASTER_REDUCE:

      s->reason = MULTICAST;
      s->nextWakeup = s->nextMulticast;
      last_multicast_done = s->multicastMade; /* until proven innocent */

      if ((s->clueTime < s->nextWakeup) && (local(clue) != CLUEFUL))
	{
	  s->reason = GETSOMECLUE;
	  s->nextWakeup = s->clueTime;
	}

      for (i = 0; i < s->numServers; i++)
	{
	  if ((s->servers[i].contact == SERVER_CONNECTED) &&
	      (s->servers[i].retryCount != 0) &&
	      (s->nextWakeup >= s->servers[i].nextRetry))
	    {
	      s->reason = RETRY;
	      s->nextWakeup = s->servers[i].nextRetry;
	      last_multicast_done = 0;
	    }

	  if ((s->servers[i].contact == SERVER_CONNECTED) &&
	      (s->nextWakeup >= s->servers[i].last_handshake + T_MB))
	    {
	      s->reason = SERVER_DISCONNECT;
	      s->nextWakeup = s->servers[i].last_handshake + T_MB;
	      last_multicast_done = 0;
	    }

	  if ((s->servers[i].contact == SERVER_DISCONNECTED) &&
	      (s->nextWakeup >= s->servers[i].last_contact + T_split))
	    {
	      s->reason = SERVER_DEATH;
	      s->nextWakeup = s->servers[i].last_contact + T_split;
	    }
	}

      if ((s->sendCount != 0 || s->acked == 0) &&
	  s->nextWakeup >= s->next)
	{
	  s->reason = SERVER_CONNECT;
	  s->nextWakeup = s->next;
	}

      /*
       * As soon as possible, make the transition from
       * MASTER_REDUCE to BACKUP. This is when everyone has
       * been delivered (or timed out on) our last multicast
       * packet. Check the SERVER_DISCONNECT code for another
       * twist.
       */
      if (local(state) == MASTER_REDUCE && last_multicast_done)
	{
	  local(state) = BACKUP; /* custodian is already set. */
	  TrashClients(s);
	  Error_Push(APP_NEWBACKUP, NULL);
	  retval = CHECK;
	  /* fall through to BACKUP case. */
	}
      else
	break;

    case BACKUP:

      /* XXX Backup times out a minute after the master when the
	 network is unidirectional M->B. Is this a problem? */

      s->reason = SERVER_DISCONNECT;
      s->nextWakeup = MIN(s->servers[local(custodian)].last_contact + T_BM,
	  s->servers[local(custodian)].last_handshake + T_BM + T_update);
      break;
    }

  fprintf(stdout, "%s wakeup in %d\n", wakeupEvents[s->reason],
	  s->nextWakeup - time((time_t *)0));
  return retval;
}

/*
 * Update the real pool information to remove the licenses we
 * just lost by losing contact with another server.
 */
void reduceFreeLicenses(s, server)
     srvinfo *s;
     int server;
{
  License *l;
  Pool *p;
  int i, taken;

  for (l = s->firstLicense, i = 0; l != NULL; l = l->next)
    for (p = l->firstPool; p != NULL; p = p->next, i++)
      {
	taken = s->servers[server].poolState[i].free /
	  s->servers[server].groupSize;
	p->free -= taken; /* note: may go negative */
      }
}

/*
 * Update the real pool information to add in the licenses we just
 * obtained from another server (that merged with us (all = 1),
 * or died (all = 0)).
 */
void restoreFreeLicenses(s, server, all)
     srvinfo *s;
     int server;
     int all;
{
  License *l;
  Pool *p;
  int i, taken;

  if (s->servers[server].poolState == NULL)
    return;

  for (l = s->firstLicense, i = 0; l != NULL; l = l->next)
    for (p = l->firstPool; p != NULL; p = p->next, i++)
      {
	taken = s->servers[server].poolState[i].free;
	if (!all)
	  taken /= s->servers[server].groupSize;
	p->free += taken;

	/* "XXX" below depends on incremental licensing policy */
	if ( /* XXX (p->free - taken <= 0) && */ p->free > 0 &&
	    taken != 0)
	  {
	    s->checkQueues = 1;
	    l->checkQueue = 1;
	  }
      }
}

/*
 * Set the real pool information to indicate all of the licenses
 * belong to us.
 */
void FillLicenses(s)
     srvinfo *s;
{
  License *l;
  Pool *p;
  int old;

  for (l = s->firstLicense; l != NULL; l = l->next)
    for (p = l->firstPool; p != NULL; p = p->next)
      {
	old = p->free;
	p->free = p->licenses - p->active;
	/* "XXX" below depends on incremental licensing policy */
	if ( p->free > 0 && p->free > old)
	  {
	    s->checkQueues = 1;
	    l->checkQueue = 1;
	  }
      }
}

/*
 * Set the real pool information to correspond to the OldPool of
 * server.
 */
void resetPools(s, server)
     srvinfo *s;
     int server;
{
  License *l;
  Pool *p;
  int i, groupMem = 0;

  for (i = 0; i < s->us; i++)
    if (s->servers[i].custodian == local(custodian))
      groupMem++; /* find out what member number (0 - N) of our group we are */

  for (l = s->firstLicense, i = 0; l != NULL; l = l->next)
    for (p = l->firstPool; p != NULL; p = p->next, i++)
      {
	p->free = s->servers[server].poolState[i].free /
	  s->servers[server].groupSize;
	if ((s->servers[server].poolState[i].free %
	     s->servers[server].groupSize) > groupMem)
	  p->free++; /* make sure the extra couple of licenses are given */
	p->active = 0;
	if (p->free > 0)
	  {
	    s->checkQueues = 1;
	    l->checkQueue = 1;
	  }
      }
}

/*
 * Frees an OldPool if it is not referenced as the last known
 * pool of any server.
 */
void freePool(s, op)
     srvinfo *s;
     OldPool *op;
{
  int i;

  if (op == NULL)
    return;

  for (i = 0; i < s->numServers; i++)
    if (op == s->servers[i].poolState)
      return;

  free(op);
}

void freeServerPool(s, server)
     srvinfo *s;
     int server;
{
  int i;
  OldPool *op;

  op = s->servers[server].poolState;
  s->servers[server].poolState = NULL;

  if (op == NULL)
    return;

  for (i = 0; i < s->numServers; i++)
    if (i != server &&
        s->servers[i].poolState == s->servers[server].poolState)
      return;

  free(s->servers[server].poolState);
  s->servers[server].poolState = NULL;
}

void setServerPool(s, server, op, groupSize)
     srvinfo *s;
     int server;
     OldPool *op;
     int groupSize;
{
  int i;

  freeServerPool(s, server);
  s->servers[server].poolState = op;
  s->servers[server].groupSize = groupSize;

  if (groupSize == 0)
    for (i = 0; i < s->numServers; i++)
      if (s->servers[i].custodian == local(custodian))
	s->servers[server].groupSize++;
}

void makeState(s, out, now, op)
     srvinfo *s;
     P0Block *out;
     time_t now;
     OldPool *op;
{
  int i;

  /*
    XXX It seems bad to have this side effect here. The last_contact
    for the local host needs to be "now" in the outgoing packet. The
    same field in the structure also needs to be now in the same
    cases, so I set it here because it makes this code nicer and
    removes the assignments from other code.
   */
  local(last_contact) = now;

  for (i = 0; i < s->numServers; i++)
    {
      P0_PutCard32(out, s->servers[i].last_contact);
      P0_PutCard32(out, s->servers[i].last_handshake);
      P0_PutCard32(out, s->servers[i].state);
      P0_PutCard32(out, s->servers[i].custodian);
      P0_PutCard32(out, s->servers[i].clue);
    }

  if (op != NULL)
    PutPoolToPacket(out, s, op);
}

/*
 * ERRORS:
 *
 * APP_SOMECLUE: INFO DEBUG
 * The server has gained SEMICLUED state.
 *
 * APP_CLUEFUL: INFO DEBUG
 * The server has become CLUEFUL.
 *
 * APP_NEWMASTERTIMEOUT: INFO LOG
 * The server, in MASTER_REDUCE state, timed out its master to be.
 *
 * APP_BACKUPTIMEOUT: INFO LOG
 * The server, in master mode, timed out a backup.
 *
 * APP_MASTERTIMEOUT: INFO LOG
 * The server, in backup mode, timed out its master.
 *
 * APP_NEWMASTER: INFO LOG
 * The server promoted itself from backup to master.
 *
 * APP_DEADSERVER: INFO LOG
 * The server timed out a server to the death.
 */
Trap server_wakeup(s, Pout)
     srvinfo *s;
     P0Packet *Pout;
{
  License *l;
  Packet *pout;
  P0Message *message;
  Packet multipacket;
  P0Block *block;
  OldPool *op;
  int i, masterCount = 0;
  time_t now;
  Trap retval = OK;
#ifdef DEBUG
  char hnam[50];
#endif

  pout = Pout->packet;

  switch(s->reason)
    {
      /*
       * Happens as a master only.
       */
    case MULTICAST:
      s->multicastMade = 1;
      s->nextMulticast += T_update;
      now = time((time_t *)0);

      op = MakeOldPoolFromCurrent(s); /* XXX trap for NULL */
      /* XXX
	 XXX Write this info to disk */

      setServerPool(s, s->us, op, 0);

      P0_CreateMessage(&message, P0_STATE, 0);
      P0_NewBlock("main", message, &block);
      makeState(s, block, now, op);
#ifdef 0

      authblock = auth(block, context);
      addblocktomessage(message, authblock);

      auth(block, server | client | group);

      Check_Auth_New(block, /* block to be checked */
		     authblock, /* appropriate associated auth block */
		     adr, /* id to be verified */
		     context,);
	               
      /*
       * Get the authentication block associated with block.
       * (assuming there's only one)
       */
      Authenticate_GetAuthBlock(message, block, &authblock);

      /*
       * Check the authentication of block against all appropriate
       * auth blocks in message. Context can provide information
       * about:
       *         user, machine, license, etc., assiciated with packet
       */
      Authenticate_Check(message, block, context);
        calls...

	  foo_check(block, authblock, context, authcontext);
#endif

      P0_CvtMessageToBlock(message, s->multi);
      P0_FreeMessage(message);

      /*
       * Transmit the state packet.
       */
      multipacket.control = 0;
      multipacket.packet = s->multi->data;
      multipacket.length = s->multi->current - s->multi->data;
      for (i = 0; i < s->numServers; i++)
	if (i != s->us) /* Talking to yourself is said to be a sign
			   of impending mental collapse. */
	  {
	    multipacket.Source = s->servers[i].activitySource;
	    multipacket.Destination = s->servers[i].connection;
#ifdef DEBUG
	    Connect_AddressToName(s->servers[i].connection,
				  hnam, sizeof(hnam));
	    fprintf(stdout, "Multicast sent to %s\n", hnam);
#endif
	    retval = Connect_SendPacket(&multipacket);

	    /*
	     * Set up retry timers. Retransmits send _this_ packet,
	     * not the real state, at the time of retransmit.
	     */
	    if (s->servers[i].contact == SERVER_CONNECTED)
	      {
		s->servers[i].retryCount = N_retry;
		s->servers[i].nextRetry = now + T_retry;
	      }
	  }
      break;

    case RETRY:
      multipacket.control = 0;
      multipacket.packet = s->multi->data;
      multipacket.length = s->multi->current - s->multi->data;
      for (i = 0; i < s->numServers; i++)
	if ((s->servers[i].retryCount != 0) &&
	    (s->nextWakeup == s->servers[i].nextRetry))
	  {
	    multipacket.Source = s->servers[i].activitySource;
	    multipacket.Destination = s->servers[i].connection;
#ifdef DEBUG
	    Connect_AddressToName(s->servers[i].connection,
				  hnam, sizeof(hnam));
	    fprintf(stdout, "Multicast retry sent to %s\n", hnam);
#endif
	    retval = Connect_SendPacket(&multipacket);
	    s->servers[i].retryCount--;
	    s->servers[i].nextRetry += T_retry;
	  }
      break;

    case GETSOMECLUE:
      switch(local(clue))
	{
	case CLUELESS:
	  local(clue) = SEMICLUED;
	  s->clueTime = time((time_t *)0) + N_MC * T_ping; /* XXX */
	  Error_Push(APP_SOMECLUE, NULL);
	  retval = CHECK;
	  break;

	case SEMICLUED:
	  /*
	   * During the semiclued state, we were allowed to queue
	   * up requests. Becoming clueful allows us to start granting
	   * queued licenses.
	   */
	  local(clue) = CLUEFUL;
	  s->checkQueues = 1;
	  for (l = s->firstLicense; l != NULL; l = l->next)
	    if (l->queue != NULL)
	      l->checkQueue = 1;

	  Error_Push(APP_CLUEFUL, NULL);
	  retval = CHECK;
	  break;
	}
      break;
      
    case SERVER_DISCONNECT:
      /*
       * For each server which times out now...
       */
      for (i = 0; i < s->numServers; i++)
	if ((s->servers[i].contact == SERVER_CONNECTED) &&
	    (s->nextWakeup >= s->servers[i].last_handshake + T_MB))
	  {
	    /*
	     * If we time out our future master, drop back into
	     * MASTER mode instead of later falling to BACKUP.
	     */
	    if (local(state) == MASTER_REDUCE &&
		local(custodian) == i)
	      {
		local(state) = MASTER;
		local(custodian) = s->us;

		s->servers[i].contact = SERVER_DISCONNECTED;
		s->servers[i].state = MASTER;
		s->servers[i].custodian = i;

		Error_Push(APP_NEWMASTERTIMEOUT, NULL);
		retval |= CHECK;

		continue;
	      }

	    /*
	     * If we were the master of the server we're timing out,
	     * assume it's turned into a master itself and taken its
	     * share of licenses from us.
	     */
	    if (local(state) == MASTER &&
		s->servers[i].custodian == s->us)
	      {
		s->servers[i].contact = SERVER_DISCONNECTED;
		s->servers[i].state = MASTER;
		s->servers[i].custodian = i;

		/*
		 * Note that if the state of the server we are timing
		 * out was MASTER_REDUCE, the right thing still happens
		 * here.
		 */

		reduceFreeLicenses(s, i);

		Error_Push(APP_BACKUPTIMEOUT, NULL);
		retval |= CHECK;

		continue;
	      }

	    /*
	     * If we're timing out our master, promote ourselves and take
	     * our share of licenses.
	     */
	    if (local(state) == BACKUP && local(custodian) == i)
	      {
		s->servers[i].contact = SERVER_DISCONNECTED;
		resetPools(s, local(custodian));
		local(state) = MASTER;
		local(custodian) = s->us;
		s->nextMulticast = time((time_t *)0);

		Error_Push(APP_MASTERTIMEOUT, NULL);
		Error_Push(APP_NEWMASTER, NULL);
		retval = CHECK;
	      }
	  }

      /*
       * We lost at least one server. Assume clients may be lost,
       * and set up so only clients with activity after now are
       * timed out normally.
       * XXX is this correct for MASTER_REDUCE -> MASTER lossage? yes...
       */
      staleFreeze(s, time((time_t *)0));

      break;

    case SERVER_DEATH:
      /*
       * We now assume the server is just dead, and not granting
       * licenses.
       */
      for (i = 0; i < s->numServers; i++)
	{
	  if ((s->servers[i].contact == SERVER_DISCONNECTED) &&
	      (s->nextWakeup >= s->servers[i].last_contact + T_split))
	    {
	      s->servers[i].contact = SERVER_DEAD;
	      s->servers[i].state = BACKUP; /* relevance is not MASTER */
	      restoreFreeLicenses(s, i, 0);

	      Error_Push(APP_DEADSERVER, NULL);
	      retval = CHECK;
	    }

	  if (s->servers[i].state == MASTER)
	    masterCount++;
	}

      if (masterCount == 1) /* We're the last; all others assumed dead. */
	{
	  staleUnfreeze(s); /* XXX different for net freezes */

	  FillLicenses(s);
	}

      break;

    case SERVER_CONNECT:
      if (s->acked == 1) /* sendCount > 0, or this event not called */
	{
	  s->acked = 0;
	  s->sendCount--;
	  s->next += T_reduce;
	}
      else
	{
	  s->acksFailed++;
	  if (s->acksFailed == F_reduce)
	    { /* give up */
	      s->acked = 1;
	      s->sendCount = 0;
	      break;
	    }
	  else
	    {
	      s->acked = 0;
	      s->sendCount = N_reduce - 1; /* since we're bypassing the -- */
	      s->next += T_reduce;
	    }
	}

      Pout->type = P0_REDUCE;
      P0_PutHeader(Pout);
      P0_PutCard32(Pout, s->newState);
      P0_PutCard32(Pout, s->sendCount);
      P0_PutLength(Pout);
      pout->Source = s->servers[s->reducee].activitySource;
      pout->Destination = s->servers[s->reducee].connection;
#ifdef DEBUG
      Connect_AddressToName(s->servers[s->reducee].connection,
			    hnam, sizeof(hnam));
      fprintf(stdout, "Reduce (%d) sent to %s\n", s->sendCount, hnam);
#endif
      retval = Connect_SendPacket(pout);

      break;

    default:
      break;
    }

  return retval | compute_wakeup(s);
}

Trap Reduce(s, Pin, Pout, new)
     srvinfo *s;
     P0Packet *Pin, *Pout;
     int new;
{
  int inprogress, i;
  Card32 state, left;
  Packet *pout;
  Trap retval = OK;
#ifdef DEBUG
  char hnam[50];
#endif

  if (local(state) == BACKUP)
    return OK;

  /* Are we currently a reducee? */
  inprogress = (s->last_reduce + T_reduce * F_reduce >= Pin->packet->when);

  pout = Pout->packet;

  if (P0_GetCard32(Pin, &state) ||
      P0_GetCard32(Pin, &left))
    { /* XXX drop it on the floor; log some level of error here */
      return OK;
    }

  for (i = 0; i < s->numServers; i++)
    if (s->servers[i].connection == Pin->packet->Source)
      break;

  if (i == s->numServers)
    return OK;	/* XXX not a server connection; some interesting logging... */

  /*
   * Accept new reducer only if we are not involved in another reduction.
   */
  if ((s->sendCount == 0 && s->acked == 1) && /* we're not a reducer now */
      !inprogress /* nor a reducee */)
    {
      s->reducer = i;
      s->last_reduce = Pin->packet->when;
    }

  inprogress = (s->last_reduce + T_reduce * F_reduce >= Pin->packet->when);

  if (s->reducer == i && inprogress)
    {
      s->last_reduce = Pin->packet->when;

      Pout->type = P0_REDUCE_REPLY;
      P0_PutHeader(Pout);
      P0_PutCard32(Pout, state);
      P0_PutCard32(Pout, left);
      P0_PutLength(Pout);
      pout->Source = Pin->packet->Destination;
      pout->Destination = Pin->packet->Source;
#ifdef DEBUG
      Connect_AddressToName(pout->Destination, hnam, sizeof(hnam));
      fprintf(stdout, "Reduce reply (%d) sent to %s\n", left, hnam);
#endif
      Connect_SendPacket(pout);

      if (left == 0)
	{
	  s->servers[i].contact = SERVER_CONNECTED;
	  s->servers[i].last_handshake = Pin->packet->when; /* XXX kludge */
	  Error_Push(APP_NEWCONNECT, NULL);
	  retval = CHECK;

	  /*
	   * The requisite number of continuity checks have been made.
	   * Now we think about acting.
	   */
	  if (state == MASTER)
	    {
	      /* We get to take over when a MASTER_REDUCE
		 state packet comes. */
	    }
	  else
	    {
	      local(custodian) = i;
	      local(state) = MASTER_REDUCE;
	      s->multicastMade = 0;
	      /* We give it up. */
	    }
	}
    }

  return retval;
}

Trap ReduceReply(s, Pin, Pout, new)
     srvinfo *s;
     P0Packet *Pin, *Pout;
     int new;
{
  Card32 state, left;
  Trap retval = OK;

  if (P0_GetCard32(Pin, &state) ||
      P0_GetCard32(Pin, &left))
    { /* drop it on the floor */
      return OK;
    }

  if (s->servers[s->reducee].connection == Pin->packet->Source)
    {
      s->acked = 1;
      if (s->sendCount == 0) /* == left */
	{
	  s->servers[s->reducee].contact = SERVER_CONNECTED;
	  s->servers[s->reducee].last_handshake = Pin->packet->when; /*XXX Kl*/
	  Error_Push(APP_NEWCONNECT, NULL);
	  retval = CHECK;

	  if (state == BACKUP) /* == s->newState */
	    {
	      /* We get to take over when a MASTER_REDUCE
		 state packet comes. */
	    }
	  else
	    {
	      local(custodian) = s->reducee;
	      local(state) = MASTER_REDUCE;
	      s->multicastMade = 0;
	      /* We give it up. */
	    }
	}
    }

  /* XXX log error if not found... */
  return retval;
}

int iq(s, who)
     srvinfo *s;
     int who;
{
  int i, iq = 0;

  if (s->servers[who].poolState == NULL)
    return 0;

  for (i = 0; i < s->totalPools; i++)
    iq += s->servers[who].poolState[i].active;

  return iq;
}

/*
 * Answers the question, Am I Smarter Than Who?
 */
int smarter(s, who)
     srvinfo *s;
     int who;
{
  int myiq, whosiq;

  /* We're smarter if we have more clue. */
  if (local(clue) < s->servers[who].clue)
    return 1;

  myiq = iq(s, s->us);
  whosiq = iq(s, who);

  /* We're smarter if we have more active clients. */
  if (myiq >= whosiq)
    return 1;

  return 0;
}

void ReduceMasters(s, i)
     srvinfo *s;
     int i;
{
  if (local(state) == MASTER && s->servers[i].state == MASTER)
    {
      if ((s->us < i) && /* we should be reducer */
	  (s->sendCount == 0 && s->acked == 1) && /* we're not a reducer now */
	  (s->last_reduce + T_reduce * F_reduce < time((time_t *)0))) /*~ucee*/
	{
	  s->reducee = i;
	  s->newState = smarter(s, i) ? BACKUP : MASTER;
	  s->sendCount = N_reduce;
	  s->acked = 1;
	  s->acksFailed = 0;
	  s->next = time((time_t)0);
	}
    }
}

/*
 * ERRORS:
 *
 * APP_BADSERVER: WARNING
 * State packet received from an unknown server
 *
 * APP_NEWCONNECT: INFO LOG
 * Connection with a new server made.
 */
Trap State(s, Pin, Pout, new)
     srvinfo *s;
     P0Packet *Pin, *Pout;
     int new;
{
  int i, packetsource;
  Packet *pout;
  Card32 templastcont, templasthand, tempstate, tempcust, tempclue;
  OldPool *op;
  Trap retval = OK;
#ifdef DEBUG
  char hnam[50];
#endif

  pout = Pout->packet;

  /*
   * Update state of other servers as appropriate.
   */

  /* get appropriate server index */
  for (packetsource = 0; packetsource < s->numServers; packetsource++)
    if (s->servers[packetsource].connection == Pin->packet->Source)
      break;

  if (packetsource == s->numServers)
    Return(APP_BADSERVER, NULL);

  /*
   * Ignore replies from timed-out backups - they don't get to talk
   * to us once they time out until they become masters again. This
   * simply avoids confusion in the protocol.
   */
  if (Pin->type == P0_STATE_REPLY && /* P0_STATE mutex with BACKUP */
      s->servers[packetsource].contact == SERVER_DISCONNECTED &&
      s->servers[packetsource].state == BACKUP)
    return OK;

  /*
   * For normal backup state replies, consider the connection fine
   * and zap the retryCount.
   */
  if (Pin->type == P0_STATE_REPLY /* && Now why was this here?
      s->servers[packetsource].contact == SERVER_CONNECTED &&
      s->servers[packetsource].state == BACKUP */ )
    s->servers[packetsource].retryCount = 0; /* XXX Care about delays? */

  /* Copy any state this server has that is more up-to-date than our own. */
  for (i = 0; i < s->numServers; i++)
    {
      /* Parse packet into temporary variables */
      P0_GetCard32(Pin, &templastcont);
      P0_GetCard32(Pin, &templasthand);
      P0_GetCard32(Pin, &tempstate);
      P0_GetCard32(Pin, &tempcust);
      P0_GetCard32(Pin, &tempclue);

      /*
       * The only interesting information the remote server can have
       * about us that we don't know is the last time it heard from us.
       */
      if (i == s->us &&
	  s->servers[packetsource].last_handshake < (time_t)(templastcont))
	s->servers[packetsource].last_handshake = (time_t)(templastcont);
      else
	{
	  /* Copy information if it is more up-to-date. */

	  if (templastcont > s->servers[i].last_contact)
	    {
	      s->servers[i].last_contact = (time_t)templastcont;

	      if (s->servers[i].contact == SERVER_DEAD &&
		  s->servers[i].last_contact + T_split > Pin->packet->when)
		s->servers[i].contact = SERVER_DISCONNECTED;
	      /* XXX reallocate licenses to the server also */

	      if (packetsource == local(custodian))
		s->servers[i].last_handshake = (time_t)templasthand;
	      s->servers[i].state = tempstate;
	      s->servers[i].custodian = tempcust;
	      s->servers[i].clue = tempclue;
	    }
	}
    }

  /*
   * This is the closure of the reconnection process for the MASTER
   * side. BACKUP closure follows, and happens on receipt of the reply
   * to this packet.
   */
  if (Pin->type == P0_STATE &&
      s->servers[packetsource].state == MASTER_REDUCE &&
      s->servers[packetsource].custodian == s->us)
    {
      /* We get to take these free licenses. */
      op = GetPoolFromPacket(Pin, s); /* XXX NULL */
      setServerPool(s, packetsource, op, 1); /* This op is not divided. */
      restoreFreeLicenses(s, packetsource, 1);

      /* other state about the server got updated from the packet */

      /*
       * Continue to consider this machine in MASTER_REDUCE until
       * we get a reply from it from one of our state packets that
       * indicates otherwise. If we time out a MASTER_REDUCE server,
       * we must give it all of the free licenses back.
       */
    }

  /*
   * If this packet is from our master, remember its pool information.
   */
  if (packetsource == local(custodian) && local(state) == BACKUP)
    {
      op = GetPoolFromPacket(Pin, s); /* XXX NULL */
      setServerPool(s, packetsource, op, 0);
    }

  if (Pin->type == P0_STATE_REPLY && packetsource == local(custodian) &&
      local(state) == MASTER_REDUCE && tempstate == MASTER_REDUCE)
    { /*
       * Implies we're in MASTER_REDUCE and this is the completion
       * packet of the connection. Checking tempstate is necessary,
       * because it's possible for a STATE_REPLY to come after
       * the last REDUCE packet that corresponds to a MULTICAST
       * before the REDUCE packet.
       */
      op = GetPoolFromPacket(Pin, s); /* XXX NULL */
      setServerPool(s, packetsource, op, 0);
#ifdef DEBUG
      fprintf(stdout, "here\n");
#endif
    }

  /*
   * If this packet is from our backup and is a reply from our last
   * multicast, record that it has the current poolState.
   */
  if ((s->servers[packetsource].custodian == s->us) &&
      (s->servers[packetsource].last_handshake == local(last_contact)))
    setServerPool(s, packetsource, local(poolState), local(groupSize));

  if (Pin->type == P0_STATE)
    {
      /*
       * Send reply
       */
      makeState(s, Pout, P0_STATE_REPLY, Pin->packet->when, /* XXX? */
		(local(state) == BACKUP) ? NULL : local(poolState));
      pout->Source = Pin->packet->Destination;
      pout->Destination = Pin->packet->Source;
#ifdef DEBUG
      Connect_AddressToName(pout->Destination, hnam, sizeof(hnam));
      fprintf(stdout, "State reply sent to %s\n", hnam);
#endif
      Connect_SendPacket(pout);
    }

  /*
   * Begin master reduction process if appropriate.
   */
  ReduceMasters(s, packetsource);

  return retval;
}

Trap server_packet(s, Pin, Pout, new)
     srvinfo *s;
     P0Packet *Pin, *Pout;
     int new;
{
  Trap retval;

  switch(Pin->type)
    {
    case P0_STATE:
    case P0_STATE_REPLY:
      retval = State(s, Pin, Pout, new);
      break;

    case P0_REDUCE:
      retval = Reduce(s, Pin, Pout, new);
      break;

    case P0_REDUCE_REPLY:
      retval = ReduceReply(s, Pin, Pout, new);
      break;

    default:
      Error_Push(APP_UNKNOWNTYPE, Pin->type);
      retval = CHECK;
      break;
    }

  return retval | compute_wakeup(s);
}
