/*
 * 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 "wavemon.h"
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ether.h>
#include <net/if_arp.h>
#include <net/ethernet.h>
#include <sys/socket.h>
#include <linux/if.h>
#include <linux/wireless.h>

/* Definitions that appeared in more recent versions of wireless.h */
#ifndef IW_POWER_SAVING
#define IW_POWER_SAVING	0x4000		/* version 20 -> 21 */
#endif
#ifndef IW_MODE_MESH
#define IW_MODE_MESH	7		/* introduced in 2.6.26-rc1 */
#endif

/* Maximum length of a MAC address: 2 * 6 hex digits, 6 - 1 colons, plus '\0' */
#define MAC_ADDR_MAX	18

/*
 * Threshold for 'sane' noise levels.
 *
 * Some drivers simply set an arbitrary minimum noise level to mean 'invalid',
 * but do not set IW_QUAL_NOISE_INVALID so that the display gets stuck at a
 * "house number". The value below is suggested by and taken from the iwl3945
 * driver (constant IWL_NOISE_MEAS_NOT_AVAILABLE in iwl-3945.h).
 */
#define NOISE_DBM_SANE_MIN	-127

/* Static network interface information - see netdevice(7) */
struct if_info {		/* modified ifreq */
	struct ether_addr	hwaddr;
	struct in_addr		addr,
				netmask,
				bcast;
};
extern void if_getinf(char *ifname, struct if_info *info);

/**
 * struct iw_dyn_info  -  modified iw_req
 * @name:	interface name
 * @mode:	current operation mode (IW_MODE_xxx)
 *
 * @cap_*:	indicating capability/presence
 *
 * @essid:	Extended Service Set ID (network name)
 * @essid_ct:	index number of the @essid (starts at 1, 0 = off)
 * @nickname:	optional station nickname
 * @nwid:	Network ID (pre-802.11 hardware only)
 * @ap_addr:	BSSID or IBSSID
 *
 * @retry:	MAC-retransmission retry behaviour
 * @rts:	minimum packet size for which to perform RTS/CTS handshake
 * @frag:	802.11 frame fragmentation threshold size
 * @txpower:	TX power information
 * @power	power management information
 *
 * @freq:	frequency in Hz
 * @sens:	sensitivity threshold of the card
 * @bitrate:	bitrate (client mode)
 *
 * @key:	encryption key
 * @key_size:	length of @key in bytes
 * @key_flags:	bitmask with information about @key
 *
 */
struct iw_dyn_info {
	char		name[IFNAMSIZ];
	uint8_t		mode;

	bool		cap_essid:1,
			cap_nwid:1,
			cap_nickname:1,
			cap_freq:1,
			cap_sens:1,
			cap_bitrate:1,
			cap_txpower:1,
			cap_retry:1,
			cap_rts:1,
			cap_frag:1,
			cap_mode:1,
			cap_ap:1,
			cap_key:1,
			cap_power:1,
			cap_aplist:1;

	char		essid[IW_ESSID_MAX_SIZE+2];
	uint8_t		essid_ct;
	char		nickname[IW_ESSID_MAX_SIZE+2];
	struct iw_param nwid;
	struct sockaddr ap_addr;

	struct iw_param retry;
	struct iw_param rts;
	struct iw_param frag;
	struct iw_param txpower;
	struct iw_param power;

	float		freq;
	int32_t		sens;
	unsigned long	bitrate;

	char		key[IW_ENCODING_TOKEN_MAX];
	uint16_t	key_size;
	uint16_t	key_flags;
};

struct if_stat {
	unsigned long long	rx_packets,
				tx_packets;
	unsigned long long	rx_bytes,
				tx_bytes;
};

extern void if_getstat(char *ifname, struct if_stat *stat);

/*
 *	 Structs to communicate WiFi statistics
 */
struct iw_levelstat {
	float	signal;		/* signal level in dBm */
	float	noise;		/* noise  level in dBm */
	uint8_t	flags;		/* level validity      */
};
#define IW_LSTAT_INIT { 0, 0, IW_QUAL_LEVEL_INVALID | IW_QUAL_NOISE_INVALID }

extern void iw_sanitize(struct iw_range *range,
			struct iw_quality *qual,
			struct iw_levelstat *dbm);

/**
 * struct iw_stat - record current WiFi state
 * @range:	current range information
 * @stats:	current signal level statistics
 * @dbm:	the noise/signal of @stats in dBm
 */
struct iw_stat {
	struct iw_range		range;
	struct iw_statistics	stat;
	struct iw_levelstat	dbm;
};

extern void iw_getstat(struct iw_stat *stat);
extern void iw_cache_update(struct iw_stat *stat);

extern void iw_getinf_dyn(char *ifname, struct iw_dyn_info *info);
extern void iw_getinf_range(char *ifname, struct iw_range *range);

extern void (*iw_stat_redraw) (void);

/*
 *	Helper routines
 */
