/****************************************************************************
 * apps/wireless/wapi/src/wapi.c
 *
 *   Copyright (C) 2011, 2017, 2019 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Largely and original work, but highly influenced by sampled code provided
 * with 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 <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <netinet/ether.h>

#include "netutils/netlib.h"

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

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

/* Generic form of a command handler */

typedef CODE int (*wapi_cmd_t)(int sock, int argc, FAR char **argv);

/* Describes one command */

struct wapi_command_s
{
  FAR const char *name;
  uint8_t minargs;
  uint8_t maxargs;
  wapi_cmd_t handler;
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static int wapi_str2int(FAR const char *str);
static double wapi_str2double(FAR const char *str);
static unsigned int wapi_str2ndx(FAR const char *name,
                                 FAR const char **list);
static void wapi_showusage(FAR const char *progname, int exitcode);

static int wapi_show_cmd         (int sock, int argc, FAR char **argv);
static int wapi_ip_cmd           (int sock, int argc, FAR char **argv);
static int wapi_mask_cmd         (int sock, int argc, FAR char **argv);
static int wapi_freq_cmd         (int sock, int argc, FAR char **argv);
static int wapi_essid_cmd        (int sock, int argc, FAR char **argv);
static int wapi_psk_cmd          (int sock, int argc, FAR char **argv);
static int wapi_disconnect_cmd   (int sock, int argc, FAR char **argv);
static int wapi_mode_cmd         (int sock, int argc, FAR char **argv);
static int wapi_ap_cmd           (int sock, int argc, FAR char **argv);
static int wapi_bitrate_cmd      (int sock, int argc, FAR char **argv);
static int wapi_txpower_cmd      (int sock, int argc, FAR char **argv);
static int wapi_scan_results_cmd (int sock, int argc, FAR char **argv);
static int wapi_scan_cmd         (int sock, int argc, FAR char **argv);
static int wapi_pscan_cmd        (int sock, int argc, FAR char **argv);
static int wapi_country_cmd      (int sock, int argc, FAR char **argv);
static int wapi_sense_cmd        (int sock, int argc, FAR char **argv);
#ifdef CONFIG_WIRELESS_WAPI_INITCONF
static int wapi_reconnect_cmd    (int sock, int argc, FAR char **argv);
static int wapi_save_config_cmd  (int sock, int argc, FAR char **argv);
#endif
static int wapi_pta_prio_cmd     (int sock, int argc, FAR char **argv);
static int wapi_power_save_cmd   (int sock, int argc, FAR char **argv);

/****************************************************************************
 * Private Data
 ****************************************************************************/

static const struct wapi_command_s g_wapi_commands[] =
{
  {"help",         0, 0, NULL},
  {"show",         1, 1, wapi_show_cmd},
  {"scan",         1, 2, wapi_scan_cmd},
  {"pscan",        1, 2, wapi_pscan_cmd},
  {"scan_results", 1, 1, wapi_scan_results_cmd},
  {"ip",           2, 2, wapi_ip_cmd},
  {"mask",         2, 2, wapi_mask_cmd},
  {"freq",         3, 3, wapi_freq_cmd},
  {"essid",        3, 3, wapi_essid_cmd},
  {"psk",          3, 4, wapi_psk_cmd},
  {"disconnect",   1, 1, wapi_disconnect_cmd},
  {"mode",         2, 2, wapi_mode_cmd},
  {"ap",           2, 2, wapi_ap_cmd},
  {"bitrate",      3, 3, wapi_bitrate_cmd},
  {"txpower",      3, 3, wapi_txpower_cmd},
  {"country",      1, 2, wapi_country_cmd},
  {"sense",        1, 1, wapi_sense_cmd},
#ifdef CONFIG_WIRELESS_WAPI_INITCONF
  {"reconnect",    1, 1, wapi_reconnect_cmd},
  {"save_config",  1, 1, wapi_save_config_cmd},
#endif
  {"pta_prio",     2, 2, wapi_pta_prio_cmd},
  {"power_save",   2, 2, wapi_power_save_cmd},
};

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Maximum length of the PASSPHRASE, refer to IEEE802.11i specification */

#define PASSPHRASE_MAX_LEN  (64)

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

/****************************************************************************
 * Name: wapi_str2int
 *
 * Description:
 *   Convert a string to an integer value
 *
 ****************************************************************************/

static int wapi_str2int(FAR const char *str)
{
  FAR char *endptr;
  long value;

  value = strtol(str, &endptr, 0);
  if (*endptr != '\0')
    {
      WAPI_ERROR("ERROR: Garbage after numeric argument\n");
      exit(EXIT_FAILURE);
    }

  if (value > INT_MAX || value < INT_MIN)
    {
      WAPI_ERROR("ERROR: Integer value out of range\n");
      exit(EXIT_FAILURE);
    }

  return (int)value;
}

/****************************************************************************
 * Name: wapi_str2double
 *
 * Description:
 *   Convert a string to a double value
 *
 ****************************************************************************/

static double wapi_str2double(FAR const char *str)
{
  FAR char *endptr;
  double value;

  value = strtod(str, &endptr);
  if (*endptr != '\0')
    {
      WAPI_ERROR("ERROR: Garbage after numeric argument\n");
      exit(EXIT_FAILURE);
    }

  return value;
}

/****************************************************************************
 * Name: wapi_str2ndx
 *
 * Description:
 *   Return the index of a string in a list of strings
 *
 ****************************************************************************/

static unsigned int wapi_str2ndx(FAR const char *name, FAR const char **list)
{
  unsigned int ndx;

  /* Check the first character is enough, all prefix with WAPI_* */

  if (isdigit(name[0]))
    {
      return atoi(name);
    }

  for (ndx = 0; list[ndx]; ndx++)
    {
      if (strcmp(name, list[ndx]) == 0)
        {
          return ndx;
        }
    }

  WAPI_ERROR("ERROR: Invalid option string: %s\n", name);
  WAPI_ERROR("       Valid options include:\n");
  for (ndx = 0; list[ndx]; ndx++)
    {
      WAPI_ERROR("       - [%d] %s\n", ndx, list[ndx]);
    }

  exit(EXIT_FAILURE);
}

/****************************************************************************
 * Name: wapi_show_cmd
 *
 * Description:
 *   Gets current configuration of the ifname using WAPI accessors and prints
 *   them in a pretty fashion with their corresponding return values. If a
 *   getter succeeds, we try to set that property with the same value to test
 *   the setters as well.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_show_cmd(int sock, int argc, FAR char **argv)
{
  enum wapi_bitrate_flag_e bitrate_flag;
  enum wapi_txpower_flag_e txpower_flag;
  char essid[WAPI_ESSID_MAX_SIZE + 1];
  enum wapi_essid_flag_e essid_flag;
  FAR const char *ifname = argv[0];
  enum wapi_pta_prio_e pta_prio;
  enum wapi_freq_flag_e freq_flag;
  enum wapi_mode_e mode;
  struct ether_addr ap;
  struct in_addr addr;
  char country[4];
  double tmpfreq;
  int bitrate;
  int txpower;
  double freq;
  int sense;
  int chan;
  int ret;
  char inetaddr[INET_ADDRSTRLEN];

  printf("%s Configuration:\n", ifname);

  /* Get ip */

