/*

Part of the TCP Testing Tool (ttt).

Functions to deal with numeric specifications.

Eloy Paris <elparis@cisco.com>

$Id: numbers.c,v 1.3 2003/05/02 21:59:45 peloy Exp $

*/

#include "includes.h"

static int is_random(const char *spec)
{
	return (int) strstr("random", spec);
}

static int is_num(const char *spec)
{
	int retval = 1;
	char *s = (char *) spec;

	while (*s)
		if (!isdigit(*s++) ) {
			retval = 0;
			break;
		}

	return retval;
}

static int is_range(const char *spec)
{
	char *s;

	return (s = strchr(spec, '-') ) != NULL && isdigit(s[-1]) &&
	       isdigit(s[1]);
}

static int is_increment(const char *spec)
{
	return spec[strlen(spec) - 1] == '+';
}

static int is_decrement(const char *spec)
{
	return spec[strlen(spec) - 1] == '-';
}

struct u_int8_spec *u_int8_init(const char *number_spec)
{
	struct u_int8_spec *spec;

	if ( (spec = malloc(sizeof(struct u_int8_spec))) == NULL)
		error(1, "malloc()");

	if (isalpha(number_spec[0]) ) {
		/*
		 * If the first char. of the number specification is
		 * an alpha character (a letter) then the user might be
		 * specifying 'random' or a service name from
		 * /etc/services.
		 */
		if (is_random(number_spec) ) {
			spec->current = 0;
			spec->type = NUMTYPE_RANDOM;
			spec->range_start = 0;
			spec->range_end = 0;
		} else {
			/* Let's see if it is a port */
			struct servent *service;

			if (!(service = getservbyname(optarg, "tcp") ) )
				error(1, "Error resolving service.\n");

			/*
			 * service->s_port is already in network byte
			 * order, and we want it in host byte order.
			 */
			spec->current = ntohs(service->s_port);
			spec->type = NUMTYPE_FIXED;
			spec->range_start = 0;
			spec->range_end = 0;
		}
	} else if (is_num(number_spec) ) {
		spec->current = atoi(number_spec);
		spec->type = NUMTYPE_FIXED;
		spec->range_start = 0;
		spec->range_end = 0;
	} else if (is_range(number_spec) ) {
		spec->current = atoi(number_spec);
		spec->type = NUMTYPE_RANGE;
		spec->range_start = spec->current;
		spec->range_end = atoi(strchr(number_spec, '-') + 1);
	} else if (is_increment(number_spec) ) {
		spec->current = atoi(number_spec);
		spec->type = NUMTYPE_INCR;
		spec->range_start = 0;
		spec->range_end = 0;
	} else if (is_decrement(number_spec) ) {
		spec->current = atoi(number_spec);
		spec->type = NUMTYPE_DECR;
		spec->range_start = 0;
		spec->range_end = 0;
	} else
		spec = NULL;

	return spec;
}

/*
Retrieves the next 8-bit number based on the number specification.
*/
u_int8_t u_int8_next(struct u_int8_spec *spec)
{
	u_int8_t retval;

	if (!spec) return 0;

	switch (spec->type) {
		case NUMTYPE_FIXED:
			retval = spec->current;
			break;
		case NUMTYPE_RANGE:
			if (spec->range_start < spec->range_end) {
				retval = spec->current++;
				if (spec->current > spec->range_end)
					spec->current = spec->range_start;
			} else {
				retval = spec->current--;
				if (spec->current < spec->range_end)
					spec->current = spec->range_start;
			}
			break;
		case NUMTYPE_RANDOM:
			spec->current = retval = rand();
			break;
		case NUMTYPE_INCR:
			retval = spec->current++;
			break;
		case NUMTYPE_DECR:
			retval = spec->current--;
			break;
		default:
			/* We should never reach this point */
			retval = 0;
	}

	return retval;
}

struct u_int16_spec *u_int16_init(const char *number_spec)
{
	struct u_int16_spec *spec;

	if ( (spec = malloc(sizeof(struct u_int16_spec))) == NULL)
		error(1, "malloc()");

