/*
 * iwinfo - Wireless Information Library - Lua Bindings
 *
 *   Copyright (C) 2009 Jo-Philipp Wich <xm@subsignal.org>
 *
 * The iwinfo library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * The iwinfo library 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 the iwinfo library. If not, see http://www.gnu.org/licenses/.
 */

#include "iwinfo_lualib.h"


/*
 * ISO3166 country labels
 */

struct iwinfo_iso3166_label ISO3166_Names[] = {
	{ 0x3030 /* 00 */, "World" },
	{ 0x4144 /* AD */, "Andorra" },
	{ 0x4145 /* AE */, "United Arab Emirates" },
	{ 0x4146 /* AF */, "Afghanistan" },
	{ 0x4147 /* AG */, "Antigua and Barbuda" },
	{ 0x4149 /* AI */, "Anguilla" },
	{ 0x414C /* AL */, "Albania" },
	{ 0x414D /* AM */, "Armenia" },
	{ 0x414E /* AN */, "Netherlands Antilles" },
	{ 0x414F /* AO */, "Angola" },
	{ 0x4151 /* AQ */, "Antarctica" },
	{ 0x4152 /* AR */, "Argentina" },
	{ 0x4153 /* AS */, "American Samoa" },
	{ 0x4154 /* AT */, "Austria" },
	{ 0x4155 /* AU */, "Australia" },
	{ 0x4157 /* AW */, "Aruba" },
	{ 0x4158 /* AX */, "Aland Islands" },
	{ 0x415A /* AZ */, "Azerbaijan" },
	{ 0x4241 /* BA */, "Bosnia and Herzegovina" },
	{ 0x4242 /* BB */, "Barbados" },
	{ 0x4244 /* BD */, "Bangladesh" },
	{ 0x4245 /* BE */, "Belgium" },
	{ 0x4246 /* BF */, "Burkina Faso" },
	{ 0x4247 /* BG */, "Bulgaria" },
	{ 0x4248 /* BH */, "Bahrain" },
	{ 0x4249 /* BI */, "Burundi" },
	{ 0x424A /* BJ */, "Benin" },
	{ 0x424C /* BL */, "Saint Barthelemy" },
	{ 0x424D /* BM */, "Bermuda" },
	{ 0x424E /* BN */, "Brunei Darussalam" },
	{ 0x424F /* BO */, "Bolivia" },
	{ 0x4252 /* BR */, "Brazil" },
	{ 0x4253 /* BS */, "Bahamas" },
	{ 0x4254 /* BT */, "Bhutan" },
	{ 0x4256 /* BV */, "Bouvet Island" },
	{ 0x4257 /* BW */, "Botswana" },
	{ 0x4259 /* BY */, "Belarus" },
	{ 0x425A /* BZ */, "Belize" },
	{ 0x4341 /* CA */, "Canada" },
	{ 0x4343 /* CC */, "Cocos (Keeling) Islands" },
	{ 0x4344 /* CD */, "Congo" },
	{ 0x4346 /* CF */, "Central African Republic" },
	{ 0x4347 /* CG */, "Congo" },
	{ 0x4348 /* CH */, "Switzerland" },
	{ 0x4349 /* CI */, "Cote d'Ivoire" },
	{ 0x434B /* CK */, "Cook Islands" },
	{ 0x434C /* CL */, "Chile" },
	{ 0x434D /* CM */, "Cameroon" },
	{ 0x434E /* CN */, "China" },
	{ 0x434F /* CO */, "Colombia" },
	{ 0x4352 /* CR */, "Costa Rica" },
	{ 0x4355 /* CU */, "Cuba" },
	{ 0x4356 /* CV */, "Cape Verde" },
	{ 0x4358 /* CX */, "Christmas Island" },
	{ 0x4359 /* CY */, "Cyprus" },
	{ 0x435A /* CZ */, "Czech Republic" },
	{ 0x4445 /* DE */, "Germany" },
	{ 0x444A /* DJ */, "Djibouti" },
	{ 0x444B /* DK */, "Denmark" },
	{ 0x444D /* DM */, "Dominica" },
	{ 0x444F /* DO */, "Dominican Republic" },
	{ 0x445A /* DZ */, "Algeria" },
	{ 0x4543 /* EC */, "Ecuador" },
	{ 0x4545 /* EE */, "Estonia" },
	{ 0x4547 /* EG */, "Egypt" },
	{ 0x4548 /* EH */, "Western Sahara" },
	{ 0x4552 /* ER */, "Eritrea" },
	{ 0x4553 /* ES */, "Spain" },
	{ 0x4554 /* ET */, "Ethiopia" },
	{ 0x4649 /* FI */, "Finland" },
	{ 0x464A /* FJ */, "Fiji" },
	{ 0x464B /* FK */, "Falkland Islands" },
	{ 0x464D /* FM */, "Micronesia" },
	{ 0x464F /* FO */, "Faroe Islands" },
	{ 0x4652 /* FR */, "France" },
	{ 0x4741 /* GA */, "Gabon" },
	{ 0x4742 /* GB */, "United Kingdom" },
	{ 0x4744 /* GD */, "Grenada" },
	{ 0x4745 /* GE */, "Georgia" },
	{ 0x4746 /* GF */, "French Guiana" },
	{ 0x4747 /* GG */, "Guernsey" },
	{ 0x4748 /* GH */, "Ghana" },
	{ 0x4749 /* GI */, "Gibraltar" },
	{ 0x474C /* GL */, "Greenland" },
	{ 0x474D /* GM */, "Gambia" },
	{ 0x474E /* GN */, "Guinea" },
	{ 0x4750 /* GP */, "Guadeloupe" },
	{ 0x4751 /* GQ */, "Equatorial Guinea" },
	{ 0x4752 /* GR */, "Greece" },
	{ 0x4753 /* GS */, "South Georgia" },
	{ 0x4754 /* GT */, "Guatemala" },
	{ 0x4755 /* GU */, "Guam" },
	{ 0x4757 /* GW */, "Guinea-Bissau" },
	{ 0x4759 /* GY */, "Guyana" },
	{ 0x484B /* HK */, "Hong Kong" },
	{ 0x484D /* HM */, "Heard and McDonald Islands" },
	{ 0x484E /* HN */, "Honduras" },
	{ 0x4852 /* HR */, "Croatia" },
	{ 0x4854 /* HT */, "Haiti" },
	{ 0x4855 /* HU */, "Hungary" },
	{ 0x4944 /* ID */, "Indonesia" },
	{ 0x4945 /* IE */, "Ireland" },
	{ 0x494C /* IL */, "Israel" },
	{ 0x494D /* IM */, "Isle of Man" },
	{ 0x494E /* IN */, "India" },
	{ 0x494F /* IO */, "Chagos Islands" },
	{ 0x4951 /* IQ */, "Iraq" },
	{ 0x4952 /* IR */, "Iran" },
	{ 0x4953 /* IS */, "Iceland" },
	{ 0x4954 /* IT */, "Italy" },
	{ 0x4A45 /* JE */, "Jersey" },
	{ 0x4A4D /* JM */, "Jamaica" },
	{ 0x4A4F /* JO */, "Jordan" },
	{ 0x4A50 /* JP */, "Japan" },
	{ 0x4B45 /* KE */, "Kenya" },
	{ 0x4B47 /* KG */, "Kyrgyzstan" },
	{ 0x4B48 /* KH */, "Cambodia" },
	{ 0x4B49 /* KI */, "Kiribati" },
	{ 0x4B4D /* KM */, "Comoros" },
	{ 0x4B4E /* KN */, "Saint Kitts and Nevis" },
	{ 0x4B50 /* KP */, "North Korea" },
	{ 0x4B52 /* KR */, "South Korea" },
	{ 0x4B57 /* KW */, "Kuwait" },
	{ 0x4B59 /* KY */, "Cayman Islands" },
	{ 0x4B5A /* KZ */, "Kazakhstan" },
	{ 0x4C41 /* LA */, "Laos" },
	{ 0x4C42 /* LB */, "Lebanon" },
	{ 0x4C43 /* LC */, "Saint Lucia" },
	{ 0x4C49 /* LI */, "Liechtenstein" },
	{ 0x4C4B /* LK */, "Sri Lanka" },
	{ 0x4C52 /* LR */, "Liberia" },
	{ 0x4C53 /* LS */, "Lesotho" },
	{ 0x4C54 /* LT */, "Lithuania" },
	{ 0x4C55 /* LU */, "Luxembourg" },
	{ 0x4C56 /* LV */, "Latvia" },
	{ 0x4C59 /* LY */, "Libyan Arab Jamahiriya" },
	{ 0x4D41 /* MA */, "Morocco" },
	{ 0x4D43 /* MC */, "Monaco" },
	{ 0x4D44 /* MD */, "Moldova" },
	{ 0x4D45 /* ME */, "Montenegro" },
	{ 0x4D46 /* MF */, "Saint Martin (French part)" },
	{ 0x4D47 /* MG */, "Madagascar" },
	{ 0x4D48 /* MH */, "Marshall Islands" },
	{ 0x4D4B /* MK */, "Macedonia" },
	{ 0x4D4C /* ML */, "Mali" },
	{ 0x4D4D /* MM */, "Myanmar" },
	{ 0x4D4E /* MN */, "Mongolia" },
	{ 0x4D4F /* MO */, "Macao" },
	{ 0x4D50 /* MP */, "Northern Mariana Islands" },
	{ 0x4D51 /* MQ */, "Martinique" },
	{ 0x4D52 /* MR */, "Mauritania" },
	{ 0x4D53 /* MS */, "Montserrat" },
	{ 0x4D54 /* MT */, "Malta" },
	{ 0x4D55 /* MU */, "Mauritius" },
	{ 0x4D56 /* MV */, "Maldives" },
	{ 0x4D57 /* MW */, "Malawi" },
	{ 0x4D58 /* MX */, "Mexico" },
	{ 0x4D59 /* MY */, "Malaysia" },
	{ 0x4D5A /* MZ */, "Mozambique" },
	{ 0x4E41 /* NA */, "Namibia" },
	{ 0x4E43 /* NC */, "New Caledonia" },
	{ 0x4E45 /* NE */, "Niger" },
	{ 0x4E46 /* NF */, "Norfolk Island" },
	{ 0x4E47 /* NG */, "Nigeria" },
	{ 0x4E49 /* NI */, "Nicaragua" },
	{ 0x4E4C /* NL */, "Netherlands" },
	{ 0x4E4F /* NO */, "Norway" },
	{ 0x4E50 /* NP */, "Nepal" },
	{ 0x4E52 /* NR */, "Nauru" },
	{ 0x4E55 /* NU */, "Niue" },
	{ 0x4E5A /* NZ */, "New Zealand" },
	{ 0x4F4D /* OM */, "Oman" },
	{ 0x5041 /* PA */, "Panama" },
	{ 0x5045 /* PE */, "Peru" },
	{ 0x5046 /* PF */, "French Polynesia" },
	{ 0x5047 /* PG */, "Papua New Guinea" },
	{ 0x5048 /* PH */, "Philippines" },
	{ 0x504B /* PK */, "Pakistan" },
	{ 0x504C /* PL */, "Poland" },
	{ 0x504D /* PM */, "Saint Pierre and Miquelon" },
	{ 0x504E /* PN */, "Pitcairn" },
	{ 0x5052 /* PR */, "Puerto Rico" },
	{ 0x5053 /* PS */, "Palestinian Territory" },
	{ 0x5054 /* PT */, "Portugal" },
	{ 0x5057 /* PW */, "Palau" },
	{ 0x5059 /* PY */, "Paraguay" },
	{ 0x5141 /* QA */, "Qatar" },
	{ 0x5245 /* RE */, "Reunion" },
	{ 0x524F /* RO */, "Romania" },
	{ 0x5253 /* RS */, "Serbia" },
	{ 0x5255 /* RU */, "Russian Federation" },
	{ 0x5257 /* RW */, "Rwanda" },
	{ 0x5341 /* SA */, "Saudi Arabia" },
	{ 0x5342 /* SB */, "Solomon Islands" },
	{ 0x5343 /* SC */, "Seychelles" },
	{ 0x5344 /* SD */, "Sudan" },
	{ 0x5345 /* SE */, "Sweden" },
	{ 0x5347 /* SG */, "Singapore" },
	{ 0x5348 /* SH */, "St. Helena and Dependencies" },
	{ 0x5349 /* SI */, "Slovenia" },
	{ 0x534A /* SJ */, "Svalbard and Jan Mayen" },
	{ 0x534B /* SK */, "Slovakia" },
	{ 0x534C /* SL */, "Sierra Leone" },
	{ 0x534D /* SM */, "San Marino" },
	{ 0x534E /* SN */, "Senegal" },
	{ 0x534F /* SO */, "Somalia" },
	{ 0x5352 /* SR */, "Suriname" },
	{ 0x5354 /* ST */, "Sao Tome and Principe" },
	{ 0x5356 /* SV */, "El Salvador" },
	{ 0x5359 /* SY */, "Syrian Arab Republic" },
	{ 0x535A /* SZ */, "Swaziland" },
	{ 0x5443 /* TC */, "Turks and Caicos Islands" },
	{ 0x5444 /* TD */, "Chad" },
	{ 0x5446 /* TF */, "French Southern Territories" },
	{ 0x5447 /* TG */, "Togo" },
	{ 0x5448 /* TH */, "Thailand" },
	{ 0x544A /* TJ */, "Tajikistan" },
	{ 0x544B /* TK */, "Tokelau" },
	{ 0x544C /* TL */, "Timor-Leste" },
	{ 0x544D /* TM */, "Turkmenistan" },
	{ 0x544E /* TN */, "Tunisia" },
	{ 0x544F /* TO */, "Tonga" },
	{ 0x5452 /* TR */, "Turkey" },
	{ 0x5454 /* TT */, "Trinidad and Tobago" },
	{ 0x5456 /* TV */, "Tuvalu" },
	{ 0x5457 /* TW */, "Taiwan" },
	{ 0x545A /* TZ */, "Tanzania" },
	{ 0x5541 /* UA */, "Ukraine" },
	{ 0x5547 /* UG */, "Uganda" },
	{ 0x554D /* UM */, "U.S. Minor Outlying Islands" },
	{ 0x5553 /* US */, "United States" },
	{ 0x5559 /* UY */, "Uruguay" },
	{ 0x555A /* UZ */, "Uzbekistan" },
	{ 0x5641 /* VA */, "Vatican City State" },
	{ 0x5643 /* VC */, "St. Vincent and Grenadines" },
	{ 0x5645 /* VE */, "Venezuela" },
	{ 0x5647 /* VG */, "Virgin Islands, British" },
	{ 0x5649 /* VI */, "Virgin Islands, U.S." },
	{ 0x564E /* VN */, "Viet Nam" },
	{ 0x5655 /* VU */, "Vanuatu" },
	{ 0x5746 /* WF */, "Wallis and Futuna" },
	{ 0x5753 /* WS */, "Samoa" },
	{ 0x5945 /* YE */, "Yemen" },
	{ 0x5954 /* YT */, "Mayotte" },
	{ 0x5A41 /* ZA */, "South Africa" },
	{ 0x5A4D /* ZM */, "Zambia" },
	{ 0x5A57 /* ZW */, "Zimbabwe" },
	{ 0,               "" }
};



