Wifi: Parse HT/VHT/HE capabilities IE to derive maxNumSpatialStream

Bug: 143893522
Test: atest com.android.wifi.server and manual test with 11ac/ax AP

Change-Id: I28a60e6a9219fcf541a212d927c35ce31107cca0
diff --git a/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java b/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
index ea1cfe6..b456ef4 100644
--- a/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
+++ b/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
@@ -25,7 +25,7 @@
 
     private static final boolean DBG = false;
 
-    private static final String TAG = "NetworkDetail:";
+    private static final String TAG = "NetworkDetail";
 
     public enum Ant {
         Private,
@@ -88,6 +88,7 @@
      */
     private final int mWifiMode;
     private final int mMaxRate;
+    private final int mMaxNumberSpatialStreams;
 
     /*
      * From Interworking element:
@@ -145,6 +146,13 @@
                 new InformationElementUtil.VhtOperation();
         InformationElementUtil.HeOperation heOperation = new InformationElementUtil.HeOperation();
 
+        InformationElementUtil.HtCapabilities htCapabilities =
+                new InformationElementUtil.HtCapabilities();
+        InformationElementUtil.VhtCapabilities vhtCapabilities =
+                new InformationElementUtil.VhtCapabilities();
+        InformationElementUtil.HeCapabilities heCapabilities =
+                new InformationElementUtil.HeCapabilities();
+
         InformationElementUtil.ExtendedCapabilities extendedCapabilities =
                 new InformationElementUtil.ExtendedCapabilities();
 
@@ -175,6 +183,12 @@
                     case ScanResult.InformationElement.EID_VHT_OPERATION:
                         vhtOperation.from(ie);
                         break;
+                    case ScanResult.InformationElement.EID_HT_CAPABILITIES:
+                        htCapabilities.from(ie);
+                        break;
+                    case ScanResult.InformationElement.EID_VHT_CAPABILITIES:
+                        vhtCapabilities.from(ie);
+                        break;
                     case ScanResult.InformationElement.EID_INTERWORKING:
                         interworking.from(ie);
                         break;
@@ -201,6 +215,9 @@
                             case ScanResult.InformationElement.EID_EXT_HE_OPERATION:
                                 heOperation.from(ie);
                                 break;
+                            case ScanResult.InformationElement.EID_EXT_HE_CAPABILITIES:
+                                heCapabilities.from(ie);
+                                break;
                             default:
                                 break;
                         }
@@ -315,6 +332,10 @@
             mDtimInterval = trafficIndicationMap.mDtimPeriod;
         }
 
+        mMaxNumberSpatialStreams = Math.max(heCapabilities.getMaxNumberSpatialStreams(),
+                Math.max(vhtCapabilities.getMaxNumberSpatialStreams(),
+                htCapabilities.getMaxNumberSpatialStreams()));
+
         int maxRateA = 0;
         int maxRateB = 0;
         // If we got some Extended supported rates, consider them, if not default to 0
@@ -334,10 +355,11 @@
             mMaxRate = 0;
         }
         if (DBG) {
-            Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq
-                    + " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: " + mCenterfreq1
-                    + (extendedCapabilities.is80211McRTTResponder() ? "Support RTT responder"
-                    : "Do not support RTT responder"));
+            Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: "
+                    + mPrimaryFreq + " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: "
+                    + mCenterfreq1 + (extendedCapabilities.is80211McRTTResponder()
+                    ? " Support RTT responder" : " Do not support RTT responder")
+                    + " mMaxNumberSpatialStreams: " + mMaxNumberSpatialStreams);
             Log.v("WifiMode", mSSID
                     + ", WifiMode: " + InformationElementUtil.WifiMode.toString(mWifiMode)
                     + ", Freq: " + mPrimaryFreq
@@ -383,6 +405,7 @@
         mDtimInterval = base.mDtimInterval;
         mWifiMode = base.mWifiMode;
         mMaxRate = base.mMaxRate;
+        mMaxNumberSpatialStreams = base.mMaxNumberSpatialStreams;
     }
 
     public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
@@ -494,6 +517,10 @@
         return mWifiMode;
     }
 
+    public int getMaxNumberSpatialStreams() {
+        return mMaxNumberSpatialStreams;
+    }
+
     public int getDtimInterval() {
         return mDtimInterval;
     }
diff --git a/service/java/com/android/server/wifi/util/InformationElementUtil.java b/service/java/com/android/server/wifi/util/InformationElementUtil.java
index 9fcf069..673fce5 100644
--- a/service/java/com/android/server/wifi/util/InformationElementUtil.java
+++ b/service/java/com/android/server/wifi/util/InformationElementUtil.java
@@ -31,7 +31,7 @@
 
 public class InformationElementUtil {
     private static final String TAG = "InformationElementUtil";
-
+    private static final boolean DBG = false;
     public static InformationElement[] parseInformationElements(byte[] bytes) {
         if (bytes == null) {
             return new InformationElement[0];
@@ -421,7 +421,9 @@
 
             // Make sure the byte array length is at least the fixed size
             if (ie.bytes.length < HE_OPERATION_BASIC_LENGTH) {
-                Log.w(TAG, "Invalid HE_OPERATION len: " + ie.bytes.length);
+                if (DBG) {
+                    Log.w(TAG, "Invalid HE_OPERATION len: " + ie.bytes.length);
+                }
                 // Skipping parsing of the IE
                 return;
             }
@@ -434,7 +436,9 @@
 
             // Make sure the byte array length is at least fitting the known parameters
             if (ie.bytes.length < expectedLen) {
-                Log.w(TAG, "Invalid HE_OPERATION len: " + ie.bytes.length);
+                if (DBG) {
+                    Log.w(TAG, "Invalid HE_OPERATION len: " + ie.bytes.length);
+                }
                 // Skipping parsing of the IE
                 return;
             }
@@ -461,6 +465,146 @@
         }
     }
 
+    /**
+     * HtCapabilities: represents the HT Capabilities IE
+     */
+    public static class HtCapabilities {
+        private int mMaxNumberSpatialStreams  = 1;
+        private boolean mPresent = false;
+        /** Returns whether HT Capabilities IE is present */
+        public boolean isPresent() {
+            return mPresent;
+        }
+        /**
+         * Returns max number of spatial streams if HT Capabilities IE is found and parsed,
+         * or 1 otherwise
+         */
+        public int getMaxNumberSpatialStreams() {
+            return mMaxNumberSpatialStreams;
+        }
+
+        /** Parse HT Capabilities IE */
+        public void from(InformationElement ie) {
+            if (ie.id != InformationElement.EID_HT_CAPABILITIES) {
+                throw new IllegalArgumentException("Element id is not HT_CAPABILITIES: " + ie.id);
+            }
+            if (ie.bytes.length < 26) {
+                if (DBG) {
+                    Log.w(TAG, "Invalid HtCapabilities len: " + ie.bytes.length);
+                }
+                return;
+            }
+            int stream1 = ie.bytes[3] & Constants.BYTE_MASK;
+            int stream2 = ie.bytes[4] & Constants.BYTE_MASK;
+            int stream3 = ie.bytes[5] & Constants.BYTE_MASK;
+            int stream4 = ie.bytes[6] & Constants.BYTE_MASK;
+            if (DBG) {
+                Log.d(TAG, "HT Rx MCS set4: " + Integer.toHexString(stream4));
+                Log.d(TAG, "HT Rx MCS set3: " + Integer.toHexString(stream3));
+                Log.d(TAG, "HT Rx MCS set2: " + Integer.toHexString(stream2));
+                Log.d(TAG, "HT Rx MCS set1: " + Integer.toHexString(stream1));
+            }
+            mMaxNumberSpatialStreams = (stream4 > 0) ? 4 :
+                    ((stream3 > 0) ? 3 :
+                    ((stream2 > 0) ? 2 : 1));
+            mPresent = true;
+        }
+    }
+
+    /**
+     * VhtCapabilities: represents the VHT Capabilities IE
+     */
+    public static class VhtCapabilities {
+        private int mMaxNumberSpatialStreams = 1;
+        private boolean mPresent = false;
+        /** Returns whether VHT Capabilities IE is present */
+        public boolean isPresent() {
+            return mPresent;
+        }
+        /**
+         * Returns max number of spatial streams if VHT Capabilities IE is found and parsed,
+         * or 1 otherwise
+         */
+        public int getMaxNumberSpatialStreams() {
+            return mMaxNumberSpatialStreams;
+        }
+        /** Parse VHT Capabilities IE */
+        public void from(InformationElement ie) {
+            if (ie.id != InformationElement.EID_VHT_CAPABILITIES) {
+                throw new IllegalArgumentException("Element id is not VHT_CAPABILITIES: " + ie.id);
+            }
+            if (ie.bytes.length < 12) {
+                if (DBG) {
+                    Log.w(TAG, "Invalid VHT_CAPABILITIES len: " + ie.bytes.length);
+                }
+                return;
+            }
+            int mcsMap = ((ie.bytes[5] & Constants.BYTE_MASK) << 8)
+                    + (ie.bytes[4] & Constants.BYTE_MASK);
+            mMaxNumberSpatialStreams = parseMaxNumberSpatialStreamsFromMcsMap(mcsMap);
+            mPresent = true;
+        }
+    }
+
+    /**
+     * HeCapabilities: represents the HE Capabilities IE
+     */
+    public static class HeCapabilities {
+        private int mMaxNumberSpatialStreams = 1;
+        private boolean mPresent = false;
+        /** Returns whether HE Capabilities IE is present */
+        public boolean isPresent() {
+            return mPresent;
+        }
+        /**
+         * Returns max number of spatial streams if HE Capabilities IE is found and parsed,
+         * or 1 otherwise
+         */
+        public int getMaxNumberSpatialStreams() {
+            return mMaxNumberSpatialStreams;
+        }
+        /** Parse HE Capabilities IE */
+        public void from(InformationElement ie) {
+            if (ie.id != InformationElement.EID_EXTENSION_PRESENT
+                    || ie.idExt != InformationElement.EID_EXT_HE_CAPABILITIES) {
+                throw new IllegalArgumentException("Element id is not HE_CAPABILITIES: " + ie.id);
+            }
+            if (ie.bytes.length < 21) {
+                if (DBG) {
+                    Log.w(TAG, "Invalid HE_CAPABILITIES len: " + ie.bytes.length);
+                }
+                return;
+            }
+            int mcsMap = ((ie.bytes[18] & Constants.BYTE_MASK) << 8)
+                    + (ie.bytes[17] & Constants.BYTE_MASK);
+            mMaxNumberSpatialStreams = parseMaxNumberSpatialStreamsFromMcsMap(mcsMap);
+            mPresent = true;
+        }
+    }
+
+    private static int parseMaxNumberSpatialStreamsFromMcsMap(int mcsMap) {
+        int maxNumberSpatialStreams = 1;
+        for (int i = 8; i >= 1; --i) {
+            int streamMap = mcsMapToStreamMap(mcsMap, i);
+            // 3 means unsupported
+            if (streamMap != 3) {
+                maxNumberSpatialStreams = i;
+                break;
+            }
+        }
+        if (DBG) {
+            for (int i = 8; i >= 1; --i) {
+                int streamMap = mcsMapToStreamMap(mcsMap, i);
+                Log.d(TAG, "Rx MCS set " + i + " : " + streamMap);
+            }
+        }
+        return maxNumberSpatialStreams;
+    }
+
+    private static int mcsMapToStreamMap(int mcsMap, int i) {
+        return (mcsMap >> ((i - 1) * 2)) & 0x3;
+    }
+
     public static class Interworking {
         public NetworkDetail.Ant ant = null;
         public boolean internet = false;
diff --git a/service/tests/wifitests/src/com/android/server/wifi/util/InformationElementUtilTest.java b/service/tests/wifitests/src/com/android/server/wifi/util/InformationElementUtilTest.java
index 41b9aab..9bd1290 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/util/InformationElementUtilTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/util/InformationElementUtilTest.java
@@ -1362,6 +1362,96 @@
         assertEquals(5200, vhtOperation.getCenterFreq0());
         assertEquals(0, vhtOperation.getCenterFreq1());
     }
+    /**
+     * Verify that the expected max number of spatial stream is parsed correctly from
+     * HT capabilities IE
+     *
+     * HT capabilities IE Format:
+     * | HT Capability Information | A-MPDU Parameters | Supported MCS Set
+     *               2                      1                   16
+     * | HT Extended Capabilities | Transmit Beamforming Capabilities | ASEL Capabilities |
+     *               2                      4                                   1
+     *
+     *  Supported MCS Set Format:
+     *    B0                   B8                    B16                  B23
+     *  | Rx MCS Bitmask 1SS | Rx MCS Bitmask 2SS  | Rx MCS Bitmask 3SS | Rx MCS Bitmask 4SS
+     */
+    @Test
+    public void getMaxNumberSpatialStreamsWithHtCapabilitiesIE() throws Exception {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_HT_CAPABILITIES;
+        ie.bytes = new byte[]{(byte) 0xee, (byte) 0x01, (byte) 0x17, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00};
+        InformationElementUtil.HtCapabilities htCapabilities =
+                new InformationElementUtil.HtCapabilities();
+        htCapabilities.from(ie);
+        assertEquals(3, htCapabilities.getMaxNumberSpatialStreams());
+        assertEquals(true, htCapabilities.isPresent());
+    }
+
+    /**
+     * Verify that the expected max number of spatial stream is parsed correctly from
+     * VHT capabilities IE
+     */
+    @Test
+    public void getMaxNumberSpatialStreamsWithVhtCapabilitiesIE() throws Exception {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_VHT_CAPABILITIES;
+        /**
+         * VHT Capabilities IE Format:
+         * | VHT capabilities Info |  Supported VHT-MCS and NSS Set |
+         *           4                              8
+         *
+         * Supported VHT-MCS set Format:
+         *   B0                B2                B4                B6
+         * | Max MCS For 1SS | Max MCS For 2SS | Max MCS For 3SS | Max MCS For 4SS
+         *   B8                B10               B12               B14
+         * | Max MCS For 5SS | Max MCS For 6SS | Max MCS For 7SS | Max MCS For 8SS
+         */
+        ie.bytes = new byte[]{(byte) 0x92, (byte) 0x01, (byte) 0x80, (byte) 0x33, (byte) 0xaa,
+                (byte) 0xff, (byte) 0x00, (byte) 0x00, (byte) 0xaa, (byte) 0xff, (byte) 0x00,
+                (byte) 0x00};
+        InformationElementUtil.VhtCapabilities vhtCapabilities =
+                new InformationElementUtil.VhtCapabilities();
+        vhtCapabilities.from(ie);
+        assertEquals(4, vhtCapabilities.getMaxNumberSpatialStreams());
+        assertEquals(true, vhtCapabilities.isPresent());
+    }
+
+    /**
+     * Verify that the expected max number of spatial stream is parsed correctly from
+     * HE capabilities IE
+     */
+    @Test
+    public void getMaxNumberSpatialStreamsWithHeCapabilitiesIE() throws Exception {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_EXTENSION_PRESENT;
+        ie.idExt = InformationElement.EID_EXT_HE_CAPABILITIES;
+        /**
+         * HE Capabilities IE Format:
+         * | HE MAC Capabilities Info | HE PHY Capabilities Info | Supported HE-MCS and NSS Set |
+         *           6                              11                     4
+         *
+         * Supported HE-MCS set Format:
+         *   B0                B2                B4                B6
+         * | Max MCS For 1SS | Max MCS For 2SS | Max MCS For 3SS | Max MCS For 4SS
+         *   B8                B10               B12               B14
+         * | Max MCS For 5SS | Max MCS For 6SS | Max MCS For 7SS | Max MCS For 8SS
+         */
+        ie.bytes = new byte[]{(byte) 0x09, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x40,
+                (byte) 0x04, (byte) 0x70, (byte) 0x0c, (byte) 0x80, (byte) 0x00, (byte) 0x07,
+                (byte) 0x80, (byte) 0x04, (byte) 0x00, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa,
+                (byte) 0xaa, (byte) 0x7f, (byte) 0x1c, (byte) 0xc7, (byte) 0x71, (byte) 0x1c,
+                (byte) 0xc7, (byte) 0x71};
+        InformationElementUtil.HeCapabilities heCapabilities =
+                new InformationElementUtil.HeCapabilities();
+        heCapabilities.from(ie);
+        assertEquals(8, heCapabilities.getMaxNumberSpatialStreams());
+        assertEquals(true, heCapabilities.isPresent());
+    }
 
     // TODO: SAE, OWN, SUITE_B
 }