/* -*- Mode: C -*-
 * $Id: gxsnmp_util.c,v 1.1 2000/04/04 20:14:16 jochen Exp $
 * GXSNMP - An snmp managment application
 * Copyright (C) 1998 Gregory McLean
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc.,  59 Temple Place - Suite 330, Cambridge, MA 02139, USA.
 *
 * Helper functions.
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <glib.h>

#include "gxsnmp_util.h"
#include "debug.h"

/** 
 * match_strval:
 * @val: Numeric value to be matched to a string
 * @value_string: Table with value/string pairs. NULL string is end marker.
 *
 * Looks up a numeric value in a list and returns the corresponding 
 * description. Unlike hash tables, the list can be defined staticly in a 
 * .c or .h file.
 *
 * Return value: String if match happened. NULL otherwise.
 **/
gchar*
match_strval(guint32 val, const value_string *vs) 
{
  gint i = 0;
  
  D_FUNC_START;
  while (vs[i].strptr) 
    {
      if (vs[i].value == val)
	break;
      i++;
    }
  D_FUNC_END;
  return(vs[i].strptr);
}
static gint
int_div(guchar *buffer, gint radix, gint *len, gint *nul)
{
  gint divisor, i, mod;

  *nul = 1;
  while (*len && (*buffer == 0))
    {
      for (i=1; i<*len; i++)
        buffer[i-1] = buffer[i];
      (*len)--;
    }
  if (!*len) 
    return 0;

  divisor = 0;
  mod = 0;
  for (i=0; i<*len; i++)
    {
      divisor = mod * 256 + buffer[i];
      mod = divisor % radix;
      buffer[i] = divisor / radix;
    }
  while (*len && (*buffer == 0))
    {
      for (i=1; i<*len; i++)
        buffer[i-1] = buffer[i];
      (*len)--;
    }
  if (*len) *nul=0;
  return mod;
}
/**
 * print_radix:
 * @buffer: integer value
 * @len: length of integer
 * @radix: base
 * @shift: decimal shift
 *
 */
static gchar *
print_radix(gpointer buffer, gint len, gint radix, gint sign, gint shift)
{
  guchar *result, *temp;
  gint size, neg, i, nul;
  gchar digit[] = "0123456789ABCDEF";

  if (radix<2) radix=2;
  if (radix>16) radix=16;

  size = len*8 + 2; /* worst case + sign + trailing 0 */
  if (shift) size++; /* decimal point */
  if (size<shift+4) size=shift+4; /* additional leading 0 */

  result = g_malloc(size);
  temp = g_malloc(len);

  neg = 0;

  g_memmove(temp, buffer, len);

  if (sign && (temp[0] & 0x80))
    {
      neg = 1;
      for (i=0; i<len; i++)
	temp[i] = ~temp[i];
      i=len-1;
      while (i>=0)
	{
	  temp[i]++;
	  if (temp[i]) break;
	  i--;
	}
    }
  i=size-1;
  nul=0;
  while ((!nul || (shift && size-i-3 < shift)) && (i>=0))
    {
      if ((shift == size-i-1) && shift)
	result[i--] = '.';
      else
	result[i--] = digit[int_div(temp, radix, &len, &nul)];
    }
  if (i && neg)
    result[i--] = '-';
  g_free(temp);
  temp = g_strdup(result+i+1);
  g_free(result);
  return temp;
}
/** 
 * format_integer:
 * @buffer: integer value as received from SNMP node
 * @len: length of integer
 * @format: Format as taken from DISPLAY HINT parameter of MIB file.
 *
 * Formats an INTEGER value according to a provided DISPLAY HINT.
 *
 * When the syntax has an underlying primitive type of INTEGER, the hint
 * consists of an integer-format specification, containing two parts.
 * The first part is a single character suggesting a display format,
 * either: `x' for hexadecimal, or `d' for decimal, or `o' for octal, or
 * `b' for binary.  For all types, when rendering the value, leading
 * zeros are omitted, and for negative values, a minus sign is rendered
 * immediately before the digits.  The second part is always omitted for
 * `x', `o' and `b', and need not be present for `d'.  If present, the
 * second part starts with a hyphen and is followed by a decimal number,
 * which defines the implied decimal point when rendering the value.
 * 
 * Return value: Formatted string.
 **/
gchar*
format_integer(gpointer buffer, gint len, gchar *format)
{
  gint shift;
  if (!format) format = "";
  shift=0;
  switch(format[0])
    {
      case 'x':
	return print_radix(buffer, len, 16, 1, 0);
	break;
      case 'o':
	return print_radix(buffer, len, 8, 1, 0);
	break;
      case 'b':
	return print_radix(buffer, len, 2, 1, 0);
	break;
      case 'd':
	if (format[1] == '-')
	  shift = atoi(&(format[2]));
	if (shift<0) shift=0;
      default:
	return print_radix(buffer, len, 10, 1, shift);
    }
}

