#include <sys/types.h>
#include <sys/socket.h>
#include <linux/if.h>
#include <linux/if_ether.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <netdb.h>

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

#include <linux/ax25.h>

#include "axconfig.h"
#include "version.h"
#include "listen.h"

static void display_port(char *dev)
{
	char *port;

	if ((port = ax25_config_get_name(dev)) == NULL)
		port = dev;

	lprintf(T_PORT, "Port %s: ", port);
}

static void display_timestamp(void)
{
	time_t timenow;
	char *timestring;

	time(&timenow);

	timestring = ctime(&timenow);	
	timestring[24] = '\0';

	lprintf(T_TIMESTAMP, "[%s]\n", timestring);
}

#define ASCII		0
#define HEX 		1
#define READABLE	2

#define BUFSIZE		1500

int main(int argc, char **argv)
{
	unsigned char buffer[BUFSIZE];
	int timestamp = 0;
	int dumpstyle   = ASCII;
	int size;
	int s;
	char *port = NULL, *dev = NULL;
	struct sockaddr sa;
	int asize = sizeof(sa);
	struct ifreq ifr;
	int proto = ETH_P_AX25;

	while ((s = getopt(argc, argv, "8achip:rtv")) != -1) {
		switch (s) {
			case '8':
				sevenbit = 0;
				break;
			case 'a':
				proto = ETH_P_ALL;
				break;
			case 'c':
				color = 1;
				break;
			case 'h':
				dumpstyle = HEX;
				break;
			case 'i':
				ibmhack = 1;
				break;
			case 'p':
				port = optarg;
				break;
			case 'r':
				dumpstyle = READABLE;
				break;
			case 't':
				timestamp = 1;
				break;
			case 'v':
				printf("listen: %s\n", version);
				return 0;
			case ':':
				fprintf(stderr, "listen: option -p needs a port name\n");
				return 1;
			case '?':
				fprintf(stderr, "Usage: listen [-8] [-a] [-c] [-h] [-i] [-p port] [-r] [-t] [-v]\n");
				return 1;
		}
	}

	if (ax25_config_load_ports() == 0)
		fprintf(stderr, "listen: no AX.25 port data configured\n");

	if (port != NULL) {
		if ((dev = ax25_config_get_dev(port)) == NULL) {
			fprintf(stderr, "listen: invalid port name - %s\n", port);
			return 1;
		}
	}

	if ((s = socket(AF_INET, SOCK_PACKET, htons(proto))) == -1) {
		perror("socket");
		return 1;
	}
	
	if (color) {
		color = initcolor();	/* Initialize color support */
		if (!color)
			printf("Could not initialize color support.\n");
	}

	setservent(1);
	
	for (;;) {
		asize = sizeof(sa);

		if ((size = recvfrom(s, buffer, sizeof(buffer), 0, &sa, &asize)) == -1) {
			perror("recv");
			return 1;
		}
		
		if (dev != NULL && strcmp(dev, sa.sa_data) != 0)
			continue;
			
		if (proto == ETH_P_ALL) {
			strcpy(ifr.ifr_name, sa.sa_data);
			if (ioctl(s, SIOCGIFHWADDR, &ifr) < 0)
				perror("GIFADDR");

			if (ifr.ifr_hwaddr.sa_family == AF_AX25) {
				if (timestamp)
					display_timestamp();
				display_port(sa.sa_data);
				ki_dump(buffer, size, dumpstyle);
				lprintf(T_DATA, "\n");
			}
		} else {
			if (timestamp)
				display_timestamp();
			display_port(sa.sa_data);
			ki_dump(buffer, size, dumpstyle);
			lprintf(T_DATA, "\n");
		}
		if (color)
			refresh();
	}
}

static void ascii_dump(unsigned char *data, int length)
{
	unsigned char c;
	int  i, j;
	char buf[100];

	for (i = 0; length > 0; i += 64) {
		sprintf(buf, "%04X  ", i);
	
		for (j = 0; j < 64 && length > 0; j++) {
			c = *data++;
			length--;
			
			if ((c != '\0') && (c != '\n'))
				strncat(buf, &c, 1);
			else
				strcat(buf, ".");
		}
		
		lprintf(T_DATA, "%s\n", buf);
	}
}

static void readable_dump(unsigned char *data, int length)
{
	unsigned char c;
	int  i;
	int  cr = 1;
	char buf[BUFSIZE];

	for (i = 0; length > 0; i++) {
	
		c = *data++;
		length--;
			
		switch (c) {
			case 0x00:
				buf[i] = ' ';
			case 0x0A: /* hum... */
			case 0x0D:
				if (cr)
					buf[i] = '\n';
				else
					i--;
				break;
			default:
				buf[i] = c;
		}
		cr = (buf[i] != '\n');
	}
	if (cr)
		buf[i++] = '\n';
	buf[i++] = '\0';
	lprintf(T_DATA, "%s", buf);
}

static void hex_dump(unsigned char *data, int length)
{
	int  i, j, length2;
	unsigned char c;
	char *data2;

	char buf[4], hexd[49], ascd[17];

	length2 = length;
	data2   = data;
	
	for (i = 0; length > 0; i += 16) {
	
		hexd[0] = '\0';
		for (j = 0; j < 16; j++) {
			c = *data2++;
			length2--;
			
			if (length2 >= 0)
				sprintf(buf, "%2.2X ", c);
			else
				strcpy(buf, "   ");
			strcat(hexd, buf);
		}

		ascd[0] = '\0';
		for (j = 0; j < 16 && length > 0; j++) {
 			c = *data++;
 			length--;
 			
 			sprintf(buf, "%c", ((c != '\0') && (c != '\n')) ? c : '.');
 			strcat(ascd, buf);
		}
		
		lprintf(T_DATA, "%04X  %s | %s\n", i, hexd, ascd);
	}
}

void data_dump(unsigned char *data, int length, int dumpstyle)
{
	switch (dumpstyle) {

	case READABLE:
		readable_dump(data, length);
		break;
	case HEX:
		hex_dump(data, length);
		break;
	default:
		ascii_dump(data, length);
	}
}

int get16(unsigned char *cp)
{
	int x;
	
	x = *cp++;
	x <<= 8;
	x |= *cp++;
	
	return(x);
}

int get32(unsigned char *cp)
{
	int x;
	
	x = *cp++;
	x <<= 8;
	x |= *cp++;
	x <<= 8;
	x |= *cp++;
	x <<= 8;
	x |= *cp;
	
	return(x);
}
