/*
 * Copyright (c) 2005 Daniel Bryan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or 
 * without modification, are permitted provided that the following 
 * conditions are met:
 * - Redistributions of source code must retain the above copyright 
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in 
 *   the documentation and/or other materials provided with the 
 *   distribution.
 * - Neither the name of the author nor the names of its contributors
 *   may be used to endorse or promote products derived from this 
 *   software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <limits.h>
#include <paths.h>
#include <sys/time.h>
#include <stdarg.h>
#include <errno.h>

#include <sys/socket.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_types.h>

#include <fcntl.h>
#include <kvm.h>

#include <curses.h>

#define VER_MAJ 0
#define VER_MIN 7

#define DISP_BIT  0
#define DISP_BYTE 1
int disp_format = DISP_BYTE;

struct nlist netl[] = { {"_ifnet"}, {""} };
kvm_t *kvmd;
char *nlistf = NULL;
char *memf = NULL;
#if __FreeBSD_version >= 501113
char name[IFNAMSIZ];
#else
char name[32];
char tname[16];
#endif
unsigned long ifnetaddr = 0;

unsigned long long in_total = 0;
unsigned long long out_total = 0;
char *in_dev = NULL;

int winw,winh;
WINDOW *mainw;
WINDOW *inw;
WINDOW *outw;
unsigned int maxgh = 0;

struct timeval timea; /* update interval */
struct timeval now;
struct timeval last;

#define MAX_G 1024
unsigned int *logi[MAX_G];
unsigned int *logo[MAX_G];

/* defaults to 1mbit/1mbit connection */
/* these are set to be in bytes/s */
unsigned long in_max = 131072;
unsigned long out_max = 131072;

char *color_in = 0; /* in graph color */
char *color_out = 0; /* out graph color */

/* prints correct format, float */
void print_formatf(WINDOW *win, float x, int format) {
	if(format == DISP_BIT) {
		x *= 8;
		if(x < 1024) {
			wprintw(win,"%8.0f bit",x);
		} else if(x < 1048576) {
			wprintw(win,"%7.1f kbit",x / 1024);
		} else if(x < 1073741824) {
			wprintw(win,"%7.1f mbit",x / 1048576);
		} else {
			wprintw(win,"%7.1f gbit",x / 1073741824);
		}
	} else if(format == DISP_BYTE) {
		if(x < 1024) {
			wprintw(win,"%8.0f B",x);
		} else if(x < 1048576) {
			wprintw(win,"%7.1f kB",x / 1024);
		} else if(x < 1073741824) {
			wprintw(win,"%7.1f mB",x / 1048576);
		} else {
			wprintw(win,"%7.1f gB",x / 1073741824);
		}
	}
	return;
}

/* prints correct format, u_long */
void print_format(WINDOW *win, unsigned long x, int format) {
	
	if(format == DISP_BIT) {
		x *= 8;
		if(x < 1024) {
			wprintw(win,"%lu bit",x);
		} else if(x < 1048576) {
			wprintw(win,"%lu kbit",x / 1024);
		} else if(x < 1073741824) {
			wprintw(win,"%lu mbit",x / 1048576);
		} else {
			wprintw(win,"%lu gbit",x / 1073741824);
		}
	} else if(format == DISP_BYTE) {
		if(x < 1024) {
			wprintw(win,"%lu B",x);
		} else if(x < 1048576) {
			wprintw(win,"%lu kB",x / 1024);
		} else if(x < 1073741824) {
			wprintw(win,"%lu mB",x / 1048576);
		} else {
			wprintw(win,"%lu gB",x / 1073741824);
		}
	}
	return;
}

void free_logs() {
	int i;
	for(i=0;i<MAX_G;i++) {
		if(logi[i] != NULL) {
			free(logi[i]);
		}
		if(logo[i] != NULL) {
			free(logo[i]);
		}
        logo[i] = NULL;
        logi[i] = NULL;
	}
	return;
}

