#if !defined(lint) && !defined(__INSIGHT__)
static char sos__rcsid[] = "options.c,v 1.1.1.1 1995/06/16 17:42:42 seth Exp";
static char sos__copyright[] = "Copyright (c) 1994, 1995 SOS Corporation";
static char sos__contact[] = "SOS Corporation <sos-info@soscorp.com> +1 800 SOS UNIX";
#endif /* not lint */

/*
 * ++Copyright Released Product++
 *
 * Copyright (c) 1994, 1995 Sources of Supply Corporation ("SOS").
 * All rights reserved.
 *
 * The SOS Released Product License Agreement specifies the terms and
 * conditions for redistribution.  You may find the License Agreement
 * in the file LICENSE.
 *
 * SOS Corporation
 * 461 5th Ave.; 16th floor
 * New York, NY 10017
 *
 * +1 800 SOS UNIX
 * <sos-info@soscorp.com>
 *
 * --Copyright Released Product--
 */

/*
 * Interface to options negotiation 
 *
 */
#include <dict.h>
#include <dll.h>
#include "sos.h"

int sos_negotdebug=0;

#ifndef SHORTBUF
#define SHORTBUF 512
#endif /* SHORTBUF */

#define LOOPMAX 10

static _init_options=0;

static struct negotopt
{
  int winner;			/* 1 If I win clashes; 0 if I lose. */
  dict_h optlist;		/* Resolved options */
} negotinfo;


struct option
{
  char *name;			/* Option name */
  int localpref;		/* Local preferences for options */
  int peerpref;			/* Peer preferences for options */
  int is_valid;			/* 1 means state value is valid */
  int state;			/* current state of option (result) */
};

/*
 * Initialize the negotopt structure
 * Input: Ignored. Compatibility only.
 */
static void
sos_opt_init()
{
  _init_options = 1;
  negotinfo.winner = -1;
  negotinfo.optlist = dll_create((int *)NULL, (int *)NULL, 
				   DICT_UNORDERED, (int *)NULL);
  return;
}

/*
 * Reads a sos-style configuration file and inserts the entries with specified 
 * key into the localpref list (eg. if key="option", then a line like
 *        option foo prefer
 * will create an option named `foo' with pref-value `prefer' )
 * 
 * input: config: Handle on the configuration file
 * 	  key: String in the configuration which indicates an option 
 *		specification
 * output: 0 if no problems, -1 of a bad preference value is detected
 */
int
sos_opt_config_predisp(sos_config_t config, char *key)
{
 
  sos_config_value conf_val;
  char buf[SHORTBUF];
  char *option_key;
  char *option_value;
  char *p,*q;
  char save_char;
  int prefval;

  while (conf_val=sos_config_getnext(config, key, SOS_CONFIG_FORWARD,
				    SOS_CONFIG_NULL))
    {
      if ( sos_negotdebug )
	fprintf(stderr,"sos_config_predisp(): confval: **%s**\n", 
		conf_val);

      memset(buf, (char) 0, SHORTBUF);
      strcpy(buf, conf_val);	/* We copy so we can rip apart buf */
      
      q = buf;
      q += strspn(buf, SOS_WHITESPACE);	/* Strip leading white space */
      if ( !(p=strpbrk(q, SOS_WHITESPACE )) )
	{
	  continue;
	}

      *p='\0';			/* Separates option key from option val */

      option_key = q;
      option_value = strtok(p+1,SOS_WHITESPACE);
      
      if ( SOS_STREQ(option_value, "must_not") )
	{
	  prefval=SOS_OPT_MUST_NOT;
	}
      else if ( SOS_STREQ(option_value, "prefer_not") )
	{
	  prefval=SOS_OPT_PREFER_NOT;
	}
      else if ( SOS_STREQ(option_value, "prefer") )
	{
	  prefval=SOS_OPT_PREFER;
	}
      else if ( SOS_STREQ(option_value, "must") )
	{
	  prefval=SOS_OPT_MUST;
	}
      else 
	{
	  return -1;
	}

      if ( sos_negotdebug)
	fprintf(stderr,"sos_config_predisp(): Setting key(%s) to value(%d)\n",
		option_key, prefval);

      (void)sos_opt_set_value(option_key, SOS_OPT_LPREF, prefval);
			     
    }
  return 0; 
}