/* Determine type */
static int iwinfo_L_type(lua_State *L)
{
	const char *ifname = luaL_checkstring(L, 1);

#ifdef USE_NL80211
	if( nl80211_probe(ifname) )
		lua_pushstring(L, "nl80211");
	else
#endif

#ifdef USE_MADWIFI
	if( madwifi_probe(ifname) )
		lua_pushstring(L, "madwifi");
	else
#endif

#ifdef USE_WL
	if( wl_probe(ifname) )
		lua_pushstring(L, "wl");
	else
#endif

	if( wext_probe(ifname) )
		lua_pushstring(L, "wext");

	else
		lua_pushnil(L);

	return 1;
}

/* Shutdown backends */
static int iwinfo_L__gc(lua_State *L)
{
#ifdef USE_WL
	wl_close();
#endif
#ifdef USE_MADWIFI
	madwifi_close();
#endif
#ifdef USE_NL80211
	nl80211_close();
#endif
	wext_close();
	iwinfo_close();
}

/*
 * Build a short textual description of the crypto info
 */

static char * iwinfo_crypto_print_ciphers(int ciphers)
{
	static char str[128] = { 0 };
	char *pos = str;

	if( ciphers & IWINFO_CIPHER_WEP40 )
		pos += sprintf(pos, "WEP-40, ");

	if( ciphers & IWINFO_CIPHER_WEP104 )
		pos += sprintf(pos, "WEP-104, ");

	if( ciphers & IWINFO_CIPHER_TKIP )
		pos += sprintf(pos, "TKIP, ");

	if( ciphers & IWINFO_CIPHER_CCMP )
		pos += sprintf(pos, "CCMP, ");

	if( ciphers & IWINFO_CIPHER_WRAP )
		pos += sprintf(pos, "WRAP, ");

	if( ciphers & IWINFO_CIPHER_AESOCB )
		pos += sprintf(pos, "AES-OCB, ");

	if( ciphers & IWINFO_CIPHER_CKIP )
		pos += sprintf(pos, "CKIP, ");

	if( !ciphers || (ciphers & IWINFO_CIPHER_NONE) )
		pos += sprintf(pos, "NONE, ");

	*(pos - 2) = 0;

	return str;
}

