/*
 *	Copyright (c) 1998,2004 Rinet Corp., Novosibirsk, Russia
 *
 * Redistribution and use in source forms, with and without modification,
 * are permitted provided that this entire comment appears intact.
 *
 * THIS SOURCE CODE IS PROVIDED ``AS IS'' WITHOUT ANY WARRANTIES OF ANY KIND.
 */

#ifdef	HAVE_CONFIG_H
#include <config.h>
#endif

#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "show_stat.h"
#include "trafshow.h"
#include "screen.h"
#include "selector.h"
#include "netstat.h"
#include "getkey.h"
#include "addrtoname.h"

ShowStatMode show_stat_mode = Size;
static int find_backflow(NETSTAT **list, int items, NETSTAT *at);
static void sort_backflow(NETSTAT **list, int items);

static void
scale_size(addr, prot, data, rate)
	int *addr, *prot, *data, *rate;
{
	*addr	= line_factor * (double)SHOW_STAT_ADDR;
	*prot	= line_factor * (double)SHOW_STAT_PROT;
	*data	= line_factor * (double)SHOW_STAT_DATA;
	*rate	= line_factor * (double)SHOW_STAT_RATE;
}

static int
compare_pkt_len(p1, p2)
	register const NETSTAT **p1, **p2;
{
	if ((*p1)->pkt_len > (*p2)->pkt_len) return -1;
	if ((*p1)->pkt_len < (*p2)->pkt_len) return 1;
	return 0;
}

static int
compare_data_len(p1, p2)
	register const NETSTAT **p1, **p2;
{
	if ((*p1)->data_len > (*p2)->data_len) return -1;
	if ((*p1)->data_len < (*p2)->data_len) return 1;
	return 0;
}

static int
compare_pkt_cnt(p1, p2)
	register const NETSTAT **p1, **p2;
{
	if ((*p1)->pkt_cnt > (*p2)->pkt_cnt) return -1;
	if ((*p1)->pkt_cnt < (*p2)->pkt_cnt) return 1;
	return 0;
}

static int
find_backflow(list, items, at)
	NETSTAT **list;
	int items;
	NETSTAT *at;
{
	int i;

	/* sanity check */
	if (!list || items < 1 || !at)
		return -1;

	for (i = 0; i < items; i++) {
		if (netstat_bidir(at, list[i]))
			return i;
	}
	return -1;
}

/* too bad implementation -- it take alot of CPU cycles like deadloop. XXX */
static void
sort_backflow(list, items)
	NETSTAT **list;
	int items;
{
	int i = 0, at;
	NETSTAT *ns;

	while (i < items-1) {
		ns = list[i++];
		if ((at = find_backflow(&list[i], items - i, ns)) < 0)
			continue;
		if (at) {
			ns = list[i + at];
			memmove(&list[i + 1], &list[i], at * sizeof(NETSTAT *));
			list[i] = ns;
		}
		i++;
	}
}

/*
 * Pretty print an Internet address (net address + port).
 */
