/***************************************************************************
 *
 * Copyright (c) 1998-1999 Niels Möller
 * Copyright (c) 1999 BalaBit Computing
 * 
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: format.c,v 1.10 1999/11/22 18:26:24 bazsi Exp $
 *
 ***************************************************************************/

#include "format.h"

#include "list.h"
#include "werror.h"
#include "xalloc.h"

#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>


struct ol_string *c_format(const char *format, ...);

struct ol_string *c_format(const char *format, ...)
{
  va_list args;
  UINT32 length;
  struct ol_string *packet;

  va_start(args, format);
  length = c_vformat_length(format, args);
  va_end(args);

  packet = ol_string_alloc(length);

  va_start(args, format);
  c_vformat_write(format, length, packet->data, args);
  va_end(args);

  return packet;
}

UINT32 c_format_length(const char *format, ...)
{
  va_list args;
  UINT32 length;

  va_start(args, format);
  length = c_vformat_length(format, args);
  va_end(args);

  return length;
}

UINT32 c_format_write(const char *format, UINT32 length, UINT8 *buffer, ...)
{
  va_list args;
  UINT32 res;
  
  va_start(args, buffer);
  res = c_vformat_write(format, length, buffer, args);
  va_end(args);
  return res;
}

#if 0     
static int write_decimal_length(UINT8 *buffer, UINT32 n);
#endif

UINT32 c_vformat_length(const char *f, va_list args)
{
  UINT32 length = 0;

  while(*f)
    {
      if (*f == '%')
	{
	  int do_hex = 0;
	  int fieldlen = 0;
	  while(*++f)
	    {
	      switch (*f)
		{
		case 'f':
		  /* Do nothing */
		  break;
		case 'x':
		  do_hex = 1;
		  break;
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		  fieldlen = 10*fieldlen + (*f)-'0';
		  break;
		default:
		  goto end_options;
		}
	    }
end_options:

	  switch(*f)
	    {
	    default:
	      fatal("c_vformat_length: bad format string");
	      break;

	    case 'c':
	      (void) va_arg(args, int);
	      /* Fall through */
	    case '%':
	      f++;
	      length++;
	      break;

	    case 'i':
	      {
	        UINT32 n = va_arg(args, UINT32);
	        f++;
                length += fieldlen 
		  ? fieldlen 
		  : (do_hex ? format_size_in_hex(n) : format_size_in_decimal(n));
  	        break;
	      }
	    case 's':
	      {
		UINT32 l = va_arg(args, UINT32); /* String length */ 
		(void) va_arg(args, UINT8 *);    /* data */

		f++;

		length += l;

		break;
	      }
	    case 'S':
	      {
		struct ol_string *s = va_arg(args, struct ol_string *);
		length += s ? s->length : 6;
		f++;
		
		break;
	      }
            case 'I':
              {
                char *s;

                s = inet_ntoa(va_arg(args, struct in_addr));
                length += strlen(s);
		f++;

                break;
              }
	    case 'z':
	      {
	        char *p = va_arg(args, char *);
		unsigned l = p ? strlen(p) : 6;
		length += l;

		f++;

		break;
	      }
	    case 'r':
	      {
		UINT32 l = va_arg(args, UINT32); 
		length += l;
		(void) va_arg(args, UINT8 **);    /* pointer */

		f++;

		break;
	      }
	    }
	}
      else
	{
	  length++;
	  f++;
	}
    }
  return length;
}