static char * iwinfo_crypto_print_suites(int suites)
{
	static char str[64] = { 0 };
	char *pos = str;

	if( suites & IWINFO_KMGMT_PSK )
		pos += sprintf(pos, "PSK/");

	if( suites & IWINFO_KMGMT_8021x )
		pos += sprintf(pos, "802.1X/");

	if( !suites || (suites & IWINFO_KMGMT_NONE) )
		pos += sprintf(pos, "NONE/");

	*(pos - 1) = 0;

	return str;
}

static char * iwinfo_crypto_desc(struct iwinfo_crypto_entry *c)
{
	static char desc[512] = { 0 };

	if( c )
	{
		if( c->enabled )
		{
			/* WEP */
			if( c->auth_algs && !c->wpa_version )
			{
				if( (c->auth_algs & IWINFO_AUTH_OPEN) &&
				    (c->auth_algs & IWINFO_AUTH_SHARED) )
				{
					sprintf(desc, "WEP Open/Shared (%s)",
						iwinfo_crypto_print_ciphers(c->pair_ciphers));
				}
				else if( c->auth_algs & IWINFO_AUTH_OPEN )
				{
					sprintf(desc, "WEP Open System (%s)",
						iwinfo_crypto_print_ciphers(c->pair_ciphers));
				}
				else if( c->auth_algs & IWINFO_AUTH_SHARED )
				{
					sprintf(desc, "WEP Shared Auth (%s)",
						iwinfo_crypto_print_ciphers(c->pair_ciphers));
				}
			}

			/* WPA */
			else if( c->wpa_version )
			{
				switch(c->wpa_version) {
					case 3:
						sprintf(desc, "mixed WPA/WPA2 %s (%s)",
							iwinfo_crypto_print_suites(c->auth_suites),
							iwinfo_crypto_print_ciphers(
								c->pair_ciphers & c->group_ciphers));
						break;

					case 2:
						sprintf(desc, "WPA2 %s (%s)",
							iwinfo_crypto_print_suites(c->auth_suites),
							iwinfo_crypto_print_ciphers(
								c->pair_ciphers & c->group_ciphers));
						break;

					case 1:
						sprintf(desc, "WPA %s (%s)",
							iwinfo_crypto_print_suites(c->auth_suites),
							iwinfo_crypto_print_ciphers(
								c->pair_ciphers & c->group_ciphers));
						break;
				}
			}
			else
			{
				sprintf(desc, "None");
			}
		}
		else
		{
			sprintf(desc, "None");
		}
	}
	else
	{
		sprintf(desc, "Unknown");
	}

	return desc;
}

