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 static com.android.server.wifi.anqp.Constants.getInteger;
     19 
     20 import android.net.wifi.ScanResult.InformationElement;
     21 import android.util.Log;
     22 
     23 import com.android.server.wifi.anqp.Constants;
     24 import com.android.server.wifi.anqp.VenueNameElement;
     25 import com.android.server.wifi.hotspot2.NetworkDetail;
     26 
     27 import java.net.ProtocolException;
     28 import java.nio.BufferUnderflowException;
     29 import java.nio.ByteBuffer;
     30 import java.nio.ByteOrder;
     31 import java.util.ArrayList;
     32 import java.util.BitSet;
     33 
     34 public class InformationElementUtil {
     35 
     36     public static InformationElement[] parseInformationElements(byte[] bytes) {
     37         if (bytes == null) {
     38             return new InformationElement[0];
     39         }
     40         ByteBuffer data = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
     41 
     42         ArrayList<InformationElement> infoElements = new ArrayList<>();
     43         boolean found_ssid = false;
     44         while (data.remaining() > 1) {
     45             int eid = data.get() & Constants.BYTE_MASK;
     46             int elementLength = data.get() & Constants.BYTE_MASK;
     47 
     48             if (elementLength > data.remaining() || (eid == InformationElement.EID_SSID
     49                     && found_ssid)) {
     50                 // APs often pad the data with bytes that happen to match that of the EID_SSID
     51                 // marker.  This is not due to a known issue for APs to incorrectly send the SSID
     52                 // name multiple times.
     53                 break;
     54             }
     55             if (eid == InformationElement.EID_SSID) {
     56                 found_ssid = true;
     57             }
     58 
     59             InformationElement ie = new InformationElement();
     60             ie.id = eid;
     61             ie.bytes = new byte[elementLength];
     62             data.get(ie.bytes);
     63             infoElements.add(ie);
     64         }
     65         return infoElements.toArray(new InformationElement[infoElements.size()]);
     66     }
     67 
     68 
     69     public static class BssLoad {
     70         public int stationCount = 0;
     71         public int channelUtilization = 0;
     72         public int capacity = 0;
     73 
     74         public void from(InformationElement ie) {
     75             if (ie.id != InformationElement.EID_BSS_LOAD) {
     76                 throw new IllegalArgumentException("Element id is not BSS_LOAD, : " + ie.id);
     77             }
     78             if (ie.bytes.length != 5) {
     79                 throw new IllegalArgumentException("BSS Load element length is not 5: "
     80                                                    + ie.bytes.length);
     81             }
     82             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
     83             stationCount = data.getShort() & Constants.SHORT_MASK;
     84             channelUtilization = data.get() & Constants.BYTE_MASK;
     85             capacity = data.getShort() & Constants.SHORT_MASK;
     86         }
     87     }
     88 
     89     public static class HtOperation {
     90         public int secondChannelOffset = 0;
     91 
     92         public int getChannelWidth() {
     93             if (secondChannelOffset != 0) {
     94                 return 1;
     95             } else {
     96                 return 0;
     97             }
     98         }
     99 
    100         public int getCenterFreq0(int primaryFrequency) {
    101             //40 MHz
    102             if (secondChannelOffset != 0) {
    103                 if (secondChannelOffset == 1) {
    104                     return primaryFrequency + 10;
    105                 } else if (secondChannelOffset == 3) {
    106                     return primaryFrequency - 10;
    107                 } else {
    108                     Log.e("HtOperation", "Error on secondChannelOffset: " + secondChannelOffset);
    109                     return 0;
    110                 }
    111             } else {
    112                 return 0;
    113             }
    114         }
    115 
    116         public void from(InformationElement ie) {
    117             if (ie.id != InformationElement.EID_HT_OPERATION) {
    118                 throw new IllegalArgumentException("Element id is not HT_OPERATION, : " + ie.id);
    119             }
    120             secondChannelOffset = ie.bytes[1] & 0x3;
    121         }
    122     }
    123 
    124     public static class VhtOperation {
    125         public int channelMode = 0;
    126         public int centerFreqIndex1 = 0;
    127         public int centerFreqIndex2 = 0;
    128 
    129         public boolean isValid() {
    130             return channelMode != 0;
    131         }
    132 
    133         public int getChannelWidth() {
    134             return channelMode + 1;
    135         }
    136 
    137         public int getCenterFreq0() {
    138             //convert channel index to frequency in MHz, channel 36 is 5180MHz
    139             return (centerFreqIndex1 - 36) * 5 + 5180;
    140         }
    141 
    142         public int getCenterFreq1() {
    143             if (channelMode > 1) { //160MHz
    144                 return (centerFreqIndex2 - 36) * 5 + 5180;
    145             } else {
    146                 return 0;
    147             }
    148         }
    149 
    150         public void from(InformationElement ie) {
    151             if (ie.id != InformationElement.EID_VHT_OPERATION) {
    152                 throw new IllegalArgumentException("Element id is not VHT_OPERATION, : " + ie.id);
    153             }
    154             channelMode = ie.bytes[0] & Constants.BYTE_MASK;
    155             centerFreqIndex1 = ie.bytes[1] & Constants.BYTE_MASK;
    156             centerFreqIndex2 = ie.bytes[2] & Constants.BYTE_MASK;
    157         }
    158     }
    159 
    160     public static class Interworking {
    161         public NetworkDetail.Ant ant = null;
    162         public boolean internet = false;
    163         public VenueNameElement.VenueGroup venueGroup = null;
    164         public VenueNameElement.VenueType venueType = null;
    165         public long hessid = 0L;
    166 
    167         public void from(InformationElement ie) {
    168             if (ie.id != InformationElement.EID_INTERWORKING) {
    169                 throw new IllegalArgumentException("Element id is not INTERWORKING, : " + ie.id);
    170             }
    171             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    172             int anOptions = data.get() & Constants.BYTE_MASK;
    173             ant = NetworkDetail.Ant.values()[anOptions & 0x0f];
    174             internet = (anOptions & 0x10) != 0;
    175             // Len 1 none, 3 venue-info, 7 HESSID, 9 venue-info & HESSID
    176             if (ie.bytes.length == 3 || ie.bytes.length == 9) {
    177                 try {
    178                     ByteBuffer vinfo = data.duplicate();
    179                     vinfo.limit(vinfo.position() + 2);
    180                     VenueNameElement vne = new VenueNameElement(
    181                             Constants.ANQPElementType.ANQPVenueName, vinfo);
    182                     venueGroup = vne.getGroup();
    183                     venueType = vne.getType();
    184                 } catch (ProtocolException pe) {
    185                     /*Cannot happen*/
    186                 }
    187             } else if (ie.bytes.length != 1 && ie.bytes.length != 7) {
    188                 throw new IllegalArgumentException("Bad Interworking element length: "
    189                         + ie.bytes.length);
    190             }
    191             if (ie.bytes.length == 7 || ie.bytes.length == 9) {
    192                 hessid = getInteger(data, ByteOrder.BIG_ENDIAN, 6);
    193             }
    194         }
    195     }
    196 
    197     public static class RoamingConsortium {
    198         public int anqpOICount = 0;
    199         public long[] roamingConsortiums = null;
    200 
    201         public void from(InformationElement ie) {
    202             if (ie.id != InformationElement.EID_ROAMING_CONSORTIUM) {
    203                 throw new IllegalArgumentException("Element id is not ROAMING_CONSORTIUM, : "
    204                         + ie.id);
    205             }
    206             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    207             anqpOICount = data.get() & Constants.BYTE_MASK;
    208 
    209             int oi12Length = data.get() & Constants.BYTE_MASK;
    210             int oi1Length = oi12Length & Constants.NIBBLE_MASK;
    211             int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK;
    212             int oi3Length = ie.bytes.length - 2 - oi1Length - oi2Length;
    213             int oiCount = 0;
    214             if (oi1Length > 0) {
    215                 oiCount++;
    216                 if (oi2Length > 0) {
    217                     oiCount++;
    218                     if (oi3Length > 0) {
    219                         oiCount++;
    220                     }
    221                 }
    222             }
    223             roamingConsortiums = new long[oiCount];
    224             if (oi1Length > 0 && roamingConsortiums.length > 0) {
    225                 roamingConsortiums[0] =
    226                         getInteger(data, ByteOrder.BIG_ENDIAN, oi1Length);
    227             }
    228             if (oi2Length > 0 && roamingConsortiums.length > 1) {
    229                 roamingConsortiums[1] =
    230                         getInteger(data, ByteOrder.BIG_ENDIAN, oi2Length);
    231             }
    232             if (oi3Length > 0 && roamingConsortiums.length > 2) {
    233                 roamingConsortiums[2] =
    234                         getInteger(data, ByteOrder.BIG_ENDIAN, oi3Length);
    235             }
    236         }
    237     }
    238 
    239     public static class Vsa {
    240         private static final int ANQP_DOMID_BIT = 0x04;
    241 
    242         public NetworkDetail.HSRelease hsRelease = null;
    243         public int anqpDomainID = 0;    // No domain ID treated the same as a 0; unique info per AP.
    244 
    245         public void from(InformationElement ie) {
    246             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    247             if (ie.bytes.length >= 5 && data.getInt() == Constants.HS20_FRAME_PREFIX) {
    248                 int hsConf = data.get() & Constants.BYTE_MASK;
    249                 switch ((hsConf >> 4) & Constants.NIBBLE_MASK) {
    250                     case 0:
    251                         hsRelease = NetworkDetail.HSRelease.R1;
    252                         break;
    253                     case 1:
    254                         hsRelease = NetworkDetail.HSRelease.R2;
    255                         break;
    256                     default:
    257                         hsRelease = NetworkDetail.HSRelease.Unknown;
    258                         break;
    259                 }
    260                 if ((hsConf & ANQP_DOMID_BIT) != 0) {
    261                     if (ie.bytes.length < 7) {
    262                         throw new IllegalArgumentException(
    263                                 "HS20 indication element too short: " + ie.bytes.length);
    264                     }
    265                     anqpDomainID = data.getShort() & Constants.SHORT_MASK;
    266                 }
    267             }
    268         }
    269     }
    270 
    271     public static class ExtendedCapabilities {
    272         private static final int RTT_RESP_ENABLE_BIT = 70;
    273         private static final long SSID_UTF8_BIT = 0x0001000000000000L;
    274 
    275         public Long extendedCapabilities = null;
    276         public boolean is80211McRTTResponder = false;
    277 
    278         public ExtendedCapabilities() {
    279         }
    280 
    281         public ExtendedCapabilities(ExtendedCapabilities other) {
    282             extendedCapabilities = other.extendedCapabilities;
    283             is80211McRTTResponder = other.is80211McRTTResponder;
    284         }
    285 
    286         public boolean isStrictUtf8() {
    287             return extendedCapabilities != null && (extendedCapabilities & SSID_UTF8_BIT) != 0;
    288         }
    289 
    290         public void from(InformationElement ie) {
    291             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    292             extendedCapabilities =
    293                     Constants.getInteger(data, ByteOrder.LITTLE_ENDIAN, ie.bytes.length);
    294 
    295             int index = RTT_RESP_ENABLE_BIT / 8;
    296             byte offset = RTT_RESP_ENABLE_BIT % 8;
    297             if (ie.bytes.length < index + 1) {
    298                 is80211McRTTResponder = false;
    299             } else {
    300                 is80211McRTTResponder = (ie.bytes[index] & ((byte) 0x1 << offset)) != 0;
    301             }
    302         }
    303     }
    304 
    305     /**
    306      * parse beacon to build the capabilities
    307      *
    308      * This class is used to build the capabilities string of the scan results coming
    309      * from HAL. It parses the ieee beacon's capability field, WPA and RSNE IE as per spec,
    310      * and builds the ScanResult.capabilities String in a way that mirrors the values returned
    311      * by wpa_supplicant.
    312      */
    313     public static class Capabilities {
    314         private static final int CAP_ESS_BIT_OFFSET = 0;
    315         private static final int CAP_PRIVACY_BIT_OFFSET = 4;
    316 
    317         private static final int WPA_VENDOR_OUI_TYPE_ONE = 0x01f25000;
    318         private static final short WPA_VENDOR_OUI_VERSION = 0x0001;
    319         private static final short RSNE_VERSION = 0x0001;
    320 
    321         private static final int WPA_AKM_EAP = 0x01f25000;
    322         private static final int WPA_AKM_PSK = 0x02f25000;
    323 
    324         private static final int WPA2_AKM_EAP = 0x01ac0f00;
    325         private static final int WPA2_AKM_PSK = 0x02ac0f00;
    326         private static final int WPA2_AKM_FT_EAP = 0x03ac0f00;
    327         private static final int WPA2_AKM_FT_PSK = 0x04ac0f00;
    328         private static final int WPA2_AKM_EAP_SHA256 = 0x05ac0f00;
    329         private static final int WPA2_AKM_PSK_SHA256 = 0x06ac0f00;
    330 
    331         public Capabilities() {
    332         }
    333 
    334         // RSNE format (size unit: byte)
    335         //
    336         // | Element ID | Length | Version | Group Data Cipher Suite |
    337         //      1           1         2                 4
    338         // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
    339         //              2                            4 * m
    340         // | AKM Suite Count | AKM Suite List | RSN Capabilities |
    341         //          2               4 * n               2
    342         // | PMKID Count | PMKID List | Group Management Cipher Suite |
    343         //        2          16 * s                 4
    344         //
    345         // Note: InformationElement.bytes has 'Element ID' and 'Length'
    346         //       stripped off already
    347         private static String parseRsnElement(InformationElement ie) {
    348             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    349 
    350             try {
    351                 // version
    352                 if (buf.getShort() != RSNE_VERSION) {
    353                     // incorrect version
    354                     return null;
    355                 }
    356 
    357                 // group data cipher suite
    358                 // here we simply advance the buffer position
    359                 buf.getInt();
    360 
    361                 // found the RSNE IE, hence start building the capability string
    362                 String security = "[WPA2";
    363 
    364                 // pairwise cipher suite count
    365                 short cipherCount = buf.getShort();
    366 
    367                 // pairwise cipher suite list
    368                 for (int i = 0; i < cipherCount; i++) {
    369                     // here we simply advance the buffer position
    370                     buf.getInt();
    371                 }
    372 
    373                 // AKM
    374                 // AKM suite count
    375                 short akmCount = buf.getShort();
    376 
    377                 // parse AKM suite list
    378                 if (akmCount == 0) {
    379                     security += "-EAP"; //default AKM
    380                 }
    381                 boolean found = false;
    382                 for (int i = 0; i < akmCount; i++) {
    383                     int akm = buf.getInt();
    384                     switch (akm) {
    385                         case WPA2_AKM_EAP:
    386                             security += (found ? "+" : "-") + "EAP";
    387                             found = true;
    388                             break;
    389                         case WPA2_AKM_PSK:
    390                             security += (found ? "+" : "-") + "PSK";
    391                             found = true;
    392                             break;
    393                         case WPA2_AKM_FT_EAP:
    394                             security += (found ? "+" : "-") + "FT/EAP";
    395                             found = true;
    396                             break;
    397                         case WPA2_AKM_FT_PSK:
    398                             security += (found ? "+" : "-") + "FT/PSK";
    399                             found = true;
    400                             break;
    401                         case WPA2_AKM_EAP_SHA256:
    402                             security += (found ? "+" : "-") + "EAP-SHA256";
    403                             found = true;
    404                             break;
    405                         case WPA2_AKM_PSK_SHA256:
    406                             security += (found ? "+" : "-") + "PSK-SHA256";
    407                             found = true;
    408                             break;
    409                         default:
    410                             // do nothing
    411                             break;
    412                     }
    413                 }
    414 
    415                 // we parsed what we want at this point
    416                 security += "]";
    417                 return security;
    418             } catch (BufferUnderflowException e) {
    419                 Log.e("IE_Capabilities", "Couldn't parse RSNE, buffer underflow");
    420                 return null;
    421             }
    422         }
    423 
    424         private static boolean isWpaOneElement(InformationElement ie) {
    425             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    426 
    427             try {
    428                 // WPA OUI and type
    429                 return (buf.getInt() == WPA_VENDOR_OUI_TYPE_ONE);
    430             } catch (BufferUnderflowException e) {
    431                 Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
    432                 return false;
    433             }
    434         }
    435 
    436         // WPA type 1 format (size unit: byte)
    437         //
    438         // | Element ID | Length | OUI | Type | Version |
    439         //      1           1       3     1        2
    440         // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
    441         //              2                            4 * m
    442         // | AKM Suite Count | AKM Suite List |
    443         //          2               4 * n
    444         //
    445         // Note: InformationElement.bytes has 'Element ID' and 'Length'
    446         //       stripped off already
    447         //
    448         private static String parseWpaOneElement(InformationElement ie) {
    449             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    450 
    451             try {
    452                 // skip WPA OUI and type parsing. isWpaOneElement() should have
    453                 // been called for verification before we reach here.
    454                 buf.getInt();
    455 
    456                 // start building the string
    457                 String security = "[WPA";
    458 
    459                 // version
    460                 if (buf.getShort() != WPA_VENDOR_OUI_VERSION)  {
    461                     // incorrect version
    462                     return null;
    463                 }
    464 
    465                 // group data cipher suite
    466                 // here we simply advance buffer position
    467                 buf.getInt();
    468 
    469                 // pairwise cipher suite count
    470                 short cipherCount = buf.getShort();
    471 
    472                 // pairwise chipher suite list
    473                 for (int i = 0; i < cipherCount; i++) {
    474                     // here we simply advance buffer position
    475                     buf.getInt();
    476                 }
    477 
    478                 // AKM
    479                 // AKM suite count
    480                 short akmCount = buf.getShort();
    481 
    482                 // AKM suite list
    483                 if (akmCount == 0) {
    484                     security += "-EAP"; //default AKM
    485                 }
    486                 boolean found = false;
    487                 for (int i = 0; i < akmCount; i++) {
    488                     int akm = buf.getInt();
    489                     switch (akm) {
    490                         case WPA_AKM_EAP:
    491                             security += (found ? "+" : "-") + "EAP";
    492                             found = true;
    493                             break;
    494                         case WPA_AKM_PSK:
    495                             security += (found ? "+" : "-") + "PSK";
    496                             found = true;
    497                             break;
    498                         default:
    499                             // do nothing
    500                             break;
    501                     }
    502                 }
    503 
    504                 // we parsed what we want at this point
    505                 security += "]";
    506                 return security;
    507             } catch (BufferUnderflowException e) {
    508                 Log.e("IE_Capabilities", "Couldn't parse type 1 WPA, buffer underflow");
    509                 return null;
    510             }
    511         }
    512 
    513         /**
    514          * Parse the Information Element and the 16-bit Capability Information field
    515          * to build the ScanResult.capabilities String.
    516          *
    517          * @param ies -- Information Element array
    518          * @param beaconCap -- 16-bit Beacon Capability Information field
    519          * @return security string that mirrors what wpa_supplicant generates
    520          */
    521         public static String buildCapabilities(InformationElement[] ies, BitSet beaconCap) {
    522             String capabilities = "";
    523             boolean rsneFound = false;
    524             boolean wpaFound = false;
    525 
    526             if (ies == null || beaconCap == null) {
    527                 return capabilities;
    528             }
    529 
    530             boolean ess = beaconCap.get(CAP_ESS_BIT_OFFSET);
    531             boolean privacy = beaconCap.get(CAP_PRIVACY_BIT_OFFSET);
    532 
    533             for (InformationElement ie : ies) {
    534                 if (ie.id == InformationElement.EID_RSN) {
    535                     rsneFound = true;
    536                     capabilities += parseRsnElement(ie);
    537                 }
    538 
    539                 if (ie.id == InformationElement.EID_VSA) {
    540                     if (isWpaOneElement(ie)) {
    541                         wpaFound = true;
    542                         capabilities += parseWpaOneElement(ie);
    543                     }
    544                 }
    545             }
    546 
    547             if (!rsneFound && !wpaFound && privacy) {
    548                 //private Beacon without an RSNE or WPA IE, hence WEP0
    549                 capabilities += "[WEP]";
    550             }
    551 
    552             if (ess) {
    553                 capabilities += "[ESS]";
    554             }
    555 
    556             return capabilities;
    557         }
    558     }
    559 
    560     /**
    561      * Parser for the Traffic Indication Map (TIM) Information Element (EID 5). This element will
    562      * only be present in scan results that are derived from a Beacon Frame, not from the more
    563      * plentiful probe responses. Call 'isValid()' after parsing, to ensure the results are correct.
    564      */
    565     public static class TrafficIndicationMap {
    566         private static final int MAX_TIM_LENGTH = 254;
    567         private boolean mValid = false;
    568         public int mLength = 0;
    569         public int mDtimCount = -1;
    570         //Negative DTIM Period means no TIM element was given this frame.
    571         public int mDtimPeriod = -1;
    572         public int mBitmapControl = 0;
    573 
    574         /**
    575          * Is this a valid TIM information element.
    576          */
    577         public boolean isValid() {
    578             return mValid;
    579         }
    580 
    581         // Traffic Indication Map format (size unit: byte)
    582         //
    583         //| ElementID | Length | DTIM Count | DTIM Period | BitmapControl | Partial Virtual Bitmap |
    584         //      1          1          1            1               1                1 - 251
    585         //
    586         // Note: InformationElement.bytes has 'Element ID' and 'Length'
    587         //       stripped off already
    588         //
    589         public void from(InformationElement ie) {
    590             mValid = false;
    591             if (ie == null || ie.bytes == null) return;
    592             mLength = ie.bytes.length;
    593             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    594             try {
    595                 mDtimCount = data.get() & Constants.BYTE_MASK;
    596                 mDtimPeriod = data.get() & Constants.BYTE_MASK;
    597                 mBitmapControl = data.get() & Constants.BYTE_MASK;
    598                 //A valid TIM element must have atleast one more byte
    599                 data.get();
    600             } catch (BufferUnderflowException e) {
    601                 return;
    602             }
    603             if (mLength <= MAX_TIM_LENGTH) {
    604                 mValid = true;
    605             }
    606         }
    607     }
    608 
    609     /**
    610      * This util class determines the 802.11 standard (a/b/g/n/ac) being used
    611      */
    612     public static class WifiMode {
    613         public static final int MODE_UNDEFINED = 0; // Unknown/undefined
    614         public static final int MODE_11A = 1;       // 802.11a
    615         public static final int MODE_11B = 2;       // 802.11b
    616         public static final int MODE_11G = 3;       // 802.11g
    617         public static final int MODE_11N = 4;       // 802.11n
    618         public static final int MODE_11AC = 5;      // 802.11ac
    619         //<TODO> add support for 802.11ad and be more selective instead of defaulting to 11A
    620 
    621         /**
    622          * Use frequency, max supported rate, and the existence of VHT, HT & ERP fields in scan
    623          * scan result to determine the 802.11 Wifi standard being used.
    624          */
    625         public static int determineMode(int frequency, int maxRate, boolean foundVht,
    626                 boolean foundHt, boolean foundErp) {
    627             if (foundVht) {
    628                 return MODE_11AC;
    629             } else if (foundHt) {
    630                 return MODE_11N;
    631             } else if (foundErp) {
    632                 return MODE_11G;
    633             } else if (frequency < 3000) {
    634                 if (maxRate < 24000000) {
    635                     return MODE_11B;
    636                 } else {
    637                     return MODE_11G;
    638                 }
    639             } else {
    640                 return MODE_11A;
    641             }
    642         }
    643 
    644         /**
    645          * Map the wifiMode integer to its type, and output as String MODE_11<A/B/G/N/AC>
    646          */
    647         public static String toString(int mode) {
    648             switch(mode) {
    649                 case MODE_11A:
    650                     return "MODE_11A";
    651                 case MODE_11B:
    652                     return "MODE_11B";
    653                 case MODE_11G:
    654                     return "MODE_11G";
    655                 case MODE_11N:
    656                     return "MODE_11N";
    657                 case MODE_11AC:
    658                     return "MODE_11AC";
    659                 default:
    660                     return "MODE_UNDEFINED";
    661             }
    662         }
    663     }
    664 
    665     /**
    666      * Parser for both the Supported Rates & Extended Supported Rates Information Elements
    667      */
    668     public static class SupportedRates {
    669         public static final int MASK = 0x7F; // 0111 1111
    670         public boolean mValid = false;
    671         public ArrayList<Integer> mRates;
    672 
    673         public SupportedRates() {
    674             mRates = new ArrayList<Integer>();
    675         }
    676 
    677         /**
    678          * Is this a valid Supported Rates information element.
    679          */
    680         public boolean isValid() {
    681             return mValid;
    682         }
    683 
    684         /**
    685          * get the Rate in bits/s from associated byteval
    686          */
    687         public static int getRateFromByte(int byteVal) {
    688             byteVal &= MASK;
    689             switch(byteVal) {
    690                 case 2:
    691                     return 1000000;
    692                 case 4:
    693                     return 2000000;
    694                 case 11:
    695                     return 5500000;
    696                 case 12:
    697                     return 6000000;
    698                 case 18:
    699                     return 9000000;
    700                 case 22:
    701                     return 11000000;
    702                 case 24:
    703                     return 12000000;
    704                 case 36:
    705                     return 18000000;
    706                 case 44:
    707                     return 22000000;
    708                 case 48:
    709                     return 24000000;
    710                 case 66:
    711                     return 33000000;
    712                 case 72:
    713                     return 36000000;
    714                 case 96:
    715                     return 48000000;
    716                 case 108:
    717                     return 54000000;
    718                 default:
    719                     //ERROR UNKNOWN RATE
    720                     return -1;
    721             }
    722         }
    723 
    724         // Supported Rates format (size unit: byte)
    725         //
    726         //| ElementID | Length | Supported Rates  [7 Little Endian Info bits - 1 Flag bit]
    727         //      1          1          1 - 8
    728         //
    729         // Note: InformationElement.bytes has 'Element ID' and 'Length'
    730         //       stripped off already
    731         //
    732         public void from(InformationElement ie) {
    733             mValid = false;
    734             if (ie == null || ie.bytes == null || ie.bytes.length > 8 || ie.bytes.length < 1)  {
    735                 return;
    736             }
    737             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
    738             try {
    739                 for (int i = 0; i < ie.bytes.length; i++) {
    740                     int rate = getRateFromByte(data.get());
    741                     if (rate > 0) {
    742                         mRates.add(rate);
    743                     } else {
    744                         return;
    745                     }
    746                 }
    747             } catch (BufferUnderflowException e) {
    748                 return;
    749             }
    750             mValid = true;
    751             return;
    752         }
    753 
    754         /**
    755          * Lists the rates in a human readable string
    756          */
    757         public String toString() {
    758             StringBuilder sbuf = new StringBuilder();
    759             for (Integer rate : mRates) {
    760                 sbuf.append(String.format("%.1f", (double) rate / 1000000) + ", ");
    761             }
    762             return sbuf.toString();
    763         }
    764     }
    765 }
    766