autotest: Convert network_WiFiMatFunc/012CheckMaskedBSSID

Rewrite network_WiFiMatFunc/012CheckMaskedBSSID to use the new WiFi
framework.  In the process, move the logic to trigger a scan over to
WiFiClient.  Also expand hostapd_configure in site_linux_router to
handle SSID and BSSID configuration settings.

TEST=The following:
./run_remote_tests.sh --remote=chromeos1-shelf1-host4 wifi_masked_bssid
(Fails with TestNA because this is a RSPro cell)
./run_remote_tests.sh --remote=chromeos1-shelf1-host3 wifi_masked_bssid
(Passes)
./run_remote_tests.sh --remote=chromeos1-shelf1-host3 \
    network_WiFiRoaming --args='test_pat=006*'
(Passes; Verifies that scanning is working for older site_wifitest tests)
BUG=chromium:230660

Change-Id: I95ffbeb2cc0361fb98289fc8623fe6dfe33eb25d
Reviewed-on: https://gerrit.chromium.org/gerrit/50557
Commit-Queue: Christopher Wiley <wiley@chromium.org>
Reviewed-by: Christopher Wiley <wiley@chromium.org>
Tested-by: Christopher Wiley <wiley@chromium.org>
diff --git a/server/cros/wlan/hostap_config.py b/server/cros/wlan/hostap_config.py
index 631e112..a1c7ead 100644
--- a/server/cros/wlan/hostap_config.py
+++ b/server/cros/wlan/hostap_config.py
@@ -114,7 +114,7 @@
 
     def __init__(self, mode=None, channel=None, frequency=None,
                  n_capabilities=None, hide_ssid=None, beacon_interval=None,
-                 dtim_period=None, frag_threshold=None):
+                 dtim_period=None, frag_threshold=None, ssid=None, bssid=None):
         """Construct a HostapConfig.
 
         You may specify channel or frequency, but not both.  Both options
@@ -129,6 +129,9 @@
         @param beacon_interval int beacon interval of AP.
         @param dtim_period int include a DTIM every |dtim_period| beacons.
         @param frag_threshold int maximum outgoing data frame size.
+        @param ssid string up to 32 byte SSID overriding the router default.
+        @param bssid string like 00:11:22:33:44:55.
+
         """
         super(HostapConfig, self).__init__()
         if channel is not None and frequency is not None:
@@ -208,3 +211,8 @@
         self.beacon_interval = beacon_interval
         self.dtim_period = dtim_period
         self.frag_threshold = frag_threshold
+        if len(ssid) > 32:
+            raise error.TestFail('Tried to specify SSID that was too long.')
+
+        self.ssid = ssid
+        self.bssid = bssid
diff --git a/server/cros/wlan/wifi_client.py b/server/cros/wlan/wifi_client.py
index 23fb4c8..a637cb8 100644
--- a/server/cros/wlan/wifi_client.py
+++ b/server/cros/wlan/wifi_client.py
@@ -455,3 +455,25 @@
                                  wording)
 
         logging.debug('Power save is indeed %s.', wording)
+
+
+    def scan(self, frequencies, ssids):
+        """Request a scan and check that certain SSIDs appear in the results.
+
+        @param frequencies list of int WiFi frequencies to scan for.
+        @param ssids list of string ssids to probe request for.
+
+        """
+        scan_params = ''
+        if frequencies:
+           scan_params += ' freq %s' % ' '.join(map(str, frequencies))
+        if ssids:
+           scan_params += ' ssid "%s"' % '" "'.join(ssids)
+        result = self.host.run('%s dev %s scan%s' % (self.command_iw,
+                                                     self.wifi_if,
+                                                     scan_params))
+        scan_lines = result.stdout.splitlines()
+        for ssid in ssids:
+            if ssid and ('\tSSID: %s' % ssid) not in scan_lines:
+                raise error.TestFail('SSID %s is not in scan results: %s' %
+                                     (ssid, result.stdout))
diff --git a/server/cros/wlan/wifi_test_context_manager.py b/server/cros/wlan/wifi_test_context_manager.py
index c754730..3c5c1f8 100644
--- a/server/cros/wlan/wifi_test_context_manager.py
+++ b/server/cros/wlan/wifi_test_context_manager.py
@@ -122,16 +122,19 @@
         return self.server.wifi_ip
 
 
-    def configure(self, configuration_parameters):
+    def configure(self, configuration_parameters, multi_interface=None):
         """Configure a router with the given parameters.
 
         Configures an AP according to the specified parameters and
-        enables whatever packet captures are appropriate.
+        enables whatever packet captures are appropriate.  Will deconfigure
+        existing APs unless |multi_interface| is specified.
 
         @param configuration_parameters HostapConfig object.
+        @param multi_interface True iff having multiple configured interfaces
+                is expected for this configure call.
 
         """
-        self.router.hostap_configure(configuration_parameters)
+        self.router.hostap_configure(configuration_parameters, multi_interface)
         if self._enable_client_packet_captures:
             self.client.start_capture()
         if self._enable_router_packet_captures:
diff --git a/server/site_linux_router.py b/server/site_linux_router.py
index 84f3c86..0961e2a 100644
--- a/server/site_linux_router.py
+++ b/server/site_linux_router.py
@@ -252,6 +252,11 @@
         # Start with the default hostapd config parameters.
         conf = self.__get_default_hostap_config()
         conf['ssid'] = (self.defssid + configuration.ssid_suffix)[-32:]
+        if configuration.ssid:
+            # Let test writers overwrite the default SSID.
+            conf['ssid'] = configuration.ssid
+        if configuration.bssid:
+            conf['bssid'] = configuration.bssid
         conf['channel'] = configuration.channel
         self.hostapd['frequency'] = configuration.frequency
         conf['hw_mode'] = configuration.hw_mode
diff --git a/server/site_linux_system.py b/server/site_linux_system.py
index 08d172c..c84afbd 100644
--- a/server/site_linux_system.py
+++ b/server/site_linux_system.py
@@ -32,10 +32,9 @@
 
         self._capabilities = []
         phymap = self.phys_for_frequency
-        frequencies = phymap.iterkeys()
-        if [freq for freq in frequencies if freq > 5000]:
+        if [freq for freq in phymap.iterkeys() if freq > 5000]:
             self._capabilities.append(self.CAPABILITY_5GHZ)
-        if [freq for freq in frequencies if len(phymap[freq]) > 1]:
+        if [freq for freq in phymap.iterkeys() if len(phymap[freq]) > 1]:
             self._capabilities.append(self.CAPABILITY_MULTI_AP_SAME_BAND)
             self._capabilities.append(self.CAPABILITY_MULTI_AP)
         elif len(self.phy_bus_type) > 1:
@@ -121,7 +120,7 @@
             elif '/pci' in devpath:
                 phybus = 'pci'
             phy_bus_type[phy] = phybus
-
+        logging.debug('Got phys for frequency: %r', phys_for_frequency)
         return phys_for_frequency, phy_bus_type
 
 
@@ -301,3 +300,24 @@
         for phy, wlanif_i, phytype in self.wlanifs_in_use:
             if wlanif_i == wlanif:
                  self.wlanifs_in_use.remove((phy, wlanif, phytype))