/* initial screen draw */
void screen_init() {
	int i = 0;
	getmaxyx(stdscr,winh,winw);
	if(winh < 20 || winw < 20) {
		endwin();
		kvm_close(kvmd);
		fprintf(stderr,"Screen size is too small, sorry\n");
		exit(1);
	}
	
	mainw = newwin(winh,winw,0,0);
	maxgh = (winh-3)/2;
	inw = newwin(maxgh,winw,maxgh+3,0);
	outw = newwin(maxgh,winw,2,0);

	overwrite(mainw,stdscr);
	overwrite(inw,stdscr);
	overwrite(inw,mainw);
	overwrite(outw,stdscr);
	overwrite(outw,mainw);
	nonl();
	cbreak();
	noecho();
	curs_set(0);
	nodelay(mainw,TRUE);
	refresh();
	wmove(mainw,0,0);
	wprintw(mainw,"cnd %d.%d [%s]",VER_MAJ,VER_MIN,name);
	wattron(mainw,A_REVERSE);
	wmove(mainw,1,0);
	for(i=0;i<winw;i++) {
		waddch(mainw,' ');
	}
	wmove(mainw,maxgh+2,0);
	for(i=0;i<winw;i++) {
		waddch(mainw,' ');
	}
	/* fill in extra line */
	wmove(mainw,winh-1,0);
	for(i=0;i<winw;i++) {
		waddch(mainw,' ');
	}	
	wmove(mainw,1,(winw/2)-9);
	wprintw(mainw," out[");
	wmove(mainw,maxgh+2,(winw/2)-9);
	wprintw(mainw,"  in[");

	wmove(mainw,1,0);
	print_format(mainw,out_max,disp_format);
	wmove(mainw,maxgh+2,0);
	print_format(mainw,in_max,disp_format);
	wattroff(mainw,A_REVERSE);
	
	wrefresh(mainw);
	return;
}

void screen_check() {
	int rwinh,rwinw;
	getmaxyx(stdscr, rwinh, rwinw);
	if(rwinh == winh && rwinw == winw) {
		return; /* all is good, return */
	} else {
		delwin(mainw);
		delwin(inw);
		delwin(outw);
		/* clear graph as it will be incorrect */
		free_logs();
		screen_init();
	}
	return;
}

/* read kernel memory, based off of netstat */
int kread(u_long addr,char *buf,int size) {
	if(kvmd != NULL) {
		if(kvm_nlist(kvmd,netl) < 0) {
			if(nlistf)
				fprintf(stderr,"error, kvm_nlist(%s): %s\n",nlistf,kvm_geterr(kvmd));
			else
				fprintf(stderr,"error, kvm_nlist: %s\n",kvm_geterr(kvmd));
			exit(1);
		}
		
		if(netl[0].n_type == 0) {
			if(nlistf)
				fprintf(stderr,"error, no namelist: %s\n",nlistf);
			else
				fprintf(stderr,"error, no namelist\n");
			exit(1);
		}
	} else {
		fprintf(stderr,"error, kvm not available\n");
		exit(1);
	}
	if(!buf)
		return 0;
	if(kvm_read(kvmd,addr,buf,size) != size) {
		fprintf(stderr,"error, %s\n",kvm_geterr(kvmd));
		exit(1);
	}
	return 0;
}

