/*
 * more minor utilities for mask length calculations for IKEv2
 * Copyright (C) 2007 Michael Richardson <mcr@xelerance.com>
 * 
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Library General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/lgpl.txt>.
 * 
 * This library 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 Library General Public
 * License for more details.
 */
#include <stdlib.h>

#include "openswan.h"
#include "openswan/passert.h"

/*
 * this is stupid implementation, see goodmask.c for ideas
 * on doing it better, but note that this counts zero bits, not
 * 1 bits, and it doesn't assume that the mask is properly formed.
 *
 */
int ikev2_highorder_zerobits(ip_address b)
{
    unsigned const char *bp;
    int i, j;
    u_int32_t mask;
    size_t n;
    int zerobits = 0;

    n = addrbytesptr(&b, &bp);
    if (n == 0)
	return -1;

    zerobits = 0;
    for(j=0; j<n; j++) {
	mask = 1UL << 7;
	if(*bp) {
	    for(i=0; i<8; i++) {
		if(*bp & mask) return ((8*n)-(zerobits+i));
		mask >>= 1;
	    }
	}
	bp++;
	zerobits += 8;
    }
    return 0;
}

int ikev2_calc_iprangediff(ip_address low, ip_address high)
{
    unsigned const char *hp;
    unsigned const char *lp, *t;
    unsigned char *dp;
    ip_address diff;
    size_t n;
    size_t n2;
    int i;
    int carry = 0;

    /* initialize all the contents to sensible values */
    diff = low;

    if (addrtypeof(&high) != addrtypeof(&low))
	return -1;
    n = addrbytesptr(&high, &hp);
    if (n == 0)
	return -1;
    n2 = addrbytesptr(&low, &lp);
    if (n != n2)
	return -1;

    addrbytesptr_write(&diff, &dp);
    for(i=0; i<n; i++) {
	if(hp[i]==lp[i]) { dp[i]=0; continue; }
	break;
    }

    /* two values are the same -- no diff */
    if(i==n) return 0;
    if(hp[i] < lp[i]) {
	/* need to swap! */
	t=hp; hp=lp; lp=t;
    }
    
    for(i=n-1; i>=0; i--) {
	int val=hp[i]-lp[i]-carry;
	if(val < 0) {
	    val += 256;
	    carry=1;
	} else {
	    carry=0;
	}
	dp[i]=val;
    }

    return ikev2_highorder_zerobits(diff);
}

#ifdef IPRANGE_MAIN

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void regress(void);

int
main(int argc, char *argv[])
{
	ip_address high;
	ip_address low;
	char bh[100],bl[100];
	const char *oops;
	int n;
	int af;
	int i;

	if (argc == 2 && strcmp(argv[1], "-r") == 0) {
		regress();
		fprintf(stderr, "regress() returned?!?\n");
		exit(1);
	}

	if (argc < 3) {
		fprintf(stderr, "Usage: %s [-6] high low\n", argv[0]);
		fprintf(stderr, "   or: %s -r\n", argv[0]);
		exit(2);
	}

	af = AF_INET;
	i = 1;
	if (strcmp(argv[i], "-6") == 0) {
		af = AF_INET6;
		i++;
	}

	oops = ttoaddr(argv[i], 0, af, &high);
	if (oops != NULL) {
		fprintf(stderr, "%s: high conversion failed: %s\n", argv[0], oops);
		exit(1);
	}
	oops = ttoaddr(argv[i+1], 0, af, &low);
	if (oops != NULL) {
		fprintf(stderr, "%s: low conversion failed: %s\n", argv[0], oops);
		exit(1);
	}

	n = ikev2_calc_iprangediff(high, low);

	addrtot(&high, 0, bh, sizeof(bh));
	addrtot(&low,  0, bl, sizeof(bl));

	printf("iprange between %s and %s => %d\n", bh, bl, n);

	exit(0);
}

struct rtab {
	int family;
	char *low;
	char *high;
	int   range;
} rtab[] = {
	{4, "1.2.255.0",	"1.2.254.255",	        1},
	{4, "1.2.3.0",		"1.2.3.7",		3},
	{4, "1.2.3.0",		"1.2.3.255",		8},
	{4, "1.2.3.240",	"1.2.3.255",		4},
	{4, "0.0.0.0",		"255.255.255.255",	32},
	{4, "1.2.3.4",		"1.2.3.4",		0},
	{4, "1.2.3.0",		"1.2.3.254",		8},
	{4, "1.2.3.0",		"1.2.3.126",		7},
	{4, "1.2.3.0",		"1.2.3.125",		7},
	{4, "1.2.0.0",		"1.2.255.255",		16},
	{4, "1.2.0.0",		"1.2.0.255",		8},
	{4, "1.2.255.0",		"1.2.255.255",	8},
	{4, "1.2.255.1",		"1.2.255.255",	8},
	{4, "1.2.0.1",		"1.2.255.255",		16},
	{6, "1:2:3:4:5:6:7:0",	"1:2:3:4:5:6:7:ffff",	16},
	{6, "1:2:3:4:5:6:7:0",	"1:2:3:4:5:6:7:fff",	12},
	{6, "1:2:3:4:5:6:7:f0",	"1:2:3:4:5:6:7:ff",	4},
	{4, NULL,		NULL,			0},
};

void
regress()
{
	struct rtab *r;
	int status = 0;
	ip_address high;
	ip_address low;
	const char *oops;
	int n;
	int af;

	for (r = rtab; r->high != NULL; r++) {
		af = (r->family == 4) ? AF_INET : AF_INET6;
		oops = ttoaddr(r->high, 0, af, &high);
		if (oops != NULL) {
			printf("surprise failure converting `%s'\n", r->high);
			exit(1);
		}
		oops = ttoaddr(r->low, 0, af, &low);
		if (oops != NULL) {
			printf("surprise failure converting `%s'\n", r->low);
			exit(1);
		}
		n = ikev2_calc_iprangediff(high, low);
		if (n != -1 && r->range == -1)
			{}		/* okay, error expected */
		else if (n == -1) {
			printf("`%s'-`%s' iprangediff failed.\n",
						r->high, r->low);
			status = 1;
		} else if (r->range == -1) {
			printf("`%s'-`%s' iprangediff succeeded unexpectedly\n",
							r->high, r->low);
			status = 1;
		} else if (r->range != n) {
			printf("`%s'-`%s' gave `%d', expected `%d'\n",
			       r->high, r->low, n, r->range);
			status = 1;
		}
	}
	exit(status);
}

#endif /* IPRANGE_MAIN */

/*
 * Local Variables:
 * c-basic-offset:4
 * c-style: pluto
 * End:
 */
 