  bzero(&addr, sizeof(struct in_addr));
  ret = wapi_get_ip(sock, ifname, &addr);
  if (ret < 0)
    {
      WAPI_ERROR("ERROR: wapi_get_ip() failed: %d\n", ret);
      return ret;
    }
  else
    {
      printf("       IP: %s\n", inet_ntoa_r(addr, inetaddr,
                                            sizeof(inetaddr)));
    }

  /* Get netmask */

  bzero(&addr, sizeof(struct in_addr));
  ret = wapi_get_netmask(sock, ifname, &addr);
  if (ret < 0)
    {
      WAPI_ERROR("ERROR: wapi_get_netmask() failed: %d\n", ret);
    }
  else
    {
      printf("  NetMask: %s\n", inet_ntoa_r(addr, inetaddr,
                                            sizeof(inetaddr)));
    }

  /* Get frequency */

  ret = wapi_get_freq(sock, ifname, &freq, &freq_flag);
  if (ret < 0)
    {
      WAPI_ERROR("ERROR: wapi_get_freq() failed: %d\n", ret);
    }
  else
    {
      printf("Frequency: %g\n", freq);
      printf("     Flag: %s\n", g_wapi_freq_flags[freq_flag]);

      ret = wapi_freq2chan(sock, ifname, freq, &chan);
      if (ret < 0)
        {
          WAPI_ERROR("ERROR: wapi_freq2chan() failed: %d\n", ret);
        }
      else
        {
          printf("  Channel: %d\n", chan);
        }

      ret = wapi_chan2freq(sock, ifname, chan, &tmpfreq);
      if (ret < 0)
        {
          WAPI_ERROR("ERROR: wapi_chan2freq() failed: %d\n", ret);
        }
      else
        {
          printf("Frequency: %g\n", tmpfreq);
        }
    }

