/* $Id: wi_linux.c,v 1.3 2004/02/12 22:43:46 benny Exp $ */
/*-
 * Copyright (c) 2003,2004 Benedikt Meurer <benny@xfce.org>
 * Copyright (c) 2004 An-Cheng Huang <pach@cs.cmu.edu>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 */

#if defined(__linux__)

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <libxfce4util/libxfce4util.h>

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>

/* On newer linux headers these need to be
 * included first. It is probably a losing
 * battle though.
 */
#include <linux/types.h>
#include <linux/if.h>

/* Require wireless extensions */
#include <linux/wireless.h> 

#include <wi.h>

struct wi_device
{
  char interface[WI_MAXSTRLEN];
  int socket;
};

struct wi_device *
wi_open(const char *interface)
{
  struct wi_device *device;
  int sock;

  g_return_val_if_fail(interface != NULL, NULL);

  if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
    TRACE ("Failed to open socket, %d", sock);
    return(NULL);
  }

  TRACE ("Socket Open for interface %s, %d",interface,sock);
  
  device = g_new0(struct wi_device, 1);
  device->socket = sock;
  g_strlcpy(device->interface, interface, WI_MAXSTRLEN);

  return(device);
}

static void
close(int fd)
{
  shutdown(fd, SHUT_RDWR);
}

void
wi_close(struct wi_device *device)
{
  g_return_if_fail(device != NULL);

  close(device->socket);
  g_free(device);
}

static double
wi_get_max_quality(struct wi_device *device)
{
  struct iwreq wreq;
  double max_qual = 92.0;
  char range_buf[sizeof(struct iw_range) * 2]; // wireless tools says it is
                                              // large enough.
  int result;

  /* Set interface name */
  strncpy(wreq.ifr_name, device->interface, IFNAMSIZ);

  memset(range_buf, 0, sizeof(range_buf));

  wreq.u.data.pointer = (caddr_t) range_buf;
  wreq.u.data.length = sizeof(range_buf);
  wreq.u.data.flags = 0;
  if ((result = ioctl(device->socket, SIOCGIWRANGE, &wreq)) < 0) {
    TRACE ("Couldn't get range information, taking default.");
  } else {
    struct iw_range *range = (struct iw_range *) range_buf;
    max_qual = range->max_qual.qual;
    if (max_qual <= 0) {
      TRACE ("Got a negative value for max_qual, returning to default.");
      max_qual = 92.0;
    }
  }

  return max_qual;
}

int
wi_query(struct wi_device *device, struct wi_stats *stats)
{
#if WIRELESS_EXT <= 11
  char buffer[1024];
  char *bp;
  FILE *fp;
#endif
  double link;
  long level;
  int result;
  double max_qual = 92.0;

  struct iwreq wreq;
  struct iw_statistics wstats;
  char essid[IW_ESSID_MAX_SIZE + 1];

  g_return_val_if_fail(device != NULL, WI_INVAL);
  g_return_val_if_fail(stats != NULL, WI_INVAL);

  /* FIXME */
  g_strlcpy(stats->ws_vendor, "Unknown", WI_MAXSTRLEN);

  /* Set interface name */
  strncpy(wreq.ifr_name, device->interface, IFNAMSIZ);

  /* Get ESSID */
  wreq.u.essid.pointer = (caddr_t) essid;
  wreq.u.essid.length = IW_ESSID_MAX_SIZE + 1;
  wreq.u.essid.flags = 0;
  if ((result = ioctl(device->socket, SIOCGIWESSID, &wreq) < 0)) {
    TRACE ("Couldn't get ESSID");
    g_strlcpy(stats->ws_netname, "", WI_MAXSTRLEN);
  } else {
    /* ESSID is possibly NOT null terminated but we know its length */
    essid[wreq.u.essid.length] = 0;
    TRACE ("ESSID is %s", essid);
    g_strlcpy(stats->ws_netname, essid, WI_MAXSTRLEN);
  }

  /* Get bit rate */
  if ((result = ioctl(device->socket, SIOCGIWRATE, &wreq) < 0)) {
    TRACE ("Couldn't get bit-rate");
    stats->ws_rate = 0;
  } else {
    TRACE ("Bit-rate is %d", wreq.u.bitrate.value);
    stats->ws_rate = wreq.u.bitrate.value;
  }

#if WIRELESS_EXT > 11
  /* Get interface stats through ioctl */
  wreq.u.data.pointer = (caddr_t) &wstats;
  wreq.u.data.length = sizeof(struct iw_statistics);
  wreq.u.data.flags = 1;
  if ((result = ioctl(device->socket, SIOCGIWSTATS, &wreq)) < 0) {
    TRACE ("Returning NOSUCHDEV, got %d for socket %d", result, device->socket);
    return(WI_NOSUCHDEV);
  }
  level = wstats.qual.level;
  link = wstats.qual.qual;

  max_qual = wi_get_max_quality(device);

#else /* WIRELESS_EXT <= 11 */
  /* Get interface stats through /proc/net/wireless */
  if ((fp = fopen("/proc/net/wireless", "r")) == NULL) {
    return(WI_NOSUCHDEV);
  }

  for (;;) {
    if (fgets(buffer, sizeof(buffer), fp) == NULL)
      return(WI_NOSUCHDEV);

    if (buffer[6] == ':') {
      buffer[6] = '\0';
      for (bp = buffer; isspace(*bp); ++bp);

      if (strcmp(bp, device->interface) != 0)
        continue;

      /* we found our device, read the stats now */
      bp = buffer + 12;
      link = strtod(bp, &bp);
      level = strtol(bp + 1, &bp, 10);
      break;
    }
  }
  fclose(fp);
#endif

  /* check if we have a carrier signal */
  /* FIXME: does 0 mean no carrier? */
  if (level <= 0)
    return(WI_NOCARRIER);

  /* calculate link quality */
  if (link <= 0)
    stats->ws_quality = 0;
  else {
    /* thanks to google and wireless tools for this hint */
    stats->ws_quality = (int)rint(log(link) / log(max_qual) * 100.0);
    TRACE ("Quality: %2f, max quality: %2f", link, max_qual);
  }

  return(WI_OK);
}

#endif  /* !defined(__linux__) */