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