Merge "mmap: add mread command"
diff --git a/cmds/cpulog.c b/cmds/cpulog.c
index ff8498a..f58b17b 100644
--- a/cmds/cpulog.c
+++ b/cmds/cpulog.c
@@ -229,6 +229,8 @@
   if (optind < argc)
     usage_and_die(argv[0]);
 
+  setlinebuf(stdout);
+
   ticks_per_sec = sysconf(_SC_CLK_TCK);
 
   sleep(warmup_seconds);
diff --git a/cmds/wifi_files.c b/cmds/wifi_files.c
index b25e573..1d95683 100644
--- a/cmds/wifi_files.c
+++ b/cmds/wifi_files.c
@@ -609,6 +609,8 @@
     char buf[24];
     if (fgets(buf, sizeof(buf), fptr) != NULL)
       fprintf(wifi_show_handle, "  \"AutoType\": \"%s\"\n", buf);
+    fclose(fptr);
+    fptr = NULL;
   }
   fprintf(wifi_show_handle, "}\n");
 
diff --git a/cmds/wvtest/wvtest.sh b/cmds/wvtest/wvtest.sh
index 47b4366..19e0167 100644
--- a/cmds/wvtest/wvtest.sh
+++ b/cmds/wvtest/wvtest.sh
@@ -97,9 +97,9 @@
 	_wvfind_caller
 	_wvcheck $(_wvgetrv [ "$#" -eq 2 ]) "exactly 2 arguments"
 	echo "Comparing:" >&2
-	echo "$1" >&2
+	echo "$1" | sed -e 's/^/<<< /' >&2
 	echo "--" >&2
-	echo "$2" >&2
+	echo "$2" | sed -e 's/^/>>> /' >&2
 	_wvcheck $(_wvgetrv [ "$1" = "$2" ]) "'$1' = '$2'"
 }
 
@@ -109,9 +109,9 @@
 	_wvfind_caller
 	_wvcheck $(_wvgetrv [ "$#" -eq 2 ]) "exactly 2 arguments"
 	echo "Comparing:" >&2
-	echo "$1" >&2
+	echo "$1" | sed -e 's/^/<<< /' >&2
 	echo "--" >&2
-	echo "$2" >&2
+	echo "$2" | sed -e 's/^/>>> /' >&2
 	_wvcheck $(_wvgetrv [ "$1" != "$2" ]) "'$1' != '$2'"
 }
 
diff --git a/waveguide/waveguide.py b/waveguide/waveguide.py
index 36380bd..982bf23 100755
--- a/waveguide/waveguide.py
+++ b/waveguide/waveguide.py
@@ -51,6 +51,7 @@
 waveguide [options...]
 --
 high-power        This high-powered AP takes priority over low-powered ones
+tv-box            The AP is a TV Box; other TV Boxes should avoid it
 fake=             Create a fake instance with the given MAC address
 initial-scans=    Number of immediate full channel scans at startup [1]
 scan-interval=    Seconds between full channel scan cycles (0 to disable) [0]
@@ -197,7 +198,7 @@
 class WlanManager(object):
   """A class representing one wifi interface on the local host."""
 
-  def __init__(self, phyname, vdevname, high_power):
+  def __init__(self, phyname, vdevname, high_power, tv_box):
     self.phyname = phyname
     self.vdevname = vdevname
     self.mac = '\0\0\0\0\0\0'
@@ -206,6 +207,8 @@
     self.allowed_freqs = set()
     if high_power:
       self.flags |= wgdata.ApFlags.HighPower
+    if tv_box:
+      self.flags |= wgdata.ApFlags.TvBox
     self.bss_list = {}
     self.channel_survey_list = {}
     self.assoc_list = {}