static inline const char *iw_opmode(const uint8_t mode)
{
	static char *modes[] = { "Auto",
				 "Ad-Hoc",
				 "Managed",
				 "Master",
				 "Repeater",
				 "Secondary",
				 "Monitor",
				 "Mesh"
	};

	return mode < ARRAY_SIZE(modes) ? modes[mode] : "Unknown/bug";
}

static inline bool is_zero_ether_addr(const uint8_t *mac)
{
	return ! (mac[0] | mac[1] | mac[2] | mac[3] | mac[4] | mac[5]);
}

static inline bool is_broadcast_ether_addr(const uint8_t *mac)
{
	return (mac[0] & mac[1] & mac[2] & mac[3] & mac[4] & mac[5]) == 0xff;
}

/* Print a mac-address, include leading zeroes (unlike ether_ntoa(3)) */
static inline char *ether_addr(const struct ether_addr *ea)
{
	static char str[MAC_ADDR_MAX];

	sprintf(str, "%02X:%02X:%02X:%02X:%02X:%02X",
		ea->ether_addr_octet[0], ea->ether_addr_octet[1],
		ea->ether_addr_octet[2], ea->ether_addr_octet[3],
		ea->ether_addr_octet[4], ea->ether_addr_octet[5]);
	return str;
}

/* Print mac-address translation from /etc/ethers if available */
static inline char *ether_lookup(const struct ether_addr *ea)
{
	static char hostname[BUFSIZ];

	if (ether_ntohost(hostname, ea) == 0)
		return hostname;
	return ether_addr(ea);
}

/* Format an Ethernet mac address */
static inline char *mac_addr(const struct sockaddr *sa)
{
	struct ether_addr zero = { {0} };

	if (sa->sa_family != ARPHRD_ETHER)
		return ether_addr(&zero);
	return ether_lookup((struct ether_addr *)sa->sa_data);
}

/* Format a (I)BSSID */
static inline char *format_bssid(const struct sockaddr *ap)
{
	const struct ether_addr *bssid = (struct ether_addr *)ap->sa_data;

	if (is_zero_ether_addr(bssid->ether_addr_octet))
		return "Not-Associated";
	if (is_broadcast_ether_addr(bssid->ether_addr_octet))
		return "Invalid";
	return mac_addr(ap);
}

/* count bits set in @mask the Brian Kernighan way */
static inline uint8_t bit_count(uint32_t mask)
{
	uint8_t bits_set;

	for (bits_set = 0; mask; bits_set++)
		mask &= mask - 1;

	return bits_set;
}

/* netmask = contiguous 1's followed by contiguous 0's */
static inline uint8_t prefix_len(const struct in_addr *netmask)
{
	return bit_count(netmask->s_addr);
}

/* Convert log dBm values to linear mW */
static inline double dbm2mw(const double in)
{
	return pow(10.0, in / 10.0);
}

static inline char *dbm2units(const double in)
{
	static char with_units[0x100];
	double val = dbm2mw(in);

	if (val < 0.00000001) {
		sprintf(with_units, "%.2f pW", val * 1e9);
	} else if (val < 0.00001) {
		sprintf(with_units, "%.2f nW", val * 1e6);
	} else if (val < 0.01) {
		sprintf(with_units, "%.2f uW", val * 1e3);
	} else {
		sprintf(with_units, "%.2f mW", val);
	}
	return with_units;
}

/* Convert linear mW values to log dBm */
static inline double mw2dbm(const double in)
{
	return 10.0 * log10(in);
}

/* Format driver TX power information */
static inline char *format_txpower(const struct iw_param *txpwr)
{
	static char txline[0x40];

	if (txpwr->flags & IW_TXPOW_RELATIVE)
		snprintf(txline, sizeof(txline), "%d (no units)", txpwr->value);
	else if (txpwr->flags & IW_TXPOW_MWATT)
		snprintf(txline, sizeof(txline), "%.0f dBm (%d mW)",
				mw2dbm(txpwr->value), txpwr->value);
	else
		snprintf(txline, sizeof(txline), "%d dBm (%.2f mW)",
				txpwr->value, dbm2mw(txpwr->value));
	return txline;
}