/* Build Lua table from crypto data */
static void iwinfo_L_cryptotable(lua_State *L, struct iwinfo_crypto_entry *c)
{
	int i, j;

	lua_newtable(L);

	lua_pushboolean(L, c->enabled);
	lua_setfield(L, -2, "enabled");

	lua_pushstring(L, iwinfo_crypto_desc(c));
	lua_setfield(L, -2, "description");

	lua_pushboolean(L, (c->enabled && !c->wpa_version));
	lua_setfield(L, -2, "wep");

	lua_pushinteger(L, c->wpa_version);
	lua_setfield(L, -2, "wpa");

	lua_newtable(L);
	for( i = 0, j = 1; i < 8; i++ )
	{
		if( c->pair_ciphers & (1 << i) )
		{
			lua_pushstring(L, IWINFO_CIPHER_NAMES[i]);
			lua_rawseti(L, -2, j++);
		}
	}
	lua_setfield(L, -2, "pair_ciphers");

	lua_newtable(L);
	for( i = 0, j = 1; i < 8; i++ )
	{
		if( c->group_ciphers & (1 << i) )
		{
			lua_pushstring(L, IWINFO_CIPHER_NAMES[i]);
			lua_rawseti(L, -2, j++);
		}
	}
	lua_setfield(L, -2, "group_ciphers");

	lua_newtable(L);
	for( i = 0, j = 1; i < 8; i++ )
	{
		if( c->auth_suites & (1 << i) )
		{
			lua_pushstring(L, IWINFO_KMGMT_NAMES[i]);
			lua_rawseti(L, -2, j++);
		}
	}
	lua_setfield(L, -2, "auth_suites");

	lua_newtable(L);
	for( i = 0, j = 1; i < 8; i++ )
	{
		if( c->auth_algs & (1 << i) )
		{
			lua_pushstring(L, IWINFO_AUTH_NAMES[i]);
			lua_rawseti(L, -2, j++);
		}
	}
	lua_setfield(L, -2, "auth_algs");
}