@@ -383,13 +386,13 @@
       for b in peer_data[peer_mac_addr]:
         peer_ap = helpers.DecodeMAC(b.mac)
         txt = ('peer:%s|bssid:%s|freq:%d|cap:0x%x|phy:%d|reg:%s|rssi:%s'
-               '|last_seen:%d')
+               '|last_seen:%d|flags:0x%x')
         if all(c in string.printable for c in b.reg):
           reg = b.reg
         else:
           reg = ''
         s = txt % (peer_mac_addr, peer_ap, b.freq, b.cap, b.phy, reg, b.rssi,
-                   b.last_seen)
+                   b.last_seen, b.flags)
         peer_ap_list.append(s)
     content = '\n'.join(peer_ap_list)
     if PEER_AP_LIST_FILE[0]:
@@ -808,7 +811,7 @@
     return False
 
 
-def CreateManagers(managers, high_power):
+def CreateManagers(managers, high_power, tv_box):
   """Create WlanManager() objects, one per wifi interface."""
 
   def ParseDevList(errcode, stdout, stderr):
@@ -855,7 +858,8 @@
     for phy, dev in phy_devs.iteritems():
       if dev not in existing_devs:
         log.Debug('Creating wlan manager for (%r, %r)', phy, dev)
-        managers.append(WlanManager(phy, dev, high_power=high_power))
+        managers.append(WlanManager(phy, dev, high_power=high_power,
+                                    tv_box=tv_box))
 
   RunProc(callback=ParseDevList, args=['iw', 'dev'])
 
@@ -1055,12 +1059,13 @@
         # them.
         wlm = WlanManager(phyname='phy-%s' % fakemac[12:],
                           vdevname='wlan-%s' % fakemac[12:],
-                          high_power=opt.high_power)
+                          high_power=opt.high_power,
+                          tv_box=opt.tv_box)
         wlm.mac = helpers.EncodeMAC(fakemac)
         managers.append(wlm)
   else:
     # The list of managers is also refreshed occasionally in the main loop
-    CreateManagers(managers, high_power=opt.high_power)
+    CreateManagers(managers, high_power=opt.high_power, tv_box=opt.tv_box)
   if not managers:
     raise Exception('no wifi AP-mode devices found.  Try --fake.')
 
@@ -1118,7 +1123,7 @@
     if ((opt.tx_interval and now - last_sent > opt.tx_interval) or (
         opt.autochan_interval and now - last_autochan > opt.autochan_interval)):
       if not opt.fake:
-        CreateManagers(managers, high_power=opt.high_power)
+        CreateManagers(managers, high_power=opt.high_power, tv_box=opt.tv_box)
       for m in managers:
         m.UpdateStationInfo()
     if opt.tx_interval and now - last_sent > opt.tx_interval:
@@ -1150,10 +1155,17 @@
       for m, p in peers.values():
         seen_bss_peers = [bss for bss in p.seen_bss if bss.mac in peers]
         if p.me.mac in selfmacs: continue
-        seen_peers[helpers.DecodeMAC(p.me.mac)] = seen_bss_peers
-        for b in seen_bss_peers:
+        for i, b in enumerate(seen_bss_peers):
+          # `iw scan` can't see flags set in Waveguide.
+          # Join flags set in Waveguide with the results of the `iw scan`.
+          _, waveguide_peer = peers.get(b.mac)
+          if waveguide_peer:
+            # pylint:disable=protected-access
+            seen_bss_peers[i] = b._replace(flags=waveguide_peer.me.flags)
           if b.mac in selfmacs:
             bss_signal[helpers.DecodeMAC(p.me.mac)] = b.rssi
