/*****************************************************************
**
**	@(#)	portrange.c	(c) Jan 2010 by hoz	hznet.de
**
**	Calculates port ranges specified by
**		draft-boucadair-dhc-port-range-01
**	or
**		draft-boucadair-pppext-portrange-option-00
**
**	Try "portrange -h" for a list of options
**
*****************************************************************/
# include <stdlib.h>
# include <stdio.h>
# include <string.h>
# include <ctype.h>
# include <unistd.h>

#define VERSION	"0.2"

#if 1	/* use manual config option instead of configure for now */

# define HAVE_GETOPT_LONG	1

#else
# ifdef HAVE_CONFIG_H
#  include <config.h>
# endif
#endif

# define MAX_PORTS	0xFFFFL

#if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
# include <getopt.h>
#endif

/**     typedefs **/
typedef	unsigned int	uint;
typedef	unsigned short	ushort;
typedef	unsigned long	ulong;

/*****************************************************************
**	function declaration
*****************************************************************/
static	void	print_ports (FILE *fp, ushort pr_mask, ushort pr_value, int base);
static	void	print_range (FILE *fp, ushort pr_value, long last, long first, int base);
static	int	cntbits (ushort value);
static	long	bstr2int (const char *s);
static	long	str2int (const char *s);
static	int	is_valid_prv (ushort prvalue, ushort prmask);
static  void    usage (char *mesg);
static	const	char	*int2bstr (ulong val, int bits, int space);
static	int	printval (FILE *fp, const char *mesg,  ushort val, int base);

/*****************************************************************
**	macros & global vars
*****************************************************************/
# define	DEC	01
# define	OCT	02
# define	HEX	04
# define	BIN	010

# define        short_options   "ahbBdoxlpVs:v:"

#if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
static struct option long_options[] = {
	{"binary",		no_argument, NULL, 'b'},
	{"decimal",		no_argument, NULL, 'd'},
	{"octal",		no_argument, NULL, 'o'},
	{"hexadecimal",		no_argument, NULL, 'h'},
	{"list-values",		no_argument, NULL, 'l'},
	{"list-ports",		no_argument, NULL, 'p'},
	{"list-all",		no_argument, NULL, 'a'},
	{"version",		no_argument, NULL, 'V'},

	{"space-char",		required_argument, NULL, 's'},
	{"port-range-value",	required_argument, NULL, 'v'},

	{"help",		no_argument, NULL, 'h'},
        {0, 0, 0, 0}
};
#endif

static	const	char	*progname;
static	int	base = 0;
static	int	space = '\0';
static	ushort	pr_value = 0;
static	int	list_all = 0;
static	int	list_values = 0;
static	int	list_ports = 0;


/*****************************************************************
**	main ()
*****************************************************************/
int	main (int argc, char *argv[])
{
	const	char	*p;
	ushort	pr_mask = 0050;
	long	i;
	int	c;
#if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
	int     opt_index;
#endif
	char	errstr[255+1];


	progname = *argv;
	if ( (p = strrchr (progname, '/')) )
		progname = ++p;

        opterr = 0;
#if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
	while ( (c = getopt_long (argc, argv, short_options, long_options, &opt_index)) != -1 )
#else
	while ( (c = getopt (argc, argv, short_options)) != -1 )
#endif
	{
		switch ( c )
		{
		case 's':               /* space char */
			space = *optarg;
			break;
		case 'v':               /* mask value */
			pr_value = str2int (optarg);
#if 0
			if ( pr_value < 0 )
				usage ("illegal port range value\n");
#endif
			break;
		case 'a':               /* list all */
			list_all = 1;
			break;
		case 'l':
			list_values = 1;
			break;
		case 'p':
			list_ports = 1;
			break;
		case 'b':
			base |= BIN;
			break;
		case 'B':
			base |= BIN;
			space = ' ';
			break;
		case 'd':
			base |= DEC;
			break;
		case 'o':
			base |= OCT;
			break;
		case 'x':
			base |= HEX;
			break;
		case 'V':               /* version info */
			fprintf (stderr, "%s version %s (c) Jan 2010 by hoz  hznet.de\n", progname, VERSION);
			exit (1);
			break;
		case 'h':
			usage (NULL);
			break;
		case '?':
			if ( isprint (optopt) )
				snprintf (errstr, sizeof(errstr),
					"Unknown option \"-%c\".\n", optopt);
			else
				snprintf (errstr, sizeof (errstr),
					"Unknown option char \\x%x.\n", optopt);
			usage (errstr);
			break;
		default:
			abort();
		}
	}

	if ( base == 0 )
		base |= DEC;

	if ( (argc - optind) <= 0 )     /* no arguments left ? */
		usage ("port range mask required");

	if ( list_all )
		list_values = list_ports = 0;

	pr_mask = str2int (argv[optind]);

	if ( !is_valid_prv (pr_value, pr_mask) )
	{
		fprintf (stderr, "port range value (0x%x) doesn't match port range mask (0x%x)\n",
				pr_value, pr_mask);
		usage ("");
	}

	if ( (list_values == 0 && list_ports == 0) || list_values )
	{
		printval (stdout, "port range mask ", pr_mask, base);
		c = cntbits (pr_mask);
		fprintf (stdout, " allow %u users to use a range of %u ports\n", 1 << c, 1 << (16-c));
	}

	if ( list_values )
	{
		printval (stdout, "port range value", 0, base);
		putc ('\n', stdout);
		for ( i = 0; i <= MAX_PORTS; i++ )
			if ( (i & pr_mask) != 0 && (i & ~pr_mask) == 0 )
			{
				printval (stdout, "port range value", i, base);
				putc ('\n', stdout);
			}
	}

	if ( list_ports )
	{
		printf ("list of port ranges for port range value %d\n", pr_value);
		print_ports (stdout, pr_mask, pr_value, base);
	}

	if ( list_all )
	{
		for ( i = 0; i <= MAX_PORTS; i++ )
			if ( i == 0 || ((i & pr_mask) != 0 && (i & ~pr_mask) == 0) )
				print_ports (stdout, pr_mask, i, base);
	}
	return 0;
}

