#!/usr/bin/python

"""Tests for configs.py."""

import subprocess

import configs
import experiment_testutils
import utils
from wvtest import wvtest


_FREQ_LIST = {
    '2.4': '2412 2417 2422 2427 2432 2437 2442 2447 2452 2457 2462',
    '5': ('5180 5200 5220 5240 5745 5765 5785 5805 5825 5260 5280 5300 5320 '
          '5500 5520 5540 5560 5580 5660 5680 5700'),
}


_WPA_SUPPLICANT_CONFIG = """ctrl_interface=/var/run/wpa_supplicant
ap_scan=1
autoscan=exponential:1:30
freq_list={freq_list}
network={{
\tssid="some ssid"
\t#psk="some passphrase"
\tpsk=41821f7ca3ea5d85beea7644ed7e0fefebd654177fa06c26fbdfdc3c599a317f
\tscan_ssid=1
\tfreq_list={freq_list}
}}
"""

_WPA_SUPPLICANT_CONFIG_BSSID = """ctrl_interface=/var/run/wpa_supplicant
ap_scan=1
autoscan=exponential:1:30
freq_list={freq_list}
network={{
\tssid="some ssid"
\t#psk="some passphrase"
\tpsk=41821f7ca3ea5d85beea7644ed7e0fefebd654177fa06c26fbdfdc3c599a317f
\tscan_ssid=1
\tbssid=12:34:56:78:90:ab
\tfreq_list={freq_list}
}}
"""

# pylint: disable=g-backslash-continuation
_WPA_SUPPLICANT_CONFIG_BSSID_UNSECURED = \
"""ctrl_interface=/var/run/wpa_supplicant
ap_scan=1
autoscan=exponential:1:30
freq_list={freq_list}
network={{
\tssid="some ssid"
\tkey_mgmt=NONE
\tscan_ssid=1
\tbssid=12:34:56:78:90:ab
\tfreq_list={freq_list}
}}
"""

_WPA_SUPPLICANT_CONFIG_NO_FREQ_LIST = """ctrl_interface=/var/run/wpa_supplicant
ap_scan=1
autoscan=exponential:1:30

network={
\tssid="some ssid"
\t#psk="some passphrase"
\tpsk=41821f7ca3ea5d85beea7644ed7e0fefebd654177fa06c26fbdfdc3c599a317f
\tscan_ssid=1
}
"""


@wvtest.wvtest
def generate_wpa_supplicant_config_test():
  if subprocess.call(('which', 'wpa_passphrase')) != 0:
    utils.log(
        "Can't test generate_wpa_supplicant_config without wpa_passphrase.")
    return

  for band in ('2.4', '5'):
    opt = FakeOptDict()
    opt.band = band
    got = configs.generate_wpa_supplicant_config(
        'some ssid', 'some passphrase', opt)
    want = _WPA_SUPPLICANT_CONFIG.format(freq_list=_FREQ_LIST[band])
    wvtest.WVPASSEQ(want, got)

    opt.bssid = 'TotallyNotValid'
    wvtest.WVEXCEPT(utils.BinWifiException,
                    configs.generate_wpa_supplicant_config,
                    'some ssid', 'some passphrase', opt)

    opt.bssid = '12:34:56:78:90:Ab'
    got = configs.generate_wpa_supplicant_config(
        'some ssid', 'some passphrase', opt)
    want = _WPA_SUPPLICANT_CONFIG_BSSID.format(freq_list=_FREQ_LIST[band])
    wvtest.WVPASSEQ(want, got)

    got = configs.generate_wpa_supplicant_config(
        'some ssid', None, opt)
    want = _WPA_SUPPLICANT_CONFIG_BSSID_UNSECURED.format(
        freq_list=_FREQ_LIST[band])
    wvtest.WVPASSEQ(want, got)

    opt.no_band_restriction = True
    opt.bssid = None
    got = configs.generate_wpa_supplicant_config(
        'some ssid', 'some passphrase', opt)
    want = _WPA_SUPPLICANT_CONFIG_NO_FREQ_LIST
    wvtest.WVPASSEQ(want, got)


