/***************************************************************************
                          stuff.cpp  -  description
                             -------------------
    begin                : Sun May 6 2001
    copyright            : (C) 2001 by Stefan Winter
    email                : mail@stefan-winter.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program 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 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "interface_wireless_wirelessextensions.h"
#include <iwlib.h>
#include <qdir.h>
#include <qfile.h>
#include <qstringlist.h>

#ifndef WITHOUT_ARTS
#include <arts/artsflow.h>
#include <arts/connect.h>
#include <arts/iomanager.h>
#include <arts/referenceclean.h>
#endif

#include <iostream>
#include <string>
#include <klocale.h>
#include <kprocio.h>
#include <kdebug.h>
#include <qstring.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>


// domi:the wireless-tools people managed to change their interfaces
// around.  Someone with more knowledge of this code should look into
// moving to use the new iw_range interfaces.
#ifdef HAVE_IW_27

#ifndef HAVE_IW_27pre19
#define WIFI_CONFIG(config,field) config.field
#define WIFI_EXTRACT_EVENT_STREAM(stream, event) \
	iw_extract_event_stream(stream, event)
#else
#define WIFI_CONFIG(config,field) config.b.field
#define WIFI_EXTRACT_EVENT_STREAM(stream, event) \
	iw_extract_event_stream(stream, event, WIRELESS_EXT)
#endif

#define WIFI_GET_STATS(skfd, ifname, stats) \
	iw_get_stats (skfd, ifname, stats, 0, 0)
#else
#define WIFI_CONFIG(config,field) config.field
#define WIFI_GET_STATS(skfd, ifname, stats) \
	iw_get_stats (skfd, ifname, stats)
#define WIFI_EXTRACT_EVENT_STREAM(stream, event) \
	iw_extract_event_stream(stream, event)
#endif

/* ================================== FROM IWCONFIG.C ================================== */
int
Interface_wireless_wirelessextensions::get_info (int skfd, const QString& interface,
						 struct wireless_info& info)
{
  struct iwreq wrq;
  char ifname[20];
  snprintf(ifname, sizeof(ifname), "%s", interface.latin1() );
  memset (&info, 0, sizeof(info));

  /* Get wireless name */
  if (iw_get_ext (skfd, ifname, SIOCGIWNAME, &wrq) < 0)
    {
      /* If no wireless name : no wireless extensions */
      /* But let's check if the interface exists at all */
      struct ifreq ifr;

      strcpy (ifr.ifr_name, ifname);
      if (ioctl (skfd, SIOCGIFFLAGS, &ifr) < 0)
	return (-ENODEV);
      else
	return (-ENOTSUP);
    }
  else
    {
      strncpy (WIFI_CONFIG(info,name), wrq.u.name, IFNAMSIZ);
      WIFI_CONFIG(info,name)[IFNAMSIZ] = '\0';
    }

  /* Get ranges */
  if (iw_get_range_info (skfd, ifname, &(info.range)) >= 0)
    info.has_range = 1;

  /* Get network ID */
  if (iw_get_ext (skfd, ifname, SIOCGIWNWID, &wrq) >= 0)
    {
      WIFI_CONFIG(info,has_nwid) = 1;
      memcpy (&(WIFI_CONFIG(info,nwid)), &(wrq.u.nwid), sizeof (iwparam));
    }

  /* Get frequency / channel */
  if (iw_get_ext (skfd, ifname, SIOCGIWFREQ, &wrq) >= 0)
    {
      WIFI_CONFIG(info,has_freq) = 1;
      WIFI_CONFIG(info,freq) = iw_freq2float (&(wrq.u.freq));
    }

  /* Get sensitivity */
  if (iw_get_ext (skfd, ifname, SIOCGIWSENS, &wrq) >= 0)
    {
      info.has_sens = 1;
      memcpy (&(info.sens), &(wrq.u.sens), sizeof (iwparam));
    }

  /* Get encryption information */
  wrq.u.data.pointer = (caddr_t) WIFI_CONFIG(info,key);
  wrq.u.data.length = IW_ENCODING_TOKEN_MAX;
  wrq.u.data.flags = 0;
  if (iw_get_ext (skfd, ifname, SIOCGIWENCODE, &wrq) >= 0)
    {
      WIFI_CONFIG(info,has_key) = 1;
      WIFI_CONFIG(info,key_size) = wrq.u.data.length;
      WIFI_CONFIG(info,key_flags) = wrq.u.data.flags;
    }