/* Wrapper for assoclist */
static int iwinfo_L_assoclist(lua_State *L, int (*func)(const char *, char *, int *))
{
	int i, len;
	char rv[IWINFO_BUFSIZE];
	char macstr[18];
	const char *ifname = luaL_checkstring(L, 1);
	struct iwinfo_assoclist_entry *e;

	lua_newtable(L);
	memset(rv, 0, sizeof(rv));

	if( !(*func)(ifname, rv, &len) )
	{
		for( i = 0; i < len; i += sizeof(struct iwinfo_assoclist_entry) )
		{
			e = (struct iwinfo_assoclist_entry *) &rv[i];

			sprintf(macstr, "%02X:%02X:%02X:%02X:%02X:%02X",
				e->mac[0], e->mac[1], e->mac[2],
				e->mac[3], e->mac[4], e->mac[5]);

			lua_newtable(L);

			lua_pushnumber(L, e->signal);
			lua_setfield(L, -2, "signal");

			lua_pushnumber(L, e->noise);
			lua_setfield(L, -2, "noise");

			lua_setfield(L, -2, macstr);
		}
	}

	return 1;
}

/* Wrapper for tx power list */
static int iwinfo_L_txpwrlist(lua_State *L, int (*func)(const char *, char *, int *))
{
	int i, x, len;
	char rv[IWINFO_BUFSIZE];
	const char *ifname = luaL_checkstring(L, 1);
	struct iwinfo_txpwrlist_entry *e;

	lua_newtable(L);
	memset(rv, 0, sizeof(rv));

	if( !(*func)(ifname, rv, &len) )
	{
		for( i = 0, x = 1; i < len; i += sizeof(struct iwinfo_txpwrlist_entry), x++ )
		{
			e = (struct iwinfo_txpwrlist_entry *) &rv[i];

			lua_newtable(L);

			lua_pushnumber(L, e->mw);
			lua_setfield(L, -2, "mw");

			lua_pushnumber(L, e->dbm);
			lua_setfield(L, -2, "dbm");

			lua_rawseti(L, -2, x);
		}
	}

	return 1;
}

/* Wrapper for scan list */
static int iwinfo_L_scanlist(lua_State *L, int (*func)(const char *, char *, int *))
{
	int i, x, len;
	char rv[IWINFO_BUFSIZE];
	char macstr[18];
	const char *ifname = luaL_checkstring(L, 1);
	struct iwinfo_scanlist_entry *e;

	lua_newtable(L);
	memset(rv, 0, sizeof(rv));

	if( !(*func)(ifname, rv, &len) )
	{
		for( i = 0, x = 1; i < len; i += sizeof(struct iwinfo_scanlist_entry), x++ )
		{
			e = (struct iwinfo_scanlist_entry *) &rv[i];

			lua_newtable(L);

			/* BSSID */
			sprintf(macstr, "%02X:%02X:%02X:%02X:%02X:%02X",
				e->mac[0], e->mac[1], e->mac[2],
				e->mac[3], e->mac[4], e->mac[5]);

			lua_pushstring(L, macstr);
			lua_setfield(L, -2, "bssid");

			/* ESSID */
			if( e->ssid[0] )
			{
				lua_pushstring(L, (char *) e->ssid);
				lua_setfield(L, -2, "ssid");
			}

			/* Channel */
			lua_pushinteger(L, e->channel);
			lua_setfield(L, -2, "channel");

			/* Mode */
			lua_pushstring(L, (char *) e->mode);
			lua_setfield(L, -2, "mode");

			/* Quality, Signal */
			lua_pushinteger(L, e->quality);
			lua_setfield(L, -2, "quality");

			lua_pushinteger(L, e->quality_max);
			lua_setfield(L, -2, "quality_max");

			lua_pushnumber(L, (e->signal - 0x100));
			lua_setfield(L, -2, "signal");

			/* Crypto */
			iwinfo_L_cryptotable(L, &e->crypto);
			lua_setfield(L, -2, "encryption");

			lua_rawseti(L, -2, x);
		}
	}

	return 1;
}

