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.getRoamingConsortiums();
    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         }
    297         if (DBG) {
    298             Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq
    299                     + " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: " + mCenterfreq1
    300                     + (extendedCapabilities.is80211McRTTResponder() ? "Support RTT responder"
    301                     : "Do not support RTT responder"));
    302             Log.v("WifiMode", mSSID
    303                     + ", WifiMode: " + InformationElementUtil.WifiMode.toString(mWifiMode)
    304                     + ", Freq: " + mPrimaryFreq
    305                     + ", mMaxRate: " + mMaxRate
    306                     + ", VHT: " + String.valueOf(vhtOperation.isValid())
    307                     + ", HT: " + String.valueOf(
    308                     iesFound.contains(ScanResult.InformationElement.EID_HT_OPERATION))
    309                     + ", ERP: " + String.valueOf(
    310                     iesFound.contains(ScanResult.InformationElement.EID_ERP))
    311                     + ", SupportedRates: " + supportedRates.toString()
    312                     + " ExtendedSupportedRates: " + extendedSupportedRates.toString());
    313         }
    314     }
    315 
    316     private static ByteBuffer getAndAdvancePayload(ByteBuffer data, int plLength) {
    317         ByteBuffer payload = data.duplicate().order(data.order());
    318         payload.limit(payload.position() + plLength);
    319         data.position(data.position() + plLength);
    320         return payload;
    321     }
    322 
    323     private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
    324         mSSID = base.mSSID;
    325         mIsHiddenSsid = base.mIsHiddenSsid;
    326         mBSSID = base.mBSSID;
    327         mHESSID = base.mHESSID;
    328         mStationCount = base.mStationCount;
    329         mChannelUtilization = base.mChannelUtilization;
    330         mCapacity = base.mCapacity;
    331         mAnt = base.mAnt;
    332         mInternet = base.mInternet;
    333         mHSRelease = base.mHSRelease;
    334         mAnqpDomainID = base.mAnqpDomainID;
    335         mAnqpOICount = base.mAnqpOICount;
    336         mRoamingConsortiums = base.mRoamingConsortiums;
    337         mExtendedCapabilities =
    338                 new InformationElementUtil.ExtendedCapabilities(base.mExtendedCapabilities);
    339         mANQPElements = anqpElements;
    340         mChannelWidth = base.mChannelWidth;
    341         mPrimaryFreq = base.mPrimaryFreq;
    342         mCenterfreq0 = base.mCenterfreq0;
    343         mCenterfreq1 = base.mCenterfreq1;
    344         mDtimInterval = base.mDtimInterval;
    345         mWifiMode = base.mWifiMode;
    346         mMaxRate = base.mMaxRate;
    347     }
    348 
    349     public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
    350         return new NetworkDetail(this, anqpElements);
    351     }
    352 
    353     public boolean queriable(List<Constants.ANQPElementType> queryElements) {
    354         return mAnt != null &&
    355                 (Constants.hasBaseANQPElements(queryElements) ||
    356                  Constants.hasR2Elements(queryElements) && mHSRelease == HSRelease.R2);
    357     }
    358 
    359     public boolean has80211uInfo() {
    360         return mAnt != null || mRoamingConsortiums != null || mHSRelease != null;
    361     }
    362 
    363     public boolean hasInterworking() {
    364         return mAnt != null;
    365     }
    366 
    367     public String getSSID() {
    368         return mSSID;
    369     }
    370 
    371     public String getTrimmedSSID() {
    372         if (mSSID != null) {
    373             for (int n = 0; n < mSSID.length(); n++) {
    374                 if (mSSID.charAt(n) != 0) {
    375                     return mSSID;
    376                 }
    377             }
    378         }
    379         return "";
    380     }
    381 
    382     public long getHESSID() {
    383         return mHESSID;
    384     }
    385 
    386     public long getBSSID() {
    387         return mBSSID;
    388     }
    389 
    390     public int getStationCount() {
    391         return mStationCount;
    392     }
    393 
    394     public int getChannelUtilization() {
    395         return mChannelUtilization;
    396     }
    397 
    398     public int getCapacity() {
    399         return mCapacity;
    400     }
    401 
    402     public boolean isInterworking() {
    403         return mAnt != null;
    404     }
    405 
    406     public Ant getAnt() {
    407         return mAnt;
    408     }
    409 
    410     public boolean isInternet() {
    411         return mInternet;
    412     }
    413 
    414     public HSRelease getHSRelease() {
    415         return mHSRelease;
    416     }
    417 
    418     public int getAnqpDomainID() {
    419         return mAnqpDomainID;
    420     }
    421 
    422     public byte[] getOsuProviders() {
    423         if (mANQPElements == null) {
    424             return null;
    425         }
    426         ANQPElement osuProviders = mANQPElements.get(Constants.ANQPElementType.HSOSUProviders);
    427         return osuProviders != null ? ((RawByteElement) osuProviders).getPayload() : null;
    428     }
    429 
    430     public int getAnqpOICount() {
    431         return mAnqpOICount;
    432     }
    433 
    434     public long[] getRoamingConsortiums() {
    435         return mRoamingConsortiums;
    436     }
    437 
    438     public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() {
    439         return mANQPElements;
    440     }
    441 
    442     public int getChannelWidth() {
    443         return mChannelWidth;
    444     }
    445 
    446     public int getCenterfreq0() {
    447         return mCenterfreq0;
    448     }
    449 
    450     public int getCenterfreq1() {
    451         return mCenterfreq1;
    452     }
    453 
    454     public int getWifiMode() {
    455         return mWifiMode;
    456     }
    457 
    458     public int getDtimInterval() {
    459         return mDtimInterval;
    460     }
    461 
    462     public boolean is80211McResponderSupport() {
    463         return mExtendedCapabilities.is80211McRTTResponder();
    464     }
    465 
    466     public boolean isSSID_UTF8() {
    467         return mExtendedCapabilities.isStrictUtf8();
    468     }
    469 
    470     @Override
    471     public boolean equals(Object thatObject) {
    472         if (this == thatObject) {
    473             return true;
    474         }
    475         if (thatObject == null || getClass() != thatObject.getClass()) {
    476             return false;
    477         }
    478 
    479         NetworkDetail that = (NetworkDetail)thatObject;
    480 
    481         return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID();
    482     }
    483 
    484     @Override
    485     public int hashCode() {
    486         return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID;
    487     }
    488 
    489     @Override
    490     public String toString() {
    491         return String.format("NetworkInfo{SSID='%s', HESSID=%x, BSSID=%x, StationCount=%d, " +
    492                 "ChannelUtilization=%d, Capacity=%d, Ant=%s, Internet=%s, " +
    493                 "HSRelease=%s, AnqpDomainID=%d, " +
    494                 "AnqpOICount=%d, RoamingConsortiums=%s}",
    495                 mSSID, mHESSID, mBSSID, mStationCount,
    496                 mChannelUtilization, mCapacity, mAnt, mInternet,
    497                 mHSRelease, mAnqpDomainID,
    498                 mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums));
    499     }
    500 
    501     public String toKeyString() {
    502         return mHESSID != 0 ?
    503             String.format("'%s':%012x (%012x)", mSSID, mBSSID, mHESSID) :
    504             String.format("'%s':%012x", mSSID, mBSSID);
    505     }
    506 
    507     public String getBSSIDString() {
    508         return toMACString(mBSSID);
    509     }
    510 
    511     /**
    512      * Evaluates the ScanResult this NetworkDetail is built from
    513      * returns true if built from a Beacon Frame
    514      * returns false if built from a Probe Response
    515      */
    516     public boolean isBeaconFrame() {
    517         // Beacon frames have a 'Traffic Indication Map' Information element
    518         // Probe Responses do not. This is indicated by a DTIM period > 0
    519         return mDtimInterval > 0;
    520     }
    521 
    522     /**
    523      * Evaluates the ScanResult this NetworkDetail is built from
    524      * returns true if built from a hidden Beacon Frame
    525      * returns false if not hidden or not a Beacon
    526      */
    527     public boolean isHiddenBeaconFrame() {
    528         // Hidden networks are not 80211 standard, but it is common for a hidden network beacon
    529         // frame to either send zero-value bytes as the SSID, or to send no bytes at all.
    530         return isBeaconFrame() && mIsHiddenSsid;
    531     }
    532 
    533     public static String toMACString(long mac) {
    534         StringBuilder sb = new StringBuilder();
    535         boolean first = true;
    536         for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) {
    537             if (first) {
    538                 first = false;
    539             } else {
    540                 sb.append(':');
    541             }
    542             sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK));
    543         }
    544         return sb.toString();
    545     }
    546 }
    547