  /* Get ESSID */
  wrq.u.essid.pointer = (caddr_t) WIFI_CONFIG(info,essid);
  wrq.u.essid.length = IW_ESSID_MAX_SIZE + 1;
  wrq.u.essid.flags = 0;
  if (iw_get_ext (skfd, ifname, SIOCGIWESSID, &wrq) >= 0)
    {
      WIFI_CONFIG(info,has_essid) = 1;
      WIFI_CONFIG(info,essid_on) = wrq.u.data.flags;
    }

  /* Get AP address */
  if (iw_get_ext (skfd, ifname, SIOCGIWAP, &wrq) >= 0)
    {
      info.has_ap_addr = 1;
      memcpy (&(info.ap_addr), &(wrq.u.ap_addr), sizeof (sockaddr));
    }

  /* Get NickName */
  wrq.u.essid.pointer = (caddr_t) info.nickname;
  wrq.u.essid.length = IW_ESSID_MAX_SIZE + 1;
  wrq.u.essid.flags = 0;
  if (iw_get_ext (skfd, ifname, SIOCGIWNICKN, &wrq) >= 0)
    if (wrq.u.data.length > 1)
      info.has_nickname = 1;

  /* Get bit rate */
  if (iw_get_ext (skfd, ifname, SIOCGIWRATE, &wrq) >= 0)
    {
      info.has_bitrate = 1;
      memcpy (&(info.bitrate), &(wrq.u.bitrate), sizeof (iwparam));
    }

  /* Get RTS threshold */
  if (iw_get_ext (skfd, ifname, SIOCGIWRTS, &wrq) >= 0)
    {
      info.has_rts = 1;
      memcpy (&(info.rts), &(wrq.u.rts), sizeof (iwparam));
    }

  /* Get fragmentation threshold */
  if (iw_get_ext (skfd, ifname, SIOCGIWFRAG, &wrq) >= 0)
    {
      info.has_frag = 1;
      memcpy (&(info.frag), &(wrq.u.frag), sizeof (iwparam));
    }

  /* Get operation mode */
  if (iw_get_ext (skfd, ifname, SIOCGIWMODE, &wrq) >= 0)
    {
      WIFI_CONFIG(info,mode) = wrq.u.mode;
      if ((WIFI_CONFIG(info,mode) < IW_NUM_OPER_MODE) && (WIFI_CONFIG(info,mode) >= 0))
	WIFI_CONFIG(info,has_mode) = 1;
    }

  /* Get Power Management settings */
  wrq.u.power.flags = 0;
  if (iw_get_ext (skfd, ifname, SIOCGIWPOWER, &wrq) >= 0)
    {
      info.has_power = 1;
      memcpy (&(info.power), &(wrq.u.power), sizeof (iwparam));
    }

#if WIRELESS_EXT > 9
  /* Get Transmit Power and check if it is disabled */
  bool emitTXPowerChanged = false;
  if (iw_get_ext (skfd, ifname, SIOCGIWTXPOW, &wrq) >= 0)
    {
      if (txpower_disabled != info.txpower.disabled)
        {
	  emitTXPowerChanged = true;
        }

      info.has_txpower = 1;
      memcpy (&(info.txpower), &(wrq.u.txpower), sizeof (iwparam));
    }
  has_txpower = info.has_txpower;
  txpower_disabled = ( int )info.txpower.disabled;
  if (emitTXPowerChanged)
    {
      emit txPowerChanged();
    }
#endif

#if WIRELESS_EXT > 10
  /* Get retry limit/lifetime */
  if (iw_get_ext (skfd, ifname, SIOCGIWRETRY, &wrq) >= 0)
    {
      info.has_retry = 1;
      memcpy (&(info.retry), &(wrq.u.retry), sizeof (iwparam));
    }
#endif /* WIRELESS_EXT > 10 */

  /* Get stats */
  if (WIFI_GET_STATS (skfd, (char *) ifname, &(info.stats)) >= 0)
    {
      info.has_stats = 1;
    }

  return (0);
}

/* ================================ END IWCONFIG.C ================================ */


