Merge "conman:  Also prioritize open APs by OUI and RSSI."
diff --git a/conman/connection_manager.py b/conman/connection_manager.py
index 7e2b24b..6ccdaa2 100755
--- a/conman/connection_manager.py
+++ b/conman/connection_manager.py
@@ -23,9 +23,6 @@
 import iw
 import status
 
-GFIBER_OUIS = ['f4:f5:e8']
-VENDOR_IE_FEATURE_ID_AUTOPROVISIONING = '01'
-
 
 class FileChangeHandler(pyinotify.ProcessEvent):
   """Connects pyinotify events to ConnectionManager."""
@@ -657,10 +654,8 @@
     subprocess.call(self.IFUP + [wifi.name])
     # /bin/wifi takes a --band option but then finds the right interface for it,
     # so it's okay to just pick the first band here.
-    with_ie, without_ie = self._find_bssids(wifi.bands[0])
+    items = self._find_bssids(wifi.bands[0])
     logging.info('Done scanning on %s', wifi.name)
-    items = [(bss_info, 3) for bss_info in with_ie]
-    items += [(bss_info, 1) for bss_info in without_ie]
     if not hasattr(wifi, 'cycler'):
       wifi.cycler = cycler.AgingPriorityCycler(
           cycle_length_s=self._bssid_cycle_length_s)
@@ -670,13 +665,8 @@
     wifi.cycler.update(items)
 
   def _find_bssids(self, band):
-    def supports_autoprovisioning(oui, vendor_ie):
-      if oui not in GFIBER_OUIS:
-        return False
-
-      return vendor_ie.startswith(VENDOR_IE_FEATURE_ID_AUTOPROVISIONING)
-
-    return iw.find_bssids(band, supports_autoprovisioning, False)
+    """Wrapper used as a unit testing seam."""
+    return iw.find_bssids(band, False)
 
   def _try_next_bssid(self, wifi):
     """Attempt to connect to the next BSSID in wifi's BSSID cycler.
diff --git a/conman/iw.py b/conman/iw.py
index 300c3e2..fd56e32 100755
--- a/conman/iw.py
+++ b/conman/iw.py
@@ -17,8 +17,13 @@
     return ''
 
 
+GFIBER_OUIS = ['f4:f5:e8']
+VENDOR_IE_FEATURE_ID_AUTOPROVISIONING = '01'
+
+
 _BSSID_RE = r'BSS (?P<BSSID>([0-9a-f]{2}:?){6})\(on .*\)'
 _SSID_RE = r'SSID: (?P<SSID>.*)'
+_RSSI_RE = r'signal: (?P<RSSI>.*) dBm'
 _VENDOR_IE_RE = (r'Vendor specific: OUI (?P<OUI>([0-9a-f]{2}:?){3}), '
                  'data:(?P<data>( [0-9a-f]{2})+)')
 
@@ -26,15 +31,17 @@
 class BssInfo(object):
   """Contains info about a BSS, parsed from 'iw scan'."""
 
-  def __init__(self, bssid='', ssid='', security=None, vendor_ies=None):
+  def __init__(self, bssid='', ssid='', rssi=-100, security=None,
+               vendor_ies=None):
     self.bssid = bssid
     self.ssid = ssid
+    self.rssi = rssi
     self.vendor_ies = vendor_ies or []
     self.security = security or []
 
   def __attrs(self):
     return (self.bssid, self.ssid, tuple(sorted(self.vendor_ies)),
-            tuple(sorted(self.security)))
+            tuple(sorted(self.security)), self.rssi)
 
   def __eq__(self, other):
     # pylint: disable=protected-access
@@ -70,6 +77,10 @@
     if match:
       bss_info.ssid = match.group('SSID')
       continue
+    match = re.match(_RSSI_RE, line)
+    if match:
+      bss_info.rssi = float(match.group('RSSI'))
+      continue
     match = re.match(_VENDOR_IE_RE, line)
     if match:
       bss_info.vendor_ies.append((match.group('OUI'),
@@ -88,22 +99,20 @@
   return result
 
 
-def find_bssids(band, vendor_ie_function, include_secure):
+def find_bssids(band, include_secure):
   """Return information about interesting access points.
 
   Args:
     band:  The band on which to scan.
-    vendor_ie_function:  A function that takes a vendor IE and returns a bool.
     include_secure:  Whether to exclude secure networks.
 
   Returns:
-    Two lists of tuples of the form (SSID, BSSID info dict).  The first list has
-    BSSIDs which have a vendor IE accepted by vendor_ie_function, and the second
-    list has those which don't.
+    A list of (BSSID, priority) tuples, prioritizing BSSIDs with the GFiber
+    provisioning vendor IE > GFiber APs > other APs, and by RSSI within each
+    group.
   """
   parsed = scan_parsed(band)
-  result_with_ie = set()
-  result_without_ie = set()
+  bssids = set()
 
   for bss_info in parsed:
     if bss_info.security and not include_secure:
@@ -121,11 +130,16 @@
     if not bss_info.ssid and not bss_info.vendor_ies:
       bss_info.ssid = DEFAULT_GFIBERSETUP_SSID
 
-    for oui, data in bss_info.vendor_ies:
-      if vendor_ie_function(oui, data):
-        result_with_ie.add(bss_info)
-        break
-    else:
-      result_without_ie.add(bss_info)
+    bssids.add(bss_info)
 
-  return result_with_ie, result_without_ie
+  return [(bss_info, _bssid_priority(bss_info)) for bss_info in bssids]
+
+
+def _bssid_priority(bss_info):
+  result = 4 if bss_info.bssid[:8] in GFIBER_OUIS else 2
+  for oui, data in bss_info.vendor_ies:
+    if (oui in GFIBER_OUIS and
+        data.startswith(VENDOR_IE_FEATURE_ID_AUTOPROVISIONING)):
+      result = 5
+
+  return result + (100 + (max(bss_info.rssi, -100))) / 100.0
diff --git a/conman/iw_test.py b/conman/iw_test.py
index 3f80d6c..3bb3cf7 100755
--- a/conman/iw_test.py
+++ b/conman/iw_test.py
@@ -551,6 +551,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: 01
   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
 BSS f4:f5:e8:f1:36:43(on wcli0)
   TSF: 12499150000 usec (0d, 03:28:19)
@@ -629,49 +630,55 @@
 def find_bssids_test():
   """Test iw.find_bssids."""
   test_ie = ('00:11:22', '01 23 45 67')
+  provisioning_ie = ('f4:f5:e8', '01')
   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',
+                                 rssi=-60,
                                  security=['WEP'],
                                  vendor_ies=[test_ie])
   provisioning_bss_info = iw.BssInfo(ssid=iw.DEFAULT_GFIBERSETUP_SSID,
                                      bssid='94:b4:0f:f1:36:42',
-                                     vendor_ies=[test_ie, ssid_ie])
+                                     rssi=-66,
+                                     vendor_ies=[test_ie, provisioning_ie,
+                                                 ssid_ie])
   provisioning_bss_info_frenzy = iw.BssInfo(ssid=iw.DEFAULT_GFIBERSETUP_SSID,
-                                            bssid='f4:f5:e8:f1:36:43')
-
-  with_ie, without_ie = iw.find_bssids('wcli0', lambda o, d: o == '00:11:22',
-                                       True)
-
-  wvtest.WVPASSEQ(with_ie, set([short_scan_result, provisioning_bss_info]))
+                                            bssid='f4:f5:e8:f1:36:43',
+                                            rssi=-66)
 
   wvtest.WVPASSEQ(
-      without_ie,
-      set([provisioning_bss_info_frenzy,
-           iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:36:41'),
-           iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:3a:e1'),
-           iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:35:61'),
-           iw.BssInfo(ssid='Google', bssid='94:b4:0f:f1:36:40',
-                      security=['WPA2']),
-           iw.BssInfo(ssid='Google', bssid='94:b4:0f:f1:3a:e0',
-                      security=['WPA2']),
-           iw.BssInfo(ssid='Google', bssid='94:b4:0f:f1:35:60',
-                      security=['WPA2']),
-           iw.BssInfo(ssid='Google', bssid='94:b4:0f:f1:02:a0',
-                      security=['WPA2'])]))
+      set(iw.find_bssids('wcli0', True)),
+      set([(short_scan_result, 2.4),
+           (provisioning_bss_info, 5.34),
+           (provisioning_bss_info_frenzy, 4.34),
+           (iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:36:41', rssi=-67),
+            2.33),
+           (iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:3a:e1', rssi=-65),
+            2.35),
+           (iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:35:61', rssi=-38),
+            2.62),
+           (iw.BssInfo(ssid='Google', bssid='94:b4:0f:f1:36:40', rssi=-66,
+                       security=['WPA2']), 2.34),
+           (iw.BssInfo(ssid='Google', bssid='94:b4:0f:f1:3a:e0', rssi=-55,
+                       security=['WPA2']), 2.45),
+           (iw.BssInfo(ssid='Google', bssid='94:b4:0f:f1:35:60', rssi=-39,
+                       security=['WPA2']), 2.61),
+           (iw.BssInfo(ssid='Google', bssid='94:b4:0f:f1:02:a0', rssi=-54,
+                       security=['WPA2']), 2.46)]))
 
-  with_ie, without_ie = iw.find_bssids('wcli0', lambda o, d: o == '00:11:22',
-                                       False)
-  wvtest.WVPASSEQ(with_ie, set([provisioning_bss_info]))
   wvtest.WVPASSEQ(
-      without_ie,
-      set([provisioning_bss_info_frenzy,
-           iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:36:41'),
-           iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:3a:e1'),
-           iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:35:61')]))
+      set(iw.find_bssids('wcli0', False)),
+      set([(provisioning_bss_info, 5.34),
+           (provisioning_bss_info_frenzy, 4.34),
+           (iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:36:41', rssi=-67),
+            2.33),
+           (iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:3a:e1', rssi=-65),
+            2.35),
+           (iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:35:61', rssi=-38),
+            2.62)]))
 
 if __name__ == '__main__':
   wvtest.wvtest_main()