autotest: Add test support for 802.11ac

Update hostapd_config to support 802.11ac, and add a SimpleConnect test
for it as well. For routers without 802.11ac support, TEST_NA will be
raised.

Test is performed using Panther router with WP2, which only supports
channel width of 80MHz.

The support for 802.11ac AP for Panther requires test-ap-group build #10
or newer.

BUG=chromium:419930
TEST=Run network_WiFi_SimpleConnect.wifi_check5VHT80

Change-Id: I3fd6bc834a18d0b33af79061a8a3c2f35d0f88ec
Reviewed-on: https://chromium-review.googlesource.com/224118
Reviewed-by: Peter Qiu <[email protected]>
Tested-by: Peter Qiu <[email protected]>
Reviewed-by: Paul Stewart <[email protected]>
Commit-Queue: Peter Qiu <[email protected]>
diff --git a/client/common_lib/cros/network/iw_runner.py b/client/common_lib/cros/network/iw_runner.py
index 256fff2..be95ff2 100644
--- a/client/common_lib/cros/network/iw_runner.py
+++ b/client/common_lib/cros/network/iw_runner.py
@@ -46,7 +46,7 @@
 IwPhy = collections.namedtuple(
     'Phy', ['name', 'bands', 'modes', 'commands', 'max_scan_ssids',
             'avail_tx_antennas', 'avail_rx_antennas',
-            'supports_setting_antenna_mask'])
+            'supports_setting_antenna_mask', 'support_vht'])
 
 DEFAULT_COMMAND_IW = 'iw'
 
@@ -312,7 +312,8 @@
                             pending_phy_max_scan_ssids,
                             pending_phy_tx_antennas,
                             pending_phy_rx_antennas,
-                            pending_phy_tx_antennas and pending_phy_rx_antennas)
+                            pending_phy_tx_antennas and pending_phy_rx_antennas,
+                            pending_phy_support_vht)
             all_phys.append(new_phy)
 
         for line in output.splitlines():
@@ -327,6 +328,7 @@
                 pending_phy_max_scan_ssids = None
                 pending_phy_tx_antennas = 0
                 pending_phy_rx_antennas = 0
+                pending_phy_support_vht = False
                 continue
 
             match_section = re.match('\s*(\w.*):\s*$', line)
@@ -362,6 +364,11 @@
                     pending_phy_commands.append(command_match.group(1))
                     continue
 
+            if current_section.startswith('VHT Capabilities') and \
+                    pending_phy_name:
+                pending_phy_support_vht = True
+                continue
+
             match_avail_antennas = re.match('\s*Available Antennas: TX (\S+)'
                                             ' RX (\S+)', line)
             if match_avail_antennas and pending_phy_name:
diff --git a/server/cros/network/hostap_config.py b/server/cros/network/hostap_config.py
index bf6bac8..6e3ecc2 100644
--- a/server/cros/network/hostap_config.py
+++ b/server/cros/network/hostap_config.py
@@ -67,6 +67,8 @@
     MODE_11G = 'g'
     MODE_11N_MIXED = 'n-mixed'
     MODE_11N_PURE = 'n-only'
+    MODE_11AC_MIXED = 'ac-mixed'
+    MODE_11AC_PURE = 'ac-only'
 
     N_CAPABILITY_HT20 = object()
     N_CAPABILITY_HT40 = object()
@@ -83,6 +85,73 @@
                           N_CAPABILITY_SGI20,
                           N_CAPABILITY_SGI40]
 
