conman:  Get provisioning AP SSIDs from vendor IEs.

Provisioning network APs hide their SSIDs so as not to be visible to
users, instead putting their SSIDs in a vendor IE.  This commit
updates conman to read these SSIDs.

Change-Id: I1e9b4165d935c1ff22af7f06f8b4b03bb9c59d6d
diff --git a/conman/connection_manager_test.py b/conman/connection_manager_test.py
index 170b1fa..646d114 100755
--- a/conman/connection_manager_test.py
+++ b/conman/connection_manager_test.py
@@ -92,16 +92,19 @@
 RegDomain: 00
 """
 
-IW_SCAN_OUTPUT = """BSS 00:11:22:33:44:55(on wcli0)
+IW_SCAN_DEFAULT_OUTPUT = """BSS 00:11:22:33:44:55(on wcli0)
   SSID: s1
-  Vendor specific: OUI f4:f5:e8, data: 01
 BSS 66:77:88:99:aa:bb(on wcli0)
   SSID: s1
-  Vendor specific: OUI f4:f5:e8, data: 01
 BSS 01:23:45:67:89:ab(on wcli0)
   SSID: s2
 """
 
+IW_SCAN_HIDDEN_OUTPUT = """BSS ff:ee:dd:cc:bb:aa(on wcli0)
+  Vendor specific: OUI f4:f5:e8, data: 01
+  Vendor specific: OUI f4:f5:e8, data: 03 73 33
+"""
+
 
 @wvtest.wvtest
 def get_client_interfaces_test():
@@ -221,6 +224,8 @@
       self.interface_by_name(wifi)._initially_connected = True
 
     self.scan_has_results = False
+    self.scan_results_include_hidden = False
+    self.can_connect_to_s2 = True
 
   @property
   def IP_LINK(self):
@@ -245,37 +250,37 @@
 
     socket = os.path.join(self._wpa_control_interface, wifi.name)
 
-    if bss_info and bss_info.ssid == 's1':
+    def connect(connection_check_result):
       if wifi.attached():
         wifi.add_connected_event()
       else:
         open(socket, 'w')
-      wifi.set_connection_check_result('fail')
-      self.write_interface_status_file(wifi.name, '1')
+      wifi.set_connection_check_result(connection_check_result)
+      self.ifplugd_action(wifi.name, True)
+
+    if bss_info and bss_info.ssid == 's1':
+      connect('fail')
       return True
 
-    if bss_info and bss_info.ssid == 's2':
-      if wifi.attached():
-        wifi.add_connected_event()
-      else:
-        open(socket, 'w')
-      wifi.set_connection_check_result('restricted')
-      self.ifplugd_action(wifi.name, True)
+    if bss_info and bss_info.ssid == 's2' and self.can_connect_to_s2:
+      connect('succeed')
+      return True
+
+    if bss_info and bss_info.ssid == 's3':
+      connect('restricted')
       return True
 
     return False
 
-  def _wifi_stopclient(self, band):
-    super(ConnectionManager, self)._wifi_stopclient(band)
-    self.wifi_for_band(band).add_terminating_event()
-
   # pylint: disable=unused-argument,protected-access
   def _find_bssids(self, band):
     # Only wcli0 finds anything.
+    scan_output = ''
     if band in self.interface_by_name('wcli0').bands and self.scan_has_results:
-      iw._scan = lambda interface: IW_SCAN_OUTPUT
-    else:
-      iw._scan = lambda interface: ''
+      scan_output = IW_SCAN_DEFAULT_OUTPUT
+      if self.scan_results_include_hidden:
+        scan_output += IW_SCAN_HIDDEN_OUTPUT
+    iw._scan = lambda interface: scan_output
     return super(ConnectionManager, self)._find_bssids(band)
 
   def _update_wlan_configuration(self, wlan_configuration):
@@ -394,7 +399,7 @@
       """The actual test function."""
       run_duration_s = .01
       interface_update_period = 5
-      wifi_scan_period = 5
+      wifi_scan_period = 15
       wifi_scan_period_s = run_duration_s * wifi_scan_period
 
       # pylint: disable=protected-access
@@ -537,12 +542,39 @@
   # Wait for the connection to be processed.
   c.run_once()
   wvtest.WVPASS(c.acs())
