/*
 * wavemon - a wireless network monitoring aplication
 *
 * Copyright (c) 2001-2002 Jan Morgenstern <jan@jm-music.de>
 *
 * wavemon is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2, or (at your option) any later
 * version.
 *
 * wavemon is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along
 * with wavemon; see the file COPYING.  If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
#include "iw_if.h"

/*
 * Obtain network device information
 */

/* Interface information */
void if_getinf(char *ifname, struct if_info *info)
{
	struct ifreq ifr;
	int skfd = socket(AF_INET, SOCK_DGRAM, 0);

	if (skfd < 0)
		err_sys("%s: can not open socket", __func__);

	memset(&ifr, 0, sizeof(struct ifreq));
	memset(info, 0, sizeof(struct if_info));

	/* Copy the 6 byte Ethernet address and the 4 byte struct in_addrs */
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
	if (ioctl(skfd, SIOCGIFADDR, &ifr) >= 0)
		memcpy(&info->addr, &ifr.ifr_addr.sa_data[2], 4);
	if (ioctl(skfd, SIOCGIFHWADDR, &ifr) >= 0)
		memcpy(&info->hwaddr, &ifr.ifr_hwaddr.sa_data, 6);
	if (ioctl(skfd, SIOCGIFNETMASK, &ifr) >= 0)
		memcpy(&info->netmask, &ifr.ifr_netmask.sa_data[2], 4);
	if (ioctl(skfd, SIOCGIFBRDADDR, &ifr) >= 0)
		memcpy(&info->bcast, &ifr.ifr_broadaddr.sa_data[2], 4);
	close(skfd);
}

static FILE *open_proc_net(const char *filename)
{
	char path[128];
	FILE *fp;

	snprintf(path, sizeof(path), "/proc/net/%s", filename);
	if (access(path, F_OK) != 0)
		err_quit("'%s' not accessible - not compiled in?", path);

	fp = fopen(path, "r");
	if (fp == NULL)
		err_sys("can not open %s", path);

	return fp;
}

/*
 * Populate list of available wireless interfaces
 * Return index into array-of-lists ld.
 */
int iw_get_interface_list(void)
{
	char *lp, tmp[LISTVAL_MAX];
	int ld = ll_create();
	FILE *fp = open_proc_net("wireless");

	while (fgets(tmp, LISTVAL_MAX, fp))
		if ((lp = strchr(tmp, ':'))) {
			*lp = '\0';
			ll_push(ld, "s", tmp + strspn(tmp, " "));
		}
	fclose(fp);

	if (ll_size(ld) == 0)
		err_quit("no wireless interfaces found!");
	return ld;
}

void if_getstat(char *ifname, struct if_stat *stat)
{
	char line[0x100];
	unsigned long d;
	char *lp;
	FILE *fp = open_proc_net("dev");

	/*
	 * Inter-|   Receive                                                | Transmit
	 *  face |bytes    packets errs drop fifo frame compressed multicast|bytes packets
	 */
	while (fgets(line, sizeof(line), fp)) {
		lp = line + strspn(line, " ");
		if (!strncmp(lp, ifname, strlen(ifname))) {
			lp += strlen(ifname) + 1;
			lp += strspn(lp, " ");

			sscanf(lp, "%llu %llu %lu %lu %lu %lu %lu %lu %llu %llu",
				&stat->rx_bytes, &stat->rx_packets, &d, &d, &d, &d, &d, &d,
				&stat->tx_bytes, &stat->tx_packets);
		}
	}
	fclose(fp);
}

/*
 * obtain dynamic device information
 */