UINT32 c_vformat_write(const char *f, UINT32 size, UINT8 *buffer, va_list args)
{
  UINT8 *start = buffer;
  
  while(*f)
    {
      if (*f == '%')
	{
	  int do_free = 0;
	  int do_hex = 0;
	  int fieldlen = 0;
	  int zeropad = 0;
	  int first = 1;
	  
	  while(*++f)
	    {
	      switch (*f)
		{
		case 'f':
		  do_free = 1;
		  break;
		case 'x':
		  do_hex = 1;
		case '0':
		  if (first) zeropad = 1;
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		  first = 0;
		  fieldlen = 10*fieldlen + (*f)-'0';
		  break;
		default:
		  goto end_options;
		}
	    }
end_options:
		  
	  switch(*f)
	    {
	    default:
	      fatal("c_vformat_write: bad format string");
	      break;

	    case 'c':
	      *buffer++ = va_arg(args, int);
	      f++;

	      break;
	    case '%':
	      *buffer++ = '%';
	      f++;

	      break;

	    case 'i':
	      {
	      	UINT32 length;
		UINT32 n = va_arg(args, UINT32);

		if (!do_hex) {		
		  unsigned i;

		  length = fieldlen ? fieldlen : format_size_in_decimal(n);
		  if (n) 
		    {
		      for (i = 0; i < length; i++)
			{
			  buffer[length - i - 1] = n ? '0' + n % 10 : zeropad ? '0' : ' ';
			  n /= 10;
			}
		    }
		  else
		    buffer[0] = '0';

		}
		else {
		  char hexchars[] = "0123456789abcdef";
		  unsigned i;
		  
		  length = format_size_in_hex(n);
		  for (i = 0; i < length; i++)
		    {
		      buffer[length - i - 1] = hexchars[n & 0xf];
		      n >>= 4;
		    }
		}
  		buffer += length;
		f++;
		break;
	      }
	    case 's':
	      {
		UINT32 length = va_arg(args, UINT32);
		UINT8 *data = va_arg(args, UINT8 *);

		memcpy(buffer, data, length);
		buffer += length;
		f++;

		break;
	      }
	    case 'S':
	      {
		struct ol_string *s = va_arg(args, struct ol_string *);
		if (s) 
		  {
		    memcpy(buffer, s->data, s->length);
		    buffer += s->length;

		    if (do_free)
		      ol_string_free(s);
		  }
		else
		  {
		    memcpy(buffer, "(NULL)", 6);
		    buffer += 6;
		  }
		f++;

		break;
	      }
            case 'I':
              {
                char *s;

                s = inet_ntoa(va_arg(args, struct in_addr));
                memcpy(buffer, s, strlen(s));
		buffer += strlen(s);
		f++;
                break;
              }
	    case 'z':
	      {
		char *s = va_arg(args, char *);
		UINT32 length = s ? strlen(s) : 6;

		if (s)
		  memcpy(buffer, s, length);
		else
		  memcpy(buffer, "(NULL)", 6);
		buffer += length;
		f++;

		break;
	      }
	    case 'r':
	      {
		UINT32 length = va_arg(args, UINT32);
		UINT8 **p = va_arg(args, UINT8 **);

		if (p)
		  *p = buffer;
		buffer += length;
		f++;

		break;
	      }
	    
	    }
	}
      else
	{
	  *buffer++ = *f++;
	}
    }
  
  assert(buffer <= start + size);
  return buffer - start;
}

UINT32 format_size_in_decimal(UINT32 n)
{
  int i;
  int e;
  
  /* Table of 10^(2^n) */
  static const UINT32 powers[] = { 10UL, 100UL, 10000UL, 100000000UL };

#define SIZE (sizeof(powers) / sizeof(powers[0])) 

  /* Determine the smallest e such that n < 10^e */
  for (i = SIZE - 1 , e = 0; i >= 0; i--)
    {
      if (n >= powers[i])
	{
	  e += 1UL << i;
	  n /= powers[i];
	}
    }

#undef SIZE
  
  return e+1;
}

UINT32 format_size_in_hex(UINT32 n)
{
  UINT32 mask = 0xf0000000;
  UINT32 size = 8;
  while (n & mask) {
  	size--;
  	mask >>= 4;
  }
  return size;
}

#if 0
static int write_decimal_length(UINT8 *buffer, UINT32 n)
{
  int length = format_size_in_decimal(n);
  int i;
  
  for (i = 0; i<length; i++)
    {
      buffer[length - i - 1] = '0' + n % 10;
      n /= 10;
    }

  buffer[length] = ':';

  return length + 1;
}
#endif

/* These functions add an extra NUL-character at the end of the string
 * (not included in the length), to make it possible to pass the
 * string directly to C library functions. */

struct ol_string *format_cstring(const char *s)
{
  if (s)
    {
      struct ol_string *res = c_format("%z%c", s, 0);
      res->length--;
      return res;
    }
  return NULL; 
}

struct ol_string *make_cstring(struct ol_string *s, int free)
{
  struct ol_string *res;
  
  if (memchr(s->data, '\0', s->length))
    {
      if (free)
	ol_string_free(s);
      return 0;
    }

  res = c_format("%S%c", s, 0);
  res->length--;
  
  if (free)
    ol_string_free(s);
  return res;
}

struct ol_string *c_format_cstring(const char *format, ...)
{
  va_list args;
  UINT32 length;
  struct ol_string *packet;

  va_start(args, format);
  length = c_vformat_length(format, args);
  va_end(args);

  packet = ol_string_alloc(length + 1);
  packet->length--;

  va_start(args, format);
  c_vformat_write(format, length, packet->data, args);
  va_end(args);
  packet->data[length] = 0;

  return packet;
}


syntax highlighted by Code2HTML, v. 0.9.1