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