  /* Get the ESSID */

  bzero(essid, sizeof(essid));
  ret = wapi_get_essid(sock, ifname, essid, &essid_flag);
  if (ret < 0)
    {
      WAPI_ERROR("ERROR: wapi_get_essid() failed: %d\n", ret);
    }
  else
    {
      printf("    ESSID: %s\n", essid);
      printf("     Flag: %s\n", g_wapi_essid_flags[essid_flag]);
    }

  /* Get operating mode */

  ret = wapi_get_mode(sock, ifname, &mode);
  if (ret < 0)
    {
      WAPI_ERROR("ERROR: wapi_get_mode() failed: %d\n", ret);
    }
  else
    {
      printf("     Mode: %s\n", g_wapi_modes[mode]);
    }

  /* Get AP */

  ret = wapi_get_ap(sock, ifname, &ap);
  if (ret < 0)
    {
      WAPI_ERROR("ERROR: wapi_get_ap() failed: %d\n", ret);
    }
  else
    {
      printf("       AP: %02x:%02x:%02x:%02x:%02x:%02x\n",
             ap.ether_addr_octet[0], ap.ether_addr_octet[1],
             ap.ether_addr_octet[2], ap.ether_addr_octet[3],
             ap.ether_addr_octet[4], ap.ether_addr_octet[5]);
    }

  /* Get bitrate */

  ret = wapi_get_bitrate(sock, ifname, &bitrate, &bitrate_flag);
  if (ret < 0)
    {
      WAPI_ERROR("ERROR: wapi_get_bitrate() failed: %d\n", ret);
    }
  else
    {
      printf("  BitRate: %d\n", bitrate);
      printf("     Flag: %s\n", g_wapi_bitrate_flags[bitrate_flag]);
    }

  /* Get txpower */

  ret = wapi_get_txpower(sock, ifname, &txpower, &txpower_flag);
  if (ret < 0)
    {
      WAPI_ERROR("ERROR: wapi_get_txpower() failed: %d\n", ret);
    }
  else
    {
      printf("  TxPower: %d\n", txpower);
      printf("     Flag: %s\n", g_wapi_txpower_flags[txpower_flag]);
    }

  /* Get sensitivity */

  ret = wapi_get_sensitivity(sock, ifname, &sense);
  if (ret >= 0)
    {
      printf("    Sense: %d\n", sense);
    }

  /* Get Country Code */

  memset(country, 0, sizeof(country));
  ret = wapi_get_country(sock, ifname, country);
  if (ret >= 0)
    {
      printf("  Country: %s\n", country);
    }

  /* Get pta prio */

  ret = wapi_get_pta_prio(sock, ifname, &pta_prio);
  if (ret >= 0)
    {
      printf(" PTA prio: %d\n", pta_prio);
    }

  return 0;
}

/****************************************************************************
 * Name: wapi_ip_cmd
 *
 * Description:
 *   Set the IP address.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_ip_cmd(int sock, int argc, FAR char **argv)
{
  struct in_addr addr;

  /* Format the request */

  addr.s_addr = inet_addr(argv[1]);

  /* Set the IP address */

  return wapi_set_ip(sock, argv[0], &addr);
}

/****************************************************************************
 * Name: wapi_mask_cmd
 *
 * Description:
 *  Set the network mask
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_mask_cmd(int sock, int argc, FAR char **argv)
{
  struct in_addr addr;

  /* Format the request */

  addr.s_addr = inet_addr(argv[1]);

  /* Set the network mask */

  return wapi_set_netmask(sock, argv[0], &addr);
}