/*****************************************************************
**	print_ports ()
*****************************************************************/
static	void	print_ports (FILE *fp, ushort pr_mask, ushort pr_value, int base)
{
	long	i;
	long	first;
	long	last;

	last = pr_value - 1;
	first = pr_value;
	for ( i = 0; i <= MAX_PORTS; i++ )
		if ( (i & pr_mask) == pr_value )
		{
			if ( last + 1 < i )
			{
				// fprintf (stderr, "i = %ld first = %ld last = %ld\n", i, first, last);
				print_range (fp, pr_value, last, first, base);
				first = i;
			}
			last = i;
		}

	print_range (fp, pr_value, last, first, base);
}

/*****************************************************************
**	print_range()
*****************************************************************/
static	void	print_range (FILE *fp, ushort pr_value, long last, long first, int base)
{
	printval (fp, "", pr_value, DEC);
#if 0
	fprintf (fp, "\t(%ld ports)", last - first + 1);
#endif

	base &= ~DEC;		/* clear DEC bit in base */
	printval (fp, "\t", first, DEC);
	printval (fp, " to", last, DEC);
	if ( base )
	{
		printval (fp, "\t", first, base);
		printval (fp, " -", last, base);
	}
	putc ('\n', fp);
}

# define        sopt_usage(mesg, value) fprintf (stderr, mesg, value)
#if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
# define        lopt_usage(mesg, value) fprintf (stderr, mesg, value)
# define        loptstr(lstr, sstr)     lstr
#else
# define        lopt_usage(mesg, value)
# define        loptstr(lstr, sstr)     sstr
#endif
/*****************************************************************
**	usage()
*****************************************************************/
static  void    usage (char *mesg)
{
	if ( mesg && *mesg )
		fprintf (stderr, "error: %s\n", mesg);

	fprintf (stderr, "usage: %s [-b[-s c]|-B][-d][-o][-x] [-l] [-p] [-v prv] <port-range-mask>\n", progname);
	fprintf (stderr, "\t-v <port_range_value>%s\n", loptstr (",\n\t    --port-range-value=<prv>", ""));
	fprintf (stderr, "\t-p%s\tlist all port ranges\n", loptstr (", --list-ports", ""));
	fprintf (stderr, "\t-l%s\tlist all port range values\n", loptstr (", --list-values", ""));
	fprintf (stderr, "\t-a%s\tlist all port range value combinations\n", loptstr (", --list-all\t", ""));
	fprintf (stderr, "\t-b%s\tprint binary values\n", loptstr (", --binary\t", ""));
	fprintf (stderr, "\t-s <c>%s\tuse <char> as spacing char for -b\n", loptstr (", --space=<char>", ""));
	fprintf (stderr, "\t-B%s\tprint binary values with spaces (same as -b -s \" \")\n", loptstr (", --bin-space\t", ""));
	fprintf (stderr, "\t-d%s\tprint decimal values(default)\n", loptstr (", --decimal\t", ""));
	fprintf (stderr, "\t-o%s\tprint octal values\n", loptstr (", --octal\t", ""));
	fprintf (stderr, "\t-x%s\tprint hex values\n", loptstr (", --hexadecimal", ""));
	fprintf (stderr, "\t-V%s\tprint version\n", loptstr (", --version\t", ""));
	fprintf (stderr, "\tport range value (prv) and mask can be given as decimal, octal (0),\n");
	fprintf (stderr, "\thex (0x) or binary (0b or b) value\n");
	fprintf (stderr, "examples:\n");
	fprintf (stderr, "\t%s -B 0x03c0\n", progname);
	fprintf (stderr, "\t%s -l 0x03c0\n", progname);
	fprintf (stderr, "\t%s -p -B 0x03c0\n", progname);
	fprintf (stderr, "\t%s -v 0x4000 -p \"b0100 0000 0000 0000\"\n", progname);

	exit (1);
}

