/*
 * protini.c  -  Read and process an NDIS protocol.ini file
 *
 * Copyright (C) 1998-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  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
 *  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: protini.c,v 1.16 2007/01/06 18:31:15 gkminix Exp $
 */

#define NEED_BINARY 1
#include <common.h>
#include <nblib.h>
#include "makerom.h"
#include "protini.h"


/*
 * Definitions local to this module
 */
#define BUFSIZE		1024		/* chunk size of input buffer */
#define MAXPARLEN	15		/* max section & parameter name len */



/*
 * Structure holding each parameter argument
 */
struct argdef {
	int		 argquote;	/* flag if argument is quoted */
	size_t		 arglen;	/* length of argument string */
	char		*argptr;	/* pointer to argument string */
	struct argdef	*next;		/* pointer to next argument */
};



/*
 * Variables private to this module
 */
static char *inbuf;			/* input buffer pointer */
static char *inpos;			/* current write position in buffer */
static size_t inbufsize;		/* input buffer size */
static __u8 *outbuf;			/* memory image output buffer */
static __u8 *outpos;			/* current write position in buffer */
static size_t outbufsize;		/* output buffer size */
static long lastmod;			/* offset to last module header */
static long lastkeyword;		/* offset to last keyword header */




/*
 **************************************************************************
 *
 *		Reading of PROTOCOL.INI file
 *
 **************************************************************************
 */

/*
 * Put a character at the end of the input buffer
 */
static void putinbuf __F((c), char c)
{

  size_t len;
  char *cp;

  /* Check that we don't get at the end of the input buffer */
  len = (inpos - inbuf);
  if (len >= (inbufsize - 1)) {
	cp = (char *)nbmalloc(inbufsize + BUFSIZE);
	memcpy(cp, inbuf, inbufsize);
	free(inbuf);
	inbuf = cp;
	inbufsize += BUFSIZE;
	inpos = inbuf + len;
  }

  /* Finally put new character into line buffer */
  *inpos++ = c;
}



/*
 * Read a protocol.ini file into a local buffer. This will not
 * parse the file, as that's done later.
 */
char *readprotini __F((fname), const char *fname)
{
  FILE *infile;
  int c;

  /* Open input file */
  if ((infile = fopen(fname, "r")) == NULL) {
	prnerr("unable to open %s", fname);
	nbexit(EXIT_OPEN);
  }

  /*
   * Reset the input buffer pointers. The caller has to make sure, that
   * the old buffer is freed before calling this routine successivly.
   * Therefore, we can safely overwrite the pointers here.
   */
  inbuf = inpos = (char *)nbmalloc(BUFSIZE);
  inbufsize = BUFSIZE;

  /*
   * Simply read the whole input file without any further processing,
   * except skipping return characters, which will only be found in
   * DOS written files.
   */
  while (TRUE) {
	if ((c = fgetc(infile)) == EOF)
		break;
	if (c != '\r')
		putinbuf(c);
  }
  putinbuf('\0');
  fclose(infile);
  return(inbuf);
}




/*
 **************************************************************************
 *
 *		Processing of PROTOCOL.INI contents
 *
 **************************************************************************
 */

/*
 * Put a byte at the end of the output buffer
 */
static void putoutbuf __F((b), __u8 b)
{

  size_t len;
  __u8 *cp;

  /* Check that we don't get at the end of the input buffer */
  len = (outpos - outbuf);
  if (len >= (outbufsize - 1)) {
	cp = (__u8 *)nbmalloc(outbufsize + BUFSIZE);
	memcpy(cp, outbuf, outbufsize);
	free(outbuf);
	outbuf = cp;
	outbufsize += BUFSIZE;
	outpos = outbuf + len;
  }

  /* Finally put new character into line buffer */
  *outpos++ = b;
}



/*
 * Put a name string into a target buffer
 */