/** 
 * format_string:
 * @buffer: octet string as received from SNMP node
 * @len: length of octet string
 * @format: Format as taken from DISPLAY HINT parameter of MIB file.
 *
 * Formats an OCTET STRING value according to a provided DISPLAY HINT.
 *
 * When the syntax has an underlying primitive type of OCTET STRING, the
 * hint consists of one or more octet-format specifications.  Each
 * specification consists of five parts, with each part using and
 * removing zero or more of the next octets from the value and producing
 * the next zero or more characters to be displayed.  The octets within
 * the value are processed in order of significance, most significant
 * first.
 *
 * The five parts of a octet-format specification are:
 *
 * (1)  the (optional) repeat indicator; if present, this part is a `*',
 *      and indicates that the current octet of the value is to be used as
 *      the repeat count.  The repeat count is an unsigned integer (which
 *      may be zero) which specifies how many times the remainder of this
 *      octet-format specification should be successively applied.  If the
 *      repeat indicator is not present, the repeat count is one.
 *
 * (2)  the octet length: one or more decimal digits specifying the number
 *      of octets of the value to be used and formatted by this octet-
 *      specification.  Note that the octet length can be zero.  If less
 *      than this number of octets remain in the value, then the lesser
 *      number of octets are used.
 *
 * (3)  the display format, either:  `x' for hexadecimal, `d' for decimal,
 *      `o' for octal, `a' for ascii, or `t' for UTF-8.  If the octet
 *      length part is greater than one, and the display format part refers
 *      to a numeric format, then network-byte ordering (big-endian
 *      encoding) is used interpreting the octets in the value.  The octets
 *      processed by the `t' display format do not necessarily form an
 *      integral number of UTF-8 characters.  Trailing octets which do not
 *      form a valid UTF-8 encoded character are discarded.
 *
 * (4)  the (optional) display separator character; if present, this part
 *      is a single character which is produced for display after each
 *      application of this octet-specification; however, this character is
 *      not produced for display if it would be immediately followed by the
 *      display of the repeat terminator character for this octet-
 *      specification.  This character can be any character other than a
 *      decimal digit and a `*'.
 *
 * (5)  the (optional) repeat terminator character, which can be present
 *      only if the display separator character is present and this octet-
 *      specification begins with a repeat indicator; if present, this part
 *      is a single character which is produced after all the zero or more
 *      repeated applications (as given by the repeat count) of this
 *      octet-specification.  This character can be any character other
 *      than a decimal digit and a `*'.
 *
 * Output of a display separator character or a repeat terminator
 * character is suppressed if it would occur as the last character of
 * the display.
 *
 * If the octets of the value are exhausted before all the octet-format
 * specification have been used, then the excess specifications are
 * ignored.  If additional octets remain in the value after interpreting
 * all the octet-format specifications, then the last octet-format
 * specification is re-interpreted to process the additional octets,
 * until no octets remain in the value.
 * 
 * Return value: Formatted string.
 **/
gchar*
format_string(gpointer buffer, gint len, gchar *format)
{
  int repeat, count, size;
  gchar seperator, terminator, formtype;
  gchar *ptr;
  guchar *bufptr;
  gchar *result;

  if (!format)
    format = "1a";
  ptr = format;
  bufptr = (guchar *)buffer;

  size = len * 3 * strlen(format);
  result = g_malloc(size);

  strcpy(result, "");

  while (len)
    {
    /* Read format string */
      if (!*ptr)
        ptr = format;
      repeat = 1;
      count = 0;
      seperator = '\0';
      terminator = '\0';
      formtype = '\0';
      if (*ptr == '*')
        {
          repeat = *bufptr++;
	  len--;
	  ptr++;
	}
      while (*ptr >= '0' && *ptr <= '9')
        {
	  count = 10 * count + (*ptr++ & 0xf);
	}

      if (*ptr && (*ptr != '*') && ((*ptr < '0') || (*ptr > '9')))
        formtype = *ptr++;
      if (*ptr && (*ptr != '*') && ((*ptr < '0') || (*ptr > '9')))
        seperator = *ptr++;
      if (*ptr && (*ptr != '*') && ((*ptr < '0') || (*ptr > '9')))
        terminator = *ptr++;

      while(repeat && len)
        {
	  int pos;

          if (count > len) count = len;
	  pos = strlen(result);
          switch (formtype)
            {
	      case 'x':
	        strcpy(result + pos, print_radix(bufptr, count, 16, 0, 0));
	        break;
	      case 'd':
	        strcpy(result + pos, print_radix(bufptr, count, 10, 0, 0));
	        break;
	      case 'o':
	        strcpy(result + pos, print_radix(bufptr, count, 8, 0, 0));
	        break;
	      case 'a':
	        g_memmove(result + pos, bufptr, count);
		result[pos+count]=0;
		break;
            }
	   
          len = len - count;
          bufptr = bufptr + count;
          pos = strlen(result);
	  repeat--;
	  if (!repeat && len)
	    {
	      if (terminator)
	        {
	          result[pos++] = terminator;
	          result[pos] = 0;
	        }
	      else if (seperator)
	        {
	          result[pos++] = seperator;
	          result[pos] = 0;
	        }
            }
	  else if (seperator && len)
	    {
	      result[pos++] = seperator;
	      result[pos] = 0;
	    }
	}
    }
  ptr = g_strdup(result);
  g_free(result);
  return ptr;
}

/* EOF */
