#include <stdio.h>
#include "server.h"
#include "protocol.h"

/*
 * clientInt.c: contains code for client network interactions
 */

Trap StatusQuery(s, Pin, Pout, new)
     srvinfo *s;
     P0Packet *Pin;
     P0Packet *Pout;
     int new;
{
  Context *c;
  Trap retval, retval1;
  Packet *pin;

  pin = Pin->packet;

  if (FindContext(&c, pin->Source, Pin->context, 0))
    {
      Error_Pop();
      MakeErrorPacket(Pout, E_BADPACKET);
      retval = Connect_SendPacket(Pout->packet);
      if (new)
	{
	  retval1 = Connect_CloseConnection(pin->Destination, pin->Source);
	  return retval || retval1;
	}
      else
	return retval;
    }

  TouchContext(s, c, pin->when, pin->Destination);

  Pout->type = P0_STATUS_RESPONSE;
  P0_PutHeader(Pout);
  P0_PutCard32(Pout, c->licensesHeld);
  P0_PutCard32(Pout, c->licensesRequested);
  P0_PutLength(Pout);

  return Connect_SendPacket(Pout->packet);
}

License *findLicense(s, vendor, program, version)
     srvinfo *s;
     char *vendor, *program, *version;
{
  License *l;

  for (l = s->firstLicense; l != NULL; l = l->next)
    if (!strcmp(vendor, l->vendor) && !strcmp(program, l->program) &&
	!strcmp(version, l->version))
      return l;

  return NULL;
}

void getAuthInfo(s, desc, l, want, authVec, goodpool)
     srvinfo *s;
     Adr *desc;
     License *l;
     Card32 want, *authVec;
     Pool **goodpool;
{
  Card32 bit;
  Pool *p;

  *authVec = 0;
  *goodpool = NULL;

  Authorize_LoadInfo(desc);
/*
  if (Authorize_LoadInfo(desc))
    {
      etype = E_SERVERLOSSAGE;
      goto sendError;  XXX I don't think this code needs to be here.
    }
*/
  
  for (p = l->firstPool, bit = 1; p != NULL; p = p->next, bit = bit << 1)
    if (Authorized(s->mb, p->pb))
      {
	*authVec |= bit;
	if (!(*goodpool) && want <= p->free)
	  *goodpool = p;
      }
}

Trap makeContext(s, c, src, context, desc, authVec)
     srvinfo *s;
     Context **c;
     Addr src;
     Card16 context;
     Adr *desc;
     Card32 authVec;
{
  if (FindContext(c, src, context, ALLOC) && Error_Severity == S_FATAL)
    return CHECK;

  (*c)->user = (char *)malloc(strlen(desc->user) + 1);
  if ((*c)->user == NULL)
    {
      FreeContext(*c);
      Return(MEMORY_ALLOC, strlen(desc->user) + 1);
    }
  strcpy((*c)->user, desc->user);

  (*c)->machine = (char *)malloc(strlen(desc->machine) + 1);
  if ((*c)->machine == NULL)
    {
      free((*c)->user);
      FreeContext(*c);
      Return(MEMORY_ALLOC, strlen(desc->machine) + 1);
    }

  (*c)->authVec = authVec;
  return OK;
}