/****************************************************************************
 * Name: wapi_freq_cmd
 *
 * Description:
 *  Set the frequency
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_freq_cmd(int sock, int argc, FAR char **argv)
{
  double frequency;
  enum wapi_freq_flag_e freq_flag;

  /* Convert input strings to values */

  frequency = wapi_str2double(argv[1]);
  freq_flag = (enum wapi_freq_flag_e)wapi_str2ndx(argv[2],
                                                  g_wapi_freq_flags);

  /* Set the frequency */

  return wapi_set_freq(sock, argv[0], frequency, freq_flag);
}

/****************************************************************************
 * Name: wapi_essid_cmd
 *
 * Description:
 *  Set the ESSID
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_essid_cmd(int sock, int argc, FAR char **argv)
{
  enum wapi_essid_flag_e essid_flag;

  /* Convert input strings to values */

  essid_flag = (enum wapi_essid_flag_e)
    wapi_str2ndx(argv[2], g_wapi_essid_flags);

  /* Set the ESSID */

  return wapi_set_essid(sock, argv[0], argv[1], essid_flag);
}

/****************************************************************************
 * Name: wapi_psk_cmd
 *
 * Description:
 *  Set the Passphrase
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_psk_cmd(int sock, int argc, FAR char **argv)
{
  enum wpa_alg_e alg_flag;
  enum wpa_ver_e ver_flag;
  uint8_t auth_wpa;
  int passlen;
  int cipher;
  int ret;

  /* Check if password len >= 8 && <= 63 */

  passlen = strnlen(argv[1], 64);
  if (passlen < 8 || passlen > 63)
    {
      printf("The password should have between 8 and 63 characters!\n");
      return -EINVAL;
    }

  /* Convert input strings to values */

  if (argc > 3)
    {
      ver_flag = (enum wpa_ver_e)wapi_str2ndx(argv[3], g_wapi_wpa_ver_flags);
    }
  else
    {
      ver_flag = WPA_VER_2;
    }

  switch (ver_flag)
    {
      case WPA_VER_NONE:
        auth_wpa = IW_AUTH_WPA_VERSION_DISABLED;
        break;

      case WPA_VER_1:
        auth_wpa = IW_AUTH_WPA_VERSION_WPA;
        break;

      case WPA_VER_2:
        auth_wpa = IW_AUTH_WPA_VERSION_WPA2;
        break;

      case WPA_VER_3:
        auth_wpa = IW_AUTH_WPA_VERSION_WPA3;
        break;

      default:
        return -EINVAL;
    }

  alg_flag = (enum wpa_alg_e)wapi_str2ndx(argv[2], g_wapi_alg_flags);

  switch (alg_flag)
    {
      case WPA_ALG_NONE:
        cipher = IW_AUTH_CIPHER_NONE;
        break;

      case WPA_ALG_WEP:
        cipher = IW_AUTH_CIPHER_WEP40;
        break;

      case WPA_ALG_TKIP:
        cipher = IW_AUTH_CIPHER_TKIP;
        break;

      case WPA_ALG_CCMP:
        cipher = IW_AUTH_CIPHER_CCMP;
        break;

      default:
        return -EINVAL;
    }

  ret = wpa_driver_wext_set_auth_param(sock, argv[0],
                                       IW_AUTH_WPA_VERSION,
                                       auth_wpa);
  if (ret >= 0)
    {
      ret = wpa_driver_wext_set_auth_param(sock, argv[0],
                                           IW_AUTH_CIPHER_PAIRWISE,
                                           cipher);

      /* Set the Passphrase */

      if (ret >= 0)
        {
          ret = wpa_driver_wext_set_key_ext(sock, argv[0], alg_flag,
                                            argv[1], strlen(argv[1]));
        }
    }

  return ret;
}