	if (isalpha(number_spec[0]) ) {
		/*
		 * If the first char. of the number specification is
		 * an alpha character (a letter) then the user might be
		 * specifying 'random' or a service name from
		 * /etc/services.
		 */
		if (is_random(number_spec) ) {
			spec->current = 0;
			spec->type = NUMTYPE_RANDOM;
			spec->range_start = 0;
			spec->range_end = 0;
		} else {
			/* Let's see if it is a port */
			struct servent *service;

			if (!(service = getservbyname(optarg, "tcp") ) )
				error(1, "Error resolving service.\n");

			/*
			 * service->s_port is already in network byte
			 * order, and we want it in host byte order.
			 */
			spec->current = ntohs(service->s_port);
			spec->type = NUMTYPE_FIXED;
			spec->range_start = 0;
			spec->range_end = 0;
		}
	} else if (is_num(number_spec) ) {
		spec->current = atoi(number_spec);
		spec->type = NUMTYPE_FIXED;
		spec->range_start = 0;
		spec->range_end = 0;
	} else if (is_range(number_spec) ) {
		spec->current = atoi(number_spec);
		spec->type = NUMTYPE_RANGE;
		spec->range_start = spec->current;
		spec->range_end = atoi(strchr(number_spec, '-') + 1);
	} else if (is_increment(number_spec) ) {
		spec->current = atoi(number_spec);
		spec->type = NUMTYPE_INCR;
		spec->range_start = 0;
		spec->range_end = 0;
	} else if (is_decrement(number_spec) ) {
		spec->current = atoi(number_spec);
		spec->type = NUMTYPE_DECR;
		spec->range_start = 0;
		spec->range_end = 0;
	} else
		spec = NULL;

	return spec;
}

/*
Retrieves the next 16-bit number based on the number specification.
*/
u_int16_t u_int16_next(struct u_int16_spec *spec)
{
	u_int16_t retval;

	if (!spec) return 0;

	switch (spec->type) {
		case NUMTYPE_FIXED:
			retval = spec->current;
			break;
		case NUMTYPE_RANGE:
			if (spec->range_start < spec->range_end) {
				retval = spec->current++;
				if (spec->current > spec->range_end)
					spec->current = spec->range_start;
			} else {
				retval = spec->current--;
				if (spec->current < spec->range_end)
					spec->current = spec->range_start;
			}
			break;
		case NUMTYPE_RANDOM:
			spec->current = retval = rand();
			break;
		case NUMTYPE_INCR:
			retval = spec->current++;
			break;
		case NUMTYPE_DECR:
			retval = spec->current--;
			break;
		default:
			/* We should never reach this point */
			retval = 0;
	}

	return htons(retval);
}

struct u_int32_spec *u_int32_init(const char *number_spec)
{
	struct u_int32_spec *spec;

	if ( (spec = malloc(sizeof(struct u_int32_spec))) == NULL)
		error(1, "malloc()");

	if (is_random(number_spec) ) {
		spec->current = 0;
		spec->type = NUMTYPE_RANDOM;
		spec->range_start = 0;
		spec->range_end = 0;
	} else if (is_num(number_spec) ) {
		spec->current = strtoul(number_spec, NULL, 0);
		spec->type = NUMTYPE_FIXED;
		spec->range_start = 0;
		spec->range_end = 0;
	} else if (is_range(number_spec) ) {
		spec->current = strtoul(number_spec, NULL, 0);
		spec->type = NUMTYPE_RANGE;
		spec->range_start = spec->current;
		spec->range_end = atoi(strchr(number_spec, '-') + 1);
	} else if (is_increment(number_spec) ) {
		spec->current = strtoul(number_spec, NULL, 0);
		spec->type = NUMTYPE_INCR;
		spec->range_start = 0;
		spec->range_end = 0;
	} else if (is_decrement(number_spec) ) {
		spec->current = strtoul(number_spec, NULL, 0);
		spec->type = NUMTYPE_DECR;
		spec->range_start = 0;
		spec->range_end = 0;
	} else
		spec = NULL;

	return spec;
}

/*
Retrieves the next 32-bit number based on the number specification.
*/
u_int32_t u_int32_next(struct u_int32_spec *spec)
{
	u_int32_t retval;

	if (!spec) return 0;

	switch (spec->type) {
		case NUMTYPE_FIXED:
			retval = spec->current;
			break;
		case NUMTYPE_RANGE:
			if (spec->range_start < spec->range_end) {
				retval = spec->current++;
				if (spec->current > spec->range_end)
					spec->current = spec->range_start;
			} else {
				retval = spec->current--;
				if (spec->current < spec->range_start)
					spec->current = spec->range_end;
			}
			break;
		case NUMTYPE_RANDOM:
			spec->current = retval = rand();
			break;
		case NUMTYPE_INCR:
			retval = spec->current++;
			break;
		case NUMTYPE_DECR:
			retval = spec->current--;
			break;
		default:
			/* We should never reach this point */
			retval = 0;
	}

	return htonl(retval);
}