Interface_wireless_wirelessextensions::Interface_wireless_wirelessextensions (QStringList * ignoreInterfaces)
		: Interface_wireless(ignoreInterfaces)
{
}

bool Interface_wireless_wirelessextensions::isSocketOpen()
{
  if (socket <= 0)
    socket = iw_sockets_open ();

  return (socket > 0);
}

void Interface_wireless_wirelessextensions::setActiveDevice( QString device )
{
  kdDebug () << "activating wireless device " << device << endl;
  interface_name = device;

  if (!isSocketOpen())
	return;

  emit interfaceChanged ();
  emit strengthChanged ();
  emit statusChanged ();
  emit modeChanged ();
  emit speedChanged ();
  valid[current] = false;
  emit statsUpdated ();
}

void Interface_wireless_wirelessextensions::setNoActiveDevice( )
{
  // interface has disappeared - unplugged?
  // reset all info except stats
  has_frequency = false;
  frequency = 0.0;
  has_mode = false;
  mode = 0;
  has_key = 0;
  key = QString::null;
  key_size = 0;
  key_flags = 0;
  essid = QString::null;
  access_point_address = QString::null;
  ip_address = QString::null;
  bitrate = 0;
  has_range = false;
  has_txpower = false;
  txpower_disabled = 0;

  // propagate the changes
  setActiveDevice( QString::null );
}

QStringList Interface_wireless_wirelessextensions::available_wifi_devices()
{
  QFile procnetdev(PROC_NET_DEV);
  procnetdev.open (IO_ReadOnly);

  kdDebug () << "Autodetecting..." << endl;

  QStringList liste;
  QString device;
  while (!procnetdev.atEnd()) {
	  procnetdev.readLine (device, 9999);
	  int pos = device.find (':');
	  if (pos == -1)
		continue;
	  device = device.left(pos).stripWhiteSpace();
	  if (device.isEmpty())
		continue;
	// Some drivers create two interfaces, ethX and wifiX.  The
	// wifiX device are not useful to users because they only
	// control the physical wlan settings, not the logical ones.  If
	// we find a wifiX device then we move it to the start of the
	// list which means the first instance of KWifiManger will pick
	// up the ethX and only the second instance will pick up the
	// wifiX.
	  if (device.startsWith("wifi"))
		liste.prepend(device);
	  else
		liste.append(device);	
  }


  if (liste.isEmpty())
	kdDebug () << "No wireless interface found." << endl;

  return liste;
}

bool Interface_wireless_wirelessextensions::autodetect_device()
{
  if (!isSocketOpen())
	return false;

  QStringList liste = available_wifi_devices();

  for (QStringList::Iterator it = liste.begin (); it != liste.end (); ++it)
  {
	QString device = *it;
	kdDebug () << "[ " << device << " ] " << endl;
  	wireless_info info;
	int result = get_info (socket, device, info);
	if ((result != -ENODEV) && (result != -ENOTSUP) &&
	    (!ignoreInterfaces || ignoreInterfaces->findIndex(device)==-1))
	{
		setActiveDevice(device);
	}
   }

   if (interface_name.isEmpty())
   {
	close(socket);
	socket = -1;
	return false;
   }

   return true;
}