/****************************************************************************
 * Name: wapi_disconnect_cmd
 *
 * Description:
 *   Disconnect the AP in the range using given ifname interface.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_disconnect_cmd(int sock, int argc, FAR char **argv)
{
  wpa_driver_wext_disconnect(sock, argv[0]);

  return 0;
}

/****************************************************************************
 * Name: wapi_mode_cmd
 *
 * Description:
 *  Set the operating mode
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_mode_cmd(int sock, int argc, FAR char **argv)
{
  enum wapi_mode_e mode;

  /* Convert input strings to values */

  mode = (enum wapi_mode_e)wapi_str2ndx(argv[1], g_wapi_modes);

  /* Set operating mode */

  return wapi_set_mode(sock, argv[0], mode);
}

/****************************************************************************
 * Name: wapi_ap_cmd
 *
 * Description:
 *  Set the AP
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_ap_cmd(int sock, int argc, FAR char **argv)
{
  struct ether_addr ap;

  /* Convert input strings to values */

  sscanf(argv[1], "%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx",
         &ap.ether_addr_octet[0], &ap.ether_addr_octet[1],
         &ap.ether_addr_octet[2], &ap.ether_addr_octet[3],
         &ap.ether_addr_octet[4], &ap.ether_addr_octet[5]);

  /* Set ap */

  return wapi_set_ap(sock, argv[0], &ap);
}

/****************************************************************************
 * Name: wapi_bitrate_cmd
 *
 * Description:
 *  Set the bit rate
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_bitrate_cmd(int sock, int argc, FAR char **argv)
{
  enum wapi_bitrate_flag_e bitrate_flag;
  int bitrate;

  /* Convert input strings to values */

  bitrate      = wapi_str2int(argv[1]);
  bitrate_flag = (enum wapi_bitrate_flag_e)
    wapi_str2ndx(argv[2], g_wapi_bitrate_flags);

  /* Set bitrate */

  return wapi_set_bitrate(sock, argv[0], bitrate, bitrate_flag);
}

/****************************************************************************
 * Name: wapi_txpower_cmd
 *
 * Description:
 *  Set the TX power
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_txpower_cmd(int sock, int argc, FAR char **argv)
{
  enum wapi_txpower_flag_e txpower_flag;
  int txpower;

  /* Convert input strings to values */

  txpower      = wapi_str2int(argv[1]);
  txpower_flag = (enum wapi_txpower_flag_e)
    wapi_str2ndx(argv[2], g_wapi_txpower_flags);

  /* Set txpower */

  return wapi_set_txpower(sock, argv[0], txpower, txpower_flag);
}

/****************************************************************************
 * Name: wapi_scan_results_cmd
 *
 * Description:
 *   Print the scan results.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_scan_results_cmd(int sock, int argc, FAR char **argv)
{
  int sleepdur = 200 * 1000;
  int sleeptries = 25;
  struct wapi_list_s list;
  FAR struct wapi_scan_info_s *info;
  int ret;

  /* Wait for completion */

  do
    {
      ret = wapi_scan_stat(sock, argv[0]);
      if (ret < 0)
        {
          WAPI_ERROR("ERROR: wapi_scan_stat() failed: %d, sleeptries: %d\n",
                      ret, sleeptries);
        }
      else if (ret == 1)
        {
          usleep(sleepdur);
        }
    }
  while (--sleeptries > 0 && ret > 0);

  if (ret < 0)
    {
      return ret;
    }

  /* Collect results */

  bzero(&list, sizeof(struct wapi_list_s));
  ret = wapi_scan_coll(sock, argv[0], &list);
  if (ret < 0)
    {
      WAPI_ERROR("ERROR: wapi_scan_coll() failed: %d\n", ret);
      return ret;
    }

  /* Print found aps */

  printf("bssid / frequency / signal level / encode / ssid\n");
  for (info = list.head.scan; info; info = info->next)
    {
      printf("%02x:%02x:%02x:%02x:%02x:%02x\t%g\t%d\t%04x\t%s\n",
             info->ap.ether_addr_octet[0], info->ap.ether_addr_octet[1],
             info->ap.ether_addr_octet[2], info->ap.ether_addr_octet[3],
             info->ap.ether_addr_octet[4], info->ap.ether_addr_octet[5],
             info->freq, info->rssi, info->encode, info->essid);
    }

  /* Free ap list */

  wapi_scan_coll_free(&list);
  return 0;
}

