/****************************************************************************
 * apps/wireless/wapi/src/wireless.c
 *
 *   Copyright (C) 2011, 2017, 2019 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Adapted for NuttX from WAPI:
 *
 *   Copyright (c) 2010, Volkan YAZICI <volkan.yazici@gmail.com>
 *   All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  - Redistributions of  source code must  retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  - 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR
 * CONTRIBUTORS 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <math.h>
#include <errno.h>
#include <netinet/arp.h>

#include <nuttx/wireless/wireless.h>

#include "wireless/wapi.h"
#include "util.h"

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* Events & Streams */

struct wapi_event_stream_s
{
  FAR char *end;                  /* End of the stream */
  FAR char *current;              /* Current event in stream of events */
  FAR char *value;                /* Current value in event */
};

/****************************************************************************
 * Public Data
 ****************************************************************************/

/* Frequency */

FAR const char *g_wapi_freq_flags[] =
{
  "WAPI_FREQ_AUTO",
  "WAPI_FREQ_FIXED",
  NULL
};

/* ESSID */

FAR const char *g_wapi_essid_flags[] =
{
  "WAPI_ESSID_OFF",
  "WAPI_ESSID_ON",
  "WAPI_ESSID_DELAY_ON",
  NULL
};

/* Operating Mode */

FAR const char *g_wapi_modes[] =
{
  "WAPI_MODE_AUTO",
  "WAPI_MODE_ADHOC",
  "WAPI_MODE_MANAGED",
  "WAPI_MODE_MASTER",
  "WAPI_MODE_REPEAT",
  "WAPI_MODE_SECOND",
  "WAPI_MODE_MONITOR",
  "WAPI_MODE_MESH",
  NULL
};

/* Bit Rate */

FAR const char *g_wapi_bitrate_flags[] =
{
  "WAPI_BITRATE_AUTO",
  "WAPI_BITRATE_FIXED",
  NULL
};

/* Transmit Power */

FAR const char *g_wapi_txpower_flags[] =
{
  "WAPI_TXPOWER_DBM",
  "WAPI_TXPOWER_MWATT",
  "WAPI_TXPOWER_RELATIVE",
  NULL
};

/* Passphrase Algorithm */

FAR const char *g_wapi_alg_flags[] =
{
  "WPA_ALG_NONE",
  "WPA_ALG_WEP",
  "WPA_ALG_TKIP",
  "WPA_ALG_CCMP",
  NULL
};

/* Passphrase WPA Version */

FAR const char *g_wapi_wpa_ver_flags[] =
{
  "WPA_VER_NONE",
  "WPA_VER_1",
  "WPA_VER_2",
  "WPA_VER_3",
  NULL
};

/* PTA PRIORITY */