+    AC_CAPABILITY_VHT160 = object()
+    AC_CAPABILITY_VHT160_80PLUS80 = object()
+    AC_CAPABILITY_RXLDPC = object()
+    AC_CAPABILITY_SHORT_GI_80 = object()
+    AC_CAPABILITY_SHORT_GI_160 = object()
+    AC_CAPABILITY_TX_STBC_2BY1 = object()
+    AC_CAPABILITY_RX_STBC_1 = object()
+    AC_CAPABILITY_RX_STBC_12 = object()
+    AC_CAPABILITY_RX_STBC_123 = object()
+    AC_CAPABILITY_RX_STBC_1234 = object()
+    AC_CAPABILITY_SU_BEAMFORMER = object()
+    AC_CAPABILITY_SU_BEAMFORMEE = object()
+    AC_CAPABILITY_BF_ANTENNA_2 = object()
+    AC_CAPABILITY_SOUNDING_DIMENSION_2 = object()
+    AC_CAPABILITY_MU_BEAMFORMER = object()
+    AC_CAPABILITY_MU_BEAMFORMEE = object()
+    AC_CAPABILITY_VHT_TXOP_PS = object()
+    AC_CAPABILITY_HTC_VHT = object()
+    AC_CAPABILITY_MAX_A_MPDU_LEN_EXP0 = object()
+    AC_CAPABILITY_MAX_A_MPDU_LEN_EXP1 = object()
+    AC_CAPABILITY_MAX_A_MPDU_LEN_EXP2 = object()
+    AC_CAPABILITY_MAX_A_MPDU_LEN_EXP3 = object()
+    AC_CAPABILITY_MAX_A_MPDU_LEN_EXP4 = object()
+    AC_CAPABILITY_MAX_A_MPDU_LEN_EXP5 = object()
+    AC_CAPABILITY_MAX_A_MPDU_LEN_EXP6 = object()
+    AC_CAPABILITY_MAX_A_MPDU_LEN_EXP7 = object()
+    AC_CAPABILITY_VHT_LINK_ADAPT2 = object()
+    AC_CAPABILITY_VHT_LINK_ADAPT3 = object()
+    AC_CAPABILITY_RX_ANTENNA_PATTERN = object()
+    AC_CAPABILITY_TX_ANTENNA_PATTERN = object()
+    AC_CAPABILITIES_MAPPING = {
+            AC_CAPABILITY_VHT160: '[VHT160]',
+            AC_CAPABILITY_VHT160_80PLUS80: '[VHT160_80PLUS80]',
+            AC_CAPABILITY_RXLDPC: '[RXLDPC]',
+            AC_CAPABILITY_SHORT_GI_80: '[SHORT_GI_80]',
+            AC_CAPABILITY_SHORT_GI_160: '[SHORT_GI_160]',
+            AC_CAPABILITY_TX_STBC_2BY1: '[TX_STBC_2BY1',
+            AC_CAPABILITY_RX_STBC_1: '[RX_STBC_1]',
+            AC_CAPABILITY_RX_STBC_12: '[RX_STBC_12]',
+            AC_CAPABILITY_RX_STBC_123: '[RX_STBC_123]',
+            AC_CAPABILITY_RX_STBC_1234: '[RX_STBC_1234]',
+            AC_CAPABILITY_SU_BEAMFORMER: '[SU_BEAMFORMER]',
+            AC_CAPABILITY_SU_BEAMFORMEE: '[SU_BEAMFORMEE]',
+            AC_CAPABILITY_BF_ANTENNA_2: '[BF_ANTENNA_2]',
+            AC_CAPABILITY_SOUNDING_DIMENSION_2: '[SOUNDING_DIMENSION_2]',
+            AC_CAPABILITY_MU_BEAMFORMER: '[MU_BEAMFORMER]',
+            AC_CAPABILITY_MU_BEAMFORMEE: '[MU_BEAMFORMEE]',
+            AC_CAPABILITY_VHT_TXOP_PS: '[VHT_TXOP_PS]',
+            AC_CAPABILITY_HTC_VHT: '[HTC_VHT]',
+            AC_CAPABILITY_MAX_A_MPDU_LEN_EXP0: '[MAX_A_MPDU_LEN_EXP0]',
+            AC_CAPABILITY_MAX_A_MPDU_LEN_EXP1: '[MAX_A_MPDU_LEN_EXP1]',
+            AC_CAPABILITY_MAX_A_MPDU_LEN_EXP2: '[MAX_A_MPDU_LEN_EXP2]',
+            AC_CAPABILITY_MAX_A_MPDU_LEN_EXP3: '[MAX_A_MPDU_LEN_EXP3]',
+            AC_CAPABILITY_MAX_A_MPDU_LEN_EXP4: '[MAX_A_MPDU_LEN_EXP4]',
+            AC_CAPABILITY_MAX_A_MPDU_LEN_EXP5: '[MAX_A_MPDU_LEN_EXP5]',
+            AC_CAPABILITY_MAX_A_MPDU_LEN_EXP6: '[MAX_A_MPDU_LEN_EXP6]',
+            AC_CAPABILITY_MAX_A_MPDU_LEN_EXP7: '[MAX_A_MPDU_LEN_EXP7]',
+            AC_CAPABILITY_VHT_LINK_ADAPT2: '[VHT_LINK_ADAPT2]',
+            AC_CAPABILITY_VHT_LINK_ADAPT3: '[VHT_LINK_ADAPT3]',
+            AC_CAPABILITY_RX_ANTENNA_PATTERN: '[RX_ANTENNA_PATTERN]',
+            AC_CAPABILITY_TX_ANTENNA_PATTERN: '[TX_ANTENNA_PATTERN]'}
+
+    VHT_CHANNEL_WIDTH_40 = object()
+    VHT_CHANNEL_WIDTH_80 = object()
+    VHT_CHANNEL_WIDTH_160 = object()
+    VHT_CHANNEL_WIDTH_80_80 = object()
+
     # This is a loose merging of the rules for US and EU regulatory
     # domains as taken from IEEE Std 802.11-2012 Appendix E.  For instance,
     # we tolerate HT40 in channels 149-161 (not allowed in EU), but also