+
+        seen_peers[helpers.DecodeMAC(p.me.mac)] = seen_bss_peers
         self_signals[m.mac] = bss_signal
         peer_data[m.mac] = seen_peers
         log.Log('%s: APs=%-4d peer-APs=%s stations=%s',
diff --git a/waveguide/wgdata.py b/waveguide/wgdata.py
index 3b84839..044c8ad 100644
--- a/waveguide/wgdata.py
+++ b/waveguide/wgdata.py
@@ -35,6 +35,7 @@
   Can5G = 0x02          # device supports 5 GHz band
   Can_Mask = 0x0f       # mask of all bits referring to band capability
   HighPower = 0x10      # high-power device takes precedence over low-power
+  TvBox = 0x20          # tv boxes shouldn't connect to tv boxes (yet)
 
 
 PRE_FMT = '!4sB'
diff --git a/waveguide/wifiblaster_controller_test.py b/waveguide/wifiblaster_controller_test.py
index 9279b78..18b6564 100755
--- a/waveguide/wifiblaster_controller_test.py
+++ b/waveguide/wifiblaster_controller_test.py
@@ -15,6 +15,7 @@
 
 """Tests for WifiblasterController."""
 
+import glob
 import os
 import shutil
 import sys
@@ -103,7 +104,6 @@
   waveguide.WIFIBLASTER_DIR = tempfile.mkdtemp()
   oldpath = os.environ['PATH']
   oldtime = time.time
-  faketime = [-1]
 
   def FakeTime():
     faketime[0] += 1
@@ -115,106 +115,114 @@
     sys.path.insert(0, 'fake')
     waveguide.opt = Empty()
     waveguide.opt.status_dir = d
-    manager = waveguide.WlanManager(phyname='phy-22:22', vdevname='wlan-22:22',
-                                    high_power=True)
-    manager.UpdateStationInfo()
-    wc = waveguide.WifiblasterController([manager], d)
 
-    def WriteConfig(k, v):
-      open(os.path.join(d, 'wifiblaster.%s' % k), 'w').write(v)
+    for flags in [{'phyname': 'phy-22:22', 'vdevname': 'wlan-22:22',
+                   'high_power': True, 'tv_box': False},
+                  {'phyname': 'phy-22:22', 'vdevname': 'wlan-22:22',
+                   'high_power': False, 'tv_box': True}]:
+      # Reset time and config directory before every test run.
+      faketime = [-1]
+      for f in glob.glob(os.path.join(d, '*')):
+        os.remove(f)
 
-    WriteConfig('duration', '.1')
-    WriteConfig('enable', 'False')
-    WriteConfig('fraction', '10')
-    WriteConfig('interval', '10')
-    WriteConfig('rapidpolling', '10')
-    WriteConfig('size', '1470')
+      manager = waveguide.WlanManager(**flags)
+      manager.UpdateStationInfo()
+      wc = waveguide.WifiblasterController([manager], d)
 
-    # Disabled. No packet blasts should be run.
-    print manager.GetState()
-    for t in xrange(0, 100):
-      wc.Poll(t)
+      def WriteConfig(k, v):
+        open(os.path.join(d, 'wifiblaster.%s' % k), 'w').write(v)
 
-    def CountRuns():
-      try:
-        v = open('fake/wifiblaster.out').readlines()
-      except IOError:
-        return 0
-      else:
-        os.unlink('fake/wifiblaster.out')
-        return len(v)
+      WriteConfig('duration', '.1')
+      WriteConfig('enable', 'False')
+      WriteConfig('fraction', '10')
+      WriteConfig('interval', '10')
+      WriteConfig('rapidpolling', '10')
+      WriteConfig('size', '1470')
 
-    CountRuns()  # get rid of any leftovers
-    wvtest.WVPASSEQ(CountRuns(), 0)
+      # Disabled. No packet blasts should be run.
+      print manager.GetState()
+      for t in xrange(0, 100):
+        wc.Poll(t)
 
-    # The first packet blast should be one
-    # cycle later than the start time. This is not an implementation detail:
-    # it prevents multiple APs from running simultaneous packet blasts if
-    # packet blasts are enabled at the same time.
-    WriteConfig('enable', 'True')
-    wc.Poll(100)
-    wvtest.WVPASSGE(wc.NextBlast(), 100)
-    for t in xrange(101, 200):
-      wc.Poll(t)
-    wvtest.WVPASSGE(CountRuns(), 1)
+      def CountRuns():
+        try:
+          v = open('fake/wifiblaster.out').readlines()
+        except IOError:
+          return 0
+        else:
+          os.unlink('fake/wifiblaster.out')
+          return len(v)
 
-    # Invalid parameter.
-    # Disabled. No packet blasts should be run.
-    WriteConfig('duration', '-1')
-    for t in xrange(200, 300):
-      wc.Poll(t)
-    wvtest.WVPASSEQ(CountRuns(), 0)
+      CountRuns()  # get rid of any leftovers
+      wvtest.WVPASSEQ(CountRuns(), 0)
 
-    # Fix invalid parameter.
-    # Enabled again with 10 second average interval.
-    WriteConfig('duration', '.1')
-    for t in xrange(300, 400):
-      wc.Poll(t)
-    wvtest.WVPASSGE(CountRuns(), 1)
+      # The first packet blast should be one
+      # cycle later than the start time. This is not an implementation detail:
+      # it prevents multiple APs from running simultaneous packet blasts if
+      # packet blasts are enabled at the same time.
+      WriteConfig('enable', 'True')
+      wc.Poll(100)
+      wvtest.WVPASSGE(wc.NextBlast(), 100)
+      for t in xrange(101, 200):
+        wc.Poll(t)
+      wvtest.WVPASSGE(CountRuns(), 1)
 
-    # Run the packet blast at t=400 to restart the timer.
-    wc.Poll(400)
-    wvtest.WVPASSGE(CountRuns(), 0)
+      # Invalid parameter.
+      # Disabled. No packet blasts should be run.
+      WriteConfig('duration', '-1')
+      for t in xrange(200, 300):
+        wc.Poll(t)
+      wvtest.WVPASSEQ(CountRuns(), 0)
 
-    # Next poll should be in at most one second regardless of interval.
-    wvtest.WVPASSLE(wc.NextTimeout(), 401)
+      # Fix invalid parameter.
+      # Enabled again with 10 second average interval.
+      WriteConfig('duration', '.1')
+      for t in xrange(300, 400):
+        wc.Poll(t)
+      wvtest.WVPASSGE(CountRuns(), 1)
 
-    # Enabled with longer average interval.  The change in interval should
-    # trigger a change in next poll timeout.
-    WriteConfig('interval', '0.5')
-    old_to = wc.NextBlast()
-    wc.Poll(401)
-    wvtest.WVPASSNE(old_to, wc.NextBlast())
-    for t in xrange(402, 410):
-      wc.Poll(t)
-    wvtest.WVPASSGE(CountRuns(), 1)
+      # Run the packet blast at t=400 to restart the timer.
+      wc.Poll(400)
+      wvtest.WVPASSGE(CountRuns(), 0)
 
-    # Switch back to a longer poll interval.
-    WriteConfig('interval', '36000')
-    ok = False
-    for t in xrange(410, 600):
-      wc.Poll(t)
-      if wc.NextBlast() > t + 200:
-        ok = True
-    wvtest.WVPASS(ok)
+      # Next poll should be in at most one second regardless of interval.
+      wvtest.WVPASSLE(wc.NextTimeout(), 401)
 
-    # And then try rapid polling for a limited time
-    WriteConfig('rapidpolling', '800')
-    ok = False
-    for t in xrange(600, 700):
-      wc.Poll(t)
-      if wc.NextBlast() < t + 20:
-        ok = True
-    wvtest.WVPASS(ok)
+      # Enabled with longer average interval.  The change in interval should
+      # trigger a change in next poll timeout.
+      WriteConfig('interval', '0.5')
+      old_to = wc.NextBlast()
+      wc.Poll(401)
+      wvtest.WVPASSNE(old_to, wc.NextBlast())
+      for t in xrange(402, 410):
+        wc.Poll(t)
+      wvtest.WVPASSGE(CountRuns(), 1)
 
-    # Make sure rapid polling auto-disables
-    ok = False
-    for t in xrange(700, 900):
-      wc.Poll(t)
-      if wc.NextBlast() > t + 200:
-        ok = True
-    wvtest.WVPASS(ok)
+      # Switch back to a longer poll interval.
+      WriteConfig('interval', '36000')
+      ok = False
+      for t in xrange(410, 600):
+        wc.Poll(t)
+        if wc.NextBlast() > t + 200:
+          ok = True
+      wvtest.WVPASS(ok)
 
+      # And then try rapid polling for a limited time
+      WriteConfig('rapidpolling', '800')
+      ok = False
+      for t in xrange(600, 700):
+        wc.Poll(t)
+        if wc.NextBlast() < t + 20:
+          ok = True
+      wvtest.WVPASS(ok)
+
+      # Make sure rapid polling auto-disables
+      ok = False
+      for t in xrange(700, 900):
+        wc.Poll(t)
+        if wc.NextBlast() > t + 200:
+          ok = True
+      wvtest.WVPASS(ok)
   finally:
     time.time = oldtime
     shutil.rmtree(d)
diff --git a/wifi/configs.py b/wifi/configs.py
index f78b368..d784858 100644
--- a/wifi/configs.py
+++ b/wifi/configs.py
@@ -306,11 +306,25 @@
   return '\n'.join(hostapd_conf_parts)
 
 
-def generate_wpa_supplicant_config(ssid, passphrase):
-  return '\n'.join(
-      ('ctrl_interface=/var/run/wpa_supplicant',
-       'ap_scan=1',
-       'autoscan=exponential:1:30',
-       subprocess.check_output(('wpa_passphrase',
-                                utils.sanitize_ssid(ssid),
-                                utils.validate_and_sanitize_psk(passphrase)))))
+def generate_wpa_supplicant_config(ssid, passphrase, opt):
+  """Generate a wpa_supplicant config from the provided arguments."""
+
+  network_block = subprocess.check_output(
+      ('wpa_passphrase',
+       utils.sanitize_ssid(ssid),
+       utils.validate_and_sanitize_psk(passphrase)))
+
+  if opt.bssid:
+    network_block_lines = network_block.splitlines(True)
+    network_block_lines[-1:-1] = ['\tbssid=%s\n' %
+                                  utils.validate_and_sanitize_bssid(opt.bssid)]
+    network_block = ''.join(network_block_lines)
+
+  lines = [
+      'ctrl_interface=/var/run/wpa_supplicant',
+      'ap_scan=1',
+      'autoscan=exponential:1:30',
+      network_block
+  ]
+  return '\n'.join(lines)
+
diff --git a/wifi/configs_test.py b/wifi/configs_test.py
index 52ca4c9..a5c0c2e 100755
--- a/wifi/configs_test.py
+++ b/wifi/configs_test.py
@@ -20,6 +20,17 @@
 }
 """
 
+_WPA_SUPPLICANT_CONFIG_BSSID = """ctrl_interface=/var/run/wpa_supplicant
+ap_scan=1
+autoscan=exponential:1:30
+network={
+\tssid="some ssid"
+\t#psk="some passphrase"
+\tpsk=41821f7ca3ea5d85beea7644ed7e0fefebd654177fa06c26fbdfdc3c599a317f
+\tbssid=12:34:56:78:90:ab
+}
+"""
+
 
 @wvtest.wvtest
 def generate_wpa_supplicant_config_test():
@@ -28,10 +39,21 @@
         "Can't test generate_wpa_supplicant_config without wpa_passphrase.")
     return
 
+  opt = FakeOptDict()
   config = configs.generate_wpa_supplicant_config(
-      'some ssid', 'some passphrase')
+      'some ssid', 'some passphrase', opt)
   wvtest.WVPASSEQ(_WPA_SUPPLICANT_CONFIG, config)
 
+  opt.bssid = 'TotallyNotValid'
+  wvtest.WVEXCEPT(utils.BinWifiException,
+                  configs.generate_wpa_supplicant_config,
+                  'some ssid', 'some passphrase', opt)
+
+  opt.bssid = '12:34:56:78:90:Ab'
+  config = configs.generate_wpa_supplicant_config(
+      'some ssid', 'some passphrase', opt)
+  wvtest.WVPASSEQ(_WPA_SUPPLICANT_CONFIG_BSSID, config)
+
 
 _PHY_INFO = """Wiphy phy0
   max # scan SSIDs: 4