/*****************************************************************
**	cntbits()
*****************************************************************/
static	int	cntbits (ushort value)
{
	int	bit;

	for ( bit = 0; value; value >>= 1 )
		if ( value & 01 )
			bit++;
	return bit;
}

/*****************************************************************
**	is_valid_prv()
*****************************************************************/
static	int	is_valid_prv (ushort prvalue, ushort prmask)
{
	return (prmask | prvalue) == prmask;
}

/*****************************************************************
**	printval()
*****************************************************************/
static	int	printval (FILE *fp, const char *mesg,  ushort val, int base)
{
	int	len;

	len = fprintf (fp, "%s", mesg);
	if ( (base & DEC) == DEC ) 
		len += fprintf (fp, " %5u", val);
	if ( (base & OCT) == OCT ) 
		len += fprintf (fp, " 0%06o", val);
	if ( (base & HEX) == HEX ) 
		len += fprintf (fp, " 0x%04x", val);
	if ( (base & BIN) == BIN ) 
		len += fprintf (fp, " %s", int2bstr (val, 16, space));

	return len;
}

/*****************************************************************
**	str2int()	parse integer string based on different
**			number systems
**			dec:	"1234" 
**			hex:	"0xabc" 
**			oct:	"01234" or "o1234"
**			bin:	"b1010" or "0b010"
**	returns long value or -1L on error
*****************************************************************/
static	long	str2int (const char *s)
{
	long	val;

	while ( isspace (*s) )
		s++;

	val = -1L;
	switch ( *s )
	{
	case 'o':	/* lower case o */
	case '0':	/* starting with zero means octal */
		s++;
		if ( *s == '\0' )
			return 0L;
		/* fall through */
	case 'b':	/* this is for binary digits */
		switch ( *s )
		{
		case 'x':	/* hex digits */
			sscanf (++s, "%lx", &val);
			break;
		case 'b':
			val = bstr2int (s);
			break;
		default:
			sscanf (s, "%lo", &val);
			break;
		}
		break;
	default:
		sscanf (s, "%ld", &val);
		break;
	}

	return val;
}

/*****************************************************************
**	bstr2int()	parse a string of binary digits (up to 16bits)
**			returns the long value of the digit string	
*****************************************************************/
static	long	bstr2int (const char *s)
{
	long	val;

	while ( isspace (*s) || *s == 'b' )
		s++;

	for ( val = 0; *s; s++ )
		if ( *s == '0' || *s == '1' )
		{
			val |= (*s == '1');
			val <<= 1;
		}

	if ( val != 0 )
		val >>= 1;

	return val;
}

/*****************************************************************
**	int2bstr()	convert a unsigned long value to a string
**			of binary digits with 'bits' length
**			a spacing char is introduced after 4 digits
**			returns a pointer to a static string
*****************************************************************/
static	const	char	*int2bstr (ulong val, int bits, int space)
{
	static	char	bstr[(sizeof (ulong) * 8) + 3 + 1];
	char	*p;
	int	withdelim = 0;

	if ( space )
		withdelim = 3;

	if ( bits > sizeof (ulong) * 8 )
		return "int2bstr(): bits too big";

	p = bstr + bits + withdelim;
	*p-- = '\0';
	while ( p >= bstr )
	{
		*p-- = ((val & 01) == 1) ? '1' : '0';
		val >>= 1;
		if ( withdelim && (--bits) % 4 == 0 )
			*p-- = space;
	}

#if defined(DBG) && DBG
	fprintf (stderr, "bstr = %d len = %d\n", (sizeof (ushort) * 8) + 3 + 1, strlen (bstr));
#endif
	return bstr;
}

