/* $Id: ip4_range.c,v 1.51 2005/08/01 08:42:58 gsson Exp $ */
/*
 * Copyright (c) 2005 Henrik Gustafsson <henrik.gustafsson@fnord.se>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdlib.h>
#include <stdio.h>
#include "ip4_range.h"
#include "ip4_cidr.h"

#define MAX(a, b) (((a) > (b))?(a):(b))
#define MIN(a, b) (((a) < (b))?(a):(b))

#define IP4_MAX ((u_int32_t)(0xFFFFFFFFU))
#define IP4_MIN ((u_int32_t)(0x00000000U))

u_int32_t
ip4_distance(u_int32_t a, u_int32_t b) {
	return (a>b)?(a-b):(b-a);
}

void
ip4_range_list_init(ip4_range_list_t *list) {
	TAILQ_INIT(list);
}

void
ip4_range_list_destroy(ip4_range_list_t *list) {
	struct ip4_range_list_element *element = TAILQ_FIRST(list);
	while (!TAILQ_EMPTY(list)) {
		TAILQ_REMOVE(list, element, ip4_range_list_links);
		free(element);
		element = TAILQ_FIRST(list);
	}
	TAILQ_INIT(list);
}

int
ip4_range_overlap(const struct ip4_range *a, const struct ip4_range *b) {
	if (a->start < b->start) {
		if (a->end >= b->start) {
			return 1;
		}
	}
	else {
		if (b->end >= a->start) {
			return 1;
		}
	}
	return 0;
}

struct ip4_range_list_element *
ip4_range_list_insert_range(ip4_range_list_t *list, const struct ip4_range *range, struct ip4_range_list_element *hint) {
	struct ip4_range_list_element *element;
	struct ip4_range_list_element *tmp_element;
	struct ip4_range_list_element *new_element;

	if (list==NULL || range==NULL) { return NULL; }

	if (TAILQ_EMPTY(list)) {
		new_element = malloc(sizeof(struct ip4_range_list_element));
		if (new_element == NULL) {
			fprintf(stderr, "Allocation error\n");
			exit(-1);
		}
		new_element->range = *range;
		TAILQ_INSERT_HEAD(list, new_element, ip4_range_list_links);
		return new_element;
	}
	
	tmp_element = TAILQ_LAST(list, ip4_range_list);
	if (range->start >= tmp_element->range.start) {
			/* Overlapping or adjacent */
			if (range->start <= tmp_element->range.end+1) {
				tmp_element->range.end = MAX(range->end, tmp_element->range.end);
				return tmp_element;
			}
			
			/* Separate */
			
			new_element = malloc(sizeof(struct ip4_range_list_element));
			if (new_element == NULL) {
				fprintf(stderr, "Allocation error\n");
				exit(-1);
				/* NOTREACHED */
			}
			new_element->range = *range;
			TAILQ_INSERT_TAIL(list, new_element, ip4_range_list_links);
			return new_element;
	}	
	
	if (hint != NULL) {
		element = hint;
	}
	else {
		element = TAILQ_FIRST(list);
	}
	
	for (;element != NULL; element = TAILQ_NEXT(element, ip4_range_list_links)) {
		/* Insert before */
		if (range->start <= element->range.start) {
			if (range->end+1 >= element->range.start) {
				element->range.start = range->start;
				element->range.end = MAX(range->end, element->range.end);
				return element;
			}
			
			new_element = malloc(sizeof(struct ip4_range_list_element));
			if (new_element == NULL) {
				fprintf(stderr, "Allocation error\n");
				exit(-1);
				/* NOTREACHED */
			}
			new_element->range = *range;
			TAILQ_INSERT_BEFORE(element, new_element, ip4_range_list_links);
			return new_element;
		}
	}
	fprintf(stderr, "*dies*\n");
	exit(-1);
	/* NOTREACHED */
}

ip4_range_list_t *
ip4_range_list_dup(const ip4_range_list_t *src, ip4_range_list_t *dst) {
	struct ip4_range_list_element *src_element;
	struct ip4_range_list_element *dst_element;
	
	ip4_range_list_destroy(dst);
	TAILQ_FOREACH(src_element, src, ip4_range_list_links) {
		dst_element = malloc(sizeof(struct ip4_range_list_element));
		if (dst_element == NULL) {
			fprintf(stderr, "Allocation error\n");
			exit(-1);
			/* NOTREACHED */
		}
		dst_element->range = src_element->range;
		TAILQ_INSERT_TAIL(dst, dst_element, ip4_range_list_links);
	}
	
	return dst;
}

void
ip4_range_list_insert_range_raw(ip4_range_list_t *list, u_int32_t start, u_int32_t end, struct ip4_range_list_element *hint) {
	struct ip4_range range;
	range.start = start;
	range.end = end;
	ip4_range_list_insert_range(list, &range, hint);
}

