Fix PHY rate parsing for vht (802.11ac).

This also changes 802.11n-style MCS values 8..15 (2 spatial streams) and
16..23 (3 spatial streams) into MCS 0..7 with an opt.spatialstreams of 2 or
3 respectively.
diff --git a/app.py b/app.py
index f841cd4..f1d51bb 100644
--- a/app.py
+++ b/app.py
@@ -23,7 +23,7 @@
 
 BROADCAST = 'ff:ff:ff:ff:ff:ff'
 DEFAULT_FIELDS = ['seq', 'rate']
-AVAIL_FIELDS = ['seq', 'mcs', 'rate', 'retry',
+AVAIL_FIELDS = ['seq', 'mcs', 'spatialstreams', 'bw', 'rate', 'retry',
                 'type', 'typestr', 'dbm_antsignal', 'dbm_antnoise',
                 'bad']
 
diff --git a/wifipacket.py b/wifipacket.py
index 54c0763..9cd7deb 100755
--- a/wifipacket.py
+++ b/wifipacket.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python
 """Functions for decoding wifi pcap files."""
 
 from __future__ import print_function
@@ -43,6 +43,7 @@
 TCPDUMP_MAGIC = 0xa1b2c3d4
 TCPDUMP_VERSION = (2, 4)
 LINKTYPE_IEEE802_11_RADIOTAP = 127
+SHORT_GI_MULT = 10 / 9.
 
 
 class Flags(object):
@@ -165,64 +166,41 @@
   return out
 
 
+# (modulation_name, coding_rate, data_rate(20M, 40M, 80M, 160M))
+# To get the data rate with short guard interval, multiply by SHORT_GI_MULT.
 MCS_TABLE = [
-    (1, 'BPSK', '1/2', (6.50, 7.20, 13.50, 15.00)),
-    (1, 'QPSK', '1/2', (13.00, 14.40, 27.00, 30.00)),
-    (1, 'QPSK', '3/4', (19.50, 21.70, 40.50, 45.00)),
-    (1, '16-QAM', '1/2', (26.00, 28.90, 54.00, 60.00)),
-    (1, '16-QAM', '3/4', (39.00, 43.30, 81.00, 90.00)),
-    (1, '64-QAM', '2/3', (52.00, 57.80, 108.00, 120.00)),
-    (1, '64-QAM', '3/4', (58.50, 65.00, 121.50, 135.00)),
-    (1, '64-QAM', '5/6', (65.00, 72.20, 135.00, 150.00)),
-    (2, 'BPSK', '1/2', (13.00, 14.40, 27.00, 30.00)),
-    (2, 'QPSK', '1/2', (26.00, 28.90, 54.00, 60.00)),
-    (2, 'QPSK', '3/4', (39.00, 43.30, 81.00, 90.00)),
-    (2, '16-QAM', '1/2', (52.00, 57.80, 108.00, 120.00)),
-    (2, '16-QAM', '3/4', (78.00, 86.70, 162.00, 180.00)),
-    (2, '64-QAM', '2/3', (104.00, 115.60, 216.00, 240.00)),
-    (2, '64-QAM', '3/4', (117.00, 130.00, 243.00, 270.00)),
-    (2, '64-QAM', '5/6', (130.00, 144.40, 270.00, 300.00)),
-    (3, 'BPSK', '1/2', (19.50, 21.70, 40.50, 45.00)),
-    (3, 'QPSK', '1/2', (39.00, 43.30, 81.00, 90.00)),
-    (3, 'QPSK', '3/4', (58.50, 65.00, 121.50, 135.00)),
-    (3, '16-QAM', '1/2', (78.00, 86.70, 162.00, 180.00)),
-    (3, '16-QAM', '3/4', (117.00, 130.00, 243.00, 270.00)),
-    (3, '64-QAM', '2/3', (156.00, 173.30, 324.00, 360.00)),
-    (3, '64-QAM', '3/4', (175.50, 195.00, 364.50, 405.00)),
-    (3, '64-QAM', '5/6', (195.00, 216.70, 405.00, 450.00)),
-    (4, 'BPSK', '1/2', (26.00, 28.80, 54.00, 60.00)),
-    (4, 'QPSK', '1/2', (52.00, 57.60, 108.00, 120.00)),
-    (4, 'QPSK', '3/4', (78.00, 86.80, 162.00, 180.00)),
-    (4, '16-QAM', '1/2', (104.00, 115.60, 216.00, 240.00)),
-    (4, '16-QAM', '3/4', (156.00, 173.20, 324.00, 360.00)),
-    (4, '64-QAM', '2/3', (208.00, 231.20, 432.00, 480.00)),
-    (4, '64-QAM', '3/4', (234.00, 260.00, 486.00, 540.00)),
-    (4, '64-QAM', '5/6', (260.00, 288.80, 540.00, 600.00)),
-    (1, 'BPSK', '1/2', (0, 0, 6.50, 7.20)),
+    ('BPSK', '1/2', (6.5, 13.5, 29.3, 58.5)),
+    ('QPSK', '1/2', (13, 27, 58.5, 117)),
+    ('QPSK', '3/4', (19.5, 40.5, 87.8, 175.5)),
+    ('16-QAM', '1/2', (26, 54, 117, 234)),
+    ('16-QAM', '3/4', (39, 81, 175.5, 351)),
+    ('64-QAM', '2/3', (52, 108, 234, 468)),
+    ('64-QAM', '3/4', (58.5, 121.5, 263.3, 526.5)),
+    ('64-QAM', '5/6', (65, 135, 292.5, 585)),
+    # 802.11ac only:
+    ('256-QAM', '3/4', (78, 162, 351, 702)),
+    ('256-QAM', '5/6', (86.7, 180, 390, 780)),
 ]
 
 
 def McsToRate(known, flags, index):
   """Given MCS information for a packet, return the corresponding bitrate."""
-  if known & (1 << 0):
-    bw = (20, 40, 20, 20)[flags & 0x3]
+  if known & 0x01:
+    bw_index = (0, 1, 0, 0)[flags & 0x3]
   else:
-    bw = 20
-  if known & (1 << 2):
-    gi = ((flags & 0x4) >> 2)
+    bw_index = 0  # 20 MHz
+  if known & 0x04:
+    gi = ((flags & 0x04) >> 2)
   else:
     gi = 0
-  if known & (1 << 1):
-    mcs = index
+  gi_mult = (SHORT_GI_MULT if gi else 1)
+  if known & 0x02:
+    mcs = index & 0x07
+    nss = ((index & 0x18) >> 3) + 1
   else:
     mcs = 0
-  if bw == 20:
-    si = 0
-  else:
-    si = 2
-  if gi:
-    si += 1
-  return MCS_TABLE[mcs][3][si]
+    nss = 1
+  return bw_index, MCS_TABLE[mcs][2][bw_index] * nss * gi_mult
 
 
 def Packetize(stream):
@@ -298,12 +276,34 @@
         elif name == 'channel':
           opt.freq = v[0]
           opt.channel_flags = v[1]
+        elif name == 'rate':
+          opt.rate = v[0] / 2.  # convert multiples of 500 kb/sec -> Mb/sec
         elif name == 'ht':
           ht_known, ht_flags, ht_index = v
           opt.ht = v
-          opt.mcs = ht_index
-          opt.rate = McsToRate(ht_known, ht_flags, ht_index)
-          opt.spatialstreams = MCS_TABLE[ht_index][0]
+          opt.mcs = ht_index & 0x07
+          opt.spatialstreams = 1 + ((ht_index & 0x18) >> 3)
+          width, opt.rate = McsToRate(ht_known, ht_flags, ht_index)
+          opt.bw = 20 << width
+        elif name == 'vht':
+          (vht_known, vht_flags, vht_bw, vht_mcs_nss,
+          vhd_coding, vht_group_id, vht_partial_aid) = v
+          vmn = ord(vht_mcs_nss[0])
+          opt.mcs = (vmn & 0xf0) >> 4
+          opt.spatialstreams = vmn & 0x0f
+          if vht_bw == 0:
+            width = 0
+          elif vht_bw < 4:
+            width = 1
+          elif vht_bw < 11:
+            width = 2
+          else:
+            width = 3
+          opt.bw = 20 << width
+          gi = (vht_flags & 0x04)
+          gi_mult = (SHORT_GI_MULT if gi else 1)
+          opt.rate = (MCS_TABLE[opt.mcs][2][width]
+                      * opt.spatialstreams * gi_mult)
         else:
           opt[name] = v if len(v) > 1 else v[0]
         ofs += sz
@@ -422,18 +422,13 @@
         seq = opt.seq
       else:
         seq = 'noseq'
-      print(i+1, opt.get('flags'), opt.get('bad'))
       if 'flags' in opt:
         if opt.flags & Flags.BAD_FCS:
           continue
-      if 'mcs' in opt:
-        print(i + 1,
-            src, opt.dsmode, opt.typestr, ts, opt.rate, mac_usecs,
-            opt.orig_len, seq, opt.flags)
-      else:
-        print(i + 1,
-            src, opt.dsmode, opt.typestr, ts, opt.get('rate'), mac_usecs,
-            opt.orig_len, seq, opt.get('flags'))
+      print(i + 1,
+            src, opt.dsmode, opt.typestr, ts,
+            opt.rate, opt.get('mcs'), opt.get('spatialstreams'),
+            mac_usecs, opt.orig_len, seq, opt.get('flags'))
 
 
 def ZOpen(fn):