Home | History | Annotate | Download | only in hotspot2
      1 package com.android.server.wifi.hotspot2;
      2 
      3 import static com.android.server.wifi.hotspot2.anqp.Constants.BYTES_IN_EUI48;
      4 import static com.android.server.wifi.hotspot2.anqp.Constants.BYTE_MASK;
      5 
      6 import android.net.wifi.ScanResult;
      7 import android.util.Log;
      8 
      9 import com.android.server.wifi.hotspot2.anqp.ANQPElement;
     10 import com.android.server.wifi.hotspot2.anqp.Constants;
     11 import com.android.server.wifi.hotspot2.anqp.RawByteElement;
     12 import com.android.server.wifi.util.InformationElementUtil;
     13 
     14 import java.nio.BufferUnderflowException;
     15 import java.nio.ByteBuffer;
     16 import java.nio.CharBuffer;
     17 import java.nio.charset.CharacterCodingException;
     18 import java.nio.charset.CharsetDecoder;
     19 import java.nio.charset.StandardCharsets;
     20 import java.util.ArrayList;
     21 import java.util.List;
     22 import java.util.Map;
     23 
     24 public class NetworkDetail {
     25 
     26     private static final boolean DBG = false;
     27 
     28     private static final String TAG = "NetworkDetail:";
     29 
     30     public enum Ant {
     31         Private,
     32         PrivateWithGuest,
     33         ChargeablePublic,
     34         FreePublic,
     35         Personal,
     36         EmergencyOnly,
     37         Resvd6,
     38         Resvd7,
     39         Resvd8,
     40         Resvd9,
     41         Resvd10,
     42         Resvd11,
     43         Resvd12,
     44         Resvd13,
     45         TestOrExperimental,
     46         Wildcard
     47     }
     48 
     49     public enum HSRelease {
     50         R1,
     51         R2,
     52         Unknown
     53     }
     54 
     55     // General identifiers:
     56     private final String mSSID;
     57     private final long mHESSID;
     58     private final long mBSSID;
     59     // True if the SSID is potentially from a hidden network
     60     private final boolean mIsHiddenSsid;
     61 
     62     // BSS Load element:
     63     private final int mStationCount;
     64     private final int mChannelUtilization;
     65     private final int mCapacity;
     66 
     67     //channel detailed information
     68    /*
     69     * 0 -- 20 MHz
     70     * 1 -- 40 MHz
     71     * 2 -- 80 MHz
     72     * 3 -- 160 MHz
     73     * 4 -- 80 + 80 MHz
     74     */
     75     private final int mChannelWidth;
     76     private final int mPrimaryFreq;
     77     private final int mCenterfreq0;
     78     private final int mCenterfreq1;
     79 
     80     /*
     81      * 802.11 Standard (calculated from Capabilities and Supported Rates)
     82      * 0 -- Unknown
     83      * 1 -- 802.11a
     84      * 2 -- 802.11b
     85      * 3 -- 802.11g
     86      * 4 -- 802.11n
     87      * 7 -- 802.11ac
     88      */
     89     private final int mWifiMode;
     90     private final int mMaxRate;
     91 
     92     /*
     93      * From Interworking element:
     94      * mAnt non null indicates the presence of Interworking, i.e. 802.11u
     95      */
     96     private final Ant mAnt;
     97     private final boolean mInternet;
     98 
     99     /*
    100      * From HS20 Indication element:
    101      * mHSRelease is null only if the HS20 Indication element was not present.
    102      * mAnqpDomainID is set to -1 if not present in the element.
    103      */
    104     private final HSRelease mHSRelease;
    105     private final int mAnqpDomainID;
    106 
    107     /*
    108      * From beacon:
    109      * mAnqpOICount is how many additional OIs are available through ANQP.
    110      * mRoamingConsortiums is either null, if the element was not present, or is an array of
    111      * 1, 2 or 3 longs in which the roaming consortium values occupy the LSBs.
    112      */
    113     private final int mAnqpOICount;
    114     private final long[] mRoamingConsortiums;
    115     private int mDtimInterval = -1;
    116 
    117     private final InformationElementUtil.ExtendedCapabilities mExtendedCapabilities;
    118 
    119     private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements;
    120 
    121     public NetworkDetail(String bssid, ScanResult.InformationElement[] infoElements,
    122             List<String> anqpLines, int freq) {
    123         if (infoElements == null) {
    124             throw new IllegalArgumentException("Null information elements");
    125         }
    126 
    127         mBSSID = Utils.parseMac(bssid);
    128 
    129         String ssid = null;
    130         boolean isHiddenSsid = false;
    131         byte[] ssidOctets = null;
    132 
    133         InformationElementUtil.BssLoad bssLoad = new InformationElementUtil.BssLoad();
    134 
    135         InformationElementUtil.Interworking interworking =
    136                 new InformationElementUtil.Interworking();
    137 
    138         InformationElementUtil.RoamingConsortium roamingConsortium =
    139                 new InformationElementUtil.RoamingConsortium();
    140 
    141         InformationElementUtil.Vsa vsa = new InformationElementUtil.Vsa();
    142 
    143         InformationElementUtil.HtOperation htOperation = new InformationElementUtil.HtOperation();
    144         InformationElementUtil.VhtOperation vhtOperation =
    145                 new InformationElementUtil.VhtOperation();
    146 
    147         InformationElementUtil.ExtendedCapabilities extendedCapabilities =
    148                 new InformationElementUtil.ExtendedCapabilities();
    149 
    150         InformationElementUtil.TrafficIndicationMap trafficIndicationMap =
    151                 new InformationElementUtil.TrafficIndicationMap();
    152 
    153         InformationElementUtil.SupportedRates supportedRates =
    154                 new InformationElementUtil.SupportedRates();
    155         InformationElementUtil.SupportedRates extendedSupportedRates =
    156                 new InformationElementUtil.SupportedRates();
    157 
    158         RuntimeException exception = null;
    159 
    160         ArrayList<Integer> iesFound = new ArrayList<Integer>();
    161         try {
    162             for (ScanResult.InformationElement ie : infoElements) {
    163                 iesFound.add(ie.id);
    164                 switch (ie.id) {
    165                     case ScanResult.InformationElement.EID_SSID:
    166                         ssidOctets = ie.bytes;
    167                         break;
    168                     case ScanResult.InformationElement.EID_BSS_LOAD:
    169                         bssLoad.from(ie);
    170                         break;
    171                     case ScanResult.InformationElement.EID_HT_OPERATION:
    172                         htOperation.from(ie);
    173                         break;
    174                     case ScanResult.InformationElement.EID_VHT_OPERATION:
    175                         vhtOperation.from(ie);
    176                         break;
    177                     case ScanResult.InformationElement.EID_INTERWORKING:
    178                         interworking.from(ie);
    179                         break;
    180                     case ScanResult.InformationElement.EID_ROAMING_CONSORTIUM:
    181                         roamingConsortium.from(ie);
    182                         break;
    183                     case ScanResult.InformationElement.EID_VSA:
    184                         vsa.from(ie);
    185                         break;
    186                     case ScanResult.InformationElement.EID_EXTENDED_CAPS:
    187                         extendedCapabilities.from(ie);
    188                         break;
    189                     case ScanResult.InformationElement.EID_TIM:
    190                         trafficIndicationMap.from(ie);
    191                         break;
    192                     case ScanResult.InformationElement.EID_SUPPORTED_RATES:
    193                         supportedRates.from(ie);
    194                         break;
    195                     case ScanResult.InformationElement.EID_EXTENDED_SUPPORTED_RATES:
    196                         extendedSupportedRates.from(ie);
    197                         break;
    198                     default:
    199                         break;
    200                 }
    201             }
    202         }
    203         catch (IllegalArgumentException | BufferUnderflowException | ArrayIndexOutOfBoundsException e) {
    204             Log.d(Utils.hs2LogTag(getClass()), "Caught " + e);
    205             if (ssidOctets == null) {
    206                 throw new IllegalArgumentException("Malformed IE string (no SSID)", e);
    207             }
    208             exception = e;
    209         }
    210         if (ssidOctets != null) {
    211             /*
    212              * Strict use of the "UTF-8 SSID" bit by APs appears to be spotty at best even if the
    213              * encoding truly is in UTF-8. An unconditional attempt to decode the SSID as UTF-8 is
    214              * therefore always made with a fall back to 8859-1 under normal circumstances.
    215              * If, however, a previous exception was detected and the UTF-8 bit is set, failure to
    216              * decode the SSID will be used as an indication that the whole frame is malformed and
    217              * an exception will be triggered.
    218              */
    219             CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
    220             try {
    221                 CharBuffer decoded = decoder.decode(ByteBuffer.wrap(ssidOctets));
    222                 ssid = decoded.toString();
    223             }
    224             catch (CharacterCodingException cce) {
    225                 ssid = null;
    226             }
    227 
    228             if (ssid == null) {
    229                 if (extendedCapabilities.isStrictUtf8() && exception != null) {
    230                     throw new IllegalArgumentException("Failed to decode SSID in dubious IE string");
    231                 }
    232                 else {
    233                     ssid = new String(ssidOctets, StandardCharsets.ISO_8859_1);
    234                 }
    235             }
    236             isHiddenSsid = true;
    237             for (byte byteVal : ssidOctets) {
    238                 if (byteVal != 0) {
    239                     isHiddenSsid = false;
    240                     break;
    241                 }
    242             }
    243         }
    244 
    245         mSSID = ssid;
    246         mHESSID = interworking.hessid;
    247         mIsHiddenSsid = isHiddenSsid;
    248         mStationCount = bssLoad.stationCount;
    249         mChannelUtilization = bssLoad.channelUtilization;
    250         mCapacity = bssLoad.capacity;
    251         mAnt = interworking.ant;
    252         mInternet = interworking.internet;
    253         mHSRelease = vsa.hsRelease;
    254         mAnqpDomainID = vsa.anqpDomainID;
    255         mAnqpOICount = roamingConsortium.anqpOICount;
    256         mRoamingConsortiums = roamingConsortium.roamingConsortiums;
    257         mExtendedCapabilities = extendedCapabilities;
    258         mANQPElements = null;
    259         //set up channel info
    260         mPrimaryFreq = freq;
    261 
    262         if (vhtOperation.isValid()) {
    263             // 80 or 160 MHz
    264             mChannelWidth = vhtOperation.getChannelWidth();
    265             mCenterfreq0 = vhtOperation.getCenterFreq0();
    266             mCenterfreq1 = vhtOperation.getCenterFreq1();
    267         } else {
    268             mChannelWidth = htOperation.getChannelWidth();
    269             mCenterfreq0 = htOperation.getCenterFreq0(mPrimaryFreq);
    270             mCenterfreq1  = 0;
    271         }
    272 
    273         // If trafficIndicationMap is not valid, mDtimPeriod will be negative
    274         if (trafficIndicationMap.isValid()) {
    275             mDtimInterval = trafficIndicationMap.mDtimPeriod;
    276         }
    277 
    278         int maxRateA = 0;
    279         int maxRateB = 0;
    280         // If we got some Extended supported rates, consider them, if not default to 0
    281         if (extendedSupportedRates.isValid()) {
    282             // rates are sorted from smallest to largest in InformationElement
    283             maxRateB = extendedSupportedRates.mRates.get(extendedSupportedRates.mRates.size() - 1);
    284         }
    285         // Only process the determination logic if we got a 'SupportedRates'
    286         if (supportedRates.isValid()) {
    287             maxRateA = supportedRates.mRates.get(supportedRates.mRates.size() - 1);
    288             mMaxRate = maxRateA > maxRateB ? maxRateA : maxRateB;
    289             mWifiMode = InformationElementUtil.WifiMode.determineMode(mPrimaryFreq, mMaxRate,
    290                     vhtOperation.isValid(),
    291                     iesFound.contains(ScanResult.InformationElement.EID_HT_OPERATION),
    292                     iesFound.contains(ScanResult.InformationElement.EID_ERP));
    293         } else {
    294             mWifiMode = 0;
    295             mMaxRate = 0;
    296             Log.w("WifiMode", mSSID + ", Invalid SupportedRates!!!");
    297         }
    298         if (DBG) {
    299             Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq
    300                     + " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: " + mCenterfreq1
    301                     + (extendedCapabilities.is80211McRTTResponder() ? "Support RTT responder"
    302                     : "Do not support RTT responder"));
    303             Log.v("WifiMode", mSSID
    304                     + ", WifiMode: " + InformationElementUtil.WifiMode.toString(mWifiMode)
    305                     + ", Freq: " + mPrimaryFreq
    306                     + ", mMaxRate: " + mMaxRate
    307                     + ", VHT: " + String.valueOf(vhtOperation.isValid())
    308                     + ", HT: " + String.valueOf(
    309                     iesFound.contains(ScanResult.InformationElement.EID_HT_OPERATION))
    310                     + ", ERP: " + String.valueOf(
    311                     iesFound.contains(ScanResult.InformationElement.EID_ERP))
    312                     + ", SupportedRates: " + supportedRates.toString()
    313                     + " ExtendedSupportedRates: " + extendedSupportedRates.toString());
    314         }
    315     }
    316 
    317     private static ByteBuffer getAndAdvancePayload(ByteBuffer data, int plLength) {
    318         ByteBuffer payload = data.duplicate().order(data.order());
    319         payload.limit(payload.position() + plLength);
    320         data.position(data.position() + plLength);
    321         return payload;
    322     }
    323 
    324     private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
    325         mSSID = base.mSSID;
    326         mIsHiddenSsid = base.mIsHiddenSsid;
    327         mBSSID = base.mBSSID;
    328         mHESSID = base.mHESSID;
    329         mStationCount = base.mStationCount;
    330         mChannelUtilization = base.mChannelUtilization;
    331         mCapacity = base.mCapacity;
    332         mAnt = base.mAnt;
    333         mInternet = base.mInternet;
    334         mHSRelease = base.mHSRelease;
    335         mAnqpDomainID = base.mAnqpDomainID;
    336         mAnqpOICount = base.mAnqpOICount;
    337         mRoamingConsortiums = base.mRoamingConsortiums;
    338         mExtendedCapabilities =
    339                 new InformationElementUtil.ExtendedCapabilities(base.mExtendedCapabilities);
    340         mANQPElements = anqpElements;
    341         mChannelWidth = base.mChannelWidth;
    342         mPrimaryFreq = base.mPrimaryFreq;
    343         mCenterfreq0 = base.mCenterfreq0;
    344         mCenterfreq1 = base.mCenterfreq1;
    345         mDtimInterval = base.mDtimInterval;
    346         mWifiMode = base.mWifiMode;
    347         mMaxRate = base.mMaxRate;
    348     }
    349 
    350     public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
    351         return new NetworkDetail(this, anqpElements);
    352     }
    353 
    354     public boolean queriable(List<Constants.ANQPElementType> queryElements) {
    355         return mAnt != null &&
    356                 (Constants.hasBaseANQPElements(queryElements) ||
    357                  Constants.hasR2Elements(queryElements) && mHSRelease == HSRelease.R2);
    358     }
    359 
    360     public boolean has80211uInfo() {
    361         return mAnt != null || mRoamingConsortiums != null || mHSRelease != null;
    362     }
    363 
    364     public boolean hasInterworking() {
    365         return mAnt != null;
    366     }
    367 
    368     public String getSSID() {
    369         return mSSID;
    370     }
    371 
    372     public String getTrimmedSSID() {
    373         if (mSSID != null) {
    374             for (int n = 0; n < mSSID.length(); n++) {
    375                 if (mSSID.charAt(n) != 0) {
    376                     return mSSID;
    377                 }
    378             }
    379         }
    380         return "";
    381     }
    382 
    383     public long getHESSID() {
    384         return mHESSID;
    385     }
    386 
    387     public long getBSSID() {
    388         return mBSSID;
    389     }
    390 
    391     public int getStationCount() {
    392         return mStationCount;
    393     }
    394 
    395     public int getChannelUtilization() {
    396         return mChannelUtilization;
    397     }
    398 
    399     public int getCapacity() {
    400         return mCapacity;
    401     }
    402 
    403     public boolean isInterworking() {
    404         return mAnt != null;
    405     }
    406 
    407     public Ant getAnt() {
    408         return mAnt;
    409     }
    410 
    411     public boolean isInternet() {
    412         return mInternet;
    413     }
    414 
    415     public HSRelease getHSRelease() {
    416         return mHSRelease;
    417     }
    418 
    419     public int getAnqpDomainID() {
    420         return mAnqpDomainID;
    421     }
    422 
    423     public byte[] getOsuProviders() {
    424         if (mANQPElements == null) {
    425             return null;
    426         }
    427         ANQPElement osuProviders = mANQPElements.get(Constants.ANQPElementType.HSOSUProviders);
    428         return osuProviders != null ? ((RawByteElement) osuProviders).getPayload() : null;
    429     }
    430 
    431     public int getAnqpOICount() {
    432         return mAnqpOICount;
    433     }
    434 
    435     public long[] getRoamingConsortiums() {
    436         return mRoamingConsortiums;
    437     }
    438 
    439     public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() {
    440         return mANQPElements;
    441     }
    442 
    443     public int getChannelWidth() {
    444         return mChannelWidth;
    445     }
    446 
    447     public int getCenterfreq0() {
    448         return mCenterfreq0;
    449     }
    450 
    451     public int getCenterfreq1() {
    452         return mCenterfreq1;
    453     }
    454 
    455     public int getWifiMode() {
    456         return mWifiMode;
    457     }
    458 
    459     public int getDtimInterval() {
    460         return mDtimInterval;
    461     }
    462 
    463     public boolean is80211McResponderSupport() {
    464         return mExtendedCapabilities.is80211McRTTResponder();
    465     }
    466 
    467     public boolean isSSID_UTF8() {
    468         return mExtendedCapabilities.isStrictUtf8();
    469     }
    470 
    471     @Override
    472     public boolean equals(Object thatObject) {
    473         if (this == thatObject) {
    474             return true;
    475         }
    476         if (thatObject == null || getClass() != thatObject.getClass()) {
    477             return false;
    478         }
    479 
    480         NetworkDetail that = (NetworkDetail)thatObject;
    481 
    482         return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID();
    483     }
    484 
    485     @Override
    486     public int hashCode() {
    487         return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID;
    488     }
    489 
    490     @Override
    491     public String toString() {
    492         return String.format("NetworkInfo{SSID='%s', HESSID=%x, BSSID=%x, StationCount=%d, " +
    493                 "ChannelUtilization=%d, Capacity=%d, Ant=%s, Internet=%s, " +
    494                 "HSRelease=%s, AnqpDomainID=%d, " +
    495                 "AnqpOICount=%d, RoamingConsortiums=%s}",
    496                 mSSID, mHESSID, mBSSID, mStationCount,
    497                 mChannelUtilization, mCapacity, mAnt, mInternet,
    498                 mHSRelease, mAnqpDomainID,
    499                 mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums));
    500     }
    501 
    502     public String toKeyString() {
    503         return mHESSID != 0 ?
    504             String.format("'%s':%012x (%012x)", mSSID, mBSSID, mHESSID) :
    505             String.format("'%s':%012x", mSSID, mBSSID);
    506     }
    507 
    508     public String getBSSIDString() {
    509         return toMACString(mBSSID);
    510     }
    511 
    512     /**
    513      * Evaluates the ScanResult this NetworkDetail is built from
    514      * returns true if built from a Beacon Frame
    515      * returns false if built from a Probe Response
    516      */
    517     public boolean isBeaconFrame() {
    518         // Beacon frames have a 'Traffic Indication Map' Information element
    519         // Probe Responses do not. This is indicated by a DTIM period > 0
    520         return mDtimInterval > 0;
    521     }
    522 
    523     /**
    524      * Evaluates the ScanResult this NetworkDetail is built from
    525      * returns true if built from a hidden Beacon Frame
    526      * returns false if not hidden or not a Beacon
    527      */
    528     public boolean isHiddenBeaconFrame() {
    529         // Hidden networks are not 80211 standard, but it is common for a hidden network beacon
    530         // frame to either send zero-value bytes as the SSID, or to send no bytes at all.
    531         return isBeaconFrame() && mIsHiddenSsid;
    532     }
    533 
    534     public static String toMACString(long mac) {
    535         StringBuilder sb = new StringBuilder();
    536         boolean first = true;
    537         for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) {
    538             if (first) {
    539                 first = false;
    540             } else {
    541                 sb.append(':');
    542             }
    543             sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK));
    544         }
    545         return sb.toString();
    546     }
    547 }
    548