/*
 *	PostgreSQL type definitions for IP addresses.
 *
 *	$Id: ip.c,v 1.5 1998/06/16 05:35:10 momjian Exp $
 */

#include <stdio.h>

#include <postgres.h>
#include <utils/palloc.h>

/*
 *	This is the internal storage format for IP addresses:
 */

typedef struct ipaddr
{
	uint32		address;
	int16		width;
}			ipaddr;

/*
 *	Various forward declarations:
 */

ipaddr	   *ipaddr_in(char *str);
char	   *ipaddr_out(ipaddr * addr);

bool		ipaddr_lt(ipaddr * a1, ipaddr * a2);
bool		ipaddr_le(ipaddr * a1, ipaddr * a2);
bool		ipaddr_eq(ipaddr * a1, ipaddr * a2);
bool		ipaddr_ge(ipaddr * a1, ipaddr * a2);
bool		ipaddr_gt(ipaddr * a1, ipaddr * a2);

bool		ipaddr_ne(ipaddr * a1, ipaddr * a2);

int4		ipaddr_cmp(ipaddr * a1, ipaddr * a2);

bool		ipaddr_in_net(ipaddr * a1, ipaddr * a2);
ipaddr	   *ipaddr_mask(ipaddr * a);
ipaddr	   *ipaddr_bcast(ipaddr * a);

/*
 *	Build a mask of a given width:
 */

unsigned long
build_mask(unsigned char bits)
{
	unsigned long mask = 0;
	int			i;

	for (i = 0; i < bits; i++)
		mask = (mask >> 1) | 0x80000000;
	return mask;
}

/*
 *	IP address reader.	Note how the count returned by sscanf()
 *	is used to determine whether the mask size was specified.
 */

ipaddr *
ipaddr_in(char *str)
{
	int			a,
				b,
				c,
				d,
				w;
	ipaddr	   *result;
	int			count;

	if (strlen(str) > 0)
	{

		count = sscanf(str, "%d.%d.%d.%d/%d", &a, &b, &c, &d, &w);

		if (count < 4)
		{
			elog(ERROR, "ipaddr_in: error in parsing \"%s\"", str);
			return (NULL);
		}
		if ( count == 3 ) {
		       d = 0;
		       count = 4;
		};
		if (count == 4)
		{
			w = 32;
			if ( a >= 192 && a < 224 && d == 0 ) w = 24;
			if ( a >= 128 && a < 192 && d == 0 && c == 0 ) w = 16;
			if ( a > 0    && a < 128 && c == 0 && b == 0 && a < 128 ) w = 8;
			if ( a == 0 && b == 0 && c == 0 && d == 0 ) w = 0;
		};
		if ((a < 0) || (a > 255) || (b < 0) || (b > 255) ||
			(c < 0) || (c > 255) || (d < 0) || (d > 255) ||
			(w < 0) || (w > 32))
		{
			elog(ERROR, "ipaddr_in: illegal address \"%s\"", str);
			return (NULL);
		}
	}
	else
	{
		a = b = c = d = w = 255;  /* special case for missing address */
	}

	result = (ipaddr *) palloc(sizeof(ipaddr));

	result->address = (uint32) ((a << 24) | (b << 16) | (c << 8) | d);
	result->width = w;

	return (result);
}

/*
 *	IP address output function.  Note mask size specification
 *	generated only for subnets, not for plain host addresses.
 */

char *
ipaddr_out(ipaddr * addr)
{
	char	   *result;
	int a, b, c, d, w;
	if (addr == NULL)
		return (NULL);

	result = (char *) palloc(32);

	w = addr->width;
	a = (addr->address >> 24) & 0xff;
	b = (addr->address >> 16) & 0xff;
	c = (addr->address >>  8) & 0xff;
	d = (addr->address >>  0) & 0xff;
	/* Check by missing address (w > 32 )  */
	if ( w >= 0 && w <= 32  )
	{
		/* In case of NATURAL network don't output the prefix */
		if ( (a == 0  && b == 0 && c == 0 && d == 0 && w ==  0 ) ||
		     (a < 128 && b == 0 && c == 0 && d == 0 && w ==  8 ) ||
		     (a < 192           && c == 0 && d == 0 && w == 16 ) ||
		     (a < 224                     && d == 0 && w == 24 ) ||
		     ( d != 0 ) ) w = -1;
		if (w == -1 )
			sprintf(result, "%d.%d.%d.%d",a,b,c,d);
		else
			sprintf(result, "%d.%d.%d.%d/%d",a,b,c,d,w);
	}
	else
	{
		result[0] = 0;			/* special case for missing address */
	}
	return (result);
}

/*
 * Print ipaddr by format
 * %A - address
 * %M - maska
 * %P - prefix
 * %B - negated maska
 */
# define TXT_LEN_0 4
text *
ipaddr_print(ipaddr * addr, text *fmt)
{
	text       *result;
	char *p, *op;
	uint32          aaa;
	int a, b, c, d;
	if (addr == NULL)
		return (NULL);

	result = (text  *) palloc( sizeof(text) + 64 );

	/* Check by missing address (w > 32 )  */
	for ( p = fmt->vl_dat, op = result->vl_dat; *p && (p - fmt->vl_dat) < (fmt->vl_len - TXT_LEN_0)  && (op - result->vl_dat) < 48; p++) {
	       if ( *p != '%' ) {
		       *op++ = *p;
		       continue;
	       };
	       p++;
	       if ( *p == 'A' )
	       {
		       aaa = addr->address;
		       goto pta;
	       };
	       if ( *p == 'M' ) {
		       aaa = build_mask(addr->width);
		       goto pta;
	       }
	       if ( *p == 'B' ) {
		       aaa = build_mask(32 - addr->width) >> addr->width;
		       goto pta;
	       }
	       if ( *p == 'P' ) {
		       sprintf(op,"%d",addr->width);
		       while ( *op) op++;
		       continue;
	       };
	       *op++ = *p;
	       continue;
pta:
	       a = (aaa >> 24) & 0xff;
	       b = (aaa >> 16) & 0xff;
	       c = (aaa >>  8) & 0xff;
	       d = (aaa >>  0) & 0xff;
	       sprintf(op, "%d.%d.%d.%d",a,b,c,d);
	       while ( *op ) op++;
	       continue;
	};
	*op = 0;
	result->vl_len = (op - result->vl_dat) + TXT_LEN_0;
	return (result);
}