static char *
ip_print(ver, proto, addr, dst, size)
	int ver;
	int proto;
	const struct ip_address *addr;
	char *dst;
	int size;
{
	const char *cp = 0;
	char buf[100];

	if (ver == 4 && addr->ip_addr.s_addr) {
		/*cp = intoa(addr->ip_addr.s_addr);*/
		cp = ipaddr_string(&addr->ip_addr);
	}
#ifdef	INET6
	else if (ver == 6 && !IN6_IS_ADDR_UNSPECIFIED(&addr->ip6_addr)) {
		/*cp = inet_ntop(AF_INET6, &addr->ip6_addr, buf, sizeof(buf));*/
		cp = ip6addr_string(&addr->ip6_addr);
	}
#endif

	if (cp) {
		(void)strncpy(dst, cp, size);
		dst[size-1] = '\0';
	} else	snprintf(dst, size, "IPv%d", ver);

	if (addr->ip_port) {
		char pb[20];
		int len;
		switch (proto) {
		case IPPROTO_TCP:
			if (nflag) {
				sprintf(pb, "%d", ntohs(addr->ip_port));
				cp = pb;
			} else	cp = tcpport_string(ntohs(addr->ip_port));
			break;
		case IPPROTO_UDP:
			if (nflag) {
				sprintf(pb, "%d", ntohs(addr->ip_port));
				cp = pb;
			} else	cp = udpport_string(ntohs(addr->ip_port));
			break;
		case IPPROTO_ICMP:
			if (nflag) {
				sprintf(pb, "%04x", addr->ip_port - 1);
				cp = pb;
			} else	cp = icmp_string(addr->ip_port - 1);
			break;
#ifdef INET6
		case IPPROTO_ICMPV6:
			if (nflag) {
				sprintf(pb, "%04x", addr->ip_port - 1);
				cp = pb;
			} else	cp = icmpv6_string(addr->ip_port - 1);
			break;
#endif
		default: /* nonsense, but be strong */
			sprintf(pb, "%d", ntohs(addr->ip_port));
			cp = pb;
		}
		buf[0] = ',';
		(void)strncpy(&buf[1], cp, 10);
		buf[11] = '\0';
		len = strlen(buf);
		if (strlen(dst) + len < size)
			(void)strcat(dst, buf);
		else	(void)strcpy(&dst[size - len - 1], buf);
	}
	return dst;
}

static char *
sap_print(addr, sap, dst, size)
	const u_char *addr;
	u_char sap;
	char *dst;
	int size;
{
	char buf[20];
	int len;

	(void)strncpy(dst, etheraddr_string(addr), size);
	dst[size-1] = '\0';

	buf[0] = '/';
	if (nflag)
		sprintf(&buf[1], "sap-%02x", sap & 0xff);
	else	(void)strncpy(&buf[1], llcsap_string(sap), 10);
	buf[11] = '\0';
	len = strlen(buf);
	if (strlen(dst) + len < size)
		(void)strcat(dst, buf);
	else	(void)strcpy(&dst[size - len - 1], buf);
	return dst;
}

void
hdr2str(nh, src_buf, src_len, dst_buf, dst_len, proto_buf, proto_len)
	const struct netstat_header *nh;
	char *src_buf, *dst_buf, *proto_buf;
	int src_len, dst_len, proto_len;
{
	const NETSTAT *ns;

	if (src_buf) *src_buf = '\0';
	if (dst_buf) *dst_buf = '\0';
	if (proto_buf) *proto_buf = '\0';

	/* sanity check */
	if (!nh) return;

	ns = (NETSTAT *)nh;

	if (ns->ip_ver) {
		if (src_buf && src_len > 1) {
			ip_print(ns->ip_ver, ns->ip_proto, &ns->ip_src_addr,
				 src_buf, src_len);
		}
		if (dst_buf && dst_len > 1) {
			ip_print(ns->ip_ver, ns->ip_proto, &ns->ip_dst_addr,
				 dst_buf, dst_len);
		}
		if (proto_buf && proto_len > 1) {
			if (nflag)
				snprintf(proto_buf, proto_len, "%d", (int)ns->ip_proto);
			else	(void)strncpy(proto_buf, ipproto_string(ns->ip_proto),
					      proto_len);
			proto_buf[proto_len-1] = '\0';
		}
	} else if (ntohs(ns->eth_type) > 1500) { /* Ethernet II (DIX) */
		if (src_buf && src_len > 1) {
			(void)strncpy(src_buf, etheraddr_string(ns->eth_src_addr),
				      src_len);
			src_buf[src_len-1] = '\0';
		}
		if (dst_buf && dst_len > 1) {
			(void)strncpy(dst_buf, etheraddr_string(ns->eth_dst_addr),
				      dst_len);
			dst_buf[dst_len-1] = '\0';
		}
		if (proto_buf && proto_len > 1) {
			if (nflag)
				snprintf(proto_buf, proto_len, "%04x", ntohs(ns->eth_type));
			else	(void)strncpy(proto_buf, ethertype_string(ns->eth_type),
					      proto_len);
			proto_buf[proto_len-1] = '\0';
		}
	} else if (ns->eth_ssap == ns->eth_dsap) {
		if (src_buf && src_len > 1) {
			(void)strncpy(src_buf, etheraddr_string(ns->eth_src_addr),
				      src_len);
			src_buf[src_len-1] = '\0';
		}
		if (dst_buf && dst_len > 1) {
			(void)strncpy(dst_buf, etheraddr_string(ns->eth_dst_addr),
				      dst_len);
			dst_buf[dst_len-1] = '\0';
		}
		if (proto_buf && proto_len > 1) {
			if (nflag)
				snprintf(proto_buf, proto_len, "sap-%02x",
					 (int)(ns->eth_ssap & 0xff));
			else	(void)strncpy(proto_buf, llcsap_string(ns->eth_ssap),
					      proto_len);
			proto_buf[proto_len-1] = '\0';
		}
	} else {
		if (src_buf && src_len > 1) {
			sap_print(ns->eth_src_addr, ns->eth_ssap,
				  src_buf, src_len);
		}
		if (dst_buf && dst_len > 1) {
			sap_print(ns->eth_dst_addr, ns->eth_dsap,
				  dst_buf, dst_len);
		}
		if (proto_buf && proto_len > 1) {
			(void)strncpy(proto_buf, "802.3", proto_len);
			proto_buf[proto_len-1] = '\0';
		}
	}
}