/* Format driver Power Management information */
static inline char *format_power(const struct iw_param *pwr,
				 const struct iw_range *range)
{
	static char buf[0x80];
	double val = pwr->value;
	int len = 0;

	if (pwr->disabled)
		return "off";
	else if (pwr->flags == IW_POWER_ON)
		return "on";

	if (pwr->flags & IW_POWER_MIN)
		len += snprintf(buf + len, sizeof(buf) - len, "min ");
	if (pwr->flags & IW_POWER_MAX)
		len += snprintf(buf + len, sizeof(buf) - len, "max ");

	if (pwr->flags & IW_POWER_TIMEOUT)
		len += snprintf(buf + len, sizeof(buf) - len, "timeout ");
	else if (pwr->flags & IW_POWER_SAVING)
		len += snprintf(buf + len, sizeof(buf) - len, "saving ");
	else
		len += snprintf(buf + len, sizeof(buf) - len, "period ");

	if (pwr->flags & IW_POWER_RELATIVE && range->we_version_compiled < 21)
		len += snprintf(buf + len, sizeof(buf) - len, "%+g", val/1e6);
	else if (pwr->flags & IW_POWER_RELATIVE)
		len += snprintf(buf + len, sizeof(buf) - len, "%+g", val);
	else if (val > 1e6)
		len += snprintf(buf + len, sizeof(buf) - len, "%g s", val/1e6);
	else if (val > 1e3)
		len += snprintf(buf + len, sizeof(buf) - len, "%g ms", val/1e3);
	else
		len += snprintf(buf + len, sizeof(buf) - len, "%g us", val);

	switch (pwr->flags & IW_POWER_MODE) {
	case IW_POWER_UNICAST_R:
		len += snprintf(buf + len, sizeof(buf) - len, ", rcv unicast");
		break;
	case IW_POWER_MULTICAST_R:
		len += snprintf(buf + len, sizeof(buf) - len, ", rcv mcast");
		break;
	case IW_POWER_ALL_R:
		len += snprintf(buf + len, sizeof(buf) - len, ", rcv all");
		break;
	case IW_POWER_FORCE_S:
		len += snprintf(buf + len, sizeof(buf) - len, ", force send");
		break;
	case IW_POWER_REPEATER:
		len += snprintf(buf + len, sizeof(buf) - len, ", repeat mcast");
	}

	return buf;
}

/* See comments on 'struct iw_freq' in wireless.h */
static inline float freq_to_hz(const struct iw_freq *freq)
{
	return freq->m * pow(10, freq->e);
}

/* Return frequency or 0 on error. Based on iw_channel_to_freq() */
static inline double channel_to_freq(uint8_t chan, const struct iw_range *range)
{
	int c;

	for (c = 0; c < range->num_frequency; c++)
		/* Check if it actually has stored a frequency */
		if (range->freq[c].i == chan && range->freq[c].m > 1000)
			return freq_to_hz(&range->freq[c]);
	return 0.0;
}

/* Return channel number or -1 on error. Based on iw_freq_to_channel() */
static inline int freq_to_channel(double freq, const struct iw_range *range)
{
	int i;

	if (freq < 1.0e3)
		return -1;

	for (i = 0; i < range->num_frequency; i++)
		if (freq_to_hz(&range->freq[i]) == freq)
			return range->freq[i].i;
	return -1;
}

/* print @key in cleartext if it is in ASCII format, use hex format otherwise */
static inline char *format_key(char *key, uint8_t key_len)
{
	static char buf[128];
	int len = 0, i, is_printable;

	for (i = 0, is_printable = 1; i < key_len && is_printable; i++)
		is_printable = isprint(key[i]);

	if (is_printable)
		len += sprintf(buf, "\"");

	for (i = 0; i < key_len; i++)
		if (is_printable) {
			len += sprintf(buf + len, "%c", key[i]);
		} else {
			if (i > 0 && (i & 1) == 0)
				len += sprintf(buf + len, "-");
			len += sprintf(buf + len, "%2X", key[i]);
		}

	if (is_printable)
		sprintf(buf + len, "\"");

	return buf;
}

static inline char *format_retry(const struct iw_param *retry,
				 const struct iw_range *range)
{
	static char buf[0x80];
	double val = retry->value;
	int len = 0;

	if (retry->disabled)
		return "off";
	else if (retry->flags == IW_RETRY_ON)
		return "on";

	if (retry->flags & IW_RETRY_MIN)
		len += snprintf(buf + len, sizeof(buf) - len, "min ");
	if (retry->flags & IW_RETRY_MAX)
		len += snprintf(buf + len, sizeof(buf) - len, "max ");
	if (retry->flags & IW_RETRY_SHORT)
		len += snprintf(buf + len, sizeof(buf) - len, "short ");
	if (retry->flags & IW_RETRY_LONG)
		len += snprintf(buf + len, sizeof(buf) - len, "long ");

	if (retry->flags & IW_RETRY_LIFETIME)
		len += snprintf(buf + len, sizeof(buf) - len, "lifetime ");
	else {
		snprintf(buf + len, sizeof(buf) - len, "limit %d", retry->value);
		return buf;
	}

	if (retry->flags & IW_RETRY_RELATIVE && range->we_version_compiled < 21)
		len += snprintf(buf + len, sizeof(buf) - len, "%+g", val/1e6);
	else if (retry->flags & IW_RETRY_RELATIVE)
		len += snprintf(buf + len, sizeof(buf) - len, "%+g", val);
	else if (val > 1e6)
		len += snprintf(buf + len, sizeof(buf) - len, "%g s", val/1e6);
	else if (val > 1e3)
		len += snprintf(buf + len, sizeof(buf) - len, "%g ms", val/1e3);
	else
		len += snprintf(buf + len, sizeof(buf) - len, "%g us", val);

	return buf;
}