int main(int argc, char *argv[]) {

	unsigned long lasti = 0;
	unsigned long lasto = 0;
	unsigned long curi = 0;
	unsigned long curo = 0;
#define CURIS curi / timea.tv_sec
#define CUROS curo / timea.tv_sec
	/* all goods are defined in net/if.h and net/if_var.h */
	struct ifnet foonet;
	struct ifnethead ifnethead;
	int i = 0;
	int x = 0;
	int j = 0;
	char ch = '\0';
	timea.tv_usec = 0;
	timea.tv_sec = 2; /* default interval */
		
	while((ch = getopt(argc,argv,"i:m:n:vhs:c:bB")) != -1) {
		if(ch == 'v') {
			printf("cnd v%d.%d\n",VER_MAJ,VER_MIN);
			exit(0);
		} else if(ch == 'i') {
			in_dev = optarg;
			if(strlen(in_dev) > sizeof(name)) {
				fprintf(stderr,"error, device name is too long\n");
				exit(1);
			}
		} else if(ch == 'b') {
			disp_format = DISP_BIT;
		} else if(ch == 'B') {
			disp_format = DISP_BYTE;
		} else if(ch == 'm') {
			if(*optarg < '0' && *optarg > '9')
				break;
			if(atoi(optarg) < 1)
				break;		
			in_max = atoi(optarg);
			switch(*(optarg + strlen(optarg)-1)) {
			/* Byte -> Byte is pointless */
			case 'b': /* bit -> Byte */
				in_max /= 8;
				break;
			case 'k': /* kbit -> Byte */
				in_max *= 128;
				break;
			case 'K': /* KByte -> Byte */
				in_max *= 1024;
				break;
			case 'm': /* mbit -> Byte */
				in_max *= 131072;
				break;
			case 'M': /* MByte -> Byte */
				in_max *= 1048576;
				break;
			case 'g': /* gbit -> Byte */
				in_max *= 134217728;
				break;
			case 'G': /* GByte -> Byte */
				in_max *= 1073741824;
				break;		
			}
		} else if(ch == 'n') {
			if(*optarg < '0' && *optarg > '9')
				break;
	 		if(atoi(optarg) < 1)
				break;
			out_max = atoi(optarg);
			switch(*(optarg + strlen(optarg)-1)) {
			/* Byte -> Byte is pointless */
			case 'b': /* bit -> Byte */
				out_max /= 8;
				break;
			case 'k': /* kbit -> Byte */
				out_max *= 128;
				break;
			case 'K': /* KByte -> Byte */
				out_max *= 1024;
				break;
			case 'm': /* mbit -> Byte */
				out_max *= 131072;
				break;
			case 'M': /* MByte -> Byte */
				out_max *= 1048576;
				break;
			case 'g': /* gbit -> Byte */
				out_max *= 134217728;
				break;
			case 'G': /* GByte -> Byte */
				out_max *= 1073741824;
				break;		
			}
		} else if(ch == 'h') {
			printf("cnd %d.%d by daniel bryan\n",VER_MAJ,VER_MIN);
			printf("usage: cnd -hvbB -i INTERFACE -m SPEED -n SPEED -c [ COLOR | COLORIN,COLOROUT ]\n");
			printf("  i - select interface\n");
			printf("  b - display speed in bits\n");
			printf("  B - display speed in bytes, default\n");
			printf("  m SPEED - max input of connection, default is 1mbit\n");
			printf("  n SPEED - max output of connection, default is 1mbit\n");
			printf("  s SECONDS - update interval in seconds, default is 2\n");
			printf("  c COLOR - graph color, lowercase, ie. \"blue\"\n");
			printf("    COLORIN,COLOROUT - different in/out color, ie. \"red,blue\"\n");
			printf("  v - print version\n");
			printf("  h - print help (this)\n\n");
			
			printf("NOTE: SPEED can either be in bytes/s (uppsercase B/K/M/G), ie. \"4B\"\n");
			printf("      or bit (lowercase b/k/m/g), ie. \"4b\", defaults to bytes/s\n");
			exit(0);
		} else if(ch == 's') {
			if(*optarg >= '0' && *optarg <= '9')
				timea.tv_sec = atoi(optarg);
		} else if(ch == 'c') {
			color_in = optarg;
			if((color_out = strchr(optarg,',')) != NULL)
				color_out++;
		}
	}
	argc -= optind;
	argv += optind;
	
	for(i=0;i<sizeof(name);i++) {
		name[i] = '\0';
	}

	kvmd = kvm_openfiles(nlistf,memf,NULL,O_RDONLY,0);	
	setgid(getgid());
	kread(0,0,0);
	ifnetaddr = netl[0].n_value;
	
	if(kread(ifnetaddr,(char *)&ifnethead,sizeof ifnethead))
		return 1;
	
	ifnetaddr = (u_long)TAILQ_FIRST(&ifnethead);
	if(kread(ifnetaddr,(char *)&foonet,sizeof foonet))
		return 1;

#if __FreeBSD_version >= 501113
	strncpy(name,foonet.if_xname,sizeof(name));
#else
	if(kread((u_long)foonet.if_name, tname, 16))
		return 1;
	snprintf(name,32,"%s%d",tname,foonet.if_unit);
#endif

	while(in_dev != NULL && strncmp(in_dev,name,strlen(in_dev)) != 0) {
		ifnetaddr = (u_long)TAILQ_NEXT(&foonet,if_link);
		if(ifnetaddr < 1) {
			fprintf(stderr,"error, interface not found\n");
			exit(1);
		}
		if(kread(ifnetaddr,(char *)&foonet,sizeof foonet))
			return 1;

#if __FreeBSD_version >= 501113
		strncpy(name,foonet.if_xname,sizeof(name));
#else
		if(kread((u_long)foonet.if_name, tname, 16))
			return 1;
		snprintf(name,32,"%s%d",tname,foonet.if_unit);
#endif
			
	}

	/* start curses */
	initscr();
	/* start color setup */
	if(has_colors() != TRUE) {
		color_in = 0;
		color_out = 0;
	}
	if(color_in > 0) {
		start_color();
		use_default_colors();
		if(color_out == 0)
			color_out = color_in;
		
		if(strncmp(color_in,"black",5) == 0)
			init_pair(1, COLOR_BLACK,-1);
		else if(strncmp(color_in,"red",3) == 0)
			init_pair(1, COLOR_RED,-1);
		else if(strncmp(color_in,"green",5) == 0)
			init_pair(1, COLOR_GREEN,-1);
		else if(strncmp(color_in,"yellow",6) == 0)
			init_pair(1, COLOR_YELLOW,-1);
		else if(strncmp(color_in,"blue",4) == 0)
			init_pair(1, COLOR_BLUE,-1);
		else if(strncmp(color_in,"megenta",7) == 0)
			init_pair(1, COLOR_MAGENTA,-1);
		else if(strncmp(color_in,"cyan",4) == 0)
			init_pair(1, COLOR_CYAN,-1);
		else if(strncmp(color_in,"white",5) == 0)
			init_pair(1, COLOR_WHITE,-1);
	}
	if(color_out > 0) {
		if(strncmp(color_out,"black",5) == 0)
			init_pair(2, COLOR_BLACK,-1);
		else if(strncmp(color_out,"red",3) == 0)
			init_pair(2, COLOR_RED,-1);
		else if(strncmp(color_out,"green",5) == 0)
			init_pair(2, COLOR_GREEN,-1);
		else if(strncmp(color_out,"yellow",6) == 0)
			init_pair(2, COLOR_YELLOW,-1);
		else if(strncmp(color_out,"blue",4) == 0)
			init_pair(2, COLOR_BLUE,-1);
		else if(strncmp(color_out,"megenta",7) == 0)
			init_pair(2, COLOR_MAGENTA, -1);
		else if(strncmp(color_out,"cyan",4) == 0)
			init_pair(2, COLOR_CYAN,-1);
		else if(strncmp(color_out,"white",5) == 0)
			init_pair(2, COLOR_WHITE,-1);
	}
	/* screen init.. */
	screen_init();
	
	lasti = foonet.if_ibytes;
	lasto = foonet.if_obytes;
	gettimeofday(&last,NULL);
	
	for(i=0;i<MAX_G;i++) {
		logi[i] = NULL;
		logo[i] = NULL;
	}
	
	for(;;) {
		screen_check();
		ch = wgetch(mainw);
		/* quit when we get 'q' */
		if(ch == (int)'q' || ch == (int)' ') {
			
			endwin();
			kvm_close(kvmd);		

			exit(0);
		}
		/* clear screen */
		if(ch == (int)'c') {
            free_logs();
		}
		
		if(kread(ifnetaddr,(char *)&foonet,sizeof foonet))
			return 1;
		
		curi = foonet.if_ibytes - lasti;
		curo = foonet.if_obytes - lasto;
		in_total += curi;
		out_total += curo;
		
		wattron(mainw,A_REVERSE);
		wmove(mainw,1,(winw/2)-4);
		/* speed out */
		print_formatf(mainw,(float)CUROS,disp_format);
		if(disp_format == DISP_BYTE)
			wprintw(mainw,"/s");
		wprintw(mainw,"]");
		
		/* total out */
		wmove(mainw,1,(winw-17));
		wprintw(mainw,"total[");
		print_formatf(mainw,(float)out_total,DISP_BYTE);
		wprintw(mainw,"]");
		
		/* speed in */
		wmove(mainw,maxgh+2,(winw/2)-4);
		print_formatf(mainw,(float)CURIS,disp_format);
		if(disp_format == DISP_BYTE)
			wprintw(mainw,"/s");	
		wprintw(mainw,"]");
		
		/* total in */
		wmove(mainw,maxgh+2,(winw-17));
		wprintw(mainw,"total[");
		print_formatf(mainw,(float)in_total,DISP_BYTE);
		wprintw(mainw,"]");

		wattroff(mainw,A_REVERSE);
		
		gettimeofday(&now,NULL);
		
		wmove(mainw,winh,0);
		wrefresh(mainw);
		
		if(in_max/maxgh > 0)
			x = (curi/(in_max/maxgh)) / timea.tv_sec;
		else
			x = curi / timea.tv_sec;
		wmove(mainw,0,20);
		wrefresh(mainw);
		
		werase(inw);
		werase(outw);
		
		for(i=0;i<winw;i++) {
			if(logi[i] == NULL) {
				logi[i] = (unsigned int *)malloc(sizeof(unsigned int));
				if(logi[i] == NULL) {
					fprintf(stderr,"error,allocating memory\n");
					kvm_close(kvmd);
					endwin();
					exit(1);
				}
				*logi[i] = 0;
				break;
			}

		}
		for(i=0;i<winw;i++) {		
			if(logo[i] == NULL) {
				logo[i] = (unsigned int *)malloc(sizeof(unsigned int));
				if(logo[i] == NULL) {
					fprintf(stderr,"error,allocating memory\n");
					kvm_close(kvmd);
					endwin();
					exit(1);
				}
				*logo[i] = 0;
				break;
			}				
		}
		
		if(logi[1] != NULL && logo[1] != NULL) {
			for(i=winw-1;i>=1;i--) {
				if(logi[i] != NULL)
					*logi[i] = *logi[i-1];
			}
			for(i=winw-1;i>=1;i--) {
				if(logo[i] != NULL)
					*logo[i] = *logo[i-1];
			}			
		}
				
		*logi[0] = x;
		for(j=0;j<winw;j++) {
			if(logi[j] == NULL) {
				break;
			}
			if(logi[j] == 0)
				continue;
			if(*logi[j] > maxgh)
				*logi[j] = maxgh;
			for(i=0;i<*logi[j];i++) {
				wmove(inw,maxgh-i-1,j);
				wattron(inw,A_BOLD);
				wattron(inw,COLOR_PAIR(1));
				waddch(inw,'*');
				wattroff(inw,COLOR_PAIR(1));
				wattroff(inw,A_BOLD);
			}
		}
		wrefresh(inw);
		if(out_max/maxgh > 0)
			x = (curo/(out_max/maxgh)) / timea.tv_sec;
		else
			x = curo / timea.tv_sec;
		*logo[0] = x;
		for(j=0;j<winw;j++) {
			if(logo[j] == NULL) {
				break;
			}
			if(logo[j] == 0)
				continue;
			if(*logo[j] > maxgh)
				*logo[j] = maxgh;
			for(i=0;i<*logo[j];i++) {
				wmove(outw,maxgh-i-1,j);
				wattron(outw,A_BOLD);
				wattron(outw,COLOR_PAIR(2));
				waddch(outw,'*');
				wattroff(outw,COLOR_PAIR(2));
				wattroff(outw,A_BOLD);
			}
		}
		wrefresh(outw);
		
		gettimeofday(&last,NULL);
		select(0,NULL,NULL,NULL,&timea);
		lasti = foonet.if_ibytes;
		lasto = foonet.if_obytes;
		
	}
}



syntax highlighted by Code2HTML, v. 0.9.1