void iw_getinf_dyn(char *ifname, struct iw_dyn_info *info)
{
	struct iwreq iwr;
	int skfd = socket(AF_INET, SOCK_DGRAM, 0);

	if (skfd < 0)
		err_sys("%s: can not open socket", __func__);

	memset(info, 0, sizeof(struct iw_dyn_info));

	strncpy(iwr.ifr_name, ifname, IFNAMSIZ);
	if (ioctl(skfd, SIOCGIWNAME, &iwr) < 0)
		err_sys("can not open device '%s'", iwr.u.name);
	strncpy(info->name, iwr.u.name, IFNAMSIZ);

	iwr.u.essid.pointer = (caddr_t) info->essid;
	iwr.u.essid.length  = sizeof(info->essid);
	iwr.u.essid.flags   = 0;
	if (ioctl(skfd, SIOCGIWESSID, &iwr) >= 0) {
		info->cap_essid = 1;
		/* Convert potential ESSID index to count > 0 */
		info->essid_ct  = iwr.u.essid.flags & IW_ENCODE_INDEX ?  : 1;
		strncpy(info->essid, iwr.u.essid.pointer, IW_ESSID_MAX_SIZE);
	}

	if (ioctl(skfd, SIOCGIWNWID, &iwr) >= 0) {
		info->cap_nwid = 1;
		memcpy(&info->nwid, &iwr.u.nwid, sizeof(info->nwid));
	}

	iwr.u.essid.pointer = (caddr_t) info->nickname;
	iwr.u.essid.length  = sizeof(info->nickname);
	iwr.u.essid.flags   = 0;
	if (ioctl(skfd, SIOCGIWNICKN, &iwr) >= 0 &&
	    iwr.u.data.length > 1)
		info->cap_nickname = 1;

	if (ioctl(skfd, SIOCGIWFREQ, &iwr) >= 0) {
		info->cap_freq = 1;
		info->freq     = freq_to_hz(&iwr.u.freq);
	}

	if (ioctl(skfd, SIOCGIWSENS, &iwr) >= 0) {
		info->cap_sens = 1;
		info->sens     = iwr.u.sens.value;
	}

	if (ioctl(skfd, SIOCGIWRATE, &iwr) >= 0) {
		info->cap_bitrate = 1;
		info->bitrate     = iwr.u.bitrate.value;
	}

	if (ioctl(skfd, SIOCGIWTXPOW, &iwr) >= 0) {
		info->cap_txpower = 1;
		memcpy(&info->txpower, &iwr.u.txpower, sizeof(info->txpower));
	}

	if (ioctl(skfd, SIOCGIWPOWER, &iwr) >= 0) {
		info->cap_power = 1;
		memcpy(&info->power, &iwr.u.power, sizeof(info->power));
	}

	if (ioctl(skfd, SIOCGIWRETRY, &iwr) >= 0) {
		info->cap_retry = 1;
		memcpy(&info->retry, &iwr.u.retry, sizeof(info->retry));
	}

	if (ioctl(skfd, SIOCGIWRTS, &iwr) >= 0) {
		info->cap_rts = 1;
		memcpy(&info->rts, &iwr.u.rts, sizeof(info->rts));
	}

	if (ioctl(skfd, SIOCGIWFRAG, &iwr) >= 0) {
		info->cap_frag = 1;
		memcpy(&info->frag, &iwr.u.frag, sizeof(info->frag));
	}

	if (ioctl(skfd, SIOCGIWMODE, &iwr) >= 0) {
		info->cap_mode = 1;
		info->mode     = iwr.u.mode;
	}

	iwr.u.data.pointer = (caddr_t) info->key;
	iwr.u.data.length  = sizeof(info->key);
	iwr.u.data.flags   = 0;
	if (ioctl(skfd, SIOCGIWENCODE, &iwr) >= 0) {
		info->cap_key	= 1;
		info->key_flags	= iwr.u.data.flags;
		info->key_size	= iwr.u.data.length;
	}

	if (ioctl(skfd, SIOCGIWAP, &iwr) >= 0) {
		info->cap_ap = 1;
		memcpy(&info->ap_addr, &iwr.u.ap_addr, sizeof(struct sockaddr));
	}
	close(skfd);
}

/*
 * get range information
 */
void iw_getinf_range(char *ifname, struct iw_range *range)
{
	struct iwreq iwr;
	int skfd = socket(AF_INET, SOCK_DGRAM, 0);

	if (skfd < 0)
		err_sys("%s: can not open socket", __func__);

	memset(range, 0, sizeof(struct iw_range));

	strncpy(iwr.ifr_name, ifname, IFNAMSIZ);
	if (ioctl(skfd, SIOCGIWNAME, &iwr) < 0)
		err_sys("can not open device '%s'", iwr.u.name);

	iwr.u.data.pointer = (caddr_t) range;
	iwr.u.data.length  = sizeof(struct iw_range);
	iwr.u.data.flags   = 0;
	if (ioctl(skfd, SIOCGIWRANGE, &iwr) < 0)
		err_sys("can not get range information");
	close(skfd);
}

/*
 *	Obtain periodic IW statistics
 */