_PHY_INFO = """Wiphy phy0
  max # scan SSIDs: 4
  max scan IEs length: 2257 bytes
  Retry short limit: 7
  Retry long limit: 4
  Coverage class: 0 (up to 0m)
  Device supports RSN-IBSS.
  Device supports AP-side u-APSD.
  Device supports T-DLS.
  Supported Ciphers:
    * WEP40 (00-0f-ac:1)
    * WEP104 (00-0f-ac:5)
    * TKIP (00-0f-ac:2)
    * CCMP (00-0f-ac:4)
    * CMAC (00-0f-ac:6)
  Available Antennas: TX 0x7 RX 0x7
  Configured Antennas: TX 0x7 RX 0x7
  Supported interface modes:
     * IBSS
     * managed
     * AP
     * AP/VLAN
     * WDS
     * monitor
     * P2P-client
     * P2P-GO
  Band 1:
    Capabilities: 0x11ef
      RX LDPC
      HT20/HT40
      SM Power Save disabled
      RX HT20 SGI
      RX HT40 SGI
      TX STBC
      RX STBC 1-stream
      Max AMSDU length: 3839 bytes
      DSSS/CCK HT40
    Maximum RX AMPDU length 65535 bytes (exponent: 0x003)
    Minimum RX AMPDU time spacing: 8 usec (0x06)
    HT TX/RX MCS rate indexes supported: 0-23
    Bitrates (non-HT):
      * 1.0 Mbps
      * 2.0 Mbps (short preamble supported)
      * 5.5 Mbps (short preamble supported)
      * 11.0 Mbps (short preamble supported)
      * 6.0 Mbps
      * 9.0 Mbps
      * 12.0 Mbps
      * 18.0 Mbps
      * 24.0 Mbps
      * 36.0 Mbps
      * 48.0 Mbps
      * 54.0 Mbps
    Frequencies:
      * 2412 MHz [1] (30.0 dBm)
      * 2417 MHz [2] (30.0 dBm)
      * 2422 MHz [3] (30.0 dBm)
      * 2427 MHz [4] (30.0 dBm)
      * 2432 MHz [5] (30.0 dBm)
      * 2437 MHz [6] (30.0 dBm)
      * 2442 MHz [7] (30.0 dBm)
      * 2447 MHz [8] (30.0 dBm)
      * 2452 MHz [9] (30.0 dBm)
      * 2457 MHz [10] (30.0 dBm)
      * 2462 MHz [11] (30.0 dBm)
      * 2467 MHz [12] (disabled)
      * 2472 MHz [13] (disabled)
      * 2484 MHz [14] (disabled)
  Band 2:
    Capabilities: 0x11ef
      RX LDPC
      HT20/HT40
      SM Power Save disabled
      RX HT20 SGI
      RX HT40 SGI
      TX STBC
      RX STBC 1-stream
      Max AMSDU length: 3839 bytes
      DSSS/CCK HT40
    Maximum RX AMPDU length 65535 bytes (exponent: 0x003)
    Minimum RX AMPDU time spacing: 8 usec (0x06)
    HT TX/RX MCS rate indexes supported: 0-23
    Bitrates (non-HT):
      * 6.0 Mbps
      * 9.0 Mbps
      * 12.0 Mbps
      * 18.0 Mbps
      * 24.0 Mbps
      * 36.0 Mbps
      * 48.0 Mbps
      * 54.0 Mbps
    Frequencies:
      * 5180 MHz [36] (17.0 dBm)
      * 5200 MHz [40] (17.0 dBm)
      * 5220 MHz [44] (17.0 dBm)
      * 5240 MHz [48] (17.0 dBm)
      * 5260 MHz [52] (23.0 dBm) (no IR, radar detection)
        DFS state: usable (for 7648 sec)
        DFS CAC time: 60000 ms
      * 5280 MHz [56] (23.0 dBm) (no IR, radar detection)
        DFS state: usable (for 7648 sec)
        DFS CAC time: 60000 ms
      * 5300 MHz [60] (23.0 dBm) (no IR, radar detection)
        DFS state: usable (for 7648 sec)
        DFS CAC time: 60000 ms
      * 5320 MHz [64] (23.0 dBm) (no IR, radar detection)
        DFS state: usable (for 7648 sec)
        DFS CAC time: 60000 ms
      * 5500 MHz [100] (20.0 dBm) (no IR, radar detection)
        DFS state: usable (for 7648 sec)
        DFS CAC time: 60000 ms
      * 5520 MHz [104] (20.0 dBm) (no IR, radar detection)
        DFS state: usable (for 7648 sec)
        DFS CAC time: 60000 ms
      * 5540 MHz [108] (20.0 dBm) (no IR, radar detection)
        DFS state: usable (for 7648 sec)
        DFS CAC time: 60000 ms
      * 5560 MHz [112] (20.0 dBm) (no IR, radar detection)
        DFS state: usable (for 7648 sec)
        DFS CAC time: 60000 ms
      * 5580 MHz [116] (20.0 dBm) (no IR, radar detection)
        DFS state: usable (for 7648 sec)
        DFS CAC time: 60000 ms
      * 5600 MHz [120] (disabled)
      * 5620 MHz [124] (disabled)
      * 5640 MHz [128] (disabled)
      * 5660 MHz [132] (30.0 dBm) (no IR, radar detection)
        DFS state: usable (for 7648 sec)
        DFS CAC time: 60000 ms
      * 5680 MHz [136] (30.0 dBm) (no IR, radar detection)
        DFS state: usable (for 7648 sec)
        DFS CAC time: 60000 ms
      * 5700 MHz [140] (30.0 dBm) (no IR, radar detection)
        DFS state: usable (for 7648 sec)
        DFS CAC time: 60000 ms
      * 5745 MHz [149] (30.0 dBm)
      * 5765 MHz [153] (30.0 dBm)
      * 5785 MHz [157] (30.0 dBm)
      * 5805 MHz [161] (30.0 dBm)
      * 5825 MHz [165] (30.0 dBm)
  Supported commands:
     * new_interface
     * set_interface
     * new_key
     * start_ap
     * new_station
     * set_bss
     * authenticate
     * associate
     * deauthenticate
     * disassociate
     * join_ibss
     * remain_on_channel
     * set_tx_bitrate_mask
     * frame
     * frame_wait_cancel
     * set_wiphy_netns
     * set_channel
     * set_wds_peer
     * tdls_mgmt
     * tdls_oper
     * probe_client
     * set_noack_map
     * register_beacons
     * start_p2p_device
     * set_mcast_rate
     * channel_switch
     * Unknown command (104)
     * connect
     * disconnect
  Supported TX frame types:
     * IBSS: 0x00 0x10 0x20 0x30 0x40 0x50 0x60 0x70 0x80 0x90 0xa0 0xb0 0xc0 0xd0 0xe0 0xf0
     * managed: 0x00 0x10 0x20 0x30 0x40 0x50 0x60 0x70 0x80 0x90 0xa0 0xb0 0xc0 0xd0 0xe0 0xf0
     * AP: 0x00 0x10 0x20 0x30 0x40 0x50 0x60 0x70 0x80 0x90 0xa0 0xb0 0xc0 0xd0 0xe0 0xf0
     * AP/VLAN: 0x00 0x10 0x20 0x30 0x40 0x50 0x60 0x70 0x80 0x90 0xa0 0xb0 0xc0 0xd0 0xe0 0xf0
     * mesh point: 0x00 0x10 0x20 0x30 0x40 0x50 0x60 0x70 0x80 0x90 0xa0 0xb0 0xc0 0xd0 0xe0 0xf0
     * P2P-client: 0x00 0x10 0x20 0x30 0x40 0x50 0x60 0x70 0x80 0x90 0xa0 0xb0 0xc0 0xd0 0xe0 0xf0
     * P2P-GO: 0x00 0x10 0x20 0x30 0x40 0x50 0x60 0x70 0x80 0x90 0xa0 0xb0 0xc0 0xd0 0xe0 0xf0
     * P2P-device: 0x00 0x10 0x20 0x30 0x40 0x50 0x60 0x70 0x80 0x90 0xa0 0xb0 0xc0 0xd0 0xe0 0xf0
  Supported RX frame types:
     * IBSS: 0x40 0xb0 0xc0 0xd0
     * managed: 0x40 0xd0
     * AP: 0x00 0x20 0x40 0xa0 0xb0 0xc0 0xd0
     * AP/VLAN: 0x00 0x20 0x40 0xa0 0xb0 0xc0 0xd0
     * mesh point: 0xb0 0xc0 0xd0
     * P2P-client: 0x40 0xd0
     * P2P-GO: 0x00 0x20 0x40 0xa0 0xb0 0xc0 0xd0
     * P2P-device: 0x40 0xd0
  software interface modes (can always be added):
     * AP/VLAN
     * monitor
  valid interface combinations:
     * #{ managed } <= 2048, #{ AP } <= 8, #{ P2P-client, P2P-GO } <= 1,
       total <= 2048, #channels <= 1, STA/AP BI must match
     * #{ WDS } <= 2048,
       total <= 2048, #channels <= 1, STA/AP BI must match
     * #{ IBSS, AP } <= 1,
       total <= 1, #channels <= 1, STA/AP BI must match, radar detect widths: { 20 MHz (no HT), 20 MHz }

  HT Capability overrides:
     * MCS: ff ff ff ff ff ff ff ff ff ff
     * maximum A-MSDU length
     * supported channel width
     * short GI for 40 MHz
     * max A-MPDU length exponent
     * min MPDU start spacing
  Device supports TX status socket option.
  Device supports HT-IBSS.
  Device supports SAE with AUTHENTICATE command
  Device supports low priority scan.
  Device supports scan flush.
  Device supports AP scan.
  Device supports per-vif TX power setting
  P2P GO supports CT window setting
  Driver supports a userspace MPM
  Device supports active monitor (which will ACK incoming frames)
  Driver/device bandwidth changes during BSS lifetime (AP/GO mode)

"""