static char *cvtname __F((buf, name, len),
				__u8 *buf AND
				const char *name AND
				size_t len)
{
  hchar_t c;
  pcchar_t pc;
  const char *cp;
  size_t clen;

  /* Determine length of name */
  cp = name;
  clen = 0;
  while (*cp && len > 0) {
	if (charcollect(*cp) != 0) {
		clen++;
		len--;
	}
	cp++;
  }
  if (clen > 15)
	return("name too long");

  /* Copy name into buffer */
  cp = name;
  while (*cp && clen > 0) {
	if ((c = charcollect(*cp)) != 0) {
		pc = chartotarget(chartoupper(c));
		if (pc < 48 || (pc > 57 && pc < 65) || pc > 90)
			return("invalid character in name");
		*buf++ = pc;
		clen--;
	}
	cp++;
  }
  return(NULL);
}



/*
 * Put a section header structure into protocol.ini memory image
 */
static char *putsection __F((name, len), const char *name AND size_t len)
{
  struct module_config mod, *mp;
  unsigned long newofs;
  char *cp;
  size_t i;
  __u8 *bp;

  /* Clear module header structure */
  memzero(&mod, sizeof(mod));

  /* Copy section name into module header */
  if ((cp = cvtname(mod.name, name, len)) != NULL)
	return(cp);

  /* Setup pointers to last module header */
  newofs = (unsigned long)(outpos - outbuf);
  if (lastmod >= 0) {
	mp = (struct module_config *)(outbuf + lastmod);
	assign(mp->next.offset, htot(newofs));
	assign(mod.prev.offset, htot(lastmod));
  }
  lastmod = newofs;
  lastkeyword = -1L;

  /* Copy new module header into output buffer */
  bp = (__u8 *)&mod;
  for (i = 0; i < MODCONF_SIZE; i++)
	putoutbuf(*(bp++));
  return(NULL);
}



/*
 * Put a numeric argument into protocol.ini memory image
 */
static void putnumeric __F((num), long num)
{
  struct param_struct par;
  int i;
  __u8 *bp;

  /* Setup and copy parameter header */
  assign(par.paramtype, htot(PARAM_NUM));
  assign(par.paramlen, htot(4));
  bp = (__u8 *)&par;
  for (i = 0; i < PARAM_SIZE; i++)
	putoutbuf(*(bp++));

  /* Copy number into output image */
  for (i = 0; i < 4; i++) {
	putoutbuf((num % 256) & 0xff);
	num /= 256;
  }
}



/*
 * Put a string argument into protocol.ini memory image
 */
static void putstring __F((str, isquoted), const char *str AND int isquoted)
{
  struct param_struct par;
  hchar_t c;
  int i;
  __u8 *bp;

  /* Setup and copy parameter header */
  assign(par.paramtype, htot(PARAM_STRING));
  assign(par.paramlen, htot(bytelen(str) + 1));
  bp = (__u8 *)&par;
  for (i = 0; i < PARAM_SIZE; i++)
	putoutbuf(*(bp++));

  /* Copy string into output image */
  while (*str) {
	if ((c = charcollect(*str)) != 0)
		putoutbuf(chartotarget(isquoted ? c : chartoupper(c)));
	str++;
  }
  putoutbuf(0);
}



/*
 * Put a parameter header structure into protocol.ini memory image,
 * followed by all individual parameter arguments.
 */