/*
 * Allocate a new option structure
 */
static struct option *
sos_option_allocate()
{
  struct option *new;
  if ( (new = (struct option *)malloc (sizeof (*new))) == NULL )
    return NULL;
  
  memset(new, (char)0, sizeof(new)); 
  new->localpref = SOS_OPT_MUST_NOT;
  new->peerpref = SOS_OPT_MUST_NOT;
  new->is_valid = 0;
  new->state = SOS_OPT_OFF;

  return new;
}


/*
 * Set a new preference or state value for an option with the specfied key.
 *
 * list: Address of list (from negotopt structure) to search.
 * name: option name to set
 * field: PREF if preference field, STATE if state field.
 * value: value to set.
 *
 * Side effect: Creates a new option entry with specified value if
 * none exists. Uses strdup for name.
 */
int 
sos_opt_set_value(char *name, int field, int value)
{
  struct option *opt;
  int isnew=SOS_FALSE;

  if (field != SOS_OPT_STATE && 
      field != SOS_OPT_LPREF &&
      field != SOS_OPT_PPREF )
    {
      return -1;
    }

  if ( !_init_options )
    sos_opt_init();

  for (opt = dll_maximum(negotinfo.optlist); opt;
       opt=dll_predecessor(negotinfo.optlist, opt))
    {
      if ( SOS_STREQ(opt->name, name) )
	{
	  break;
	}
    }

  if (!opt)
    {
      isnew=SOS_TRUE;
      opt = sos_option_allocate();
      if (!opt) return -1;

      opt->name = strdup(name);
    }

  if (field == SOS_OPT_LPREF )
    {
      opt->is_valid = 0;
      opt->localpref = value;
    }
  else if (field == SOS_OPT_PPREF )
    {
      opt->is_valid = 0;
      opt->peerpref = value;
    }
  else  /* Already did condition checking `field' */
    {
      opt->is_valid = 1;
      opt->state = value;
    }
  
  if ( isnew)
    {
      dll_insert(negotinfo.optlist, opt);
    }

 return 0; 
}


/*
 * Send your peer the your options preferences and get hers in return.
 * Continue with this simple scheme until both sides agree to end the exchange.
 *
 * intput: readpeer,writepeer: read and write descriptors open on peer.
 * 	   rfun, wfun: read and write functions to use with sos_xdr_??
 *
 * return: 0 on success, -1 on failure.
 */