bool Interface_wireless_wirelessextensions::poll_device_info ()
{
  if (current < MAX_HISTORY-1)
	++current;
    else
	current = 0;

  if (interface_name.isEmpty())
	if (!autodetect_device())
		return false;

  wireless_info	info;
  int result = get_info (socket, interface_name, info);
  if ((result == -ENODEV) || (result == -ENOTSUP))
	{
	  // interface has disappeared - unplugged?
	  // reset all info except stats
	  setNoActiveDevice();

	  close(socket);
	  socket = -1;

	  return false;
	}

  bool
    emitStatusChanged = false, emitModeChanged = false, emitEssidChanged =
    false, emitSpeedChanged = false, emitStrengthChanged = false;

  iwstats	tempic2;
  WIFI_GET_STATS (socket, (char *) interface_name.latin1(), &tempic2);
  has_frequency = WIFI_CONFIG(info,has_freq);
  if (has_frequency)
    {
      if (frequency != WIFI_CONFIG(info,freq))
	emitStatusChanged = true;
      frequency = WIFI_CONFIG(info,freq);
    }
  has_mode = WIFI_CONFIG(info,has_mode);
  if (has_mode)
    {
      if (mode != WIFI_CONFIG(info,mode))
	emitModeChanged = true;
      mode = WIFI_CONFIG(info,mode);
    }
  has_key = WIFI_CONFIG(info,has_key);
  if (has_key)
    {
      if ((key != (char *) WIFI_CONFIG(info,key)) || (key_size != WIFI_CONFIG(info,key_size))
	  || (key_flags != WIFI_CONFIG(info,key_flags)))
	emitStatusChanged = true;
      key = (char *) WIFI_CONFIG(info,key);
      key_size = WIFI_CONFIG(info,key_size);
      key_flags = WIFI_CONFIG(info,key_flags);
    }
  if (essid != WIFI_CONFIG(info,essid))
    {
      emitStatusChanged = true;
      emitEssidChanged = true;
    }
  essid = (WIFI_CONFIG(info,essid_on) ? WIFI_CONFIG(info,essid) : "any");
  char
    ap_addr[256];
  iw_ether_ntop ( (const ether_addr *) info.ap_addr.sa_data, ap_addr);
  if (access_point_address != ap_addr)
    emitStatusChanged = true;
  access_point_address = ap_addr;
  if (bitrate != info.bitrate.value)
    emitSpeedChanged = true;
  bitrate = info.bitrate.value;
  has_range = info.has_range;
  if (has_range)
    range = info.range.max_qual.qual;
  sigLevel[current] = tempic2.qual.level;
  if (tempic2.qual.level < sigLevelMin)
    sigLevelMin = tempic2.qual.level;
  if (tempic2.qual.level > sigLevelMax)
    sigLevelMax = tempic2.qual.level;
  noiseLevel[current] = tempic2.qual.noise;
  if (tempic2.qual.noise < noiseLevelMin)
    noiseLevelMin = tempic2.qual.noise;
  if (tempic2.qual.noise > noiseLevelMax)
    noiseLevelMax = tempic2.qual.noise;
  int
    tempqual = tempic2.qual.qual;
  if (has_range && (range != 0))
    tempqual = tempqual * 100 / range;
  if (      qual[current != 0 ? current - 1 : MAX_HISTORY-1] != tempqual)
    emitStrengthChanged = true;
  if (  sigLevel[current != 0 ? current - 1 : MAX_HISTORY-1] != tempic2.qual.level)
    emitStrengthChanged = true;
  if (noiseLevel[current != 0 ? current - 1 : MAX_HISTORY-1] != tempic2.qual.noise)
    emitStrengthChanged = true;
  qual[current] = (tempqual <= 100 ? tempqual : 100);
  valid[current] = true;

  // try to get our local IP address
  struct sockaddr *
    sa;
  struct sockaddr_in *
    sin;
  struct ifreq
    ifr;
  /* Copy the interface name into the buffer */
  strncpy (ifr.ifr_name, interface_name.latin1 (), IFNAMSIZ);

  if (ioctl (socket, SIOCGIFADDR, &ifr) == -1)
    {
      if (ip_address != "unavailable")
	emitStatusChanged = true;
      ip_address = "unavailable";
    }
  /* Now the buffer will contain the information we requested */
  sa = (struct sockaddr *) &(ifr.ifr_addr);
  if (sa->sa_family == AF_INET)
    {
      sin = (struct sockaddr_in *) sa;
      if (ip_address != (QString) inet_ntoa (sin->sin_addr))
	emitStatusChanged = true;
      ip_address = (QString) inet_ntoa (sin->sin_addr);
    }
  else
    {
      ip_address = "unavailable";
    }
  if (emitStatusChanged)
    emit statusChanged ();
  if (emitStrengthChanged)
    emit strengthChanged ();
  if (emitModeChanged)
    emit modeChanged ();
  if (emitSpeedChanged)
    emit speedChanged ();
  if (emitEssidChanged)
    emit essidChanged (essid);
  emit statsUpdated ();
  return true;
}

QString Interface_wireless_wirelessextensions::print_scanning_token (struct iw_event * event)
{
  QString
    result;
  /* Now, let's decode the event */
  switch (event->cmd)
    {
    case SIOCGIWESSID:
      {
	char
	  essid[IW_ESSID_MAX_SIZE + 1];
	if ((event->u.essid.pointer) && (event->u.essid.length))
	  memcpy (essid, event->u.essid.pointer, event->u.essid.length);
	essid[event->u.essid.length] = '\0';
	if (event->u.essid.flags) {
	  result = essid;
	  }
      }
      break;
    default:
      break;

    }				/* switch(event->cmd) */
  /* May have changed */
  return result;
}