/****************************************************************************
 * Name: wapi_scan_cmd
 *
 * Description:
 *   Use the given ifname interface and active mode to scan the APs
 *   available in the range.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_scan_cmd(int sock, int argc, FAR char **argv)
{
  FAR const char *essid;
  int ret;

  essid = argc > 1 ? argv[1] : NULL;

  /* Start scan */

  ret = wapi_escan_init(sock, argv[0], IW_SCAN_TYPE_ACTIVE, essid);
  if (ret < 0)
    {
      return ret;
    }

  return wapi_scan_results_cmd(sock, 1, argv);
}

/****************************************************************************
 * Name: wapi_pscan_cmd
 *
 * Description:
 *   Use the given ifname interface and passive mode to scan the APs
 *   available in the range.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_pscan_cmd(int sock, int argc, FAR char **argv)
{
  FAR const char *essid;
  int ret;

  essid = argc > 1 ? argv[1] : NULL;

  /* Start scan */

  ret = wapi_escan_init(sock, argv[0], IW_SCAN_TYPE_PASSIVE, essid);
  if (ret < 0)
    {
      return ret;
    }

  return wapi_scan_results_cmd(sock, 1, argv);
}

/****************************************************************************
 * Name: wapi_country_cmd
 *
 * Description:
 *  Set/Get the country code
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_country_cmd(int sock, int argc, FAR char **argv)
{
  char country[4];
  int ret;

  if (argc == 1)
    {
      memset(country, 0, sizeof(country));
      ret = wapi_get_country(sock, argv[0], country);
      if (ret >= 0)
        {
          printf("%s\n", country);
        }

      return ret;
    }

  return wapi_set_country(sock, argv[0], argv[1]);
}

/****************************************************************************
 * Name: wapi_sense_cmd
 *
 * Description:
 *  Get the sensitivity(RSSI)
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_sense_cmd(int sock, int argc, FAR char **argv)
{
  int sense;
  int ret;

  ret = wapi_get_sensitivity(sock, argv[0], &sense);
  if (ret >= 0)
    {
      printf("%d\n", sense);
    }

  return ret;
}

#ifdef CONFIG_WIRELESS_WAPI_INITCONF

/****************************************************************************
 * Name: wapi_reconnect_cmd
 *
 * Description:
 *   Reconnect the AP in the range using given ifname interface.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_reconnect_cmd(int sock, int argc, FAR char **argv)
{
  struct wpa_wconfig_s conf;
  FAR void *load;
  int ret;

  load = wapi_load_config(argv[0], NULL, &conf);
  if (load == NULL)
    {
      return -EINVAL;
    }

  ret = wpa_driver_wext_associate(&conf);

  wapi_unload_config(load);

  return ret;
}

/****************************************************************************
 * Name: wapi_save_config_cmd
 *
 * Description:
 *   Scans available APs in the range using given ifname interface.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_save_config_cmd(int sock, int argc, FAR char **argv)
{
  char essid[WAPI_ESSID_MAX_SIZE + 1];
  char bssid[20];
  enum wapi_essid_flag_e essid_flag;
  struct wpa_wconfig_s conf;
  struct ether_addr ap;
  uint8_t if_flags;
  uint32_t value;
  size_t psk_len;
  char psk[PASSPHRASE_MAX_LEN];
  int ret;

  ret = netlib_getifstatus(argv[0], &if_flags);
  if (ret < 0)
    {
      return ret;
    }

  if (!IFF_IS_RUNNING(if_flags))
    {
      return -EINVAL;
    }

  psk_len = sizeof(psk);

  memset(&conf, 0, sizeof(struct wpa_wconfig_s));
  ret = wapi_get_mode(sock, argv[0], &conf.sta_mode);
  if (ret < 0)
    {
      return ret;
    }

  memset(essid, 0, sizeof(essid));
  ret = wapi_get_essid(sock, argv[0], essid, &essid_flag);
  if (ret < 0)
    {
      return ret;
    }

  conf.ssid = essid;
  conf.ssidlen = strnlen(essid, sizeof(essid));

  ret = wapi_get_ap(sock, argv[0], &ap);
  if (ret < 0)
    {
      return ret;
    }

  conf.bssid = ether_ntoa_r(&ap, bssid);

  memset(psk, 0, sizeof(psk));
  ret = wpa_driver_wext_get_key_ext(sock,
                                    argv[0],
                                    &conf.alg,
                                    psk,
                                    &psk_len);
  if (ret >= 0)
    {
      conf.passphrase = psk;
      conf.phraselen = psk_len;
    }

  ret = wpa_driver_wext_get_auth_param(sock,
                                       argv[0],
                                       IW_AUTH_WPA_VERSION,
                                       &value);
  if (ret < 0)
    {
      conf.auth_wpa = IW_AUTH_WPA_VERSION_WPA2;
    }
  else
    {
      conf.auth_wpa = value;
    }

  ret = wpa_driver_wext_get_auth_param(sock,
                                       argv[0],
                                       IW_AUTH_CIPHER_PAIRWISE,
                                       &value);
  if (ret < 0)
    {
      if (conf.phraselen > 0)
        conf.cipher_mode = IW_AUTH_CIPHER_CCMP;
      else
        conf.cipher_mode = IW_AUTH_CIPHER_NONE;
    }
  else
    {
      conf.cipher_mode = value;
    }

  return wapi_save_config(argv[0], NULL, &conf);
}
#endif

/****************************************************************************
 * Name: wapi_pta_prio_cmd
 *
 * Description:
 *   Manually configure the pta priority.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_pta_prio_cmd(int sock, int argc, FAR char **argv)
{
  enum wapi_pta_prio_e pta_prio;

  /* Convert input strings to values */

  pta_prio = (enum wapi_pta_prio_e)
    wapi_str2ndx(argv[1], g_wapi_pta_prio_flags);

  /* Set operating mode */

  return wapi_set_pta_prio(sock, argv[0], pta_prio);
}

