/*
 *	PostgreSQL type definitions for IP addresses.
 *
 *	$Id: ip.c,v 1.2 1998/02/14 17:58:03 scrappy 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 == 4)
      w = 32;

    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 = 0;	/* special case for missing address */
  }

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

  result->address = (uint32) ((a<<24)|(b<<16)|(c<<8)|d);
  result->address &= build_mask(w);
  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;

  if (addr == NULL)
    return(NULL);

  result = (char *)palloc(32);

  if (addr->address > 0) {
    if (addr->width == 32)
      sprintf(result, "%d.%d.%d.%d",
	      (addr->address >> 24) & 0xff,
	      (addr->address >> 16) & 0xff,
	      (addr->address >> 8) & 0xff,
	      addr->address & 0xff);
    else
      sprintf(result, "%d.%d.%d.%d/%d",
	      (addr->address >> 24) & 0xff,
	      (addr->address >> 16) & 0xff,
	      (addr->address >> 8) & 0xff,
	      addr->address & 0xff,
	      addr->width);
  } else {
    result[0] = 0;		/* special case for missing address */
  }
  return(result);
}

/*
 *	Boolean tests for magnitude.
 */

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

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

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

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

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

bool ipaddr_ne(ipaddr *a1, ipaddr *a2) {
  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
    return 0;
}

/*
 *	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;
}

/*
 *	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;
}

/*
 *	eof
 */