/*
 *	Boolean tests for magnitude.
 */

bool
ipaddr_lt(ipaddr * a1, ipaddr * a2)
{
	if ( a1->address == a2->address ) return(a1->width <  a2->width);
	return (a1->address < a2->address);
};

bool
ipaddr_le(ipaddr * a1, ipaddr * a2)
{
	if ( a1->address == a2->address ) return(a1->width <= a2->width);
	return (a1->address <= a2->address);
};

bool
ipaddr_eq(ipaddr * a1, ipaddr * a2)
{
	if ( a1->address == a2->address ) return(a1->width == a2->width);
	return (a1->address == a2->address);
};

bool
ipaddr_ge(ipaddr * a1, ipaddr * a2)
{
	if ( a1->address == a2->address ) return(a1->width >= a2->width);
	return (a1->address >= a2->address);
};

bool
ipaddr_gt(ipaddr * a1, ipaddr * a2)
{
	if ( a1->address == a2->address ) return(a1->width > a2->width);
	return (a1->address > a2->address);
};

bool
ipaddr_ne(ipaddr * a1, ipaddr * a2)
{
	if ( a1->address == a2->address ) return(a1->width != a2->width);
	return (a1->address != a2->address);
};

/*
 *	Comparison function for sorting:
 */

int4
ipaddr_cmp(ipaddr * a1, ipaddr * a2)
{
	if (a1->address < a2->address)
		return -1;
	else if (a1->address > a2->address)
		return 1;
	else
	{
	    if (a1->width < a2->width)
		    return -1;
	    else if (a1->width > a2->width)
		    return 1;
	}
	return 0;
}

/*
 *      The number of hosts in the network
 */
int4
ipaddr_len(ipaddr * a)
{
	if ( a->width > 32 || a->width < 0 ) return(0);
	return(1 << (32 - a->width));
}

/*
 *      The number of network bits
 */
int4
ipaddr_pref(ipaddr * a)
{
	if ( a->width > 32 || a->width < 0 ) return(0);
	return(a->width);
}

/*
 *      The host addr as an integer
 */
int4
ipaddr_integer(ipaddr * a)
{
	return(a->address);
}

/*
 *	Test whether an address is within a given subnet:
 */

bool
ipaddr_in_net(ipaddr * a1, ipaddr * a2)
{
	uint32		maskbits;

	if (a1->width < a2->width)
		return FALSE;
	if ((a1->width == 32) && (a2->width == 32))
		return ipaddr_eq(a1, a2);
	maskbits = build_mask(a2->width);
	if ((a1->address & maskbits) == (a2->address & maskbits))
		return TRUE;
	return FALSE;
}

/*
 *      Test whether an address is the network or a host in the network:
 */

bool
ipaddr_is_net(ipaddr * a)
{
	uint32		maskbits;

	if (a->width == 32)
		return FALSE;
	maskbits = build_mask(a->width);
	if ( (a->address & maskbits) == a->address )
		return TRUE;
	return FALSE;
}

/*
 *	Pick out just the mask of a network:
 */

ipaddr *
ipaddr_mask(ipaddr * a)
{
	ipaddr	   *result;

	result = (ipaddr *) palloc(sizeof(ipaddr));
	result->address = build_mask(a->width);
	result->width = 32;

	return result;
}

/*
 *	Return the broadcast address of a network:
 */

ipaddr *
ipaddr_bcast(ipaddr * a)
{
	ipaddr	   *result;

	result = (ipaddr *) palloc(sizeof(ipaddr));
	result->address = a->address;
	result->address |= (build_mask(32 - a->width) >> a->width);
	result->width = 32;

	return result;
}

/*
 *      Return the base network of the address/network:
 */

ipaddr *
ipaddr_net(ipaddr * a)
{
	ipaddr	   *result;

	result = (ipaddr *) palloc(sizeof(ipaddr));
	result->address = a->address;
	result->address &= build_mask(a->width);
	result->width = a->width;

	return result;
}

/*
 * Compose ipaddr from ADDR and PREFIX
 */
ipaddr *
ipaddr_compose(int4 addr, int4 pref)
{
	ipaddr	   *result;

	result = (ipaddr *) palloc(sizeof(ipaddr));
	if ( pref < 0 || pref > 32 ) {
	       pref = 255;
	       addr = 0;
	};
	result->address = addr;
	result->width = pref;
	return result;
}

/*
 *      Plus and Minus operators
 */
ipaddr *
ipaddr_plus(ipaddr * a, int4 i)
{
	ipaddr	   *result;

	result = (ipaddr *) palloc(sizeof(ipaddr));
	result->address = a->address + i;
	result->width = a->width;

	return result;
}

ipaddr *
ipaddr_minus(ipaddr * a, int4 i)
{
	ipaddr	   *result;

	result = (ipaddr *) palloc(sizeof(ipaddr));
	result->address = a->address - i;
	result->width = a->width;

	return result;
}

/*
 *	eof
 */