int
sos_opt_exchange_prefs (int readpeer, 
			int (*rfun)(int fd, caddr_t buf, __SIZE_TYPE__ len),
			int writepeer,
			int (*wfun)(int fd, caddr_t buf, __SIZE_TYPE__ len) )
{
  struct option *opt;
  int rval;
  int localclose;
  int peerclose;
  char *rname;
  char *endmsg;
  int loopcntl =0; 

 redo: /* Come back here to rerun the the protocol */

  if ( loopcntl++ == LOOPMAX )
    {
      /*
       * Things are just taking too long. Denial of service perhaps?
       */
      return -1;
    }
	

  if ( sos_negotdebug )
    fprintf(stderr,"sos_opt_exchange_prefs(): Entering\n");

  if ( !_init_options )
    sos_opt_init();

  /*
   * Send your preferences
   */
  for (opt = dll_maximum(negotinfo.optlist); opt;
       opt=dll_predecessor(negotinfo.optlist, opt))
    {
      if ( sos_xdr_wencode (writepeer, wfun, "Si",
			    opt->name, opt->localpref) <= 0)
	{
	  if ( sos_negotdebug )
	    fprintf(stderr,"sos_opt_exchange_prefs(): Error Writing local pref\n");
	  return -1;
	}
    }

  if ( sos_negotdebug )
    fprintf(stderr,"sos_opt_exchange_prefs(): Sent local prefs\n");
  /*
   * Let her know that you are done.
   */
  if ( sos_xdr_wencode (writepeer, wfun, "Si", SOS_OPT_END, 
			SOS_OPT_SEND_LOCAL) <= 0 )
    {
      if ( sos_negotdebug )
	fprintf(stderr,"sos_opt_exchange_prefs(): Error sending local end msg\n");
      return -1;
    }

  if ( sos_negotdebug )
    fprintf(stderr,"sos_opt_exchange_prefs(): End local prefs\n");  
  /*
   * Get her preferences
   */
  while (1)
    {
      if (sos_xdr_wdecode (readpeer, rfun, "Si", &rname, &rval ) != 2 )
	{
	  if ( sos_negotdebug )
	    fprintf(stderr,"sos_opt_exchange_prefs(): Error reading a peer pref\n");
	  return -1;
	}

      if ( SOS_STREQ(rname, SOS_OPT_END)  )
	{
	  if ( rval == SOS_OPT_SEND_LOCAL )
	    break;
	  else
	    /*
	     * Someone sent the wrong END message. Allow them to resend the
	     * correct one.
	     */
	    continue;
	}
      
      if ( sos_opt_set_value(rname, SOS_OPT_PPREF, rval) < 0 )
	{
	  return -1;
	}
    }
  
  if ( sos_negotdebug )
    fprintf(stderr,"sos_opt_exchange_prefs(): Read all the peer prefs\n");

  /*
   * Send final negotiation close offer -- The 'off-the-shelf' routine will
   * always offer close, but you should feel free to take whatever action you 
   * feel like hacking in.  If you want another go just send a SOS_OPT_NO_CLOSE
   * and compliant listeners *will* go through the protocol again.
   * In particular, you may have received some option names from your peer 
   * that you did not initially make any comment on. At this point your peer
   * will be assuming that your feeling is MUST NOT and will react acordingly.
   * By updating your local preference value and repeating the loop you will
   * actually say what you like.
   */
  localclose=SOS_OPT_CLOSE;
  if ( sos_xdr_wencode(writepeer, wfun, "Si", SOS_OPT_END, 
		       localclose ) <= 0 )
    {
      return -1;
    }

  if ( sos_xdr_wdecode(readpeer, rfun, "Si", &endmsg, &peerclose ) !=2  )
    {
      return -1;
    }

  if ( !SOS_STREQ(endmsg, SOS_OPT_END))
    {
      /*
       * Bad protocol -- Kill
       */
      return -1;
    }

  if ( localclose == SOS_OPT_NO_CLOSE || peerclose == SOS_OPT_NO_CLOSE)
    goto redo;
    
  return 0;
}


/*
 * Simple debuging routing to print out the state of the option structures.
 *
 * input: none
 * output: none
 */
void
sos_opt_print_opts()
{
  struct option *opt;
  

  if ( !_init_options )
    sos_opt_init();

  printf("***************** OPTIONS *****************\n");
  for (opt = dll_maximum(negotinfo.optlist); opt;
       opt=dll_predecessor(negotinfo.optlist, opt))
    {
      printf("Name: %s\n", opt->name);
      printf("\tlocal: %d\n", opt->localpref);
      printf("\tpeer: %d\n", opt->peerpref);
      printf("\tstate: %s\n", (opt->state == SOS_OPT_ON)?"SOS_OPT_ON":
	     "SOS_OPT_OFF");
    }
  printf("*******************************************\n");
}


/*
 * Resolve the local and peer preferences (ie set state).
 * Clash entries are overwritten depending ont the value of am_winner.
 * So feel free to cheat,but keep in mind that your peer will not be, 
 * so `cheating' will probably only screw you up.
 *
 * intput: am_winner: either SOS_OPT_WINNER or SOS_OPT_LOSER. 
 * output: 0 if everything resolves, -1 if there is an incompatibility.
 */
