#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>

#include <net/if.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include "rip98d.h"

#define	UNMATCH_ROUTE		0
#define	NO_ROUTE		1
#define	REPLACE_ROUTE		2
#define	ADDITIONAL_ROUTE	3

static int cmp_route(struct route_struct *route, struct in_addr addr, int bits, int metric)
{
	unsigned long int old_mask, new_mask;
	unsigned long int old_addr, new_addr;

	old_mask = ntohl(bits2mask(route->bits));
	new_mask = ntohl(bits2mask(bits));

	bcopy((char *)&route->addr, (char *)&old_addr, sizeof(struct in_addr));
	bcopy((char *)&addr,        (char *)&new_addr, sizeof(struct in_addr));

	if (bits < route->bits) {
		new_addr = ntohl(new_addr) & new_mask;
		old_addr = ntohl(old_addr) & new_mask;
	} else {
		new_addr = ntohl(new_addr) & old_mask;
		old_addr = ntohl(old_addr) & old_mask;
	}

	if (route->action == DEL_ROUTE || route->action == NEW_ROUTE)
		return UNMATCH_ROUTE;
		
	if (old_addr != new_addr)
		return UNMATCH_ROUTE;

	if (route->metric <= metric)
		return NO_ROUTE;

	if (debug) {
		printf("cmp_route: comparing old: %s/%d to ", inet_ntoa(route->addr), route->bits);
		printf("new: %s/%d\n", inet_ntoa(addr), bits);
	}

	if (route->bits <= bits) {
		if (route->action == ORIG_ROUTE) {
			if (debug)
				printf("\tbetter route, replacing existing route\n");
			return REPLACE_ROUTE;
		} else {
			if (debug)
				printf("\tbetter route, adding additional route\n");
			return ADDITIONAL_ROUTE;
		}
	} else {
		if (debug)
			printf("\tbetter route, adding additional route\n");
		return ADDITIONAL_ROUTE;
	}
}

void receive_routes(int s)
{
	unsigned char message[500], *p;
	struct sockaddr_in rem_addr;
	struct in_addr addr;
	struct route_struct *route, *new;
	struct sockaddr_in trg;
	struct rtentry rt;
	unsigned long int netmask;
	int bits, metric;
	int size, found, matched;
	int mess_len;
	int i;

	size = sizeof(struct sockaddr_in);

	mess_len = recvfrom(s, message, 500, 0, (struct sockaddr *)&rem_addr, &size);

	if (message[0] != RIPCMD_RESPONSE || message[1] != RIP_VERSION_98) {
		if (debug) {
			printf("receive_routes: invalid RIP98 header received\n");
			printf("\tcmd: %d vers: %d\n", message[0], message[1]);
		}
		return;
	}

	for (found = FALSE, i = 0; i < dest_count; i++) {
		if (bcmp((char *)&rem_addr.sin_addr, (char *)&dest_list[i].dest_addr, sizeof(struct in_addr)) == 0) {
			found = TRUE;
			break;
		}
	}

	if (!found) {
		if (debug)
			printf("receive_routes: RIP98 message from unknown address %s\n", inet_ntoa(rem_addr.sin_addr));
		return;
	}

	if (debug)
		printf("receive_routes: RIP98 message received from %s\n", inet_ntoa(rem_addr.sin_addr));

	p = message + RIP98_HEADER;

	while (p < message + mess_len) {
		bcopy((char *)p, (char *)&addr, sizeof(addr));
		bits   = p[4];
		metric = p[5];

		if (debug)
			printf("\troute to %s/%d metric %d\n", inet_ntoa(addr), bits, metric);

		metric++;
		
		if (metric > RIP98_INFINITY)
			metric = RIP98_INFINITY;

		found   = FALSE;
		matched = FALSE;

		for (route = first_route; route != NULL; route = route->next) {

			switch (cmp_route(route, addr, bits, metric)) {

				case NO_ROUTE:
					matched = TRUE;
					break;

				case REPLACE_ROUTE:
					route->action = DEL_ROUTE;
				
				case ADDITIONAL_ROUTE:
					if (!found) {
						if ((new = malloc(sizeof(struct route_struct))) == NULL) {
							fprintf(stderr, "rip98d: out of memory\n");
							return;
						}

						new->addr   = addr;
						new->bits   = bits;
						new->metric = metric;
						new->action = NEW_ROUTE;
				
						new->next   = first_route;
						first_route = new;
	
						found = TRUE;
					}

					matched = TRUE;
					break;

				default:
					break;
			}
		}

		if (!matched) {
			if ((new = malloc(sizeof(struct route_struct))) == NULL) {
				fprintf(stderr, "rip98d: out of memory\n");
				return;
			}

			new->addr   = addr;
			new->bits   = bits;
			new->metric = metric;
			new->action = NEW_ROUTE;
				
			new->next   = first_route;
			first_route = new;
		}
	
		p += RIP98_ENTRY;
	}
	
	for (route = first_route; route != NULL; route = route->next) {
		if (route->action == DEL_ROUTE) {
			bzero((char *)&rt, sizeof(rt));

			trg.sin_family = AF_INET;
			trg.sin_addr   = route->addr;
			trg.sin_port   = 0;
			bcopy((char *)&trg, (char *)&rt.rt_dst, sizeof(struct sockaddr));

			if (ioctl(s, SIOCDELRT, &rt) < 0)
				perror("del route");
		}
	}

	for (route = first_route; route != NULL; route = route->next) {
		if (route->action == NEW_ROUTE) {
			bzero((char *)&rt, sizeof(rt));

			trg.sin_family = AF_INET;
			trg.sin_addr   = route->addr;
			trg.sin_port   = 0;
			bcopy((char *)&trg, (char *)&rt.rt_dst, sizeof(struct sockaddr));

			rt.rt_flags = RTF_UP | RTF_GATEWAY | RTF_DYNAMIC;

			if (route->bits == 32) {
				rt.rt_flags |= RTF_HOST;
			} else {
				netmask = bits2mask(route->bits);
			
				trg.sin_family = AF_INET;
				bcopy((char *)&netmask, (char *)&trg.sin_addr, sizeof(struct in_addr));
				trg.sin_port   = 0;
				bcopy((char *)&trg, (char *)&rt.rt_genmask, sizeof(struct sockaddr));
			}

			rt.rt_metric = route->metric;

			trg.sin_family = AF_INET;
			trg.sin_addr   = rem_addr.sin_addr;
			trg.sin_port   = 0;
			bcopy((char *)&trg, (char *)&rt.rt_gateway, sizeof(struct sockaddr));

			if (ioctl(s, SIOCADDRT, &rt) < 0)
				perror("add route");
		}
	}
}