Trap AccessInit(s, Pin, Pout, new)
     srvinfo *s;
     P0Packet *Pin, *Pout;
     int new;
{
  Adr desc;
  Context *c;
  Packet *pin;
  License *currentl;
  Pool *goodpool;
  Card32 authVec;
  Card32 have, want;
  Card16 keepopen;
  int etype;
  Trap retval;

  pin = Pin->packet;

  /*
   * Parse the data out of the packet.
   */
  if (P0_GetString(Pin, &(desc.user)) ||
      P0_GetString(Pin, &(desc.machine)) ||
      P0_GetString(Pin, &(desc.hostname)) ||
      P0_GetCard32(Pin, &(desc.ip)) ||
      P0_GetString(Pin, &(desc.vendor)) ||
      P0_GetString(Pin, &(desc.program)) ||
      P0_GetString(Pin, &(desc.version)) ||
      P0_GetCard32(Pin, &have) ||
      P0_GetCard32(Pin, &want) ||
      P0_GetCard16(Pin, &keepopen))
    {
      etype = E_BADPACKET;
    sendError:
      MakeErrorPacket(Pout, etype);
      retval = Connect_SendPacket(Pout->packet);
      if (new)
	return Connect_CloseConnection(pin->Destination, pin->Source);
      else
	return retval;
    }

  fprintf(stdout, "Request from %s@%s (%s; %s) for %s %s %s\n",
	  desc.user, desc.hostname, inet_ntoa(desc.ip),
	  desc.machine, desc.vendor, desc.program, desc.version);

  if (local(clue) == CLUELESS)
    {
      etype = E_CLUELESS;
      goto sendError;
    }

  if (!FindContext(&c, pin->Source, Pin->context, NOALLOC))
    {
      /*
       * The Access Request applied to an existing context.
       * Should be a result of a lost reply. Send another
       * reply (by just falling past the else).
       */
      fprintf(stdout, "found Context\n");
    }
  else
    {
      /*
       * This is potentially a new context. Allocate it if the request
       * is satisfiable (the user has authorization for any pool and
       * is willing to be queued, or the user has authorization for a
       * pool with available licenses).
       */

      currentl = findLicense(s, desc.vendor, desc.program, desc.version);

      if (currentl == NULL) /* Package unknown. */
	{
	  etype = E_PACKAGEUNKNOWN;
	  goto sendError;
	}

      /*
       * We have found the license they requested. Compute
       * authorization against its pools, and remember
       * the first authorized pool with sufficient licenses.
       *
       * XXX - remember to deal with trapping MAXPOOLS 32
       */

      getAuthInfo(s, &desc, currentl, want, &authVec, &goodpool);

      if (!authVec)
	{
	  /*
	   * There was no pool that the user was authorized to
	   * access. Return this error.
	   */
	  etype = E_ACCESSDENIED;
	  goto sendError;
	}

      if (keepopen == 0 && goodpool == NULL)
	{
	  /*
	   * Adequate licenses were not available, and
	   * the program did not wish to be queued.
	   */
	  etype = E_NORESOURCES;
	  goto sendError;
	}

      /*
       * At this point, either there are sufficient
       * licenses available to satisfy the request,
       * or the user was willing to be queued. So
       * create a new licensing context.
       */

      if (makeContext(s, &c, pin->Source, Pin->context, &desc, authVec))
	{
	  etype = E_SERVERLOSSAGE;
	  goto sendError;
	}

      /*
       * Put into stale queue
       */
      AddStale(s, c, pin->when, pin->Destination);

      if (local(clue) == CLUEFUL && goodpool != NULL)
	{
	  /*
	   * Resources are available. Grant them.
	   */
	  c->what = goodpool;
	  c->licensesHeld = want;
	  c->licensesRequested = want;
	  goodpool->free -= want; /* XXX check want>0 */
	}
      else
	{
	  /*
	   * Resources unavailable. Queue them.
	   */
	  fprintf(stderr, "queued\n");
	  /*
	   * In this case, c->what is only used to indicate what
	   * license c is associated with, not which pool, since
	   * that is not determined now.
	   */
	  c->what = currentl->firstPool;
	  c->licensesHeld = 0;
	  c->licensesRequested = want;
	  
	  Enqueue(c);
	}
    }

  /*
   * Now the request has been fully processed.
   * Send the reply.
   */
  Pout->type = P0_ACCESS_RESPONSE;
  Pout->context = Pin->context;
  P0_PutHeader(Pout);
  P0_PutCard32(Pout, c->licensesHeld);
  P0_PutCard32(Pout, c->licensesRequested);
  P0_PutLength(Pout);

  return Connect_SendPacket(Pout->packet);
}

/*
 * ERRORS:
 *
 * APP_UNKNOWNTYPE: FATAL
 * The packet type was unknown.
 */
Trap client_packet(s, Pin, Pout, new)
     srvinfo *s;
     P0Packet *Pin, *Pout;
     int new;
{
  Trap retval;

  switch(Pin->type)
    {
    case P0_ACCESS_INITIALIZE:
      retval = AccessInit(s, Pin, Pout, new);
      break;

    case P0_STATUS_QUERY:
      retval = StatusQuery(s, Pin, Pout, new);
      break;

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

  return retval;
}