int
sos_opt_resolv_negot(int am_winner)
{
  struct option *opt;

  if ( am_winner != SOS_OPT_WINNER && am_winner != SOS_OPT_LOSER )
    {
      /* EBADINPUT */
      return -1;
    }

  if ( !_init_options )
    sos_opt_init();
  
  for (opt = dll_maximum(negotinfo.optlist); opt;
       opt=dll_predecessor(negotinfo.optlist, opt))
    {
      switch ( opt->localpref )
	{
	case SOS_OPT_MUST:
	  if ( opt->peerpref == SOS_OPT_MUST_NOT )
	    return -1;
	  opt->state = SOS_OPT_ON;
	  break;
	case SOS_OPT_PREFER:
	  switch (opt->peerpref)
	    {
	    case SOS_OPT_MUST:
	    case SOS_OPT_PREFER:
	      opt->state = SOS_OPT_ON;
	      break;
	    case SOS_OPT_PREFER_NOT:
	      opt->state = ( am_winner == SOS_OPT_WINNER)?
		SOS_OPT_ON:SOS_OPT_OFF;
	      break;
	    case SOS_OPT_MUST_NOT:
	      opt->state = SOS_OPT_OFF;
	      break;
	    }
	  break;
	case SOS_OPT_PREFER_NOT:
	  switch (opt->peerpref )
	    {
	    case SOS_OPT_MUST:
	      opt->state = SOS_OPT_ON;
	    case SOS_OPT_PREFER:
	      opt->state = ( am_winner == SOS_OPT_WINNER)?
		SOS_OPT_OFF:SOS_OPT_ON;
	      break;
	    case SOS_OPT_PREFER_NOT:
	    case SOS_OPT_MUST_NOT:
	      opt->state = SOS_OPT_OFF;
	      break;
	    }
	  break;
	case SOS_OPT_MUST_NOT:
	  if ( opt->peerpref == SOS_OPT_MUST )
	    return -1;
	  opt->state = SOS_OPT_OFF;
	  break;
	} /* End opt->localpref */

      opt->is_valid = 1;
    }

  return 0;

}


/*
 * High level options negotiatiation routine. Provides a simple interface for 
 * applications seeking to use the negotiation facilities in a completely 
 * standard manner. It simply decides which peer will win preference clashes, 
 * exchanges option preferences, and resolves options. The result is a 
 * correctly configured negotinfo structure.
 *
 * input: See sos_opt_exchange_prefs
 * output: 0 on success, -1 on failure.
 */
int 
sos_negotiate_options(int readpeer, 
		      int (*rfun)(int fd, caddr_t buf, __SIZE_TYPE__ len),
		      int writepeer,
		      int (*wfun)(int fd, caddr_t buf, __SIZE_TYPE__ len) )

{
  int am_winner;

  if ((am_winner=sos_fairtoss_win_lose(readpeer, rfun, writepeer, wfun)) < 0 )
    {
      return -1;
    }

  am_winner = (am_winner == SOS_FAIRTOSS_WIN)?SOS_OPT_WINNER:SOS_OPT_LOSER;
  
  if ( sos_opt_exchange_prefs(readpeer, rfun, writepeer, wfun) < 0 )
    {
      return -1;
    }

  if ( sos_opt_resolv_negot(am_winner)  < 0 )
    {
      return -1;
    }
  return 0;
}


/*
 * Return the state of the indicated option
 * input: name: string indicating option name
 * output: SOS_OPT_ON, SOS_OPT_OFF, or -1 (if not found).
 */
int
sos_opt_get_state(char *name)
{
  struct option *opt;

  if ( !_init_options )
    sos_opt_init();

  for (opt = dll_maximum(negotinfo.optlist); opt;
       opt=dll_predecessor(negotinfo.optlist, opt))
    {
      if ( SOS_STREQ(name, opt->name) && opt->is_valid )
	{
	    return opt->state;
	}
    }
  return -1;
}