static int rnd_signal(int min, int max)
{
	static float rlvl, rlvl_next;
	static float step = 1.0;
	int i;

	for (i = 0; i < 1; i++)
		if (rlvl < rlvl_next) {
			if (rlvl_next - rlvl < step)
				step /= 2;
			rlvl += step;
		} else if (rlvl > rlvl_next) {
			if (rlvl - rlvl_next < step)
				step /= 2;
			rlvl -= step;
		}
	step += (rand() / (float)RAND_MAX) - 0.5;
	if ((rlvl == rlvl_next) || (step < 0.05)) {
		rlvl_next = (rand() / (float)RAND_MAX) * (max - min) + min;
		step = rand() / (float)RAND_MAX;
	}
	return rlvl;
}

static int rnd_noise(int min, int max)
{
	static float rlvl, rlvl_next;
	static float step = 1.0;
	int i;

	for (i = 0; i < 1; i++)
		if (rlvl < rlvl_next) {
			if (rlvl_next - rlvl < step)
				step /= 2;
			rlvl += step;
		} else if (rlvl > rlvl_next) {
			if (rlvl - rlvl_next < step)
				step /= 2;
			rlvl -= step;
		}
	step += (rand() / (float)RAND_MAX) - 0.5;
	if ((rlvl == rlvl_next) || (step < 0.05)) {
		rlvl_next = (rand() / (float)RAND_MAX) * (max - min) + min;
		step = rand() / (float)RAND_MAX;
	}
	return rlvl;
}

/* Random signal/noise */
static void iw_getstat_random(struct iw_statistics *stat)
{
	stat->qual.level = rnd_signal(-102, 10);
	stat->qual.noise = rnd_noise(-102, -30);
}

/* Code in part taken from wireless extensions #30 */
static void iw_getstat_old_style(struct iw_statistics *stat)
{
	char line[0x100], *lp;
	int tmp;
	FILE *fp = open_proc_net("wireless");

	while (fgets(line, sizeof(line), fp)) {
		for (lp = line; *lp && isspace(*lp); lp++)
			;
		if (strncmp(lp, conf.ifname, strlen(conf.ifname)) == 0 &&
		    lp[strlen(conf.ifname)] == ':') {
			lp += strlen(conf.ifname) + 1;

			/* status */
			lp = strtok(lp, " ");
			sscanf(lp, "%X", &tmp);
			stat->status = (unsigned short)tmp;

			/* link quality */
			lp = strtok(NULL, " ");
			if (strchr(lp, '.') != NULL)
				stat->qual.updated |= IW_QUAL_QUAL_UPDATED;
			sscanf(lp, "%d", &tmp);
			stat->qual.qual = (unsigned char)tmp;

			/* signal level */
			lp = strtok(NULL, " ");
			if (strchr(lp, '.') != NULL)
				stat->qual.updated |= IW_QUAL_LEVEL_UPDATED;
			sscanf(lp, "%d", &tmp);
			stat->qual.level = (unsigned char)tmp;

			/* noise level */
			lp = strtok(NULL, " ");
			if (strchr(lp, '.') != NULL)
				stat->qual.updated |= IW_QUAL_NOISE_UPDATED;
			sscanf(lp, "%d", &tmp);
			stat->qual.noise = (unsigned char)tmp;

			/* # of packets w/ invalid nwid */
			lp = strtok(NULL, " ");
			sscanf(lp, "%u", &stat->discard.nwid);

			/* # of packets w/ invalid key */
			lp = strtok(NULL, " ");
			sscanf(lp, "%u", &stat->discard.code);

			/* # of packets w/ bad attitude */
			lp = strtok(NULL, " ");
			sscanf(lp, "%u", &stat->discard.misc);

			/* each interface appears just once */
			break;
		}
	}
	fclose(fp);
}

static void iw_getstat_new_style(struct iw_statistics *stat)
{
	struct iwreq wrq;
	int skfd = socket(AF_INET, SOCK_DGRAM, 0);

	if (skfd < 0)
		err_sys("%s: can not open socket", __func__);

	wrq.u.data.pointer = (caddr_t) stat;
	wrq.u.data.length  = sizeof(*stat);
	wrq.u.data.flags   = 0;
	strncpy(wrq.ifr_name, conf.ifname, IFNAMSIZ);

	if (ioctl(skfd, SIOCGIWSTATS, &wrq) < 0) {
		/*
		 * iw_handler_get_iwstats() returns EOPNOTSUPP if
		 * there are no statistics. Bail out in this case.
		 */
		if (errno != EOPNOTSUPP)
			err_sys("can not obtain iw statistics");
		errno = 0;
		memset(&wrq, 0, sizeof(wrq));
	}
	close(skfd);
}