static int
show_stat_header(dst, size, ph)
	char *dst;
	int size;
	const PCAP_HANDLER *ph;
{
	int addr_sz, prot_sz, data_sz, rate_sz;
	char src_buf[100], dst_buf[100];
	const char *data_ptr, *rate_ptr;

	/* sanity check */
	if (!dst || size < 1 || !ph)
		return 0;

	scale_size(&addr_sz, &prot_sz, &data_sz, &rate_sz);

	(void)strcpy(src_buf, "Source");
	(void)strcpy(dst_buf, "Destination");
	if (ph->masklen >= 0) {
		sprintf(src_buf + strlen(src_buf), "/%d", ph->masklen);
		sprintf(dst_buf + strlen(dst_buf), "/%d", ph->masklen);
	}

	data_ptr = rate_ptr = "?";
	switch (show_stat_mode) {
	case Size:
		data_ptr = "Size";
		rate_ptr = "CPS";
		break;
	case Data:
		data_ptr = "Data";
		rate_ptr = "CPS";
		break;
	case Packets:
		data_ptr = "Packets";
		rate_ptr = "PPS";
		break;
	}
	snprintf(dst, size,
		 "%-*.*s %-*.*s %-*.*s %-*.*s %-*.*s",
		 addr_sz, addr_sz,	src_buf,
		 addr_sz, addr_sz,	dst_buf,
		 prot_sz, prot_sz,	"Protocol",
		 data_sz, data_sz,	data_ptr,
		 rate_sz, rate_sz,	rate_ptr);
	return 0;
}