-  wvtest.WVFAIL(c.internet())
+  wvtest.WVPASS(c.internet())
   wvtest.WVFAIL(c.client_up('2.4'))
   wvtest.WVPASS(c.wifi_for_band('2.4').current_route())
 
-  # Now, create a WLAN configuration which should be connected to.  Also, test
-  # that atomic writes/renames work.
+  # Now, create a WLAN configuration which should be connected to.
+  ssid = 'wlan'
+  psk = 'password'
+  c.write_wlan_config('2.4', ssid, psk)
+  c.disable_access_point('2.4')
+  c.run_once()
+  wvtest.WVPASS(c.client_up('2.4'))
+  wvtest.WVPASS(c.wifi_for_band('2.4').current_route())
+  wvtest.WVPASS(c.has_status_files([status.P.CONNECTED_TO_WLAN]))
+
+  # Now, remove the WLAN configuration and make sure we are disconnected.  Then
+  # disable the previously used ACS connection via s2, add the user's WLAN to
+  # the scan results, and scan again.  This time, the first SSID tried should be
+  # 's3', which is not present in the scan results but *is* advertised by the
+  # secure AP running the user's WLAN.
+  c.can_connect_to_s2 = False
+  c.scan_results_include_hidden = True
+  c.delete_wlan_config('2.4')
+  c.run_once()
+  wvtest.WVFAIL(c.has_status_files([status.P.CONNECTED_TO_WLAN]))
+  c.run_until_interface_update()
+  c.run_until_scan('2.4')
+  c.run_until_interface_update()
+  wvtest.WVPASS(c.has_status_files([status.P.CONNECTED_TO_OPEN]))
+  wvtest.WVPASSEQ(c.last_provisioning_attempt.ssid, 's3')
+  wvtest.WVPASSEQ(c.last_provisioning_attempt.bssid, 'ff:ee:dd:cc:bb:aa')
+
+  # Now, recreate the same WLAN configuration, which should be connected to.
+  # Also, test that atomic writes/renames work.
   ssid = 'wlan'
   psk = 'password'
   c.write_wlan_config('2.4', ssid, psk, atomic=True)
diff --git a/conman/iw.py b/conman/iw.py
index 7b40a80..b014220 100755
--- a/conman/iw.py
+++ b/conman/iw.py
@@ -6,6 +6,9 @@
 import subprocess
 
 
+FIBER_OUI = 'f4:f5:e8'
+
+
 def _scan(band, **kwargs):
   try:
     return subprocess.check_output(('wifi', 'scan', '-b', band), **kwargs)
@@ -36,6 +39,9 @@
     # pylint: disable=protected-access
     return self.__attrs() == other.__attrs()
 
+  def __ne__(self, other):
+    return not self.__eq__(other)
+
   def __hash__(self):
     return hash(self.__attrs())
 
@@ -101,6 +107,14 @@
   for bss_info in parsed:
     if bss_info.security and not include_secure:
       continue
+
+    for oui, data in bss_info.vendor_ies:
+      if oui == FIBER_OUI:
+        octets = data.split()
+        if octets[0] == '03' and not bss_info.ssid:
+          bss_info.ssid = ''.join(octets[1:]).decode('hex')
+          continue
+
     for oui, data in bss_info.vendor_ies:
       if vendor_ie_function(oui, data):
         result_with_ie.add(bss_info)
diff --git a/conman/iw_test.py b/conman/iw_test.py
index c069c91..9c259e8 100755
--- a/conman/iw_test.py
+++ b/conman/iw_test.py
@@ -486,6 +486,72 @@
      * BK: CW 15-1023, AIFSN 7
      * VI: CW 7-15, AIFSN 2, TXOP 3008 usec
      * VO: CW 3-7, AIFSN 2, TXOP 1504 usec