@@ -312,6 +334,7 @@
     self.channel = 'auto'
     self.autotype = 'NONDFS'
     self.ssid = 'TEST_SSID'
+    self.bssid = ''
     self.encryption = 'WPA2_PSK_AES'
     self.force_restart = False
     self.hidden_mode = False
diff --git a/wifi/utils.py b/wifi/utils.py
index fab7022..e665654 100644
--- a/wifi/utils.py
+++ b/wifi/utils.py
@@ -6,6 +6,7 @@
 
 import collections
 import os
+import re
 import subprocess
 import sys
 import time
@@ -257,6 +258,16 @@
                  if unicodedata.category(c)[0] != 'C').encode('utf-8')
 
 
+def validate_and_sanitize_bssid(bssid):
+  maybe_octets = bssid.lower().split(':')
+  if (len(maybe_octets) == 6 and
+      all(re.match('[0-9a-f]{2}', maybe_octet)
+          for maybe_octet in maybe_octets)):
+    return ':'.join(maybe_octets)
+  else:
+    raise BinWifiException('%s is not a valid BSSID', bssid)
+
+
 def validate_and_sanitize_psk(psk):
   """Validates a PSK and removes control characters.
 
diff --git a/wifi/wifi.py b/wifi/wifi.py
index 5ee618b..896f1ac 100755
--- a/wifi/wifi.py
+++ b/wifi/wifi.py
@@ -22,7 +22,7 @@
 
 _OPTSPEC_FORMAT = """
 {bin} set           Enable or modify access points.  Takes all options unless otherwise specified.