static int
show_stat_line(dst, size, ns_list, idx)
	char *dst;
	int size;
	const NETSTAT **ns_list;
	int idx;
{
	int addr_sz, prot_sz, data_sz, rate_sz;
	const NETSTAT *ns;
	char src_buf[100], dst_buf[100], proto_buf[20], data_buf[20], rate_buf[20];

	/* sanity check */
	if (!dst || size < 1 || !ns_list)
		return 0;

	ns = ns_list[idx];

	scale_size(&addr_sz, &prot_sz, &data_sz, &rate_sz);

	hdr2str(&ns->ns_hdr,
		src_buf, MIN(sizeof(src_buf), addr_sz + 1),
		dst_buf, MIN(sizeof(dst_buf), addr_sz + 1),
		proto_buf, MIN(sizeof(proto_buf), prot_sz + 1));

	data_buf[0] = '\0';
	rate_buf[0] = '\0';
	switch (show_stat_mode) {
	case Size:
		if (ns->pkt_len >= 10000)
			snprintf(data_buf, sizeof(data_buf), "%uK", ns->pkt_len / 1024);
		else	snprintf(data_buf, sizeof(data_buf), "%u", ns->pkt_len);

		if (ns->pkt_len_rate >= 10000)
			snprintf(rate_buf, sizeof(rate_buf), "%uK", ns->pkt_len_rate / 1024);
		else if (ns->pkt_len_rate > 0)
			snprintf(rate_buf, sizeof(rate_buf), "%u", ns->pkt_len_rate);
		break;
	case Data:
		if (ns->data_len >= 10000)
			snprintf(data_buf, sizeof(data_buf), "%uK", ns->data_len / 1024);
		else	snprintf(data_buf, sizeof(data_buf), "%u", ns->data_len);

		if (ns->data_len_rate >= 10000)
			snprintf(rate_buf, sizeof(rate_buf), "%uK", ns->data_len_rate / 1024);
		else if (ns->data_len_rate > 0)
			snprintf(rate_buf, sizeof(rate_buf), "%u", ns->data_len_rate);
		break;
	case Packets:
		snprintf(data_buf, sizeof(data_buf), "%u", ns->pkt_cnt);

		if (ns->pkt_cnt_rate > 0)
			snprintf(rate_buf, sizeof(rate_buf), "%u", ns->pkt_cnt_rate);
		break;
	}

	snprintf(dst, size,
		 "%-*.*s %-*.*s %-*.*s %-*.*s %-*.*s",
		 addr_sz, addr_sz,	src_buf,
		 addr_sz, addr_sz,	dst_buf,
		 prot_sz, prot_sz,	proto_buf,
		 data_sz, data_sz,	data_buf,
		 rate_sz, rate_sz,	rate_buf);

	return ns->attr;
}

static int
show_stat_footer(dst, size, ph)
	char *dst;
	int size;
	const PCAP_HANDLER *ph;
{
	const PCAP_HANDLER *top;
	int addr_sz, prot_sz, data_sz, rate_sz, depth;
	u_int64_t total = 0, rate = 0;
	char stat_buf[50], data_buf[20], rate_buf[20];

	/* sanity check */
	if (!dst || size < 1 || !ph)
		return 0;

	scale_size(&addr_sz, &prot_sz, &data_sz, &rate_sz);

	depth = 0;
	for (top = ph->top; top; top = top->top) depth++;
	if (depth) {
		snprintf(stat_buf, sizeof(stat_buf), "%d Flows (depth %d)",
			 netstat_count(ph), depth);
	} else {
		snprintf(stat_buf, sizeof(stat_buf), "%d Flows",
			 netstat_count(ph));
	}

	switch (show_stat_mode) {
	case Size:
		total = ph->pkt_len;
		rate = ph->pkt_len_rate;
		break;
	case Data:
		total = ph->data_len;
		rate = ph->data_len_rate;
		break;
	case Packets:
		total = ph->pkt_cnt;
		rate = ph->pkt_cnt_rate;
		break;
	}

	if (total >= 10000000)
		snprintf(data_buf, sizeof(data_buf), "%uM",
			 (unsigned int)(total / (1024 * 1024)));
	else if (total >= 10000)
		snprintf(data_buf, sizeof(data_buf), "%uK",
			 (unsigned int)(total / 1024));
	else	snprintf(data_buf, sizeof(data_buf), "%u",
			 (unsigned int)total);

	if (rate >= 10000000)
		snprintf(rate_buf, sizeof(rate_buf), "%uM",
			 (unsigned int)(rate / (1024 * 1024)));
	else if (rate >= 10000)
		snprintf(rate_buf, sizeof(rate_buf), "%uK",
			 (unsigned int)(rate / 1024));
	else	snprintf(rate_buf, sizeof(rate_buf), "%u",
			 (unsigned int)rate);

	snprintf(dst, size,
		 "%-*.*s %-*.*s %-*.*s %-*.*s %-*.*s",
		 addr_sz, addr_sz,	ph->name,
		 addr_sz, addr_sz,	stat_buf,
		 prot_sz, prot_sz,	"Total:",
		 data_sz, data_sz,	data_buf,
		 rate_sz, rate_sz,	rate_buf);
	return 0;
}