+BSS 94:b4:0f:f1:36:41(on wcli0)
+  TSF: 12499150000 usec (0d, 03:28:19)
+  freq: 2437
+  beacon interval: 100 TUs
+  capability: ESS Privacy ShortPreamble SpectrumMgmt ShortSlotTime RadioMeasure (0x1531)
+  signal: -66.00 dBm
+  last seen: 2350 ms ago
+  Information elements from Probe Response frame:
+  SSID:
+  Supported rates: 36.0* 48.0 54.0
+  DS Parameter set: channel 6
+  TIM: DTIM Count 0 DTIM Period 1 Bitmap Control 0x0 Bitmap[0] 0x0
+  Country: US Environment: Indoor/Outdoor
+    Channels [1 - 11] @ 36 dBm
+  Power constraint: 0 dB
+  TPC report: TX power: 3 dBm
+  ERP: <no flags>
+  BSS Load:
+     * station count: 0
+     * channel utilisation: 28/255
+     * available admission capacity: 27500 [*32us]
+  HT capabilities:
+    Capabilities: 0x19ad
+      RX LDPC
+      HT20
+      SM Power Save disabled
+      RX HT20 SGI
+      TX STBC
+      RX STBC 1-stream
+      Max AMSDU length: 7935 bytes
+      DSSS/CCK HT40
+    Maximum RX AMPDU length 65535 bytes (exponent: 0x003)
+    Minimum RX AMPDU time spacing: 4 usec (0x05)
+    HT RX MCS rate indexes supported: 0-23
+    HT TX MCS rate indexes are undefined
+  HT operation:
+     * primary channel: 6
+     * secondary channel offset: no secondary
+     * STA channel width: 20 MHz
+     * RIFS: 1
+     * HT protection: nonmember
+     * non-GF present: 1
+     * OBSS non-GF present: 1
+     * dual beacon: 0
+     * dual CTS protection: 0
+     * STBC beacon: 0
+     * L-SIG TXOP Prot: 0
+     * PCO active: 0
+     * PCO phase: 0
+  Overlapping BSS scan params:
+     * passive dwell: 20 TUs
+     * active dwell: 10 TUs
+     * channel width trigger scan interval: 300 s
+     * scan passive total per channel: 200 TUs
+     * scan active total per channel: 20 TUs
+     * BSS width channel transition delay factor: 5
+     * OBSS Scan Activity Threshold: 0.25 %
+  Extended capabilities: HT Information Exchange Supported, Extended Channel Switching, BSS Transition, 6
+  WMM:   * Parameter version 1
+     * u-APSD
+     * BE: CW 15-1023, AIFSN 3
+     * BK: CW 15-1023, AIFSN 7
+     * VI: CW 7-15, AIFSN 2, TXOP 3008 usec
+     * VO: CW 3-7, AIFSN 2, TXOP 1504 usec
+  Vendor specific: OUI 00:11:22, data: 01 23 45 67
+  Vendor specific: OUI f4:f5:e8, data: 03 47 46 69 62 65 72 53 65 74 75 70 41 75 74 6f 6d 61 74 69 6f 6e
 """
 
 
@@ -498,15 +564,23 @@
 @wvtest.wvtest
 def find_bssids_test():
   """Test iw.find_bssids."""
+  test_ie = ('00:11:22', '01 23 45 67')
+  ssid_ie = (
+      'f4:f5:e8',
+      '03 47 46 69 62 65 72 53 65 74 75 70 41 75 74 6f 6d 61 74 69 6f 6e',
+  )
   short_scan_result = iw.BssInfo(ssid='short scan result',
                                  bssid='00:23:97:57:f4:d8',
                                  security=['WEP'],
-                                 vendor_ies=[('00:11:22', '01 23 45 67')])
+                                 vendor_ies=[test_ie])
+  provisioning_bss_info = iw.BssInfo(ssid='GFiberSetupAutomation',
+                                     bssid='94:b4:0f:f1:36:41',
+                                     vendor_ies=[test_ie, ssid_ie])
 
   with_ie, without_ie = iw.find_bssids('wcli0', lambda o, d: o == '00:11:22',
                                        True)
 
-  wvtest.WVPASSEQ(with_ie, set([short_scan_result]))
+  wvtest.WVPASSEQ(with_ie, set([short_scan_result, provisioning_bss_info]))
 
   wvtest.WVPASSEQ(
       without_ie,
@@ -524,7 +598,7 @@
 
   with_ie, without_ie = iw.find_bssids('wcli0', lambda o, d: o == '00:11:22',
                                        False)
-  wvtest.WVPASSEQ(with_ie, set())
+  wvtest.WVPASSEQ(with_ie, set([provisioning_bss_info]))
   wvtest.WVPASSEQ(
       without_ie,
       set([iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:36:41'),