_HOSTAPD_CONFIG = """ctrl_interface=/var/run/hostapd
interface=wlan0

ssid=TEST_SSID
utf8_ssid=1
auth_algs=1
hw_mode=g
channel=1
country_code=US
ieee80211d=1
ieee80211h=1
ieee80211n=1









ht_capab=[HT20][RX-STBC1]

"""

_HOSTAPD_CONFIG_WDS = """ctrl_interface=/var/run/hostapd
interface=wlan0

ssid=TEST_SSID
utf8_ssid=1
auth_algs=1
hw_mode=g
channel=1
country_code=US
ieee80211d=1
ieee80211h=1
ieee80211n=1






wds_sta=1


ht_capab=[HT20][RX-STBC1]

"""

_HOSTAPD_CONFIG_BRIDGE = """ctrl_interface=/var/run/hostapd
interface=wlan0
bridge=br0
ssid=TEST_SSID
utf8_ssid=1
auth_algs=1
hw_mode=g
channel=1
country_code=US
ieee80211d=1
ieee80211h=1
ieee80211n=1









ht_capab=[HT20][RX-STBC1]

"""

_HOSTAPD_CONFIG_PROVISION_VIA = """ctrl_interface=/var/run/hostapd
interface=wlan0

ssid=TEST_SSID
utf8_ssid=1
auth_algs=1
hw_mode=g
channel=1
country_code=US
ieee80211d=1
ieee80211h=1
ieee80211n=1




ignore_broadcast_ssid=1


vendor_elements=dd04f4f5e801dd0df4f5e803544553545f53534944

ht_capab=[HT20][RX-STBC1]

"""