@@ -185,6 +254,17 @@
 
 
     @property
+    def _hostapd_vht_capabilities(self):
+        """@return string suitable for the vht_capab= line in a hostapd config.
+        """
+        ret = []
+        for cap in self.AC_CAPABILITIES_MAPPING.keys():
+            if cap in self._ac_capabilities:
+                ret.append(self.AC_CAPABILITIES_MAPPING[cap])
+        return ''.join(ret)
+
+
+    @property
     def _require_ht(self):
         """@return True iff clients should be required to support HT."""
         # TODO(wiley) Why? (crbug.com/237370)
@@ -194,6 +274,12 @@
 
 
     @property
+    def _require_vht(self):
+        """@return True iff clients should be required to support VHT."""
+        return self._mode == self.MODE_11AC_PURE
+
+
+    @property
     def _hw_mode(self):
         """@return string hardware mode understood by hostapd."""
         if self._mode == self.MODE_11A:
@@ -202,7 +288,7 @@
             return self.MODE_11B
         if self._mode == self.MODE_11G:
             return self.MODE_11G
-        if self._mode in (self.MODE_11N_MIXED, self.MODE_11N_PURE):
+        if self._is_11n or self.is_11ac:
             # For their own historical reasons, hostapd wants it this way.
             if self._frequency > 5000:
                 return self.MODE_11A
@@ -219,6 +305,12 @@
 
 
     @property
+    def is_11ac(self):
+        """@return True iff we're trying to host an 802.11ac network."""
+        return self._mode in (self.MODE_11AC_MIXED, self.MODE_11AC_PURE)
+
+
+    @property
     def channel(self):
         """@return int channel number for self.frequency."""
         return self.get_channel_for_frequency(self.frequency)
@@ -337,7 +429,10 @@
                  dtim_period=None, frag_threshold=None, ssid=None, bssid=None,
                  force_wmm=None, security_config=None,
                  pmf_support=PMF_SUPPORT_DISABLED,
-                 obss_interval=None):
+                 obss_interval=None,
+                 vht_channel_width=None,
+                 vht_center_channel=None,
+                 ac_capabilities=[]):
         """Construct a HostapConfig.
 
         You may specify channel or frequency, but not both.  Both options
@@ -361,6 +456,9 @@
             client supports/must support 802.11w.
         @param obss_interval int interval in seconds that client should be
             required to do background scans for overlapping BSSes.
+        @param vht_channel_width object channel width
+        @param vht_center_channel int center channel of segment 0.
+        @param ac_capabilities list of AC_CAPABILITY_x defined above.
 
         """
         super(HostapConfig, self).__init__()
@@ -412,6 +510,20 @@
         self._security_config = (copy.copy(security_config) or
                                 xmlrpc_security_types.SecurityConfig())
         self._obss_interval = obss_interval
+        if vht_channel_width == self.VHT_CHANNEL_WIDTH_40:
+            self._vht_oper_chwidth = 0
+        elif vht_channel_width == self.VHT_CHANNEL_WIDTH_80:
+            self._vht_oper_chwidth = 1
+        elif vht_channel_width == self.VHT_CHANNEL_WIDTH_160:
+            self._vht_oper_chwidth = 2
+        elif vht_channel_width == self.VHT_CHANNEL_WIDTH_80_80:
+            self._vht_oper_chwidth = 3
+        elif vht_channel_width is not None:
+            raise error.TestFail('Invalid channel width')
+        # TODO(zqiu) Add checking for center channel based on the channel width
+        # and operating channel.
+        self._vht_oper_centr_freq_seg0_idx = vht_center_channel
+        self._ac_capabilities = set(ac_capabilities)
 
 
     def __repr__(self):
@@ -504,13 +616,21 @@
         conf['hw_mode'] = self._hw_mode
         if self._hide_ssid:
             conf['ignore_broadcast_ssid'] = 1
-        if self._is_11n:
+        if self._is_11n or self.is_11ac:
             conf['ieee80211n'] = 1
             conf['ht_capab'] = self._hostapd_ht_capabilities
