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

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

#include <netinet/in.h>

#include <linux/if.h>
#include <linux/if_ether.h>
#include <linux/ax25.h>
#include <linux/netrom.h>

#include "config.h"
#include "netromd.h"

struct port_struct port_list[20];

int port_count = 0;
int debug      = 0;
int bpq_ether  = 0;

ax25_address my_call;
ax25_address node_call;

static int bcast_config_load_ports(void)
{
	char buffer[255];
	FILE *fp;
	
	if ((fp = fopen(CONFIG_FILE, "r")) == NULL) {
		perror(CONFIG_FILE);
		return -1;
	}
		
	while (fgets(buffer, 255, fp) != NULL) {
		if (sscanf(buffer, "%d %d %d %d %d",
				&port_list[port_count].port, 
				&port_list[port_count].minimum_obs,
				&port_list[port_count].default_qual,
				&port_list[port_count].worst_qual,
				&port_list[port_count].verbose) == -1) {
			fprintf(stderr, "netromd: unable to parse: %s", buffer);
			return -1;
		}

		port_list[port_count].port--;
		
		if (port_list[port_count].port < 0 || port_list[port_count].port >= ax25_config_num_ports()) {
			fprintf(stderr, "netromd: invalid port number\n");
			return -1;
		}

		port_list[port_count].device = strdup(ax25_config_get_dev(port_list[port_count].port));
		port_list[port_count].paclen = ax25_config_get_paclen(port_list[port_count].port);

		if (strncmp(port_list[port_count].device, "sl", 2) != 0)
			bpq_ether = 1;
		
		if (port_list[port_count].minimum_obs < 0 || port_list[port_count].minimum_obs > 6) {
			fprintf(stderr, "netromd: invalid minimum obsolescence\n");
			return -1;
		}

		if (port_list[port_count].default_qual < 0 || port_list[port_count].default_qual > 255) {
			fprintf(stderr, "netromd: invalid default quality\n");
			return -1;
		}

		if (port_list[port_count].worst_qual < 0 || port_list[port_count].worst_qual > 255) {
			fprintf(stderr, "netromd: invalid worst quality\n");
			return -1;
		}

		if (port_list[port_count].verbose != 0 && port_list[port_count].verbose != 1) {
			fprintf(stderr, "netromd: invalid verbose setting\n");
			return -1;
		}
		
		port_count++;
	}
	
	fclose(fp);

	if (port_count == 0)
		return -1;
	
	return 0;	
}

int ax25cmp(ax25_address *a, ax25_address *b)
{
	int i;

	for (i = 0; i < 6; i++) {
		if ((a->ax25_call[i] & 0xFE) != (b->ax25_call[i] & 0xFE))
			return 1;
	}

	if ((a->ax25_call[6] & 0x1E) == (b->ax25_call[6] & 0x1E))
		return 0;
		
	return 2;
}

char *ax2asc(ax25_address *a)
{
	static char buffer[11];
	char c, *s;
	int n;

	for (n = 0, s = buffer; n < 6; n++) {
		c = (a->ax25_call[n] >> 1) & 0x7F;
		
		if (c != ' ') *s++ = c;
	}
	
	sprintf(s, "-%d", (a->ax25_call[6] >> 1) & 0x0F);

	return buffer;
}

int main(int argc, char **argv)
{
	unsigned char buffer[512], *p;
	int len, size, s, i, found;
	struct sockaddr sa;
	int asize = sizeof(sa);
	struct timeval timeout;
	time_t timenow, timelast = 0;
	int interval = 3600;
	int localval = 255;
	int proto    = ETH_P_AX25;
	fd_set fdset;

	if (ax25_config_load_ports() == -1) {
		fprintf(stderr, "netromd: no AX.25 ports defined\n");
		return 1;
	}
	
	if (nr_config_load_ports() == -1) {
		fprintf(stderr, "netromd: no NET/ROM ports defined\n");
		return 1;
	}

	if (bcast_config_load_ports() == -1) {
		fprintf(stderr, "netromd: no NET/ROM broadcast ports defined\n");
		return 1;
	}

	while ((i = getopt(argc, argv, "dl:t:")) != -1) {
		switch (i) {
			case 'd':
				debug = 1;
				break;
			case 'l':
				localval = atoi(optarg);
				if (localval < 10 || localval > 255) {
					fprintf(stderr, "netromd: invalid local quality\n");
					return 1;
				}
				break;
			case 't':
				interval = atoi(optarg) * 60;
				if (interval < 60 || interval > 7200) {
					fprintf(stderr, "netromd: invalid time interval\n");
					return 1;
				}
				break;
			case '?':
			case ':':
				fprintf(stderr, "usage: netromd [-d] [-l quality] [-t interval]\n");
				return 1;
		}
	}

	if (bpq_ether) proto = ETH_P_ALL;

	convert_call_entry(nr_config_get_addr(0), (char *)&my_call);
	convert_call_entry("NODES", (char *)&node_call);
	
	if ((s = socket(AF_INET, SOCK_PACKET, htons(proto))) == -1) {
		perror("rx socket");
		return 1;
	}
	
	for (;;) {
		FD_ZERO(&fdset);
		FD_SET(s, &fdset);

		timeout.tv_sec  = 30;
		timeout.tv_usec = 0;
	
		select(s + 1, &fdset, NULL, NULL, &timeout);

		if (FD_ISSET(s, &fdset)) {
			if ((size = recvfrom(s, buffer, sizeof(buffer), 0, &sa, &asize)) == -1) {
				perror("recv");
				return 1;
			}

			p     = buffer;
			len   = size;
			found = FALSE;

			if (strncmp(sa.sa_data, "sl", 2) != 0) {
				if (p[12] == 0x08 && p[13] == 0xFF) {
					len   = p[14] + p[15] * 256 - 5;
					p    += 16;
					found = TRUE;
				}
			} else {
				len  -= 1;
				p    += 1;
				found = TRUE;
			}

			if (found) {
				if (ax25cmp((ax25_address *)(p + 0), &node_call) == 0 &&
				    ax25cmp((ax25_address *)(p + 7), &my_call) != 0 &&
				    p[15] == NETROM_PID && p[16] == 0xFF) {
					for (i = 0; i < port_count; i++) {
						if (strcmp(port_list[i].device, sa.sa_data) == 0) {
							if (debug)
								fprintf(stdout, "netromd: received NODES broadcast on port %d dev %s from %s\n", port_list[i].port, port_list[i].device, ax2asc((ax25_address *)(p + 7)));
							receive_nodes(p + 17, len - 17, (ax25_address *)(p + 7), i);
							break;
						}
					}
				}
			}
		}

		time(&timenow);

		if ((timenow - timelast) > interval) {
			timelast = timenow;
			transmit_nodes(localval);
		}
	}
}