/****************************************************************************
 * Name: wapi_power_save_cmd
 *
 * Description:
 *   Manually configure the power save status.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int wapi_power_save_cmd(int sock, int argc, FAR char **argv)
{
  bool on = false;

  if (strcmp(argv[1], "on") == 0)
    {
      on = true;
    }

  /* Set power save status */

  return wapi_set_power_save(sock, argv[0], on);
}

/****************************************************************************
 * Name: wapi_showusage
 *
 * Description:
 *   Show program usage.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void wapi_showusage(FAR const char *progname, int exitcode)
{
  int i;

  fprintf(stderr, "Usage:\n");
  fprintf(stderr, "\t%s show         <ifname>\n", progname);
  fprintf(stderr, "\t%s scan         <ifname>\n", progname);
  fprintf(stderr, "\t%s pscan        <ifname>\n", progname);
  fprintf(stderr, "\t%s scan_results <ifname>\n", progname);
  fprintf(stderr, "\t%s ip           <ifname> <IP address>\n", progname);
  fprintf(stderr, "\t%s mask         <ifname> <mask>\n", progname);
  fprintf(stderr, "\t%s freq         <ifname> <frequency>  <index/flag>\n",
                   progname);
  fprintf(stderr, "\t%s essid        <ifname> <essid>      <index/flag>\n",
                   progname);
  fprintf(stderr, "\t%s psk          <ifname> <passphrase> <index/flag> "
                  "[wpa]\n", progname);
  fprintf(stderr, "\t%s disconnect   <ifname>\n", progname);
  fprintf(stderr, "\t%s mode         <ifname>              <index/mode>\n",
                   progname);
  fprintf(stderr, "\t%s ap           <ifname>              <MAC address>\n",
                   progname);
  fprintf(stderr, "\t%s bitrate      <ifname> <bitrate>    <index/flag>\n",
                   progname);
  fprintf(stderr, "\t%s txpower      <ifname> <txpower>    <index/flag>\n",
                   progname);
  fprintf(stderr, "\t%s country      <ifname> <country code>\n", progname);
  fprintf(stderr, "\t%s sense        <ifname>\n", progname);
#ifdef CONFIG_WIRELESS_WAPI_INITCONF
  fprintf(stderr, "\t%s reconnect    <ifname>\n", progname);
  fprintf(stderr, "\t%s save_config  <ifname>\n", progname);
#endif
  fprintf(stderr, "\t%s pta_prio     <ifname>  <index/flag>\n", progname);
  fprintf(stderr, "\t%s power_save   <ifname>  <on|off>\n", progname);
  fprintf(stderr, "\t%s help\n", progname);

  fprintf(stderr, "\nFrequency Flags:\n");
  for (i = 0; g_wapi_freq_flags[i]; i++)
    {
      fprintf(stderr, "\t[%d] %s\n", i, g_wapi_freq_flags[i]);
    }

  fprintf(stderr, "\nESSID Flags:\n");
  for (i = 0; g_wapi_essid_flags[i]; i++)
    {
      fprintf(stderr, "\t[%d] %s\n", i, g_wapi_essid_flags[i]);
    }

  fprintf(stderr, "\nPassphrase algorithm Flags:\n");
  for (i = 0; g_wapi_alg_flags[i]; i++)
    {
      fprintf(stderr, "\t[%d] %s\n", i, g_wapi_alg_flags[i]);
    }

  fprintf(stderr, "\nPassphrase WPA version:\n");
  for (i = 0; g_wapi_wpa_ver_flags[i]; i++)
    {
      fprintf(stderr, "\t[%d] %s\n", i, g_wapi_wpa_ver_flags[i]);
    }

  fprintf(stderr, "\nOperating Modes:\n");
  for (i = 0; g_wapi_modes[i]; i++)
    {
      fprintf(stderr, "\t[%d] %s\n", i, g_wapi_modes[i]);
    }

  fprintf(stderr, "\nBitrate Flags:\n");
  for (i = 0; g_wapi_bitrate_flags[i]; i++)
    {
      fprintf(stderr, "\t[%d] %s\n", i, g_wapi_bitrate_flags[i]);
    }

  fprintf(stderr, "\nTX power Flags:\n");
  for (i = 0; g_wapi_txpower_flags[i]; i++)
    {
      fprintf(stderr, "\t[%d] %s\n", i, g_wapi_txpower_flags[i]);
    }

  fprintf(stderr, "\npta prio Flags:\n");
  for (i = 0; g_wapi_pta_prio_flags[i]; i++)
    {
      fprintf(stderr, "\t[%d] %s\n", i, g_wapi_pta_prio_flags[i]);
    }

  exit(exitcode);
}

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

int main(int argc, FAR char *argv[])
{
  FAR const char *cmdname;
  FAR const struct wapi_command_s *wapicmd;
  int sock;
  int i;
  int ret;

  /* Get the command */

  if (argc < 2)
    {
      WAPI_ERROR("ERROR: Missing command\n");
      wapi_showusage(argv[0], EXIT_FAILURE);
    }

  cmdname = argv[1];

  /* Find the command in the g_wapi_command[] list */

  wapicmd = NULL;
  for (i = 0; i < nitems(g_wapi_commands); i++)
    {
      FAR const struct wapi_command_s *cmd = &g_wapi_commands[i];
      if (strcmp(cmdname, cmd->name) == 0)
        {
          wapicmd = cmd;
          break;
        }
    }

  if (wapicmd == NULL)
    {
      WAPI_ERROR("ERROR: Unsupported command: %s\n", cmdname);
      wapi_showusage(argv[0], EXIT_FAILURE);
    }

  if (wapicmd->maxargs + 2 < argc)
    {
      WAPI_ERROR("ERROR: Garbage at end of command ignored\n");
    }
  else if (wapicmd->minargs + 2 > argc)
    {
      WAPI_ERROR("ERROR: Missing required command options: %s\n",
                 cmdname);
      wapi_showusage(argv[0], EXIT_FAILURE);
    }

  /* Special case the help command which has no arguments, no handler,
   * and does not need a socket.
   */

  if (wapicmd->handler == NULL)
    {
      wapi_showusage(argv[0], EXIT_SUCCESS);
    }

  /* Create a communication socket */

  sock = wapi_make_socket();
  if (sock < 0)
    {
      WAPI_ERROR("ERROR: wapi_make_socket() failed: %d\n", sock);
      return EXIT_FAILURE;
    }

  ret = wapicmd->handler(sock, argc - 2, argc == 2 ? NULL : &argv[2]);
  if (ret < 0)
    {
      WAPI_ERROR("ERROR: Process command (%s) failed.\n", cmdname);
    }

  /* Close communication socket */

  close(sock);
  return ret;
}