/* Wrapper for frequency list */
static int iwinfo_L_freqlist(lua_State *L, int (*func)(const char *, char *, int *))
{
	int i, x, len;
	char rv[IWINFO_BUFSIZE];
	const char *ifname = luaL_checkstring(L, 1);
	struct iwinfo_freqlist_entry *e;

	lua_newtable(L);
	memset(rv, 0, sizeof(rv));

	if( !(*func)(ifname, rv, &len) )
	{
		for( i = 0, x = 1; i < len; i += sizeof(struct iwinfo_freqlist_entry), x++ )
		{
			e = (struct iwinfo_freqlist_entry *) &rv[i];

			lua_newtable(L);

			/* MHz */
			lua_pushinteger(L, e->mhz);
			lua_setfield(L, -2, "mhz");

			/* Channel */
			lua_pushinteger(L, e->channel);
			lua_setfield(L, -2, "channel");

			/* Restricted (DFS/TPC/Radar) */
			lua_pushboolean(L, e->restricted);
			lua_setfield(L, -2, "restricted");

			lua_rawseti(L, -2, x);
		}
	}

	return 1;
}

/* Wrapper for crypto settings */
static int iwinfo_L_encryption(lua_State *L, int (*func)(const char *, char *))
{
	const char *ifname = luaL_checkstring(L, 1);
	struct iwinfo_crypto_entry c = { 0 };

	if( !(*func)(ifname, (char *)&c) )
	{
		iwinfo_L_cryptotable(L, &c);
		return 1;
	}

	lua_pushnil(L);
	return 1;
}

/* Wrapper for hwmode list */
static int iwinfo_L_hwmodelist(lua_State *L, int (*func)(const char *, int *))
{
	const char *ifname = luaL_checkstring(L, 1);
	int hwmodes = 0;

	if( !(*func)(ifname, &hwmodes) )
	{
		lua_newtable(L);

		lua_pushboolean(L, hwmodes & IWINFO_80211_A);
		lua_setfield(L, -2, "a");

		lua_pushboolean(L, hwmodes & IWINFO_80211_B);
		lua_setfield(L, -2, "b");

		lua_pushboolean(L, hwmodes & IWINFO_80211_G);
		lua_setfield(L, -2, "g");

		lua_pushboolean(L, hwmodes & IWINFO_80211_N);
		lua_setfield(L, -2, "n");

		return 1;
	}

	lua_pushnil(L);
	return 1;
}

/* Wrapper for mbbsid_support */
static int iwinfo_L_mbssid_support(lua_State *L, int (*func)(const char *, int *))
{
	const char *ifname = luaL_checkstring(L, 1);
	int support = 0;

	if( !(*func)(ifname, &support) )
	{
		lua_pushboolean(L, support);
		return 1;
	}

	lua_pushnil(L);
	return 1;
}

/* Wrapper for country list */
static char * iwinfo_L_country_lookup(char *buf, int len, int iso3166)
{
	int i;
	struct iwinfo_country_entry *c;

	for( i = 0; i < len; i += sizeof(struct iwinfo_country_entry) )
	{
		c = (struct iwinfo_country_entry *) &buf[i];

		if( c->iso3166 == iso3166 )
			return c->ccode;
	}

	return NULL;
}

static int iwinfo_L_countrylist(lua_State *L, int (*func)(const char *, char *, int *))
{
	int len, i, j;
	char rv[IWINFO_BUFSIZE], alpha2[3];
	char *ccode;
	const char *ifname = luaL_checkstring(L, 1);
	struct iwinfo_iso3166_label *l;

	lua_newtable(L);
	memset(rv, 0, sizeof(rv));

	if( !(*func)(ifname, rv, &len) )
	{
		for( l = ISO3166_Names, j = 1; l->iso3166; l++ )
		{
			if( (ccode = iwinfo_L_country_lookup(rv, len, l->iso3166)) != NULL )
			{
				sprintf(alpha2, "%c%c",
					(l->iso3166 / 256), (l->iso3166 % 256));

				lua_newtable(L);

				lua_pushstring(L, alpha2);
				lua_setfield(L, -2, "alpha2");

				lua_pushstring(L, ccode);
				lua_setfield(L, -2, "ccode");

				lua_pushstring(L, l->name);
				lua_setfield(L, -2, "name");

				lua_rawseti(L, -2, j++);
			}
		}
	}

	return 1;
}