static char *putparam __F((name, len, args, argnum),
				const char *name AND
				size_t len AND
				const struct argdef *args AND
				int argnum)
{
  const struct argdef *ap;
  struct keyword_entry key, *kp;
  unsigned long newofs;
  __u8 *bp;
  char *cp, *str;
  int isquoted;
  long numeric;
  size_t i;

  /* Clear module header structure */
  memzero(&key, sizeof(key));

  /* Copy section name into module header */
  if ((cp = cvtname(key.name, name, len)) != NULL)
	return(cp);

  /* Set number of parameters */
  assign(key.numparams, htot(argnum));

  /* Setup pointers to last keyword header */
  newofs = (unsigned long)(outpos - outbuf);
  if (lastkeyword >= 0) {
	kp = (struct keyword_entry *)(outbuf + lastkeyword);
	assign(kp->next.offset, htot(newofs));
	assign(key.prev.offset, htot(lastkeyword));
  }
  lastkeyword = newofs;

  /* Copy new keyword header into output buffer */
  bp = (__u8 *)&key;
  for (i = 0; i < KEYWORD_SIZE; i++)
	putoutbuf(*(bp++));

  /* Put each parameter into output buffer */
  ap = args;
  while (ap != NULL) {
	if (ap->arglen > 0 && ap->argptr != NULL) {
		str = (char *)nbmalloc(ap->arglen + 1);
		memcpy(str, ap->argptr, ap->arglen);
		str[ap->arglen] = '\0';
		isquoted = ap->argquote;
		if (str[0] == '0' && toupper(str[1]) == 'X') {
			cp = str + 2;
			while (*cp && isxdigit((int)(*cp)))
				cp++;
			if (*cp)
				return("invalid character in hexadecimal argument");
			sscanf(str, "%lx", &numeric);
			if (numeric > 0x7FFFFFFFL)
				return("hexadecimal number too large");
			putnumeric(numeric);
		} else if (((str[0] == '+' || str[0] == '-') &&
		           isdigit((int)(str[1]))) || isdigit((int)(str[0]))) {
			cp = str;
			if (*cp == '+' || *cp == '-')
				cp++;
			while (*cp && isdigit((int)(*cp)))
				cp++;
			if (*cp)
				return("invalid character in decimal argument");
			cp = str;
			if (*cp == '+')
				cp++;
			sscanf(cp, "%ld", &numeric);
			if (numeric > 0x7FFFFFFFL)
				return("decimal number too large");
			else if (numeric < -0x7FFFFFFFL)
				return("decimal number too small");
			putnumeric(numeric);
		} else {
			putstring(str, isquoted);
		}
		free(str);
	}
	ap = ap->next;
  }
  return(NULL);
}



/*
 * Parse the contents of a PROTOCOL.INI file. This will remove
 * unnecessary comments and blanks and convert parameter and section
 * names to upper case as required per the NDIS specification. Then
 * put everything into the NDIS binary memory format.
 */
