conman:  Also prioritize open APs by OUI and RSSI.

Before this commit, open APs are only prioritized by presence of the
GFiber provisioning OUI.  Frenzy devices cannot see this, so OUI
serves as a proxy (it will be rare for an open GFiber AP not to
provide ACS access).  And further prioritizing by RSSI should be a
minor performance boost.

BUG=30376078

Change-Id: Ie59457e3f18fd097d1400ecd5e280e721459052f
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()