/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Developed by J.R. Oldroyd <fbsd@opal.com>, December 2012.

// For FreeBSD we use the getifaddrs(3) to obtain the list of interfaces
// and then check for those with an 802.11 media type and able to return
// a list of stations. This is similar to ifconfig(8).

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/if_media.h>
#ifdef __DragonFly__
#include <netproto/802_11/ieee80211_ioctl.h>
#else
#include <net80211/ieee80211_ioctl.h>
#endif

#include <ifaddrs.h>
#include <string.h>
#include <unistd.h>

#include "nsWifiAccessPoint.h"

using namespace mozilla;

static nsresult
FreeBSDGetAccessPointData(nsCOMArray<nsWifiAccessPoint> &accessPoints)
{
  // get list of interfaces
  struct ifaddrs *ifal;
  if (getifaddrs(&ifal) < 0) {
    return NS_ERROR_FAILURE;
  }

  accessPoints.Clear();

  // loop through the interfaces
  nsresult rv = NS_ERROR_FAILURE;
  struct ifaddrs *ifa;
  for (ifa = ifal; ifa; ifa = ifa->ifa_next) {
    // limit to one interface per address
    if (ifa->ifa_addr->sa_family != AF_LINK) {
      continue;
    }

    // store interface name in socket structure
    struct ifreq ifr;
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, ifa->ifa_name, sizeof(ifr.ifr_name));
    ifr.ifr_addr.sa_family = AF_LOCAL;

    // open socket to interface
    int s = socket(ifr.ifr_addr.sa_family, SOCK_DGRAM, 0);
    if (s < 0) {
      continue;
    }

    // clear interface media structure
    struct ifmediareq ifmr;
    memset(&ifmr, 0, sizeof(ifmr));
    strncpy(ifmr.ifm_name, ifa->ifa_name, sizeof(ifmr.ifm_name));

    // get interface media information
    if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) {
      close(s);
      continue;
    }

    // check interface is a WiFi interface
    if (IFM_TYPE(ifmr.ifm_active) != IFM_IEEE80211) {
      close(s);
      continue;
    }

    // perform WiFi scan
    struct ieee80211req i802r;
    char iscanbuf[32*1024];
    memset(&i802r, 0, sizeof(i802r));
    strncpy(i802r.i_name, ifa->ifa_name, sizeof(i802r.i_name));
    i802r.i_type = IEEE80211_IOC_SCAN_RESULTS;
    i802r.i_data = iscanbuf;
    i802r.i_len = sizeof(iscanbuf);
    if (ioctl(s, SIOCG80211, &i802r) < 0) {
      close(s);
      continue;
    }

    // close socket
    close(s);

    // loop through WiFi networks and build geoloc-lookup structure
    char *vsr = (char *) i802r.i_data;
    unsigned len = i802r.i_len;
    while (len >= sizeof(struct ieee80211req_scan_result)) {
      struct ieee80211req_scan_result *isr =
        (struct ieee80211req_scan_result *) vsr;

      // determine size of this entry
      char *id;
      int idlen;
      if (isr->isr_meshid_len) {
        id = vsr + isr->isr_ie_off + isr->isr_ssid_len;
        idlen = isr->isr_meshid_len;
      } else {
        id = vsr + isr->isr_ie_off;
        idlen = isr->isr_ssid_len;
      }

      // copy network data
      char ssid[IEEE80211_NWID_LEN+1];
      strncpy(ssid, id, idlen);
      ssid[idlen] = '\0';
      nsWifiAccessPoint *ap = new nsWifiAccessPoint();
      ap->setSSID(ssid, strlen(ssid));
      ap->setMac(isr->isr_bssid);
      ap->setSignal(isr->isr_rssi);
      accessPoints.AppendObject(ap);
      rv = NS_OK;

      // log the data
      LOG(( "FreeBSD access point: "
            "SSID: %s, MAC: %02x-%02x-%02x-%02x-%02x-%02x, "
            "Strength: %d, Channel: %dMHz\n",
            ssid, isr->isr_bssid[0], isr->isr_bssid[1], isr->isr_bssid[2],
            isr->isr_bssid[3], isr->isr_bssid[4], isr->isr_bssid[5],
            isr->isr_rssi, isr->isr_freq));

      // increment pointers
      len -= isr->isr_len;
      vsr += isr->isr_len;
    }
  }

  freeifaddrs(ifal);

  return rv;
}

nsresult
nsWifiMonitor::DoScan()
{
  // Regularly get the access point data.

  nsCOMArray<nsWifiAccessPoint> lastAccessPoints;
  nsCOMArray<nsWifiAccessPoint> accessPoints;

  do {
    nsresult rv = FreeBSDGetAccessPointData(accessPoints);
    if (NS_FAILED(rv))
      return rv;

    bool accessPointsChanged = !AccessPointsEqual(accessPoints, lastAccessPoints);
    ReplaceArray(lastAccessPoints, accessPoints);

    rv = CallWifiListeners(lastAccessPoints, accessPointsChanged);
    NS_ENSURE_SUCCESS(rv, rv);

    // wait for some reasonable amount of time. pref?
    LOG(("waiting on monitor\n"));

    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
    mon.Wait(PR_SecondsToInterval(kDefaultWifiScanInterval));
  }
  while (mKeepGoing);

  return NS_OK;
}