FAR const char *g_wapi_pta_prio_flags[] =
{
  "WAPI_PTA_PRIORITY_COEX_MAXIMIZED",
  "WAPI_PTA_PRIORITY_COEX_HIGH",
  "WAPI_PTA_PRIORITY_BALANCED",
  "WAPI_PTA_PRIORITY_WLAN_HIGHD",
  "WAPI_PTA_PRIORITY_WLAN_MAXIMIZED",
  NULL
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: wapi_freq2float
 *
 * Description:
 *   Converts internal representation of frequencies to a floating point.
 *
 ****************************************************************************/

static inline double wapi_freq2float(const struct iw_freq *freq)
{
  return ((double)freq->m) * pow(10, freq->e);
}

/****************************************************************************
 * Name: wapi_float2freq
 *
 * Description:
 *   Converts a floating point the our internal representation of
 *   frequencies.
 *
 ****************************************************************************/

static inline void wapi_float2freq(double floatfreq, struct iw_freq *freq)
{
  freq->e = (short)floor(log10(floatfreq));
  if (freq->e > 8)
    {
      freq->m = ((long)(floor(floatfreq / pow(10, freq->e - 6)))) * 100;
      freq->e -= 8;
    }
  else
    {
      freq->m = (long)floatfreq;
      freq->e = 0;
    }
}

/****************************************************************************
 * Name: wapi_parse_mode
 *
 * Description:
 *
 ****************************************************************************/

static int wapi_parse_mode(int iw_mode, FAR enum wapi_mode_e *wapi_mode)
{
  switch (iw_mode)
    {
    case WAPI_MODE_AUTO:
    case WAPI_MODE_ADHOC:
    case WAPI_MODE_MANAGED:
    case WAPI_MODE_MASTER:
    case WAPI_MODE_REPEAT:
    case WAPI_MODE_SECOND:
    case WAPI_MODE_MONITOR:
      *wapi_mode = iw_mode;
      return 0;

    default:
      WAPI_ERROR("ERROR: Unknown mode: %d\n", iw_mode);
      return -EINVAL;
    }
}

/****************************************************************************
 * Name: wapi_make_ether
 *
 * Description:
 *
 ****************************************************************************/

static int wapi_make_ether(FAR struct ether_addr *addr, int byte)
{
  WAPI_VALIDATE_PTR(addr);
  memset(addr, byte, sizeof(struct ether_addr));
  return 0;
}

/****************************************************************************
 * Name: wapi_event_stream_init
 *
 * Description:
 *   Initialize a stream to access the events.
 *
 ****************************************************************************/

static void wapi_event_stream_init(FAR struct wapi_event_stream_s *stream,
                                   FAR char *data, size_t len)
{
  memset(stream, 0, sizeof(struct wapi_event_stream_s));
  stream->current = data;
  stream->end = &data[len];
}

/****************************************************************************
 * Name: wapi_event_stream_extract
 *
 * Description:
 *   Extract the next event from the stream.
 *
 ****************************************************************************/

static int wapi_event_stream_extract(FAR struct wapi_event_stream_s *stream,
                                     FAR struct iw_event *iwe)
{
  int ret = 1;
  FAR struct iw_event *iwe_stream;

  iwe_stream = (FAR struct iw_event *)stream->current;

  if (stream->current + offsetof(struct iw_event, u) > stream->end ||
      iwe_stream->len == 0)
    {
      /* Nothing to process */

      return 0;
    }

  if (stream->current + iwe_stream->len > stream->end ||
      iwe_stream->len < offsetof(struct iw_event, u))
    {
      return -EINVAL;
    }

  switch (iwe_stream->cmd)
    {
      case SIOCGIWESSID:
      case SIOCGIWENCODE:
      case IWEVGENIE:
        iwe->cmd = iwe_stream->cmd;
        iwe->len = offsetof(struct iw_event, u) + sizeof(struct iw_point);
        iwe->u.data.flags = iwe_stream->u.data.flags;
        iwe->u.data.length = iwe_stream->u.data.length;

        iwe->u.data.pointer = (FAR void *)(stream->current +
                              offsetof(struct iw_event, u) +
                              (unsigned long)iwe_stream->u.data.pointer);
        break;

      default:
        if (iwe_stream->len > sizeof(*iwe))
          {
            WAPI_ERROR("Unhandled event size 0x%x %d\n", iwe_stream->cmd,
                                                         iwe_stream->len);
            iwe->cmd = 0;
            iwe->len = offsetof(struct iw_event, u);
            break;
          }

        memcpy(iwe, iwe_stream, iwe_stream->len);
    }

  /* Update stream to next event */

  stream->current += iwe_stream->len;
  return ret;
}

/****************************************************************************
 * Name: wapi_scan_event
 *
 * Description:
 *
 ****************************************************************************/

static int wapi_scan_event(FAR struct iw_event *event,
                           FAR struct wapi_list_s *list)
{
  FAR struct wapi_scan_info_s *info;

  /* Get current "wapi_info_t". */

  info = list->head.scan;

  /* Decode the event. */

  switch (event->cmd)
    {
    case SIOCGIWAP:
      {
        struct wapi_scan_info_s *temp;

        /* Allocate a new cell. */

        temp = malloc(sizeof(struct wapi_scan_info_s));
        if (!temp)
          {
            WAPI_STRERROR("malloc()");
            return -ENOMEM;
          }

        /* Reset it. */

        bzero(temp, sizeof(struct wapi_scan_info_s));
        temp->encode = 0xffff;

        /* Save cell identifier. */

        memcpy(&temp->ap, &event->u.ap_addr.sa_data,
               sizeof(struct ether_addr));

        /* Push it to the head of the list. */

        temp->next = info;
        list->head.scan = temp;

        break;
      }

    case SIOCGIWFREQ:
      {
        info->has_freq = 1;

        if (event->u.freq.e == 0)
          {
            /* Some drivers do not report frequency, but a channel.
             * Try to map this to frequency by assuming they are using
             * IEEE 802.11b/g.  But don't overwrite a previously parsed
             * frequency if the driver sends both frequency and channel,
             * since the driver may be sending an A-band channel that we
             * don't handle here.
             */

            if (event->u.freq.m >= 1 && event->u.freq.m <= 13)
              {
                info->freq = 2407 + 5 * event->u.freq.m;
              }
            else if (event->u.freq.m == 14)
              {
                info->freq = 2484;
              }
            else if (event->u.freq.m >= 36 && event->u.freq.m <= 165)
              {
                info->freq = 5000 + 5 * event->u.freq.m;
              }
          }
        else
          {
            info->freq = wapi_freq2float(&(event->u.freq));
          }

        break;
      }

    case SIOCGIWMODE:
      {
        int ret = wapi_parse_mode(event->u.mode, &info->mode);
        if (ret >= 0)
          {
            info->has_mode = 1;
            break;
          }
        else
          {
            return ret;
          }
      }

    case SIOCGIWESSID:
      {
        info->has_essid = 1;
        info->essid_flag = (event->u.data.flags) ? WAPI_ESSID_ON
                                                 : WAPI_ESSID_OFF;
        memset(info->essid, 0, (WAPI_ESSID_MAX_SIZE + 1));
        if ((event->u.essid.pointer) && (event->u.essid.length))
          {
            memcpy(info->essid, event->u.essid.pointer,
                   event->u.essid.length);
          }

        break;
      }

    case SIOCGIWRATE:

      /* Scan may return a list of bitrates. As we have space for only a
       * single bitrate, we only keep the largest one.
       */

      if (!info->has_bitrate || event->u.bitrate.value > info->bitrate)
        {
          info->has_bitrate = 1;
          info->bitrate = event->u.bitrate.value;
        }

      break;

    case IWEVQUAL:
      {
        if (event->u.qual.updated & IW_QUAL_DBM)
          {
            info->has_rssi = 1;
            info->rssi = event->u.qual.level;

            /* Report signal levels in dBm */

            if (info->rssi >= 0x40)
              {
                info->rssi -= 0x100;
              }
          }

        break;
      }

    case SIOCGIWENCODE:
      {
        info->has_encode = 1;
        info->encode = event->u.data.flags;
        break;
      }
    }

  return 0;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: wapi_get_freq
 *
 * Description:
 *   Gets the operating frequency of the device.
 *
 ****************************************************************************/

int wapi_get_freq(int sock, FAR const char *ifname, FAR double *freq,
                  FAR enum wapi_freq_flag_e *flag)
{
  struct iwreq wrq =
  {
  };

  int ret;

  WAPI_VALIDATE_PTR(freq);
  WAPI_VALIDATE_PTR(flag);

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCGIWFREQ, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0)
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCGIWFREQ, errcode);
      ret = -errcode;
    }
  else
    {
      /* Set flag. */

      if (IW_FREQ_AUTO == wrq.u.freq.flags)
        {
          *flag = WAPI_FREQ_AUTO;
        }
      else if (IW_FREQ_FIXED == wrq.u.freq.flags)
        {
          *flag = WAPI_FREQ_FIXED;
        }
      else
        {
          WAPI_ERROR("ERROR: Unknown flag: %d\n", wrq.u.freq.flags);
          return -EINVAL;
        }

      /* Set freq. */

      *freq = wapi_freq2float(&(wrq.u.freq));
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_set_freq
 *
 * Description:
 *   Sets the operating frequency of the device.
 *
 ****************************************************************************/

int wapi_set_freq(int sock, FAR const char *ifname, double freq,
                  enum wapi_freq_flag_e flag)
{
  struct iwreq wrq =
  {
  };

  int ret;

  /* Set freq. */

  wapi_float2freq(freq, &(wrq.u.freq));

  /* Set flag. */

  switch (flag)
    {
    case WAPI_FREQ_AUTO:
      wrq.u.freq.flags = IW_FREQ_AUTO;
      break;

    case WAPI_FREQ_FIXED:
      wrq.u.freq.flags = IW_FREQ_FIXED;
      break;
    }

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCSIWFREQ, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0)
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCSIWFREQ, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_freq2chan
 *
 * Description:
 *   Finds corresponding channel for the supplied freq.
 *
 * Returned Value:
 *   0, on success; -2, if not found; otherwise, ioctl() return value.
 *
 ****************************************************************************/

int wapi_freq2chan(int sock, FAR const char *ifname, double freq,
                   FAR int *chan)
{
  struct iwreq wrq =
  {
  };

  char buf[sizeof(struct iw_range) * 2];
  int ret;

  WAPI_VALIDATE_PTR(chan);

  /* Prepare request. */

  bzero(buf, sizeof(buf));
  wrq.u.data.pointer = buf;
  wrq.u.data.length = sizeof(buf);
  wrq.u.data.flags = 0;

  /* Get range. */

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCGIWRANGE, (unsigned long)((uintptr_t)&wrq));
  if (ret >= 0)
    {
      struct iw_range *range = (struct iw_range *)buf;
      int k;

      /* Compare the frequencies as double to ignore differences in encoding.
       * Slower, but safer...
       */

      for (k = 0; k < range->num_frequency; k++)
        {
          if (freq == wapi_freq2float(&(range->freq[k])))
            {
              *chan = range->freq[k].i;
              return 0;
            }
        }

      /* Oops! Nothing found. */

      ret = -2;
    }
  else
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCGIWRANGE, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_chan2freq
 *
 * Description:
 *   Finds corresponding frequency for the supplied chan.
 *
 * Returned Value:
 *   0, on success; -2, if not found; otherwise, ioctl() return value.
 *
 ****************************************************************************/

int wapi_chan2freq(int sock, FAR const char *ifname, int chan,
                   FAR double *freq)
{
  struct iwreq wrq =
  {
  };

  char buf[sizeof(struct iw_range) * 2];
  int ret;

  WAPI_VALIDATE_PTR(freq);

  /* Prepare request. */

  bzero(buf, sizeof(buf));
  wrq.u.data.pointer = buf;
  wrq.u.data.length = sizeof(buf);
  wrq.u.data.flags = 0;

  /* Get range. */

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCGIWRANGE, (unsigned long)((uintptr_t)&wrq));
  if (ret >= 0)
    {
      struct iw_range *range = (struct iw_range *)buf;
      int k;

      for (k = 0; k < range->num_frequency; k++)
        {
          if (chan == range->freq[k].i)
            {
              *freq = wapi_freq2float(&(range->freq[k]));
              return 0;
            }
        }

      /* Oops! Nothing found. */

      ret = -2;
    }
  else
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCGIWRANGE, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_get_essid
 *
 * Description:
 *   Gets ESSID of the device.
 *
 * Input Parameters:
 *   essid - Used to store the ESSID of the device. Buffer must have
 *           enough space to store WAPI_ESSID_MAX_SIZE+1 characters.
 *
 ****************************************************************************/

int wapi_get_essid(int sock, FAR const char *ifname, FAR char *essid,
                   FAR enum wapi_essid_flag_e *flag)
{
  struct iwreq wrq =
  {
  };

  int ret;

  WAPI_VALIDATE_PTR(essid);
  WAPI_VALIDATE_PTR(flag);

  wrq.u.essid.pointer = essid;
  wrq.u.essid.length = WAPI_ESSID_MAX_SIZE + 1;
  wrq.u.essid.flags = 0;

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCGIWESSID, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0)
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCGIWESSID, errcode);
      ret = -errcode;
    }
  else
    {
      *flag = (wrq.u.essid.flags) ? WAPI_ESSID_ON : WAPI_ESSID_OFF;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_set_essid
 *
 * Description:
 *    Sets ESSID of the device.
 *
 *    essid At most WAPI_ESSID_MAX_SIZE characters are read.
 *
 ****************************************************************************/

int wapi_set_essid(int sock, FAR const char *ifname, FAR const char *essid,
                   enum wapi_essid_flag_e flag)
{
  char buf[WAPI_ESSID_MAX_SIZE + 1];
  struct iwreq wrq =
  {
  };

  int ret;

  /* Prepare request. */

  snprintf(buf, sizeof(buf), "%s", essid);
  wrq.u.essid.pointer = buf;
  wrq.u.essid.length = strlen(buf);
  wrq.u.essid.flags = flag;

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCSIWESSID, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0)
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCSIWESSID, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_get_mode
 *
 * Description:
 *   Gets the operating mode of the device.
 *
 ****************************************************************************/

int wapi_get_mode(int sock, FAR const char *ifname,
                  FAR enum wapi_mode_e *mode)
{
  struct iwreq wrq =
  {
  };

  int ret;

  WAPI_VALIDATE_PTR(mode);

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCGIWMODE, (unsigned long)((uintptr_t)&wrq));
  if (ret >= 0)
    {
      ret = wapi_parse_mode(wrq.u.mode, mode);
    }
  else
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCGIWMODE, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_set_mode
 *
 * Description:
 *   Sets the operating mode of the device.
 *
 ****************************************************************************/

int wapi_set_mode(int sock, FAR const char *ifname, enum wapi_mode_e mode)
{
  struct iwreq wrq =
  {
  };

  int ret;

  wrq.u.mode = mode;

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCSIWMODE, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0)
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCSIWMODE, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_make_broad_ether
 *
 * Description:
 *   Creates an Ethernet broadcast address.
 *
 ****************************************************************************/

int wapi_make_broad_ether(FAR struct ether_addr *sa)
{
  return wapi_make_ether(sa, 0xff);
}

/****************************************************************************
 * Name: wapi_make_null_ether
 *
 * Description:
 *   Creates an ethernet NULL address.
 *
 ****************************************************************************/

int wapi_make_null_ether(FAR struct ether_addr *sa)
{
  return wapi_make_ether(sa, 0x00);
}

/****************************************************************************
 * Name: wapi_get_ap
 *
 * Description:
 *   Gets access point address of the device.
 *
 * Input Parameters:
 *   ap - Set the to MAC address of the device. (For "any", a broadcast
 *        ethernet address; for "off", a null ethernet address is used.)
 *
 ****************************************************************************/

int wapi_get_ap(int sock, FAR const char *ifname, FAR struct ether_addr *ap)
{
  struct iwreq wrq =
  {
  };

  int ret;

  WAPI_VALIDATE_PTR(ap);

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCGIWAP, (unsigned long)((uintptr_t)&wrq));
  if (ret >= 0)
    {
      memcpy(ap, wrq.u.ap_addr.sa_data, sizeof(struct ether_addr));
    }
  else
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCGIWAP, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_set_ap
 *
 * Description:
 *   Sets access point address of the device.
 *
 ****************************************************************************/

int wapi_set_ap(int sock, FAR const char *ifname,
                FAR const struct ether_addr *ap)
{
  struct iwreq wrq =
  {
  };

  int ret;

  WAPI_VALIDATE_PTR(ap);

  wrq.u.ap_addr.sa_family = ARPHRD_ETHER;
  memcpy(wrq.u.ap_addr.sa_data, ap, sizeof(struct ether_addr));
  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);

  ret = ioctl(sock, SIOCSIWAP, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0)
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCSIWAP, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_get_bitrate
 *
 * Description:
 *   Gets bitrate of the device.
 *
 ****************************************************************************/

int wapi_get_bitrate(int sock, FAR const char *ifname,
                     FAR int *bitrate, FAR enum wapi_bitrate_flag_e *flag)
{
  struct iwreq wrq =
  {
  };

  int ret;

  WAPI_VALIDATE_PTR(bitrate);
  WAPI_VALIDATE_PTR(flag);

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCGIWRATE, (unsigned long)((uintptr_t)&wrq));
  if (ret >= 0)
    {
      /* Check if enabled. */

      if (wrq.u.bitrate.disabled)
        {
          WAPI_ERROR("ERROR: Bitrate is disabled\n");
          return -EINVAL;
        }

      /* Get bitrate. */

      *bitrate = wrq.u.bitrate.value;
      *flag = wrq.u.bitrate.fixed ? WAPI_BITRATE_FIXED : WAPI_BITRATE_AUTO;
    }
  else
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCGIWRATE, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_set_bitrate
 *
 * Description:
 *   Sets bitrate of the device.
 *
 ****************************************************************************/

int wapi_set_bitrate(int sock, FAR const char *ifname, int bitrate,
                     enum wapi_bitrate_flag_e flag)
{
  struct iwreq wrq =
  {
  };

  int ret;

  wrq.u.bitrate.value = bitrate;
  wrq.u.bitrate.fixed = (flag == WAPI_BITRATE_FIXED);

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCSIWRATE, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0)
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCSIWRATE, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_dbm2mwatt
 *
 * Description:
 *   Converts a value in dBm to a value in milliWatt.
 *
 ****************************************************************************/

int wapi_dbm2mwatt(int dbm)
{
  return floor(pow(10, (((double)dbm) / 10)));
}

/****************************************************************************
 * Name: wapi_mwatt2dbm
 *
 * Description:
 *   Converts a value in milliWatt to a value in dBm.
 *
 ****************************************************************************/

int wapi_mwatt2dbm(int mwatt)
{
  return ceil(10 * log10(mwatt));
}

/****************************************************************************
 * Name: wapi_get_txpower
 *
 * Description:
 *   Gets txpower of the device.
 *
 ****************************************************************************/

int wapi_get_txpower(int sock, FAR const char *ifname, FAR int *power,
                     FAR enum wapi_txpower_flag_e *flag)
{
  struct iwreq wrq =
  {
  };

  int ret;

  WAPI_VALIDATE_PTR(power);
  WAPI_VALIDATE_PTR(flag);

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCGIWTXPOW, (unsigned long)((uintptr_t)&wrq));
  if (ret >= 0)
    {
      /* Check if enabled. */

      if (wrq.u.txpower.disabled)
        {
          return -EINVAL;
        }

      /* Get flag. */

      switch (wrq.u.txpower.flags & IW_TXPOW_TYPE)
        {
          case IW_TXPOW_DBM:
            *flag = WAPI_TXPOWER_DBM;
            break;
          case IW_TXPOW_MWATT:
            *flag = WAPI_TXPOWER_MWATT;
            break;
          case IW_TXPOW_RELATIVE:
            *flag = WAPI_TXPOWER_RELATIVE;
            break;

          default:
            WAPI_ERROR("ERROR: Unknown flag: %d\n", wrq.u.txpower.flags);
            return -EINVAL;
        }

      /* Get power. */

      *power = wrq.u.txpower.value;
    }
  else
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCGIWTXPOW, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_set_txpower
 *
 * Description:
 *   Sets txpower of the device.
 *
 ****************************************************************************/

int wapi_set_txpower(int sock, FAR const char *ifname, int power,
                     enum wapi_txpower_flag_e flag)
{
  struct iwreq wrq =
  {
  };

  int ret;

  /* Construct the request. */

  wrq.u.txpower.value = power;
  switch (flag)
    {
    case WAPI_TXPOWER_DBM:
      wrq.u.txpower.flags = IW_TXPOW_DBM;
      break;

    case WAPI_TXPOWER_MWATT:
      wrq.u.txpower.flags = IW_TXPOW_MWATT;
      break;

    case WAPI_TXPOWER_RELATIVE:
      wrq.u.txpower.flags = IW_TXPOW_RELATIVE;
      break;
    }

  /* Issue the set command. */

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCSIWTXPOW, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0)
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCSIWTXPOW, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_scan_channel_init
 *
 * Description:
 *   Starts a scan on the given interface. Root privileges are required to
 *   start a scan with specified channels.
 *
 ****************************************************************************/

int wapi_scan_channel_init(int sock, FAR const char *ifname,
                           FAR const char *essid,
                           uint8_t *channels, int num_channels)
{
  return wapi_escan_channel_init(sock, ifname, IW_SCAN_TYPE_ACTIVE, essid,
                                 channels, num_channels);
}

/****************************************************************************
 * Name: wapi_escan_channel_init
 *
 * Description:
 *   Starts a scan on the given interface. Root privileges are required to
 *   start a scan with specified channels.
 *
 ****************************************************************************/

int wapi_escan_channel_init(int sock, FAR const char *ifname,
                           uint8_t scan_type, FAR const char *essid,
                           uint8_t *channels, int num_channels)
{
  struct iw_scan_req req;
  struct iwreq wrq =
  {
  };

  size_t essid_len;
  int ret;
  int i;

  memset(&req, 0, sizeof(req));

  if (essid && (essid_len = strlen(essid)) > 0)
    {
      req.essid_len    = essid_len;
      memcpy(req.essid, essid, essid_len);
      wrq.u.data.flags = IW_SCAN_THIS_ESSID;
    }

  if (channels && num_channels > 0)
    {
      req.num_channels = num_channels;
      for (i = 0; i < num_channels; i++)
        {
          req.channel_list[i].m = channels[i];
        }
    }

  req.scan_type       = scan_type;
  req.bssid.sa_family = ARPHRD_ETHER;
  memset(req.bssid.sa_data, 0xff, IFHWADDRLEN);
  wrq.u.data.pointer  = (caddr_t)&req;
  wrq.u.data.length   = sizeof(req);

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCSIWSCAN, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0)
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCSIWSCAN, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_scan_init
 *
 * Description:
 *   Starts a scan on the given interface. Root privileges are required to
 *   start a scan.
 *
 ****************************************************************************/

int wapi_scan_init(int sock, FAR const char *ifname, FAR const char *essid)
{
  return wapi_scan_channel_init(sock, ifname, essid, NULL, 0);
}

/****************************************************************************
 * Name: wapi_escan_init
 *
 * Description:
 *   Starts a extended scan on the given interface, you can specify the scan
 *   type. Root privileges are required to start a scan.
 *
 ****************************************************************************/

int wapi_escan_init(int sock, FAR const char *ifname,
                   uint8_t scan_type, FAR const char *essid)
{
  return wapi_escan_channel_init(sock, ifname, scan_type, essid, NULL, 0);
}

/****************************************************************************
 * Name: wapi_scan_stat
 *
 * Description:
 *   Checks the status of the scan process.
 *
 * Returned Value:
 *   Zero, if data is ready; 1, if data is not ready; negative on failure.
 *
 ****************************************************************************/

int wapi_scan_stat(int sock, FAR const char *ifname)
{
  struct iwreq wrq =
  {
  };

  int ret;
  char buf;

  wrq.u.data.pointer = &buf;

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCGIWSCAN, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0)
    {
      if (errno == E2BIG)
        {
          /* Data is ready, but not enough space, which is expected. */

          return 0;
        }
      else if (errno == EAGAIN)
        {
          /* Data is not ready. */

          return 1;
        }

      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCGIWSCAN, errcode);
      ret = -errcode;
    }
  else
    {
      ret = 0;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_scan_coll
 *
 * Description:
 *   Collects the results of a scan process.
 *
 * Input Parameters:
 *   aps - Pushes collected  struct wapi_scan_info_s into this list.
 *
 ****************************************************************************/

int wapi_scan_coll(int sock, FAR const char *ifname,
                   FAR struct wapi_list_s *aps)
{
  FAR char *buf;
  int buflen;
  struct iwreq wrq =
  {
  };

  int ret;

  WAPI_VALIDATE_PTR(aps);

  buflen = CONFIG_WIRELESS_WAPI_SCAN_MAX_DATA;
  buf = malloc(buflen);
  if (!buf)
    {
      WAPI_STRERROR("malloc()");
      return -ENOMEM;
    }

retry:
  memset(buf, 0, buflen);

  /* Collect results. */

  wrq.u.data.pointer = buf;
  wrq.u.data.length  = buflen;
  wrq.u.data.flags   = 0;
  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);

  ret = ioctl(sock, SIOCGIWSCAN, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0 && errno == E2BIG)
    {
      FAR char *tmp;

      buflen *= 2;
      tmp = malloc(buflen);
      free(buf);
      if (!tmp)
        {
          WAPI_STRERROR("malloc()");
          return -ENOMEM;
        }

      buf = tmp;
      goto retry;
    }

  /* There is still something wrong. It's either EAGAIN or some other ioctl()
   * failure. We don't bother, let the user deal with it.
   */

  if (ret < 0)
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCGIWSCAN, errcode);
      free(buf);
      return -errcode;
    }

  /* We have the results, process them. */

  if (wrq.u.data.length)
    {
      struct iw_event iwe;
      struct wapi_event_stream_s stream;

      wapi_event_stream_init(&stream, buf, wrq.u.data.length);
      do
        {
          /* Get the next event from the stream */

          ret = wapi_event_stream_extract(&stream, &iwe);
          if (ret > 0)
            {
              int eventret = wapi_scan_event(&iwe, aps);
              if (eventret < 0)
                {
                  ret = eventret;
                }
            }
          else if (ret < 0)
            {
              WAPI_ERROR("ERROR: wapi_event_stream_extract() failed!\n");
            }
        }
      while (ret > 0);
    }

  /* Free request buffer. */

  free(buf);
  return ret;
}