_HOSTAPD_CONFIG_WPA = """wpa=2
wpa_passphrase=asdfqwer
wpa_key_mgmt=WPA-PSK
wpa_pairwise=CCMP
"""


class FakeOptDict(object):
  """A fake options.OptDict containing default values."""

  def __init__(self):
    self.band = '2.4 5'
    self.channel = 'auto'
    self.autotype = 'NONDFS'
    self.ssid = 'TEST_SSID'
    self.bssid = ''
    self.encryption = 'WPA2_PSK_AES'
    self.force_restart = False
    self.hidden_mode = False
    self.enable_wmm = False
    self.short_guard_interval = False
    self.protocols = 'a/b/g/n/ac'
    self.width = '20'
    self.bridge = ''
    self.extra_short_timeouts = False
    self.yottasecond_timeouts = False
    self.persist = False
    self.interface_suffix = ''
    self.client_isolation = False
    self.supports_provisioning = False
    self.no_band_restriction = False
    self.wds = False


def wpa_passphrase(ssid, passphrase):
  return configs.make_network_block(
      configs.wpa_network_lines(ssid, passphrase))


def wpa_passphrase_subprocess(ssid, passphrase):
  return subprocess.check_output(
      ('wpa_passphrase',
       utils.sanitize_ssid(ssid),
       utils.validate_and_sanitize_psk(passphrase)))