#ifdef USE_WL
/* Broadcom */
LUA_WRAP_INT(wl,channel)
LUA_WRAP_INT(wl,frequency)
LUA_WRAP_INT(wl,txpower)
LUA_WRAP_INT(wl,bitrate)
LUA_WRAP_INT(wl,signal)
LUA_WRAP_INT(wl,noise)
LUA_WRAP_INT(wl,quality)
LUA_WRAP_INT(wl,quality_max)
LUA_WRAP_STRING(wl,mode)
LUA_WRAP_STRING(wl,ssid)
LUA_WRAP_STRING(wl,bssid)
LUA_WRAP_STRING(wl,country)
LUA_WRAP_LIST(wl,assoclist)
LUA_WRAP_LIST(wl,txpwrlist)
LUA_WRAP_LIST(wl,scanlist)
LUA_WRAP_LIST(wl,freqlist)
LUA_WRAP_LIST(wl,countrylist)
LUA_WRAP_LIST(wl,hwmodelist)
LUA_WRAP_LIST(wl,encryption)
LUA_WRAP_LIST(wl,mbssid_support)
#endif

#ifdef USE_MADWIFI
/* Madwifi */
LUA_WRAP_INT(madwifi,channel)
LUA_WRAP_INT(madwifi,frequency)
LUA_WRAP_INT(madwifi,txpower)
LUA_WRAP_INT(madwifi,bitrate)
LUA_WRAP_INT(madwifi,signal)
LUA_WRAP_INT(madwifi,noise)
LUA_WRAP_INT(madwifi,quality)
LUA_WRAP_INT(madwifi,quality_max)
LUA_WRAP_STRING(madwifi,mode)
LUA_WRAP_STRING(madwifi,ssid)
LUA_WRAP_STRING(madwifi,bssid)
LUA_WRAP_STRING(madwifi,country)
LUA_WRAP_LIST(madwifi,assoclist)
LUA_WRAP_LIST(madwifi,txpwrlist)
LUA_WRAP_LIST(madwifi,scanlist)
LUA_WRAP_LIST(madwifi,freqlist)
LUA_WRAP_LIST(madwifi,countrylist)
LUA_WRAP_LIST(madwifi,hwmodelist)
LUA_WRAP_LIST(madwifi,encryption)
LUA_WRAP_LIST(madwifi,mbssid_support)
#endif

#ifdef USE_NL80211
/* NL80211 */
LUA_WRAP_INT(nl80211,channel)
LUA_WRAP_INT(nl80211,frequency)
LUA_WRAP_INT(nl80211,txpower)
LUA_WRAP_INT(nl80211,bitrate)
LUA_WRAP_INT(nl80211,signal)
LUA_WRAP_INT(nl80211,noise)
LUA_WRAP_INT(nl80211,quality)
LUA_WRAP_INT(nl80211,quality_max)
LUA_WRAP_STRING(nl80211,mode)
LUA_WRAP_STRING(nl80211,ssid)
LUA_WRAP_STRING(nl80211,bssid)
LUA_WRAP_STRING(nl80211,country)
LUA_WRAP_LIST(nl80211,assoclist)
LUA_WRAP_LIST(nl80211,txpwrlist)
LUA_WRAP_LIST(nl80211,scanlist)
LUA_WRAP_LIST(nl80211,freqlist)
LUA_WRAP_LIST(nl80211,countrylist)
LUA_WRAP_LIST(nl80211,hwmodelist)
LUA_WRAP_LIST(nl80211,encryption)
LUA_WRAP_LIST(nl80211,mbssid_support)
#endif

/* Wext */
LUA_WRAP_INT(wext,channel)
LUA_WRAP_INT(wext,frequency)
LUA_WRAP_INT(wext,txpower)
LUA_WRAP_INT(wext,bitrate)
LUA_WRAP_INT(wext,signal)
LUA_WRAP_INT(wext,noise)
LUA_WRAP_INT(wext,quality)
LUA_WRAP_INT(wext,quality_max)
LUA_WRAP_STRING(wext,mode)
LUA_WRAP_STRING(wext,ssid)
LUA_WRAP_STRING(wext,bssid)
LUA_WRAP_STRING(wext,country)
LUA_WRAP_LIST(wext,assoclist)
LUA_WRAP_LIST(wext,txpwrlist)
LUA_WRAP_LIST(wext,scanlist)
LUA_WRAP_LIST(wext,freqlist)
LUA_WRAP_LIST(wext,countrylist)
LUA_WRAP_LIST(wext,hwmodelist)
LUA_WRAP_LIST(wext,encryption)
LUA_WRAP_LIST(wext,mbssid_support)