/****************************************************************************
 * Name: wapi_scan_coll_free
 *
 * Description:
 *   Free the scan results.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void wapi_scan_coll_free(FAR struct wapi_list_s *list)
{
  FAR struct wapi_scan_info_s *temp;
  FAR struct wapi_scan_info_s *info;

  if (list == NULL)
    {
      return;
    }

  info = list->head.scan;
  while (info)
    {
      temp = info->next;
      free(info);
      info = temp;
    }
}

/****************************************************************************
 * Name: wapi_set_country
 *
 * Description:
 *    Set the country code
 *
 ****************************************************************************/

int wapi_set_country(int sock, FAR const char *ifname,
                     FAR const char *country)
{
  struct iwreq wrq =
  {
  };

  int ret;

  /* Prepare request. */

  wrq.u.data.pointer = (FAR void *)country;
  wrq.u.data.length = 2;

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCSIWCOUNTRY, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0)
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCSIWCOUNTRY, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_get_country
 *
 * Description:
 *    Get the country code
 *
 ****************************************************************************/

int wapi_get_country(int sock, FAR const char *ifname,
                     FAR char *country)
{
  struct iwreq wrq =
  {
  };

  int ret;

  /* Prepare request. */

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  wrq.u.data.pointer = (FAR void *)country;
  wrq.u.data.length = 3;
  ret = ioctl(sock, SIOCGIWCOUNTRY, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0)
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCGIWSENS, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_get_sensitivity
 *
 * Description:
 *    Get the wlan Sensitivity
 *
 ****************************************************************************/

int wapi_get_sensitivity(int sock, FAR const char *ifname, FAR int *sense)
{
  struct iwreq wrq =
  {
  };

  int ret;

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCGIWSENS, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0)
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCGIWSENS, errcode);
      ret = -errcode;
    }
  else
    {
      *sense = -wrq.u.sens.value;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_set_pta_prio
 *
 * Description:
 *   Sets the pta priority of the device.
 *
 ****************************************************************************/

int wapi_set_pta_prio(int sock, FAR const char *ifname,
                      enum wapi_pta_prio_e pta_prio)
{
  struct iwreq wrq =
  {
  };

  int ret;

  wrq.u.param.value = pta_prio;

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCSIWPTAPRIO, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0)
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCSIWPTAPRIO, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_get_pta_prio
 *
 * Description:
 *   Gets the pta priority of the device.
 *
 ****************************************************************************/

int wapi_get_pta_prio(int sock, FAR const char *ifname,
                      enum wapi_pta_prio_e *pta_prio)
{
  struct iwreq wrq =
  {
  };

  int ret;

  WAPI_VALIDATE_PTR(pta_prio);

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCGIWPTAPRIO, (unsigned long)((uintptr_t)&wrq));
  if (ret >= 0)
    {
      *pta_prio = wrq.u.param.value;
    }
  else
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCGIWPTAPRIO, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_set_pmksa
 *
 * Description:
 *   Set the wlan pmksa.
 *
 ****************************************************************************/

int wapi_set_pmksa(int sock, FAR const char *ifname,
                   FAR const uint8_t *pmk, int len)
{
  struct iwreq wrq =
  {
  };

  int ret;

  WAPI_VALIDATE_PTR(pmk);

  wrq.u.data.pointer = (FAR void *)pmk;
  wrq.u.data.length = len;

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCSIWPMKSA, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0)
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCSIWPMKSA, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_get_pmksa
 *
 * Description:
 *   Get the wlan pmksa.
 *
 ****************************************************************************/

int wapi_get_pmksa(int sock, FAR const char *ifname,
                   FAR uint8_t *pmk, int len)
{
  struct iwreq wrq =
  {
  };

  int ret;

  WAPI_VALIDATE_PTR(pmk);

  wrq.u.data.pointer = pmk;
  wrq.u.data.length = len;

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = ioctl(sock, SIOCGIWPMKSA, (unsigned long)((uintptr_t)&wrq));
  if (ret < 0)
    {
      int errcode = errno;
      WAPI_IOCTL_STRERROR(SIOCGIWPMKSA, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_extend_params
 *
 * Description:
 *   wapi extension interface for privatization method.
 *
 ****************************************************************************/

int wapi_extend_params(int sock, int cmd, FAR struct iwreq *wrq)
{
  int ret;

  WAPI_VALIDATE_PTR(wrq);

  if (cmd < SIOCIWFIRSTPRIV || cmd > SIOCIWLASTPRIV)
    {
      wlerr("extend ioctl cmd invalid");
      return -EINVAL;
    }

  ret = ioctl(sock, cmd, (unsigned long)((uintptr_t)wrq));
  if (ret < 0)
    {
      int errcode = errno;
      wlerr("extend ioctl(%d): %d", cmd, errcode);
      ret = -errcode;
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_set_power_save
 *
 * Description:
 *   Set power save status of wifi.
 *
 ****************************************************************************/

int wapi_set_power_save(int sock, FAR const char *ifname, bool on)
{
  struct iwreq wrq =
  {
  };

  int ret;

  wrq.u.power.flags = on;
  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = wapi_extend_params(sock, SIOCSIWPWSAVE, &wrq);

  return ret;
}

/****************************************************************************
 * Name: wapi_get_power_save
 *
 * Description:
 *   Get power save status of wifi.
 *
 ****************************************************************************/

int wapi_get_power_save(int sock, FAR const char *ifname, bool *on)
{
  struct iwreq wrq =
  {
  };

  int ret;

  WAPI_VALIDATE_PTR(on);

  strlcpy(wrq.ifr_name, ifname, IFNAMSIZ);
  ret = wapi_extend_params(sock, SIOCGIWPWSAVE, &wrq);
  if (ret >= 0)
    {
      *on = wrq.u.power.flags;
    }

  return ret;
}