void ip4_range_list_insert_cidr(ip4_range_list_t *list, u_int32_t address, u_int8_t prefix, struct ip4_range_list_element *hint) {
	struct ip4_range range;
	range.start = address;
	range.end = ip4_cidr_top_address(address, prefix);
	ip4_range_list_insert_range(list, &range, hint);
}

ip4_range_list_t *
ip4_range_list_union(const ip4_range_list_t *lhs, const ip4_range_list_t *rhs, ip4_range_list_t *result) {
	struct ip4_range_list_element *lhs_element;
	struct ip4_range_list_element *rhs_element;
	
	if (lhs == NULL || rhs == NULL || result == NULL) {
		return result;
	}

	lhs_element = TAILQ_FIRST(lhs);
	rhs_element = TAILQ_FIRST(rhs);

	ip4_range_list_destroy(result);

	/*
	 * Try to keep them sorted. Will make insertion faster.
	 */	
	while (lhs_element != NULL || rhs_element != NULL) {
		while (lhs_element != NULL && (rhs_element == NULL || lhs_element->range.start <= rhs_element->range.start)) {
			ip4_range_list_insert_range(result, &(lhs_element->range), NULL);
			lhs_element = TAILQ_NEXT(lhs_element, ip4_range_list_links);
		}
		while (rhs_element != NULL && (lhs_element == NULL || rhs_element->range.start <= lhs_element->range.start)) {
			ip4_range_list_insert_range(result, &(rhs_element->range), NULL);
			rhs_element = TAILQ_NEXT(rhs_element, ip4_range_list_links);
		}
	}

	return result;
}

ip4_range_list_t *
ip4_range_list_intersect(const ip4_range_list_t *lhs, const ip4_range_list_t *rhs, ip4_range_list_t *result) {
	struct ip4_range_list_element *lhs_element;
	struct ip4_range_list_element *rhs_element;
	
	if (lhs == NULL || rhs == NULL || result == NULL) {
		return result;
	}
	
	lhs_element = TAILQ_FIRST(lhs);
	rhs_element = TAILQ_FIRST(rhs);

	ip4_range_list_destroy(result);
	
	/*
	 * FIXME: Way too many comparisions...
	 */
	 
	while (lhs_element != NULL && rhs_element != NULL) {
		if (ip4_range_overlap(&(lhs_element->range), &(rhs_element->range))) {

			ip4_range_list_insert_range_raw(result, MAX(lhs_element->range.start, rhs_element->range.start), MIN(lhs_element->range.end, rhs_element->range.end), NULL);
			
			if (lhs_element->range.end > rhs_element->range.end) {
				rhs_element = TAILQ_NEXT(rhs_element, ip4_range_list_links);
			}
			else if (lhs_element->range.end < rhs_element->range.end) {
				lhs_element = TAILQ_NEXT(lhs_element, ip4_range_list_links);
			}
			else {
				rhs_element = TAILQ_NEXT(rhs_element, ip4_range_list_links);
				lhs_element = TAILQ_NEXT(lhs_element, ip4_range_list_links);
			}
		}
		else {
			if (lhs_element->range.start > rhs_element->range.end) {
				rhs_element = TAILQ_NEXT(rhs_element, ip4_range_list_links);
			}
			else if (rhs_element->range.start > lhs_element->range.end) {
				lhs_element = TAILQ_NEXT(lhs_element, ip4_range_list_links);
			}
		}
	}

	return result;
}

ip4_range_list_t *
ip4_range_list_difference(const ip4_range_list_t *lhs, const ip4_range_list_t *rhs, ip4_range_list_t *result) {
	struct ip4_range_list_element *lhs_element;
	struct ip4_range_list_element *rhs_element;
	struct ip4_range range;
	
	if (lhs == NULL || rhs == NULL || result == NULL) {
		return result;
	}
	
	/*
	 * FIXME: Ugly :/
	 */

	lhs_element = TAILQ_FIRST(lhs);
	rhs_element = TAILQ_FIRST(rhs);

	ip4_range_list_destroy(result);
	if (lhs_element != NULL) {
		range = lhs_element->range;
		while (lhs_element != NULL) {
			
			if (rhs_element != NULL) {
				while (rhs_element != NULL && rhs_element->range.end < range.start) {
					rhs_element = TAILQ_NEXT(rhs_element, ip4_range_list_links);
				}
			}
			
			if (rhs_element != NULL) {
				if (ip4_range_overlap(&range, &(rhs_element->range))) {
					/* Merge difference */
					if (range.start < rhs_element->range.start) {
						ip4_range_list_insert_range_raw(result, range.start, rhs_element->range.start-1, NULL);
					}
					
					if (range.end > rhs_element->range.end) {
						range.start = rhs_element->range.end + 1;
						rhs_element = TAILQ_NEXT(rhs_element, ip4_range_list_links);
					}
					else {
						lhs_element = TAILQ_NEXT(lhs_element, ip4_range_list_links);
						if (lhs_element != NULL) {
							range = lhs_element->range;
						}
					}
				}
				else {
					ip4_range_list_insert_range(result, &range, NULL);			
					lhs_element = TAILQ_NEXT(lhs_element, ip4_range_list_links);
					if (lhs_element != NULL) {
						range = lhs_element->range;
					}
				}
			}
			else {
				ip4_range_list_insert_range(result, &range, NULL);			
				lhs_element = TAILQ_NEXT(lhs_element, ip4_range_list_links);
				if (lhs_element != NULL) {
					range = lhs_element->range;
				}
			}		
		}
	}
	return result;
}