int
show_stat_input(ph, ch)
	PCAP_HANDLER *ph;
	int ch;
{
	switch (ch) {
	case '[':       /* rotate list mode left */
	case K_LEFT:
		if (show_stat_mode == Size)
			show_stat_mode = Packets;
		else	show_stat_mode--;
		return 1;
	case ']':       /* rotate list mode right */
	case K_RIGHT:
		if (show_stat_mode == Packets)
			show_stat_mode = Size;
		else	show_stat_mode++;
		return 1;
	case K_CTRL('R'): /* reset current netstat hash */
		if (ph) {
			netstat_purge(ph, 0);
			show_stat_list(ph);
			return 1;
		}
		break;
	case K_TAB:	/* follow to backflow */
		if (ph) {
			SELECTOR *sp = show_stat_selector(ph);
			int idx = selector_get(sp);

			if (idx >= 0) {
				NETSTAT **ns_list = (NETSTAT **)sp->list;
				const NETSTAT *ns = ns_list[idx];

				for (idx = 0; idx < sp->items; idx++) {
					if (netstat_bidir(ns, ns_list[idx])) {
						selector_set(idx, sp);
						return 1;
					}
				}
			}
		}
		break;
	}
	return 0;
}

SELECTOR *
show_stat_selector(ph)
	PCAP_HANDLER *ph;
{
	if (!ph) return 0;
	if (!ph->selector && (ph->selector = selector_init()) != 0) {
		ph->selector->get_header = show_stat_header;
		ph->selector->get_line = show_stat_line;
		ph->selector->get_footer = show_stat_footer;
	}
	return ph->selector;
}

NETSTAT *
show_stat_get(ph, idx)
	PCAP_HANDLER *ph;
	int idx;
{
	SELECTOR *sp;
	NETSTAT **ns_list;

	/* sanity check */
	if (!ph || idx < 0 || (sp = show_stat_selector(ph)) == 0 || idx >= sp->items)
		return 0;

	ns_list = (NETSTAT **)sp->list;
	return ns_list[idx];
}

int
show_stat_search(ph, str)
	PCAP_HANDLER *ph;
	const char *str;
{
	SELECTOR *sp;
	NETSTAT **ns_list;
	const NETSTAT *ns;
	int idx;
	char src_buf[100], dst_buf[100], proto_buf[20];

	/* sanity check */
	if (!ph || !str || *str == '\0' || (sp = show_stat_selector(ph)) == 0)
		return -1;

	ns_list = (NETSTAT **)sp->list;
	for (idx = 0; idx < sp->items; idx++) {
		ns = ns_list[idx];
		hdr2str(&ns->ns_hdr,
			src_buf, sizeof(src_buf),
			dst_buf, sizeof(dst_buf),
			proto_buf, sizeof(proto_buf));
		if (strstr(src_buf, str) ||
		    strstr(dst_buf, str) ||
		    strstr(proto_buf, str))
			return idx;
	}
	return -1;
}

SELECTOR *
show_stat_list(ph)
	PCAP_HANDLER *ph;
{
	SELECTOR *sp;
	int cnt;

	/* sanity check */
	if (!ph || (sp = show_stat_selector(ph)) == 0)
		return 0;

	sp->header = ph;
	sp->footer = ph;

	if ((cnt = netstat_fetch((NETSTAT ***)&sp->list, ph)) < 0)
		return sp;

	sp->items = cnt;
	if (cnt < 2) /* no sorting required */
		return sp;

	/* sort it accroding to current mode */
	switch (show_stat_mode) {
	case Size:
		qsort(sp->list, sp->items, sizeof(NETSTAT *), compare_pkt_len);
		break;
	case Data:
		qsort(sp->list, sp->items, sizeof(NETSTAT *), compare_data_len);
		break;
	case Packets:
		qsort(sp->list, sp->items, sizeof(NETSTAT *), compare_pkt_cnt);
		break;
	}
	if (popbackflow)
		sort_backflow(sp->list, sp->items);

	return sp;
}