/*
 * Generate dBm values and perform sanity checks on values.
 * Code in part taken from wireless extensions #30
 * @range: range information, read-only
 * @qual:  wireless statistics, read-write
 * @dbm:   dBm level information, write-only
 */
void iw_sanitize(struct iw_range *range, struct iw_quality *qual,
		 struct iw_levelstat *dbm)
{
	memset(dbm, 0, sizeof(*dbm));

	if (qual->level != 0 || (qual->updated & (IW_QUAL_DBM | IW_QUAL_RCPI))) {
		/*
		 * RCPI (IEEE 802.11k) statistics:
		 *    RCPI = int{(Power in dBm +110)*2}
		 *    for 0 dBm > Power > -110 dBm
		 */
		if (qual->updated & IW_QUAL_RCPI) {
			if (!(qual->updated & IW_QUAL_LEVEL_INVALID))
				dbm->signal = (double)(qual->level / 2.0) - 110.0;
			if (!(qual->updated & IW_QUAL_NOISE_INVALID))
				dbm->noise  = (double)(qual->noise / 2.0) - 110.0;

		} else if ((qual->updated & IW_QUAL_DBM) ||
			/*
			 * Statistics in dBm (absolute power measurement)
			 * These are encoded in the range -192 .. 63
			 */
			   qual->level > range->max_qual.level) {

			if (!(qual->updated & IW_QUAL_LEVEL_INVALID)) {
				dbm->signal = qual->level;
				if (qual->level >= 64)
					dbm->signal -= 0x100;
			}
			if (!(qual->updated & IW_QUAL_NOISE_INVALID)) {
				dbm->noise = qual->noise;
				if (qual->noise >= 64)
					dbm->noise -= 0x100;
			}
		} else {
			/*
			 * Relative values (0 -> max)
			 */
			if (!(qual->updated & IW_QUAL_LEVEL_INVALID))
				dbm->signal = mw2dbm(qual->level);
			if (!(qual->updated & IW_QUAL_NOISE_INVALID))
				dbm->noise  = mw2dbm(qual->noise);
		}
	} else {
		qual->updated |= IW_QUAL_ALL_INVALID;
	}

	/*
	 * Value sanity checks
	 *
	 * These rules serve to avoid "insensible" level displays. Please do send
	 * comments and/or bug reports if you encounter room for improvement.
	 *
	 *  1) if noise level is valid, but signal level is not, displaying just
	 *     the noise level does not reveal very much - can be omitted;
	 *  2) if the noise level is below an "invalid" magic value (see iw_if.h),
	 *     declare the noise value to be invalid;
	 *  3) SNR is only displayed if both signal and noise values are valid.
	 */
	if (qual->updated & IW_QUAL_LEVEL_INVALID)
		qual->updated |= IW_QUAL_NOISE_INVALID;
	if (dbm->noise <= NOISE_DBM_SANE_MIN)
		qual->updated |= IW_QUAL_NOISE_INVALID;
}

void iw_getstat(struct iw_stat *iw)
{
	memset(&iw->stat, 0, sizeof(iw->stat));

	if (conf.random)
		iw_getstat_random(&iw->stat);
	else if (iw->range.we_version_compiled > 11)
		iw_getstat_new_style(&iw->stat);
	else
		iw_getstat_old_style(&iw->stat);

	iw_sanitize(&iw->range, &iw->stat.qual, &iw->dbm);
}