void parseprotini __F((protini, image, imglen),
				const char *protini AND
				__u8 **image AND
				size_t *imglen)
{
  int errnum = 0;
  int linenum = 0;
  char *errtext = NULL;
  char *inptr, *lineptr, *cp;
  char *paramnameptr;
  size_t paramnamelen;
  size_t len;
  int isquoted = FALSE;
  int sectionnum = 0;
  int paramnum = 0;
  int argnum;
  struct argdef *args, *arglast, *ap;

  /* Don't do anything if protocol.ini image is empty */
  assert(image != NULL && imglen  != NULL);
  if (protini == NULL || !*protini) {
	*image = NULL;
	*imglen = 0;
	return;
  }

  /*
   * Reset the output buffer pointers. The caller has to make sure, that
   * the old buffer is freed before calling this routine successivly.
   * Therefore, we can safely overwrite the pointers here.
   */
  outbuf = outpos = (__u8 *)nbmalloc(BUFSIZE);
  outbufsize = BUFSIZE;
  lastmod = lastkeyword = -1L;

  /* Scan through input buffer and handle each line seperately */
  inbuf = NULL;
  copystr(&inbuf, protini);
  inptr = inbuf;
  while (*inptr) {

	/* Isolate the next line from the input buffer */
	linenum++;
	lineptr = inptr;
	while (*inptr && *inptr != '\n')
		inptr++;
	if (*inptr == '\n') {
		*inptr = '\0';
		inptr++;
	}

	/* Skip any blanks at the beginning of the line */
	while (*lineptr == ' ' || *lineptr == '\t')
		lineptr++;

	/* Skip any blank line and any comment at the beginning of the line */
	if (!*lineptr || *lineptr == ';')
		continue;

	/* Handle section name */
	if (*lineptr == '[') {
		if (sectionnum > 0 && paramnum == 0) {
			errtext = "empty section";
			goto doerr;
		}
		cp = ++lineptr;
		while (*lineptr && *lineptr != ']')
			lineptr++;
		if (!*lineptr) {
			errtext = "unterminated section name";
			goto doerr;
		}
		len = lineptr - cp;
		if ((errtext = putsection(cp, len)) != NULL)
			goto doerr;
		lineptr++;
		while (*lineptr == ' ' || *lineptr == '\t')
			lineptr++;
		if (*lineptr && *lineptr != ';') {
			errtext = "junk after section name";
			goto doerr;
		}
		sectionnum++;
		paramnum = 0;
		continue;
	}

	/* We can only accept parameters within a section */
	if (sectionnum == 0) {
		errtext = "text outside section definition";
		goto doerr;
	}

	/* Isolate parameter name */
	paramnameptr = lineptr;
	while (*lineptr &&
	       !(*lineptr == ' ' || *lineptr == '\t' || *lineptr == '='))
		lineptr++;
	paramnamelen = lineptr - paramnameptr;

	/* Check for equal sign and isolate all arguments */
	argnum = 0;
	args = arglast = NULL;
	while (*lineptr == ' ' || *lineptr == '\t')
		lineptr++;
	if (*lineptr == '=') {
		lineptr++;
		while (TRUE) {
			while (*lineptr == ' ' || *lineptr == '\t')
				lineptr++;
			isquoted = (*lineptr == '"');
			if (isquoted) {
				cp = ++lineptr;
				while (*lineptr && *lineptr != '"')
					lineptr++;
				if (!*lineptr) {
					errtext = "unterminated string character";
					goto doerr;
				}
				len = lineptr - cp;
				lineptr++;
			} else {
				cp = lineptr;
				while (*lineptr &&
				       !(*lineptr == ',' || *lineptr == ';' ||
				         *lineptr == '\t' || *lineptr == ' '))
					lineptr++;
				len = lineptr - cp;
			}
			if (len > 0) {
				ap = (struct argdef *)nbmalloc(sizeof(struct argdef));
				ap->argquote = isquoted;
				ap->arglen = len;
				ap->argptr = cp;
				ap->next = NULL;
				if (args == NULL)
					args = ap;
				if (arglast != NULL)
					arglast->next = ap;
				arglast = ap;
				argnum++;
			}
			while (*lineptr == ' ' || *lineptr == '\t')
				lineptr++;
			if (*lineptr != ',')
				break;
			lineptr++;
		}
	}

	/* Check for correct termination of argument list */
	if (*lineptr && *lineptr != ';') {
		errtext = "junk after parameter";
		goto doerr;
	}

	/* Put parameter into memory image and clear argument list */
	if ((errtext = putparam(paramnameptr, paramnamelen, args, argnum)) != NULL)
		goto doerr;
	paramnum++;
	while (args != NULL) {
		ap = args;
		args = args->next;
		free(ap);
	}
	continue;

	/* Check for error */
doerr:
	if (errtext != NULL) {
		prnerr("protocol.ini [%d]: %s", linenum, errtext);
		errtext = NULL;
		errnum++;
	}
  }

  /* Exit if error */
  if (errnum > 0)
	nbexit(EXIT_MAKEROM_PROTINI);

  /* The last section should not be empty */
  if (sectionnum == 0) {
	free(outbuf);
	outpos = outbuf = NULL;
  } else if (paramnum == 0) {
	prnerr("unexpected end of protocol.ini");
	nbexit(EXIT_MAKEROM_PROTINI);
  }

  /* Prepare return values */
  *image = outbuf;
  *imglen = (outpos - outbuf);
  free(inbuf);
}