ip4_range_list_t *
ip4_range_list_invert(const ip4_range_list_t *rhs, ip4_range_list_t *result) {
	struct ip4_range_list_element *rhs_element;
	u_int32_t start;
	
	if (rhs == NULL || result == NULL) {
		return result;
	}
	
	rhs_element = TAILQ_FIRST(rhs);
	ip4_range_list_destroy(result);
	
	if (rhs_element == NULL) {
		ip4_range_list_insert_range_raw(result, IP4_MIN, IP4_MAX, NULL);
		return result;
	}
	
	if (rhs_element->range.start > 0) {
		ip4_range_list_insert_range_raw(result, 0, rhs_element->range.start-1, NULL);
	}

	start = rhs_element->range.end + 1;
	rhs_element = TAILQ_NEXT(rhs_element, ip4_range_list_links);
	while (rhs_element != NULL) {
		ip4_range_list_insert_range_raw(result, start + 1, rhs_element->range.start - 1, NULL);
		start = rhs_element->range.end;
		rhs_element = TAILQ_NEXT(rhs_element, ip4_range_list_links);
	}

	if (start < IP4_MAX) {
		ip4_range_list_insert_range_raw(result, start+1, IP4_MAX, NULL);
	}

	return result;
}

void
ip4_range_list_output_cidr(FILE *f, ip4_range_list_t *list) {
	struct ip4_range_list_element *element;
	u_int32_t diff_address;
	u_int8_t diff_prefix;
	
	u_int32_t cidr_address;
	u_int8_t cidr_prefix;
	
	TAILQ_FOREACH(element, list, ip4_range_list_links) {
		diff_address = ip4_distance(element->range.start, element->range.end);
		diff_prefix = 32 - ip4_cidr_suffix(diff_address+1);
		
		if (diff_address == 0) {
			cidr_address = element->range.start;
			cidr_prefix = 32;
			ip4_cidr_output(f, cidr_address, cidr_prefix);
			fprintf(f, "\n");
		}
		else if (diff_address == IP4_MAX) {
			cidr_address = 0;
			cidr_prefix = 0;
			ip4_cidr_output(f, cidr_address, cidr_prefix);
			fprintf(f, "\n");
		}
		else {
			cidr_address = element->range.start;
			cidr_prefix = ip4_cidr_prefix(cidr_address);
			while (cidr_address <= element->range.end && cidr_address >= element->range.start) {
				
				if (cidr_prefix < diff_prefix)
					cidr_prefix = diff_prefix;
					
				ip4_cidr_output(f, cidr_address, cidr_prefix);
				fprintf(f, "\n");
				
				cidr_address = ip4_cidr_top_address(cidr_address, cidr_prefix)+1;
				cidr_prefix = ip4_cidr_prefix(cidr_address);
				
				diff_address = ip4_distance(cidr_address, element->range.end);
				diff_prefix = 32 - ip4_cidr_suffix(diff_address+1);
			}
		}
	}	
	
}

void
ip4_range_list_output_range(FILE *f, ip4_range_list_t *list) {
	struct ip4_range_list_element *element;

	TAILQ_FOREACH(element, list, ip4_range_list_links) {
		ip4_range_output(f, element->range.start, element->range.end);
		fprintf(f, "\n");
	}	
	
}

void
ip4_range_list_output_single(FILE *f, ip4_range_list_t *list) {
	struct ip4_range_list_element *element;

	TAILQ_FOREACH(element, list, ip4_range_list_links) {
		ip4_range_output_single(f, element->range.start, element->range.end);
	}	
	
}

void ip4_range_output(FILE *f, u_int32_t start, u_int32_t end) {
        fprintf(f, "%d.%d.%d.%d", start >> 24 & 255, start >> 16 & 255, start >> 8 & 255, start & 255);
        fprintf(f, "-");
        fprintf(f, "%d.%d.%d.%d", end >> 24 & 255, end >> 16 & 255, end >> 8 & 255, end & 255);
}

void ip4_range_output_single(FILE *f, u_int32_t start, u_int32_t end) {
	u_int32_t n;
	for (n = start; n <= end; n++) {
        fprintf(f, "%d.%d.%d.%d\n", n >> 24 & 255, n >> 16 & 255, n >> 8 & 255, n & 255);
	}
}


syntax highlighted by Code2HTML, v. 0.9.1