+
+
+    def require_capabilities(self, requirements, fatal_failure=False):
+        """Require capabilities of this LinuxSystem.
+
+        Check that capabilities in |requirements| exist on this system.
+        Raise and exception to skip but not fail the test if said
+        capabilities are not found.  Pass |fatal_failure| to cause this
+        error to become a test failure.
+
+        @param requirements list of CAPABILITY_* defined above.
+        @param fatal_failure bool True iff failures should be fatal.
+
+        """
+        to_be_raised = error.TestNAError
+        if fatal_failure:
+            to_be_raised = error.TestFail
+        missing = [cap for cap in requirements if not cap in self.capabilities]
+        if missing:
+            raise to_be_raised('AP on %s is missing required capabilites: %r' %
+                               (self.role, missing))
diff --git a/server/site_tests/network_WiFiMatFunc/012CheckMaskedBSSID b/server/site_tests/network_WiFiMatFunc/012CheckMaskedBSSID
deleted file mode 100644
index 2f59ef6..0000000
--- a/server/site_tests/network_WiFiMatFunc/012CheckMaskedBSSID
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright (c) 2013 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.
-
-# This test is designed to check that we can successfully scan for two
-# different SSIDs which respond with the same BSSID.  Some client
-# firmwares may hold only one scan entry per BSSID and will foil attempts
-# to connect to certain types of APs.
-
-{ "name":"MaskedBSSID",
-  "steps":[
-    [ "create",         { "type":"hostap" } ],
-
-    # Create an AP, manually specifying both the SSID and BSSID.
-    [ "config",         { "channel":"2412", "mode":"11b",
-                          "bssid":"00:01:02:03:04:05",
-                          "ssid": "CrOS_Masked1" } ],
-
-    # Create a second AP that responds to probe requests with the same BSSID
-    # but an different SSID.  These APs together are meant to emulate
-    # situations that occur with some types of APs which broadcast or
-    # respond with more than one (non-empty) SSID.
-    [ "config",         { "channel":"2412", "mode":"11b",
-                          "bssid":"00:01:02:03:04:05", "multi_interface":None,
-                          "ssid": "CrOS_Masked2" } ],
-
-    # We cannot connect to this AP, since there are two separate APs that
-    # respond to the same BSSID, but we can test to make sure both SSIDs
-    # appears in the scan.
-    [ "scan",           { "freq": [ "2412" ],
-                          "ssid": [ "CrOS_Masked1", "CrOS_Masked2" ] } ],
-
-    [ "destroy" ],
-  ],
-}
diff --git a/server/site_tests/network_WiFi_MaskedBSSID/control.wifi_masked_bssid b/server/site_tests/network_WiFi_MaskedBSSID/control.wifi_masked_bssid
new file mode 100644
index 0000000..31b326e
--- /dev/null
+++ b/server/site_tests/network_WiFi_MaskedBSSID/control.wifi_masked_bssid
@@ -0,0 +1,25 @@
+# Copyright (c) 2013 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 = 'wiley@chromium.com'
+NAME = 'network_WiFi_MaskedBSSID'
+TIME = 'SHORT'
+TEST_TYPE = 'Server'
+
+DOC = """
+This test is designed to check that we can successfully scan for two
+different SSIDs which respond with the same BSSID.  Some client
+firmwares may hold only one scan entry per BSSID and will foil attempts
+to connect to certain types of APs.
+"""
+
+
+def run(machine):
+    host = hosts.create_host(machine)
+    job.run_test('network_WiFi_MaskedBSSID',
+                 host=host,
+                 raw_cmdline_args=args)
+
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/network_WiFi_MaskedBSSID/network_WiFi_MaskedBSSID.py b/server/site_tests/network_WiFi_MaskedBSSID/network_WiFi_MaskedBSSID.py
new file mode 100644
index 0000000..1a5b653
--- /dev/null
+++ b/server/site_tests/network_WiFi_MaskedBSSID/network_WiFi_MaskedBSSID.py
@@ -0,0 +1,46 @@
+# Copyright (c) 2013 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.
+
+from autotest_lib.server import site_linux_system
+from autotest_lib.server.cros.wlan import hostap_config
+from autotest_lib.server.cros.wlan import wifi_cell_test_base
+
+
+class network_WiFi_MaskedBSSID(wifi_cell_test_base.WiFiCellTestBase):
+    """Test behavior around masked BSSIDs."""
+    version = 1
+
+
+    def run_once_impl(self):
+        """Test body.
+
+        Set up two APs on the same channel/bssid but with different SSIDs.
+        Check that we can see both APs in scan results.
+
+        """
+        self.context.router.require_capabilities(
+                [site_linux_system.LinuxSystem.CAPABILITY_MULTI_AP_SAME_BAND])
+        frequency = 2412
+        configurations = [hostap_config.HostapConfig(
+                          frequency=frequency,
+                          mode=hostap_config.HostapConfig.MODE_11B,
+                          bssid='00:11:22:33:44:55',
+                          ssid=('CrOS_Masked%d' % i)) for i in range(2)]
+        # Create an AP, manually specifying both the SSID and BSSID.
+        self.context.configure(configurations[0])
+        # Create a second AP that responds to probe requests with the same BSSID
+        # but an different SSID.  These APs together are meant to emulate
+        # situations that occur with some types of APs which broadcast or
+        # respond with more than one (non-empty) SSID.
+        self.context.configure(configurations[1], multi_interface=True)
+        # We cannot connect to this AP, since there are two separate APs that
+        # respond to the same BSSID, but we can test to make sure both SSIDs
+        # appears in the scan.
+        self.context.client.scan([frequency],
+                                 [config.ssid for config in configurations])
+        self.context.router.deconfig()
+
+
+
+
diff --git a/server/site_wifitest.py b/server/site_wifitest.py
index dc3ce4a..320ac83 100644
--- a/server/site_wifitest.py
+++ b/server/site_wifitest.py
@@ -392,11 +392,8 @@
                 raise error.TestNAError(
                     "%s: client is missing required capability: %s" %
                     (self.name, requirement))
-        for requirement in self.router_requirements:
-            if not requirement in self.wifi.capabilities:
-                raise error.TestNAError(
-                    "%s: AP is missing required capability: %s" %
-                    (self.name, requirement))
+
+        self.wifi.require_capabilities(self.router_requirements)
 
         for step_number, s in enumerate(self.steps):
             method = s[0]
@@ -1745,22 +1742,18 @@
 
 
     def scan(self, params):
-        scan_params = ''
-        frequencies = params.get('freq', [])
-        if frequencies:
-           scan_params += ' freq %s' % ' '.join(frequencies)
+        """Ask the DUT to scan for SSIDs on frequencies.
+
+        |params| may contain 'freq' and 'ssid' keys, which should
+        map to lists of frequencies and ssids respectively.  Asserts
+        that we find all 'ssid' members listed.
+
+        @param params dict of settings for scan.
+
+        """
+        frequencies = map(int, params.get('freq', []))
         ssids = params.get('ssid', [])
-        if ssids:
-           scan_params += ' ssid "%s"' % '" "'.join(ssids)
-        result = self.client.run("%s dev %s scan%s" %
-                                 (self.client_proxy.command_iw,
-                                  self.client_wlanif,
-                                  scan_params))
-        scan_lines = result.stdout.splitlines()
-        for ssid in ssids:
-            if ssid and ('\tSSID: %s' % ssid) not in scan_lines:
-                raise error.TestFail('SSID %s is not in scan results: %s' %
-                                     (ssid, result.stdout))
+        self.client_proxy.scan(frequencies, ssids)
 
 
     def time_sync(self, params):