void dump_parameters(void)
{
	struct iw_dyn_info info;
	struct iw_stat iw;
	struct if_stat nstat;
	int i;

	iw_getinf_dyn(conf.ifname, &info);
	iw_getinf_range(conf.ifname, &iw.range);
	iw_getstat(&iw);
	if_getstat(conf.ifname, &nstat);

	printf("\n");
	printf("Configured device: %s\n", conf.ifname);
	printf("       WE version: %d (source version %d)\n\n",
	       iw.range.we_version_compiled, iw.range.we_version_source);

	if (info.cap_essid) {
		if (info.essid_ct > 1)
			printf("            essid: \"%s\" [%d]\n",
						info.essid, info.essid_ct);
		else if (info.essid_ct)
			printf("            essid: \"%s\"\n", info.essid);
		else
			printf("            essid: off/any\n");
	}

	if (info.cap_nickname)
		printf("             nick: \"%s\"\n", info.nickname);

	if (info.cap_nwid) {
		if (info.nwid.disabled)
			printf("             nwid: off/any\n");
		else
			printf("             nwid: %X\n", info.nwid.value);
	}

	/* Some drivers only return the channel (e.g. ipw2100) */
	if (info.cap_freq && info.freq < 256)
		info.freq = channel_to_freq(info.freq, &iw.range);
	if (info.cap_freq && info.freq > 1e3) {
		i = freq_to_channel(info.freq, &iw.range);
		if (i >= 0)
			printf("          channel: %d\n", i);
		printf("        frequency: %g GHz\n", info.freq / 1.0e9);
	} else
		printf("        frequency: n/a\n");

	if (info.cap_sens) {
		if (info.sens < 0)
			printf("      sensitivity: %d dBm\n", info.sens);
		else
			printf("      sensitivity: %d/%d\n", info.sens,
						   iw.range.sensitivity);
	}

	if (info.cap_txpower && info.txpower.disabled)
		printf("         tx-power: off\n");
	else if (info.cap_txpower && info.txpower.fixed)
		printf("         tx-power: %s\n", format_txpower(&info.txpower));
	else if (info.cap_txpower)
		printf("         TX-power: %s\n", format_txpower(&info.txpower));

	printf("             mode: %s\n", iw_opmode(info.mode));

	if (info.mode != 1 && info.cap_ap)
		printf("     access point: %s\n", format_bssid(&info.ap_addr));

	if (info.cap_bitrate)
		printf("          bitrate: %g Mbit/s\n", info.bitrate / 1.0e6);
	else
		printf("          bitrate: n/a\n");

	printf("            retry: ");
	if (info.cap_retry)
		printf("%s\n", format_retry(&info.retry, &iw.range));
	else
		printf("n/a\n");

	printf("          rts thr: ");
	if (info.cap_rts) {
		if (info.rts.disabled)
			printf("off\n");
		else
			printf("%d B %s\n", info.rts.value,
			       info.rts.fixed ? "" : "(auto-select)");
	} else
		printf("n/a\n");

	printf("         frag thr: ");
	if (info.cap_frag) {
		if (info.frag.disabled)
			printf("off\n");
		else
			printf("%d B %s\n", info.frag.value,
			       info.frag.fixed ? "" : "(auto-select)");
	} else {
		printf("n/a\n");
	}

	printf("       encryption: ");
	if (info.cap_key) {
		if (info.key_flags & IW_ENCODE_DISABLED || info.key_size == 0) {
			printf("off");
		} else {
			printf("%s", format_key(info.key, info.key_size));
			i = info.key_flags & IW_ENCODE_INDEX;
			if (i > 1)
				printf(" [%d]", i);
			if (info.key_flags & IW_ENCODE_RESTRICTED)
				printf(", restricted");
			if (info.key_flags & IW_ENCODE_OPEN)
				printf(", open");
		}
	} else {
		printf("n/a");
	}

	printf("\n");
	printf(" power management: ");
	if (info.cap_power)
		printf("%s\n", format_power(&info.power, &iw.range));
	else
		printf("n/a\n");

	printf("\n");
	printf("     link quality: %d/%d\n", iw.stat.qual.qual,
	       iw.range.max_qual.qual);
	printf("     signal level: %.0f dBm (%s)\n", iw.dbm.signal,
	       dbm2units(iw.dbm.signal));
	printf("      noise level: %.0f dBm (%s)\n", iw.dbm.noise,
	       dbm2units(iw.dbm.noise));
	printf("              SNR: %.0f dB\n", iw.dbm.signal - iw.dbm.noise);

	/* RX stats */
	printf("         RX total: %'llu packets (%s)\n", nstat.rx_packets,
	       byte_units(nstat.rx_bytes));
	printf("     invalid nwid: %'u\n", iw.stat.discard.nwid);
	printf("      invalid key: %'u\n", iw.stat.discard.code);
	if (iw.range.we_version_compiled > 11) {
		printf("   invalid fragm.: %'u\n", iw.stat.discard.fragment);
		printf("   missed beacons: %'u\n", iw.stat.miss.beacon);
	}
	printf("      misc errors: %'u\n", iw.stat.discard.misc);

	/* TX stats */
	printf("         TX total: %'llu packets (%s)\n", nstat.tx_packets,
	       byte_units(nstat.tx_bytes));
	if (iw.range.we_version_compiled > 11)
		printf(" exc. MAC retries: %'u\n", iw.stat.discard.retries);

	printf("\n");
}