#ifdef USE_WL
/* Broadcom table */
static const luaL_reg R_wl[] = {
	LUA_REG(wl,channel),
	LUA_REG(wl,frequency),
	LUA_REG(wl,txpower),
	LUA_REG(wl,bitrate),
	LUA_REG(wl,signal),
	LUA_REG(wl,noise),
	LUA_REG(wl,quality),
	LUA_REG(wl,quality_max),
	LUA_REG(wl,mode),
	LUA_REG(wl,ssid),
	LUA_REG(wl,bssid),
	LUA_REG(wl,country),
	LUA_REG(wl,assoclist),
	LUA_REG(wl,txpwrlist),
	LUA_REG(wl,scanlist),
	LUA_REG(wl,freqlist),
	LUA_REG(wl,countrylist),
	LUA_REG(wl,hwmodelist),
	LUA_REG(wl,encryption),
	LUA_REG(wl,mbssid_support),
	{ NULL, NULL }
};
#endif

#ifdef USE_MADWIFI
/* Madwifi table */
static const luaL_reg R_madwifi[] = {
	LUA_REG(madwifi,channel),
	LUA_REG(madwifi,frequency),
	LUA_REG(madwifi,txpower),
	LUA_REG(madwifi,bitrate),
	LUA_REG(madwifi,signal),
	LUA_REG(madwifi,noise),
	LUA_REG(madwifi,quality),
	LUA_REG(madwifi,quality_max),
	LUA_REG(madwifi,mode),
	LUA_REG(madwifi,ssid),
	LUA_REG(madwifi,bssid),
	LUA_REG(madwifi,country),
	LUA_REG(madwifi,assoclist),
	LUA_REG(madwifi,txpwrlist),
	LUA_REG(madwifi,scanlist),
	LUA_REG(madwifi,freqlist),
	LUA_REG(madwifi,countrylist),
	LUA_REG(madwifi,hwmodelist),
	LUA_REG(madwifi,encryption),
	LUA_REG(madwifi,mbssid_support),
	{ NULL, NULL }
};
#endif

#ifdef USE_NL80211
/* NL80211 table */
static const luaL_reg R_nl80211[] = {
	LUA_REG(nl80211,channel),
	LUA_REG(nl80211,frequency),
	LUA_REG(nl80211,txpower),
	LUA_REG(nl80211,bitrate),
	LUA_REG(nl80211,signal),
	LUA_REG(nl80211,noise),
	LUA_REG(nl80211,quality),
	LUA_REG(nl80211,quality_max),
	LUA_REG(nl80211,mode),
	LUA_REG(nl80211,ssid),
	LUA_REG(nl80211,bssid),
	LUA_REG(nl80211,country),
	LUA_REG(nl80211,assoclist),
	LUA_REG(nl80211,txpwrlist),
	LUA_REG(nl80211,scanlist),
	LUA_REG(nl80211,freqlist),
	LUA_REG(nl80211,countrylist),
	LUA_REG(nl80211,hwmodelist),
	LUA_REG(nl80211,encryption),
	LUA_REG(nl80211,mbssid_support),
	{ NULL, NULL }
};
#endif

/* Wext table */
static const luaL_reg R_wext[] = {
	LUA_REG(wext,channel),
	LUA_REG(wext,frequency),
	LUA_REG(wext,txpower),
	LUA_REG(wext,bitrate),
	LUA_REG(wext,signal),
	LUA_REG(wext,noise),
	LUA_REG(wext,quality),
	LUA_REG(wext,quality_max),
	LUA_REG(wext,mode),
	LUA_REG(wext,ssid),
	LUA_REG(wext,bssid),
	LUA_REG(wext,country),
	LUA_REG(wext,assoclist),
	LUA_REG(wext,txpwrlist),
	LUA_REG(wext,scanlist),
	LUA_REG(wext,freqlist),
	LUA_REG(wext,countrylist),
	LUA_REG(wext,hwmodelist),
	LUA_REG(wext,encryption),
	LUA_REG(wext,mbssid_support),
	{ NULL, NULL }
};

/* Common */
static const luaL_reg R_common[] = {
	{ "type", iwinfo_L_type },
	{ "__gc", iwinfo_L__gc  },
	{ NULL, NULL }
};


LUALIB_API int luaopen_iwinfo(lua_State *L) {
	luaL_register(L, IWINFO_META, R_common);

#ifdef USE_WL
	luaL_newmetatable(L, IWINFO_WL_META);
	luaL_register(L, NULL, R_wl);
	lua_pushvalue(L, -1);
	lua_setfield(L, -2, "__index");
	lua_setfield(L, -2, "wl");
#endif

#ifdef USE_MADWIFI
	luaL_newmetatable(L, IWINFO_MADWIFI_META);
	luaL_register(L, NULL, R_madwifi);
	lua_pushvalue(L, -1);
	lua_setfield(L, -2, "__index");
	lua_setfield(L, -2, "madwifi");
#endif

#ifdef USE_NL80211
	luaL_newmetatable(L, IWINFO_NL80211_META);
	luaL_register(L, NULL, R_nl80211);
	lua_pushvalue(L, -1);
	lua_setfield(L, -2, "__index");
	lua_setfield(L, -2, "nl80211");
#endif

	luaL_newmetatable(L, IWINFO_WEXT_META);
	luaL_register(L, NULL, R_wext);
	lua_pushvalue(L, -1);
	lua_setfield(L, -2, "__index");
	lua_setfield(L, -2, "wext");

	return 1;
}