@wvtest.wvtest
def wpa_passphrase_test():
  """Make sure the configs we generate are the same as wpa_passphrase."""
  for testdata in (
      ('some ssid', 'some passphrase'),
      (r'some\ssid', 'some passphrase'),
      ('some ssid', r'some\passphrase'),
      ('some"ssid', 'some passphrase'),
      ('some ssid', 'some"passphrase')):
    got, want = wpa_passphrase(*testdata), wpa_passphrase_subprocess(*testdata)
    wvtest.WVPASSEQ(got, want)


@wvtest.wvtest
def wpa_raw_psk_test():
  """Make sure we do the right thing when we get a raw PSK too."""
  ssid = 'some ssid'
  psk = '41821f7ca3ea5d85beea7644ed7e0fefebd654177fa06c26fbdfdc3c599a317f'

  got = wpa_passphrase(ssid, psk)
  # hard code since `wpa_passphrase` dies if given a 64-character hex psk
  want = """network={
\tssid="some ssid"
\tpsk=41821f7ca3ea5d85beea7644ed7e0fefebd654177fa06c26fbdfdc3c599a317f
}
"""
  wvtest.WVPASSEQ(got, want)


# pylint: disable=protected-access
@wvtest.wvtest
def generate_hostapd_config_test():
  """Tests generate_hostapd_config."""
  unused_raii = experiment_testutils.MakeExperimentDirs()

  opt = FakeOptDict()

  # Test a simple case.
  config = configs.generate_hostapd_config(
      _PHY_INFO, 'wlan0', '2.4', '1', '20', set(('a', 'b', 'g', 'n', 'ac')),
      'asdfqwer', opt)
  wvtest.WVPASSEQ('\n'.join((_HOSTAPD_CONFIG,
                             _HOSTAPD_CONFIG_WPA,
                             '# Experiments: ()\n')), config)

  # Test bridge.
  default_bridge, opt.bridge = opt.bridge, 'br0'
  config = configs.generate_hostapd_config(
      _PHY_INFO, 'wlan0', '2.4', '1', '20', set(('a', 'b', 'g', 'n', 'ac')),
      'asdfqwer', opt)
  wvtest.WVPASSEQ('\n'.join((_HOSTAPD_CONFIG_BRIDGE,
                             _HOSTAPD_CONFIG_WPA,
                             '# Experiments: ()\n')),
                  config)
  opt.bridge = default_bridge

  # Test WDS.
  default_wds, opt.wds = opt.wds, True
  config = configs.generate_hostapd_config(
      _PHY_INFO, 'wlan0', '2.4', '1', '20', set(('a', 'b', 'g', 'n', 'ac')),
      'asdfqwer', opt)
  wvtest.WVPASSEQ('\n'.join((_HOSTAPD_CONFIG_WDS,
                             _HOSTAPD_CONFIG_WPA,
                             '# Experiments: ()\n')),
                  config)
  opt.wds = default_wds

  # Test provisioning IEs.
  default_hidden_mode, opt.hidden_mode = opt.hidden_mode, True
  default_supports_provisioning, opt.supports_provisioning = (
      opt.supports_provisioning, True)
  config = configs.generate_hostapd_config(
      _PHY_INFO, 'wlan0', '2.4', '1', '20', set(('a', 'b', 'g', 'n', 'ac')),
      'asdfqwer', opt)
  wvtest.WVPASSEQ('\n'.join((_HOSTAPD_CONFIG_PROVISION_VIA,
                             _HOSTAPD_CONFIG_WPA,
                             '# Experiments: ()\n')),
                  config)
  opt.hidden_mode = default_hidden_mode
  opt.supports_provisioning = default_supports_provisioning

  # Test with no encryption.
  default_encryption, opt.encryption = opt.encryption, 'NONE'
  config = configs.generate_hostapd_config(
      _PHY_INFO, 'wlan0', '2.4', '1', '20', set(('a', 'b', 'g', 'n', 'ac')),
      'asdfqwer', opt)
  wvtest.WVPASSEQ(_HOSTAPD_CONFIG + '\n# Experiments: ()\n', config)
  opt.encryption = default_encryption

  # Now enable extra short timeout intervals and the Wifi80211k experiment.
  experiment_testutils.enable('Wifi80211k')
  opt.extra_short_timeouts = 2
  new_config = '\n'.join((
      _HOSTAPD_CONFIG,
      _HOSTAPD_CONFIG_WPA,
      configs._EXPERIMENT_80211K_TPL.format(interface='wlan0'),
      configs._EXTRA_SHORT_TIMEOUTS2_TPL,
      '# Experiments: (Wifi80211k)\n'))
  config = configs.generate_hostapd_config(
      _PHY_INFO, 'wlan0', '2.4', '1', '20', set(('a', 'b', 'g', 'n', 'ac')),
      'asdfqwer', opt)
  wvtest.WVPASSEQ(new_config, config)

  opt.extra_short_timeouts = 1
  new_config = '\n'.join((
      _HOSTAPD_CONFIG,
      _HOSTAPD_CONFIG_WPA,
      configs._EXPERIMENT_80211K_TPL.format(interface='wlan0'),
      configs._EXTRA_SHORT_TIMEOUTS1_TPL,
      '# Experiments: (Wifi80211k)\n'))
  config = configs.generate_hostapd_config(
      _PHY_INFO, 'wlan0', '2.4', '1', '20', set(('a', 'b', 'g', 'n', 'ac')),
      'asdfqwer', opt)
  wvtest.WVPASSEQ(new_config, config)

  opt.extra_short_timeouts = 2
  opt.yottasecond_timeouts = 1
  new_config = '\n'.join((
      _HOSTAPD_CONFIG,
      _HOSTAPD_CONFIG_WPA,
      configs._EXPERIMENT_80211K_TPL.format(interface='wlan0'),
      configs._YOTTASECOND_TIMEOUTS_TPL,
      '# Experiments: (Wifi80211k)\n'))
  config = configs.generate_hostapd_config(
      _PHY_INFO, 'wlan0', '2.4', '1', '20', set(('a', 'b', 'g', 'n', 'ac')),
      'asdfqwer', opt)
  wvtest.WVPASSEQ(new_config, config)


@wvtest.wvtest
def create_vendor_ie_test():
  wvtest.WVPASSEQ(configs.create_vendor_ie('01'), 'dd04f4f5e801')
  wvtest.WVPASSEQ(configs.create_vendor_ie('03', 'GFiberSetupAutomation'),
                  'dd19f4f5e80347466962657253657475704175746f6d6174696f6e')


if __name__ == '__main__':
  wvtest.wvtest_main()