-{bin} setclient     Enable or modify wifi clients.  Takes -b, -P, -s, -S.
+{bin} setclient     Enable or modify wifi clients.  Takes -b, -P, -s, --bssid, -S.
 {bin} stop|off      Disable access points and clients.  Takes -b, -P, -S.
 {bin} stopap        Disable access points.  Takes -b, -P, -S.
 {bin} stopclient    Disable wifi clients.  Takes -b, -P, -S.
@@ -33,6 +33,7 @@
 c,channel=                        Channel to use [auto]
 a,autotype=                       Autochannel method to use (LOW, HIGH, DFS, NONDFS, ANY,OVERLAP) [NONDFS]
 s,ssid=                           SSID to use [{ssid}]
+bssid=                            BSSID to use []
 e,encryption=                     Encryption type to use (WPA_PSK_AES, WPA2_PSK_AES, WPA12_PSK_AES, WPA_PSK_TKIP, WPA2_PSK_TKIP, WPA12_PSK_TKIP, WEP, or NONE) [WPA2_PSK_AES]
 f,force-restart                   Force restart even if already running with these options
 H,hidden-mode                     Enable hidden mode (disable SSID advertisements)
@@ -834,7 +835,7 @@
           ('ip', 'link', 'set', interface, 'address', mac_address))
 
   wpa_config = configs.generate_wpa_supplicant_config(
-      opt.ssid, os.environ['WIFI_CLIENT_PSK'])
+      opt.ssid, os.environ['WIFI_CLIENT_PSK'], opt)
   if not _maybe_restart_wpa_supplicant(interface, wpa_config, opt):
     return False