+        if self.is_11ac:
+            conf['ieee80211ac'] = 1
+            conf['vht_oper_chwidth'] = self._vht_oper_chwidth
+            conf['vht_oper_centr_freq_seg0_idx'] = \
+                    self._vht_oper_centr_freq_seg0_idx
+            conf['vht_capab'] = self._hostapd_vht_capabilities
         if self._wmm_enabled:
             conf['wmm_enabled'] = 1
         if self._require_ht:
             conf['require_ht'] = 1
+        if self._require_vht:
+            conf['require_vht'] = 1
         if self._beacon_interval:
             conf['beacon_int'] = self._beacon_interval
         if self._dtim_period:
diff --git a/server/site_linux_system.py b/server/site_linux_system.py
index 3739a55..4e519fe 100644
--- a/server/site_linux_system.py
+++ b/server/site_linux_system.py
@@ -33,6 +33,7 @@
     CAPABILITY_IBSS = 'ibss_supported'
     CAPABILITY_SEND_MANAGEMENT_FRAME = 'send_management_frame'
     CAPABILITY_TDLS = 'tdls'
+    CAPABILITY_VHT = 'vht'
 
 
     @property
@@ -178,6 +179,8 @@
         for phy in self.phy_list:
             if 'tdls_mgmt' in phy.commands or 'tdls_oper' in phy.commands:
                 caps.add(self.CAPABILITY_TDLS)
+            if phy.support_vht:
+                caps.add(self.CAPABILITY_VHT)
         return caps
 
 
diff --git a/server/site_tests/network_WiFi_SimpleConnect/control.wifi_check5VHT80 b/server/site_tests/network_WiFi_SimpleConnect/control.wifi_check5VHT80
new file mode 100644
index 0000000..dad5c7d
--- /dev/null
+++ b/server/site_tests/network_WiFi_SimpleConnect/control.wifi_check5VHT80
@@ -0,0 +1,43 @@
+# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+AUTHOR = 'zqiu, wiley, pstew, quiche'
+NAME = 'network_WiFi_SimpleConnect.wifi_check5VHT80'
+TIME = 'SHORT'
+TEST_TYPE = 'Server'
+SUITE = 'wifi_matfunc, wifi_correctness_cros_core, wifi_release'
+DEPENDENCIES = 'wificell'
+
+DOC = """
+This test verifies that DUT can connect to an open 802.11ac network
+on channel 36 with center channel of 42 and channel width of 80MHz.
+"""
+
+
+from autotest_lib.client.common_lib.cros.network import xmlrpc_datatypes
+from autotest_lib.server.cros.network import hostap_config
+
+
+def run(machine):
+    n_caps = [hostap_config.HostapConfig.N_CAPABILITY_HT40_PLUS]
+    ac_caps = [hostap_config.HostapConfig.AC_CAPABILITY_SHORT_GI_80]
+    ac_mode = hostap_config.HostapConfig.MODE_11AC_MIXED
+    channel_width_80_mhz = hostap_config.HostapConfig.VHT_CHANNEL_WIDTH_80
+    configurations = [(hostap_config.HostapConfig(
+                                  channel=36,
+                                  mode=ac_mode,
+                                  n_capabilities=n_caps,
+                                  vht_channel_width=channel_width_80_mhz,
+                                  vht_center_channel=42,
+                                  ac_capabilities=ac_caps),
+                       xmlrpc_datatypes.AssociationParameters())]
+    host = hosts.create_host(machine)
+    job.run_test('network_WiFi_SimpleConnect',
+                 tag=NAME.split('.')[1],
+                 host=host,
+                 raw_cmdline_args=args,
+                 additional_params=configurations)
+
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/network_WiFi_SimpleConnect/network_WiFi_SimpleConnect.py b/server/site_tests/network_WiFi_SimpleConnect/network_WiFi_SimpleConnect.py
index 63522b6..91d9b0f 100644
--- a/server/site_tests/network_WiFi_SimpleConnect/network_WiFi_SimpleConnect.py
+++ b/server/site_tests/network_WiFi_SimpleConnect/network_WiFi_SimpleConnect.py
@@ -4,6 +4,8 @@
 
 import logging
 
+from autotest_lib.client.common_lib import error
+from autotest_lib.server import site_linux_system
 from autotest_lib.server.cros.network import wifi_cell_test_base
 
 
@@ -25,6 +27,12 @@
     def run_once(self):
         """Sets up a router, connects to it, pings it, and repeats."""
         for router_conf, client_conf in self._configurations:
+            if router_conf.is_11ac:
+                router_caps = self.context.router.capabilities
+                if site_linux_system.LinuxSystem.CAPABILITY_VHT not in \
+                        router_caps:
+                    raise error.TestNAError('Router does not have AC support')
+
             self.context.configure(router_conf)
             self.context.router.start_capture(
                     router_conf.frequency,