QStringList Interface_wireless_wirelessextensions::get_available_networks ()
{
    QStringList	networks;
    struct iwreq wrq;
    unsigned char buffer[IW_SCAN_MAX_DATA];	/* Results */
    struct timeval tv;				/* Select timeout */
    int	timeout = 5000000;			/* 5s */

    if (interface_name.isEmpty())
	return networks;

    /* Init timeout value -> 250ms */
    tv.tv_sec = 0;
    tv.tv_usec = 250000;

    /*
     * Here we should look at the command line args and set the IW_SCAN_ flags
     * properly
     */
    wrq.u.param.flags = IW_SCAN_DEFAULT;
    wrq.u.param.value = 0;	/* Later */

    /* Initiate Scanning */
    wrq.u.data.pointer = NULL;
    wrq.u.data.flags = 0;
    wrq.u.data.length = 0;
    if (iw_set_ext
	(socket, (char *) interface_name.latin1 (), SIOCSIWSCAN, &wrq) < 0)
      {
	if (errno != EPERM)
	  {
	    kdWarning() << "Interface does not support scanning (errno=" << errno << ")" << endl;
	    return networks;
	  }
	/* If we don't have the permission to initiate the scan, we may
	 * still have permission to read left-over results.
	 * But, don't wait !!! */
	tv.tv_usec = 0;
      }
    timeout -= tv.tv_usec;

    /* Forever */
    while (1)
      {
	fd_set rfds;		/* File descriptors for select */
	int last_fd;		/* Last fd */
	int ret;

	/* Guess what ? We must re-generate rfds each time */
	FD_ZERO (&rfds);
	last_fd = -1;

	/* In here, add the rtnetlink fd in the list */

	/* Wait until something happens */
	// ret = select (last_fd + 1, &rfds, NULL, NULL, &tv);
	ret = sleep(3);

	/* Check if there was an error */
	if (ret > 0)
	  {
	    if (errno == EAGAIN || errno == EINTR)
	      continue;
	    kdWarning() << "Unhandled signal - aborting scan..." << endl;
	    return networks;
	  }

	/* Check if there was a timeout */
	if (ret == 0)
	  {
	    /* Try to read the results */
	    wrq.u.data.pointer = (char *) buffer;
	    wrq.u.data.flags = 0;
	    wrq.u.data.length = sizeof (buffer);
	    if (iw_get_ext
		(socket, (char *) interface_name.latin1 (), SIOCGIWSCAN,
		 &wrq) < 0)
	      {
		/* Check if results not available yet */
		// if (errno == EAGAIN)
		  // {
		    /* Restart timer for only 100ms */
		    //tv.tv_sec = 0;
		    //tv.tv_usec = 100000;
		    //timeout -= tv.tv_usec;
		    //if (timeout > 0)
		    //  continue;	/* Try again later */
		  //}

		/* Bad error */
		kdWarning() << "Failed to read scan data - aborting scan..." << endl;
		return networks;
	      }
	    else
	      /* We have the results, go to process them */
	      break;
	  }

	/* In here, check if event and event type
	 * if scan event, read results. All errors bad & no reset timeout */
      }

    if (wrq.u.data.length)
      {
	struct iw_event
	  iwe;
	struct stream_descr
	  stream;
	int
	  ret;

	iw_init_event_stream (&stream, (char *) buffer, wrq.u.data.length);
	do
	  {
	    /* Extract an event and print it */

	    ret = WIFI_EXTRACT_EVENT_STREAM(&stream, &iwe);

	    /* Currently, we only return the ESSIDs. We could
	       collect further data, but that's something for
	       maybe KDE 4.0 */

	    if (ret > 0 && iwe.cmd == SIOCGIWESSID) {
	      QString tempnet = print_scanning_token (&iwe);
	      if (!tempnet.isEmpty()) 
	        { networks += tempnet; }
		else 
		{ networks += (QString)i18n("(hidden network)"); }
	    }
	  }
	while (ret > 0);
      }

    return networks;
}

#include "interface_wireless_wirelessextensions.moc"
