Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.server.wifi.util;
     17 
     18 import android.net.wifi.ScanResult;
     19 import android.net.wifi.ScanResult.InformationElement;
     20 import android.util.Log;
     21 
     22 import com.android.server.wifi.ByteBufferReader;
     23 import com.android.server.wifi.hotspot2.NetworkDetail;
     24 import com.android.server.wifi.hotspot2.anqp.Constants;
     25 
     26 import java.nio.BufferUnderflowException;
     27 import java.nio.ByteBuffer;
     28 import java.nio.ByteOrder;
     29 import java.util.ArrayList;
     30 import java.util.BitSet;
     31 
     32 public class InformationElementUtil {
     33     private static final String TAG = "InformationElementUtil";
     34 
     35     public static InformationElement[] parseInformationElements(byte[] bytes) {
     36         if (bytes == null) {
     37             return new InformationElement[0];
     38         }
     39         ByteBuffer data = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
     40 
     41         ArrayList<InformationElement> infoElements = new ArrayList<>();
     42         boolean found_ssid = false;
     43         while (data.remaining() > 1) {
     44             int eid = data.get() & Constants.BYTE_MASK;
     45             int elementLength = data.get() & Constants.BYTE_MASK;
     46 
     47             if (elementLength > data.remaining() || (eid == InformationElement.EID_SSID
     48                     && found_ssid)) {
     49                 // APs often pad the data with bytes that happen to match that of the EID_SSID
     50                 // marker.  This is not due to a known issue for APs to incorrectly send the SSID
     51                 // name multiple times.
     52                 break;
     53             }
     54             if (eid == InformationElement.EID_SSID) {
     55                 found_ssid = true;
     56             }
     57 
     58             InformationElement ie = new InformationElement();
     59             ie.id = eid;
     60             ie.bytes = new byte[elementLength];
     61             data.get(ie.bytes);
     62             infoElements.add(ie);
     63         }
     64         return infoElements.toArray(new InformationElement[infoElements.size()]);
     65     }
     66 
     67     /**
     68      * Parse and retrieve the Roaming Consortium Information Element from the list of IEs.
     69      *
     70      * @param ies List of IEs to retrieve from
     71      * @return {@link RoamingConsortium}
     72      */
     73     public static RoamingConsortium getRoamingConsortiumIE(InformationElement[] ies) {
     74         RoamingConsortium roamingConsortium = new RoamingConsortium();
     75         if (ies != null) {
     76             for (InformationElement ie : ies) {
     77                 if (ie.id == InformationElement.EID_ROAMING_CONSORTIUM) {
     78                     try {
     79                         roamingConsortium.from(ie);
     80                     } catch (RuntimeException e) {
     81                         Log.e(TAG, "Failed to parse Roaming Consortium IE: " + e.getMessage());
     82                     }
     83                 }
     84             }
     85         }
     86         return roamingConsortium;
     87     }
     88 
     89     /**
     90      * Parse and retrieve the Hotspot 2.0 Vendor Specific Information Element from the list of IEs.
     91      *
     92      * @param ies List of IEs to retrieve from
     93      * @return {@link Vsa}
     94      */
     95     public static Vsa getHS2VendorSpecificIE(InformationElement[] ies) {
     96         Vsa vsa = new Vsa();
     97         if (ies != null) {
     98             for (InformationElement ie : ies) {
     99                 if (ie.id == InformationElement.EID_VSA) {
    100                     try {
    101                         vsa.from(ie);
    102                     } catch (RuntimeException e) {
    103                         Log.e(TAG, "Failed to parse Vendor Specific IE: " + e.getMessage());
    104                     }
    105                 }
    106             }
    107         }
    108         return vsa;
    109     }
    110 
    111     /**
    112      * Parse and retrieve the Interworking information element from the list of IEs.
    113      *
    114      * @param ies List of IEs to retrieve from
    115      * @return {@link Interworking}
    116      */
    117     public static Interworking getInterworkingIE(InformationElement[] ies) {
    118         Interworking interworking = new Interworking();
    119         if (ies != null) {
    120             for (InformationElement ie : ies) {
    121                 if (ie.id == InformationElement.EID_INTERWORKING) {
    122                     try {
    123                         interworking.from(ie);
    124                     } catch (RuntimeException e) {
    125                         Log.e(TAG, "Failed to parse Interworking IE: " + e.getMessage());
    126                     }
    127                 }
    128             }
    129         }
    130         return interworking;
    131     }
    132 
    133     public static class BssLoad {
    134         public int stationCount = 0;
    135         public int channelUtilization = 0;
    136         public int capacity = 0;
    137 
    138         public void from(InformationElement ie) {
    139             if (ie.id != InformationElement.EID_BSS_LOAD) {
    140                 throw new IllegalArgumentException("Element id is not BSS_LOAD, : " + ie.id);
    141             }
    142             if (ie.bytes.length != 5) {
    143                 throw new IllegalArgumentException("BSS Load element length is not 5: "
    144                                                    + ie.bytes.length);
    145             }
    146             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    147             stationCount = data.getShort() & Constants.SHORT_MASK;
    148             channelUtilization = data.get() & Constants.BYTE_MASK;
    149             capacity = data.getShort() & Constants.SHORT_MASK;
    150         }
    151     }
    152 
    153     public static class HtOperation {
    154         public int secondChannelOffset = 0;
    155 
    156         public int getChannelWidth() {
    157             if (secondChannelOffset != 0) {
    158                 return 1;
    159             } else {
    160                 return 0;
    161             }
    162         }
    163 
    164         public int getCenterFreq0(int primaryFrequency) {
    165             //40 MHz
    166             if (secondChannelOffset != 0) {
    167                 if (secondChannelOffset == 1) {
    168                     return primaryFrequency + 10;
    169                 } else if (secondChannelOffset == 3) {
    170                     return primaryFrequency - 10;
    171                 } else {
    172                     Log.e("HtOperation", "Error on secondChannelOffset: " + secondChannelOffset);
    173                     return 0;
    174                 }
    175             } else {
    176                 return 0;
    177             }
    178         }
    179 
    180         public void from(InformationElement ie) {
    181             if (ie.id != InformationElement.EID_HT_OPERATION) {
    182                 throw new IllegalArgumentException("Element id is not HT_OPERATION, : " + ie.id);
    183             }
    184             secondChannelOffset = ie.bytes[1] & 0x3;
    185         }
    186     }
    187 
    188     public static class VhtOperation {
    189         public int channelMode = 0;
    190         public int centerFreqIndex1 = 0;
    191         public int centerFreqIndex2 = 0;
    192 
    193         public boolean isValid() {
    194             return channelMode != 0;
    195         }
    196 
    197         public int getChannelWidth() {
    198             return channelMode + 1;
    199         }
    200 
    201         public int getCenterFreq0() {
    202             //convert channel index to frequency in MHz, channel 36 is 5180MHz
    203             return (centerFreqIndex1 - 36) * 5 + 5180;
    204         }
    205 
    206         public int getCenterFreq1() {
    207             if (channelMode > 1) { //160MHz
    208                 return (centerFreqIndex2 - 36) * 5 + 5180;
    209             } else {
    210                 return 0;
    211             }
    212         }
    213 
    214         public void from(InformationElement ie) {
    215             if (ie.id != InformationElement.EID_VHT_OPERATION) {
    216                 throw new IllegalArgumentException("Element id is not VHT_OPERATION, : " + ie.id);
    217             }
    218             channelMode = ie.bytes[0] & Constants.BYTE_MASK;
    219             centerFreqIndex1 = ie.bytes[1] & Constants.BYTE_MASK;
    220             centerFreqIndex2 = ie.bytes[2] & Constants.BYTE_MASK;
    221         }
    222     }
    223 
    224     public static class Interworking {
    225         public NetworkDetail.Ant ant = null;
    226         public boolean internet = false;
    227         public long hessid = 0L;
    228 
    229         public void from(InformationElement ie) {
    230             if (ie.id != InformationElement.EID_INTERWORKING) {
    231                 throw new IllegalArgumentException("Element id is not INTERWORKING, : " + ie.id);
    232             }
    233             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    234             int anOptions = data.get() & Constants.BYTE_MASK;
    235             ant = NetworkDetail.Ant.values()[anOptions & 0x0f];
    236             internet = (anOptions & 0x10) != 0;
    237             // There are only three possible lengths for the Interworking IE:
    238             // Len 1: Access Network Options only
    239             // Len 3: Access Network Options & Venue Info
    240             // Len 7: Access Network Options & HESSID
    241             // Len 9: Access Network Options, Venue Info, & HESSID
    242             if (ie.bytes.length != 1
    243                     && ie.bytes.length != 3
    244                     && ie.bytes.length != 7
    245                     && ie.bytes.length != 9) {
    246                 throw new IllegalArgumentException(
    247                         "Bad Interworking element length: " + ie.bytes.length);
    248             }
    249 
    250             if (ie.bytes.length == 3 || ie.bytes.length == 9) {
    251                 int venueInfo = (int) ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 2);
    252             }
    253 
    254             if (ie.bytes.length == 7 || ie.bytes.length == 9) {
    255                 hessid = ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 6);
    256             }
    257         }
    258     }
    259 
    260     public static class RoamingConsortium {
    261         public int anqpOICount = 0;
    262 
    263         private long[] roamingConsortiums = null;
    264 
    265         public long[] getRoamingConsortiums() {
    266             return roamingConsortiums;
    267         }
    268 
    269         public void from(InformationElement ie) {
    270             if (ie.id != InformationElement.EID_ROAMING_CONSORTIUM) {
    271                 throw new IllegalArgumentException("Element id is not ROAMING_CONSORTIUM, : "
    272                         + ie.id);
    273             }
    274             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    275             anqpOICount = data.get() & Constants.BYTE_MASK;
    276 
    277             int oi12Length = data.get() & Constants.BYTE_MASK;
    278             int oi1Length = oi12Length & Constants.NIBBLE_MASK;
    279             int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK;
    280             int oi3Length = ie.bytes.length - 2 - oi1Length - oi2Length;
    281             int oiCount = 0;
    282             if (oi1Length > 0) {
    283                 oiCount++;
    284                 if (oi2Length > 0) {
    285                     oiCount++;
    286                     if (oi3Length > 0) {
    287                         oiCount++;
    288                     }
    289                 }
    290             }
    291             roamingConsortiums = new long[oiCount];
    292             if (oi1Length > 0 && roamingConsortiums.length > 0) {
    293                 roamingConsortiums[0] =
    294                         ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi1Length);
    295             }
    296             if (oi2Length > 0 && roamingConsortiums.length > 1) {
    297                 roamingConsortiums[1] =
    298                         ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi2Length);
    299             }
    300             if (oi3Length > 0 && roamingConsortiums.length > 2) {
    301                 roamingConsortiums[2] =
    302                         ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi3Length);
    303             }
    304         }
    305     }
    306 
    307     public static class Vsa {
    308         private static final int ANQP_DOMID_BIT = 0x04;
    309 
    310         public NetworkDetail.HSRelease hsRelease = null;
    311         public int anqpDomainID = 0;    // No domain ID treated the same as a 0; unique info per AP.
    312 
    313         public void from(InformationElement ie) {
    314             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    315             if (ie.bytes.length >= 5 && data.getInt() == Constants.HS20_FRAME_PREFIX) {
    316                 int hsConf = data.get() & Constants.BYTE_MASK;
    317                 switch ((hsConf >> 4) & Constants.NIBBLE_MASK) {
    318                     case 0:
    319                         hsRelease = NetworkDetail.HSRelease.R1;
    320                         break;
    321                     case 1:
    322                         hsRelease = NetworkDetail.HSRelease.R2;
    323                         break;
    324                     default:
    325                         hsRelease = NetworkDetail.HSRelease.Unknown;
    326                         break;
    327                 }
    328                 if ((hsConf & ANQP_DOMID_BIT) != 0) {
    329                     if (ie.bytes.length < 7) {
    330                         throw new IllegalArgumentException(
    331                                 "HS20 indication element too short: " + ie.bytes.length);
    332                     }
    333                     anqpDomainID = data.getShort() & Constants.SHORT_MASK;
    334                 }
    335             }
    336         }
    337     }
    338 
    339     /**
    340      * This IE contained a bit field indicating the capabilities being advertised by the STA.
    341      * The size of the bit field (number of bytes) is indicated by the |Length| field in the IE.
    342      *
    343      * Refer to Section 8.4.2.29 in IEEE 802.11-2012 Spec for capability associated with each
    344      * bit.
    345      *
    346      * Here is the wire format of this IE:
    347      * | Element ID | Length | Capabilities |
    348      *       1           1          n
    349      */
    350     public static class ExtendedCapabilities {
    351         private static final int RTT_RESP_ENABLE_BIT = 70;
    352         private static final int SSID_UTF8_BIT = 48;
    353 
    354         public BitSet capabilitiesBitSet;
    355 
    356         /**
    357          * @return true if SSID should be interpreted using UTF-8 encoding
    358          */
    359         public boolean isStrictUtf8() {
    360             return capabilitiesBitSet.get(SSID_UTF8_BIT);
    361         }
    362 
    363         /**
    364          * @return true if 802.11 MC RTT Response is enabled
    365          */
    366         public boolean is80211McRTTResponder() {
    367             return capabilitiesBitSet.get(RTT_RESP_ENABLE_BIT);
    368         }
    369 
    370         public ExtendedCapabilities() {
    371             capabilitiesBitSet = new BitSet();
    372         }
    373 
    374         public ExtendedCapabilities(ExtendedCapabilities other) {
    375             capabilitiesBitSet = other.capabilitiesBitSet;
    376         }
    377 
    378         /**
    379          * Parse an ExtendedCapabilities from the IE containing raw bytes.
    380          *
    381          * @param ie The Information element data
    382          */
    383         public void from(InformationElement ie) {
    384             capabilitiesBitSet = BitSet.valueOf(ie.bytes);
    385         }
    386     }
    387 
    388     /**
    389      * parse beacon to build the capabilities
    390      *
    391      * This class is used to build the capabilities string of the scan results coming
    392      * from HAL. It parses the ieee beacon's capability field, WPA and RSNE IE as per spec,
    393      * and builds the ScanResult.capabilities String in a way that mirrors the values returned
    394      * by wpa_supplicant.
    395      */
    396     public static class Capabilities {
    397         private static final int CAP_ESS_BIT_OFFSET = 0;
    398         private static final int CAP_PRIVACY_BIT_OFFSET = 4;
    399 
    400         private static final int WPA_VENDOR_OUI_TYPE_ONE = 0x01f25000;
    401         private static final int WPS_VENDOR_OUI_TYPE = 0x04f25000;
    402         private static final short WPA_VENDOR_OUI_VERSION = 0x0001;
    403         private static final short RSNE_VERSION = 0x0001;
    404 
    405         private static final int WPA_AKM_EAP = 0x01f25000;
    406         private static final int WPA_AKM_PSK = 0x02f25000;
    407 
    408         private static final int WPA2_AKM_EAP = 0x01ac0f00;
    409         private static final int WPA2_AKM_PSK = 0x02ac0f00;
    410         private static final int WPA2_AKM_FT_EAP = 0x03ac0f00;
    411         private static final int WPA2_AKM_FT_PSK = 0x04ac0f00;
    412         private static final int WPA2_AKM_EAP_SHA256 = 0x05ac0f00;
    413         private static final int WPA2_AKM_PSK_SHA256 = 0x06ac0f00;
    414 
    415         private static final int WPA_CIPHER_NONE = 0x00f25000;
    416         private static final int WPA_CIPHER_TKIP = 0x02f25000;
    417         private static final int WPA_CIPHER_CCMP = 0x04f25000;
    418 
    419         private static final int RSN_CIPHER_NONE = 0x00ac0f00;
    420         private static final int RSN_CIPHER_TKIP = 0x02ac0f00;
    421         private static final int RSN_CIPHER_CCMP = 0x04ac0f00;
    422         private static final int RSN_CIPHER_NO_GROUP_ADDRESSED = 0x07ac0f00;
    423 
    424         public ArrayList<Integer> protocol;
    425         public ArrayList<ArrayList<Integer>> keyManagement;
    426         public ArrayList<ArrayList<Integer>> pairwiseCipher;
    427         public ArrayList<Integer> groupCipher;
    428         public boolean isESS;
    429         public boolean isPrivacy;
    430         public boolean isWPS;
    431 
    432         public Capabilities() {
    433         }
    434 
    435         // RSNE format (size unit: byte)
    436         //
    437         // | Element ID | Length | Version | Group Data Cipher Suite |
    438         //      1           1         2                 4
    439         // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
    440         //              2                            4 * m
    441         // | AKM Suite Count | AKM Suite List | RSN Capabilities |
    442         //          2               4 * n               2
    443         // | PMKID Count | PMKID List | Group Management Cipher Suite |
    444         //        2          16 * s                 4
    445         //
    446         // Note: InformationElement.bytes has 'Element ID' and 'Length'
    447         //       stripped off already
    448         private void parseRsnElement(InformationElement ie) {
    449             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    450 
    451             try {
    452                 // version
    453                 if (buf.getShort() != RSNE_VERSION) {
    454                     // incorrect version
    455                     return;
    456                 }
    457 
    458                 // found the RSNE IE, hence start building the capability string
    459                 protocol.add(ScanResult.PROTOCOL_WPA2);
    460 
    461                 // group data cipher suite
    462                 groupCipher.add(parseRsnCipher(buf.getInt()));
    463 
    464                 // pairwise cipher suite count
    465                 short cipherCount = buf.getShort();
    466                 ArrayList<Integer> rsnPairwiseCipher = new ArrayList<>();
    467                 // pairwise cipher suite list
    468                 for (int i = 0; i < cipherCount; i++) {
    469                     rsnPairwiseCipher.add(parseRsnCipher(buf.getInt()));
    470                 }
    471                 pairwiseCipher.add(rsnPairwiseCipher);
    472 
    473                 // AKM
    474                 // AKM suite count
    475                 short akmCount = buf.getShort();
    476                 ArrayList<Integer> rsnKeyManagement = new ArrayList<>();
    477 
    478                 for (int i = 0; i < akmCount; i++) {
    479                     int akm = buf.getInt();
    480                     switch (akm) {
    481                         case WPA2_AKM_EAP:
    482                             rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP);
    483                             break;
    484                         case WPA2_AKM_PSK:
    485                             rsnKeyManagement.add(ScanResult.KEY_MGMT_PSK);
    486                             break;
    487                         case WPA2_AKM_FT_EAP:
    488                             rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_EAP);
    489                             break;
    490                         case WPA2_AKM_FT_PSK:
    491                             rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_PSK);
    492                             break;
    493                         case WPA2_AKM_EAP_SHA256:
    494                             rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP_SHA256);
    495                             break;
    496                         case WPA2_AKM_PSK_SHA256:
    497                             rsnKeyManagement.add(ScanResult.KEY_MGMT_PSK_SHA256);
    498                             break;
    499                         default:
    500                             // do nothing
    501                             break;
    502                     }
    503                 }
    504                 // Default AKM
    505                 if (rsnKeyManagement.isEmpty()) {
    506                     rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP);
    507                 }
    508                 keyManagement.add(rsnKeyManagement);
    509             } catch (BufferUnderflowException e) {
    510                 Log.e("IE_Capabilities", "Couldn't parse RSNE, buffer underflow");
    511             }
    512         }
    513 
    514         private static int parseWpaCipher(int cipher) {
    515             switch (cipher) {
    516                 case WPA_CIPHER_NONE:
    517                     return ScanResult.CIPHER_NONE;
    518                 case WPA_CIPHER_TKIP:
    519                     return ScanResult.CIPHER_TKIP;
    520                 case WPA_CIPHER_CCMP:
    521                     return ScanResult.CIPHER_CCMP;
    522                 default:
    523                     Log.w("IE_Capabilities", "Unknown WPA cipher suite: "
    524                             + Integer.toHexString(cipher));
    525                     return ScanResult.CIPHER_NONE;
    526             }
    527         }
    528 
    529         private static int parseRsnCipher(int cipher) {
    530             switch (cipher) {
    531                 case RSN_CIPHER_NONE:
    532                     return ScanResult.CIPHER_NONE;
    533                 case RSN_CIPHER_TKIP:
    534                     return ScanResult.CIPHER_TKIP;
    535                 case RSN_CIPHER_CCMP:
    536                     return ScanResult.CIPHER_CCMP;
    537                 case RSN_CIPHER_NO_GROUP_ADDRESSED:
    538                     return ScanResult.CIPHER_NO_GROUP_ADDRESSED;
    539                 default:
    540                     Log.w("IE_Capabilities", "Unknown RSN cipher suite: "
    541                             + Integer.toHexString(cipher));
    542                     return ScanResult.CIPHER_NONE;
    543             }
    544         }
    545 
    546         private static boolean isWpsElement(InformationElement ie) {
    547             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    548             try {
    549                 // WPS OUI and type
    550                 return (buf.getInt() == WPS_VENDOR_OUI_TYPE);
    551             } catch (BufferUnderflowException e) {
    552                 Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
    553                 return false;
    554             }
    555         }
    556 
    557         private static boolean isWpaOneElement(InformationElement ie) {
    558             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    559 
    560             try {
    561                 // WPA OUI and type
    562                 return (buf.getInt() == WPA_VENDOR_OUI_TYPE_ONE);
    563             } catch (BufferUnderflowException e) {
    564                 Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
    565                 return false;
    566             }
    567         }
    568 
    569         // WPA type 1 format (size unit: byte)
    570         //
    571         // | Element ID | Length | OUI | Type | Version |
    572         //      1           1       3     1        2
    573         // | Group Data Cipher Suite |
    574         //             4
    575         // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
    576         //              2                            4 * m
    577         // | AKM Suite Count | AKM Suite List |
    578         //          2               4 * n
    579         //
    580         // Note: InformationElement.bytes has 'Element ID' and 'Length'
    581         //       stripped off already
    582         //
    583         private void parseWpaOneElement(InformationElement ie) {
    584             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    585 
    586             try {
    587                 // skip WPA OUI and type parsing. isWpaOneElement() should have
    588                 // been called for verification before we reach here.
    589                 buf.getInt();
    590 
    591                 // version
    592                 if (buf.getShort() != WPA_VENDOR_OUI_VERSION)  {
    593                     // incorrect version
    594                     return;
    595                 }
    596 
    597                 // start building the string
    598                 protocol.add(ScanResult.PROTOCOL_WPA);
    599 
    600                 // group data cipher suite
    601                 groupCipher.add(parseWpaCipher(buf.getInt()));
    602 
    603                 // pairwise cipher suite count
    604                 short cipherCount = buf.getShort();
    605                 ArrayList<Integer> wpaPairwiseCipher = new ArrayList<>();
    606                 // pairwise chipher suite list
    607                 for (int i = 0; i < cipherCount; i++) {
    608                     wpaPairwiseCipher.add(parseWpaCipher(buf.getInt()));
    609                 }
    610                 pairwiseCipher.add(wpaPairwiseCipher);
    611 
    612                 // AKM
    613                 // AKM suite count
    614                 short akmCount = buf.getShort();
    615                 ArrayList<Integer> wpaKeyManagement = new ArrayList<>();
    616 
    617                 // AKM suite list
    618                 for (int i = 0; i < akmCount; i++) {
    619                     int akm = buf.getInt();
    620                     switch (akm) {
    621                         case WPA_AKM_EAP:
    622                             wpaKeyManagement.add(ScanResult.KEY_MGMT_EAP);
    623                             break;
    624                         case WPA_AKM_PSK:
    625                             wpaKeyManagement.add(ScanResult.KEY_MGMT_PSK);
    626                             break;
    627                         default:
    628                             // do nothing
    629                             break;
    630                     }
    631                 }
    632                 // Default AKM
    633                 if (wpaKeyManagement.isEmpty()) {
    634                     wpaKeyManagement.add(ScanResult.KEY_MGMT_EAP);
    635                 }
    636                 keyManagement.add(wpaKeyManagement);
    637             } catch (BufferUnderflowException e) {
    638                 Log.e("IE_Capabilities", "Couldn't parse type 1 WPA, buffer underflow");
    639             }
    640         }
    641 
    642         /**
    643          * Parse the Information Element and the 16-bit Capability Information field
    644          * to build the InformationElemmentUtil.capabilities object.
    645          *
    646          * @param ies -- Information Element array
    647          * @param beaconCap -- 16-bit Beacon Capability Information field
    648          */
    649 
    650         public void from(InformationElement[] ies, BitSet beaconCap) {
    651             protocol = new ArrayList<Integer>();
    652             keyManagement = new ArrayList<ArrayList<Integer>>();
    653             groupCipher = new ArrayList<Integer>();
    654             pairwiseCipher = new ArrayList<ArrayList<Integer>>();
    655 
    656             if (ies == null || beaconCap == null) {
    657                 return;
    658             }
    659             isESS = beaconCap.get(CAP_ESS_BIT_OFFSET);
    660             isPrivacy = beaconCap.get(CAP_PRIVACY_BIT_OFFSET);
    661             for (InformationElement ie : ies) {
    662                 if (ie.id == InformationElement.EID_RSN) {
    663                     parseRsnElement(ie);
    664                 }
    665 
    666                 if (ie.id == InformationElement.EID_VSA) {
    667                     if (isWpaOneElement(ie)) {
    668                         parseWpaOneElement(ie);
    669                     }
    670                     if (isWpsElement(ie)) {
    671                         // TODO(b/62134557): parse WPS IE to provide finer granularity information.
    672                         isWPS = true;
    673                     }
    674                 }
    675             }
    676         }
    677 
    678         private String protocolToString(int protocol) {
    679             switch (protocol) {
    680                 case ScanResult.PROTOCOL_NONE:
    681                     return "None";
    682                 case ScanResult.PROTOCOL_WPA:
    683                     return "WPA";
    684                 case ScanResult.PROTOCOL_WPA2:
    685                     return "WPA2";
    686                 default:
    687                     return "?";
    688             }
    689         }
    690 
    691         private String keyManagementToString(int akm) {
    692             switch (akm) {
    693                 case ScanResult.KEY_MGMT_NONE:
    694                     return "None";
    695                 case ScanResult.KEY_MGMT_PSK:
    696                     return "PSK";
    697                 case ScanResult.KEY_MGMT_EAP:
    698                     return "EAP";
    699                 case ScanResult.KEY_MGMT_FT_EAP:
    700                     return "FT/EAP";
    701                 case ScanResult.KEY_MGMT_FT_PSK:
    702                     return "FT/PSK";
    703                 case ScanResult.KEY_MGMT_EAP_SHA256:
    704                     return "EAP-SHA256";
    705                 case ScanResult.KEY_MGMT_PSK_SHA256:
    706                     return "PSK-SHA256";
    707                 default:
    708                     return "?";
    709             }
    710         }
    711 
    712         private String cipherToString(int cipher) {
    713             switch (cipher) {
    714                 case ScanResult.CIPHER_NONE:
    715                     return "None";
    716                 case ScanResult.CIPHER_CCMP:
    717                     return "CCMP";
    718                 case ScanResult.CIPHER_TKIP:
    719                     return "TKIP";
    720                 default:
    721                     return "?";
    722             }
    723         }
    724 
    725         /**
    726          * Build the ScanResult.capabilities String.
    727          *
    728          * @return security string that mirrors what wpa_supplicant generates
    729          */
    730         public String generateCapabilitiesString() {
    731             String capabilities = "";
    732             // private Beacon without an RSNE or WPA IE, hence WEP0
    733             boolean isWEP = (protocol.isEmpty()) && isPrivacy;
    734 
    735             if (isWEP) {
    736                 capabilities += "[WEP]";
    737             }
    738             for (int i = 0; i < protocol.size(); i++) {
    739                 capabilities += "[" + protocolToString(protocol.get(i));
    740                 if (i < keyManagement.size()) {
    741                     for (int j = 0; j < keyManagement.get(i).size(); j++) {
    742                         capabilities += ((j == 0) ? "-" : "+")
    743                                 + keyManagementToString(keyManagement.get(i).get(j));
    744                     }
    745                 }
    746                 if (i < pairwiseCipher.size()) {
    747                     for (int j = 0; j < pairwiseCipher.get(i).size(); j++) {
    748                         capabilities += ((j == 0) ? "-" : "+")
    749                                 + cipherToString(pairwiseCipher.get(i).get(j));
    750                     }
    751                 }
    752                 capabilities += "]";
    753             }
    754             if (isESS) {
    755                 capabilities += "[ESS]";
    756             }
    757             if (isWPS) {
    758                 capabilities += "[WPS]";
    759             }
    760 
    761             return capabilities;
    762         }
    763     }
    764 
    765     /**
    766      * Parser for the Traffic Indication Map (TIM) Information Element (EID 5). This element will
    767      * only be present in scan results that are derived from a Beacon Frame, not from the more
    768      * plentiful probe responses. Call 'isValid()' after parsing, to ensure the results are correct.
    769      */
    770     public static class TrafficIndicationMap {
    771         private static final int MAX_TIM_LENGTH = 254;
    772         private boolean mValid = false;
    773         public int mLength = 0;
    774         public int mDtimCount = -1;
    775         //Negative DTIM Period means no TIM element was given this frame.
    776         public int mDtimPeriod = -1;
    777         public int mBitmapControl = 0;
    778 
    779         /**
    780          * Is this a valid TIM information element.
    781          */
    782         public boolean isValid() {
    783             return mValid;
    784         }
    785 
    786         // Traffic Indication Map format (size unit: byte)
    787         //
    788         //| ElementID | Length | DTIM Count | DTIM Period | BitmapControl | Partial Virtual Bitmap |
    789         //      1          1          1            1               1                1 - 251
    790         //
    791         // Note: InformationElement.bytes has 'Element ID' and 'Length'
    792         //       stripped off already
    793         //
    794         public void from(InformationElement ie) {
    795             mValid = false;
    796             if (ie == null || ie.bytes == null) return;
    797             mLength = ie.bytes.length;
    798             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    799             try {
    800                 mDtimCount = data.get() & Constants.BYTE_MASK;
    801                 mDtimPeriod = data.get() & Constants.BYTE_MASK;
    802                 mBitmapControl = data.get() & Constants.BYTE_MASK;
    803                 //A valid TIM element must have atleast one more byte
    804                 data.get();
    805             } catch (BufferUnderflowException e) {
    806                 return;
    807             }
    808             if (mLength <= MAX_TIM_LENGTH && mDtimPeriod > 0) {
    809                 mValid = true;
    810             }
    811         }
    812     }
    813 
    814     /**
    815      * This util class determines the 802.11 standard (a/b/g/n/ac) being used
    816      */
    817     public static class WifiMode {
    818         public static final int MODE_UNDEFINED = 0; // Unknown/undefined
    819         public static final int MODE_11A = 1;       // 802.11a
    820         public static final int MODE_11B = 2;       // 802.11b
    821         public static final int MODE_11G = 3;       // 802.11g
    822         public static final int MODE_11N = 4;       // 802.11n
    823         public static final int MODE_11AC = 5;      // 802.11ac
    824         //<TODO> add support for 802.11ad and be more selective instead of defaulting to 11A
    825 
    826         /**
    827          * Use frequency, max supported rate, and the existence of VHT, HT & ERP fields in scan
    828          * scan result to determine the 802.11 Wifi standard being used.
    829          */
    830         public static int determineMode(int frequency, int maxRate, boolean foundVht,
    831                 boolean foundHt, boolean foundErp) {
    832             if (foundVht) {
    833                 return MODE_11AC;
    834             } else if (foundHt) {
    835                 return MODE_11N;
    836             } else if (foundErp) {
    837                 return MODE_11G;
    838             } else if (frequency < 3000) {
    839                 if (maxRate < 24000000) {
    840                     return MODE_11B;
    841                 } else {
    842                     return MODE_11G;
    843                 }
    844             } else {
    845                 return MODE_11A;
    846             }
    847         }
    848 
    849         /**
    850          * Map the wifiMode integer to its type, and output as String MODE_11<A/B/G/N/AC>
    851          */
    852         public static String toString(int mode) {
    853             switch(mode) {
    854                 case MODE_11A:
    855                     return "MODE_11A";
    856                 case MODE_11B:
    857                     return "MODE_11B";
    858                 case MODE_11G:
    859                     return "MODE_11G";
    860                 case MODE_11N:
    861                     return "MODE_11N";
    862                 case MODE_11AC:
    863                     return "MODE_11AC";
    864                 default:
    865                     return "MODE_UNDEFINED";
    866             }
    867         }
    868     }
    869 
    870     /**
    871      * Parser for both the Supported Rates & Extended Supported Rates Information Elements
    872      */
    873     public static class SupportedRates {
    874         public static final int MASK = 0x7F; // 0111 1111
    875         public boolean mValid = false;
    876         public ArrayList<Integer> mRates;
    877 
    878         public SupportedRates() {
    879             mRates = new ArrayList<Integer>();
    880         }
    881 
    882         /**
    883          * Is this a valid Supported Rates information element.
    884          */
    885         public boolean isValid() {
    886             return mValid;
    887         }
    888 
    889         /**
    890          * get the Rate in bits/s from associated byteval
    891          */
    892         public static int getRateFromByte(int byteVal) {
    893             byteVal &= MASK;
    894             switch(byteVal) {
    895                 case 2:
    896                     return 1000000;
    897                 case 4:
    898                     return 2000000;
    899                 case 11:
    900                     return 5500000;
    901                 case 12:
    902                     return 6000000;
    903                 case 18:
    904                     return 9000000;
    905                 case 22:
    906                     return 11000000;
    907                 case 24:
    908                     return 12000000;
    909                 case 36:
    910                     return 18000000;
    911                 case 44:
    912                     return 22000000;
    913                 case 48:
    914                     return 24000000;
    915                 case 66:
    916                     return 33000000;
    917                 case 72:
    918                     return 36000000;
    919                 case 96:
    920                     return 48000000;
    921                 case 108:
    922                     return 54000000;
    923                 default:
    924                     //ERROR UNKNOWN RATE
    925                     return -1;
    926             }
    927         }
    928 
    929         // Supported Rates format (size unit: byte)
    930         //
    931         //| ElementID | Length | Supported Rates  [7 Little Endian Info bits - 1 Flag bit]
    932         //      1          1          1 - 8
    933         //
    934         // Note: InformationElement.bytes has 'Element ID' and 'Length'
    935         //       stripped off already
    936         //
    937         public void from(InformationElement ie) {
    938             mValid = false;
    939             if (ie == null || ie.bytes == null || ie.bytes.length > 8 || ie.bytes.length < 1)  {
    940                 return;
    941             }
    942             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    943             try {
    944                 for (int i = 0; i < ie.bytes.length; i++) {
    945                     int rate = getRateFromByte(data.get());
    946                     if (rate > 0) {
    947                         mRates.add(rate);
    948                     } else {
    949                         return;
    950                     }
    951                 }
    952             } catch (BufferUnderflowException e) {
    953                 return;
    954             }
    955             mValid = true;
    956             return;
    957         }
    958 
    959         /**
    960          * Lists the rates in a human readable string
    961          */
    962         public String toString() {
    963             StringBuilder sbuf = new StringBuilder();
    964             for (Integer rate : mRates) {
    965                 sbuf.append(String.format("%.1f", (double) rate / 1000000) + ", ");
    966             }
    967             return sbuf.toString();
    968         }
    969     }
    970 }
    971