Home | History | Annotate | Download | only in hotspot2
      1 package com.android.server.wifi.hotspot2;
      2 
      3 import android.net.wifi.ScanResult;
      4 import android.util.Log;
      5 
      6 import com.android.server.wifi.anqp.ANQPElement;
      7 import com.android.server.wifi.anqp.Constants;
      8 import com.android.server.wifi.anqp.VenueNameElement;
      9 
     10 import java.net.ProtocolException;
     11 import java.nio.BufferUnderflowException;
     12 import java.nio.ByteBuffer;
     13 import java.nio.ByteOrder;
     14 import java.nio.CharBuffer;
     15 import java.nio.charset.CharacterCodingException;
     16 import java.nio.charset.CharsetDecoder;
     17 import java.nio.charset.StandardCharsets;
     18 import java.util.List;
     19 import java.util.Map;
     20 
     21 import static com.android.server.wifi.anqp.Constants.BYTES_IN_EUI48;
     22 import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
     23 import static com.android.server.wifi.anqp.Constants.getInteger;
     24 
     25 public class NetworkDetail {
     26 
     27     private static final int EID_SSID = 0;
     28     private static final int EID_BSSLoad = 11;
     29     private static final int EID_HT_OPERATION = 61;
     30     private static final int EID_VHT_OPERATION = 192;
     31     private static final int EID_Interworking = 107;
     32     private static final int EID_RoamingConsortium = 111;
     33     private static final int EID_ExtendedCaps = 127;
     34     private static final int EID_VSA = 221;
     35 
     36     private static final int ANQP_DOMID_BIT = 0x04;
     37     private static final int RTT_RESP_ENABLE_BIT = 70;
     38 
     39     private static final long SSID_UTF8_BIT = 0x0001000000000000L;
     40     //turn off when SHIP
     41     private static final boolean DBG = true;
     42     private static final boolean VDBG = false;
     43 
     44     private static final String TAG = "NetworkDetail:";
     45 
     46     public enum Ant {
     47         Private,
     48         PrivateWithGuest,
     49         ChargeablePublic,
     50         FreePublic,
     51         Personal,
     52         EmergencyOnly,
     53         Resvd6,
     54         Resvd7,
     55         Resvd8,
     56         Resvd9,
     57         Resvd10,
     58         Resvd11,
     59         Resvd12,
     60         Resvd13,
     61         TestOrExperimental,
     62         Wildcard
     63     }
     64 
     65     public enum HSRelease {
     66         R1,
     67         R2,
     68         Unknown
     69     }
     70 
     71     // General identifiers:
     72     private final String mSSID;
     73     private final long mHESSID;
     74     private final long mBSSID;
     75 
     76     // BSS Load element:
     77     private final int mStationCount;
     78     private final int mChannelUtilization;
     79     private final int mCapacity;
     80 
     81     //channel detailed information
     82    /*
     83     * 0 -- 20 MHz
     84     * 1 -- 40 MHz
     85     * 2 -- 80 MHz
     86     * 3 -- 160 MHz
     87     * 4 -- 80 + 80 MHz
     88     */
     89     private final int mChannelWidth;
     90     private final int mPrimaryFreq;
     91     private final int mCenterfreq0;
     92     private final int mCenterfreq1;
     93     private final boolean m80211McRTTResponder;
     94     /*
     95      * From Interworking element:
     96      * mAnt non null indicates the presence of Interworking, i.e. 802.11u
     97      * mVenueGroup and mVenueType may be null if not present in the Interworking element.
     98      */
     99     private final Ant mAnt;
    100     private final boolean mInternet;
    101     private final VenueNameElement.VenueGroup mVenueGroup;
    102     private final VenueNameElement.VenueType mVenueType;
    103 
    104     /*
    105      * From HS20 Indication element:
    106      * mHSRelease is null only if the HS20 Indication element was not present.
    107      * mAnqpDomainID is set to -1 if not present in the element.
    108      */
    109     private final HSRelease mHSRelease;
    110     private final int mAnqpDomainID;
    111 
    112     /*
    113      * From beacon:
    114      * mAnqpOICount is how many additional OIs are available through ANQP.
    115      * mRoamingConsortiums is either null, if the element was not present, or is an array of
    116      * 1, 2 or 3 longs in which the roaming consortium values occupy the LSBs.
    117      */
    118     private final int mAnqpOICount;
    119     private final long[] mRoamingConsortiums;
    120 
    121     private final Long mExtendedCapabilities;
    122 
    123     private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements;
    124 
    125     public NetworkDetail(String bssid, String infoElements, List<String> anqpLines, int freq) {
    126 
    127         if (infoElements == null) {
    128             throw new IllegalArgumentException("Null information element string");
    129         }
    130         int separator = infoElements.indexOf('=');
    131         if (separator<0) {
    132             throw new IllegalArgumentException("No element separator");
    133         }
    134 
    135         mBSSID = Utils.parseMac(bssid);
    136 
    137         ByteBuffer data = ByteBuffer.wrap(Utils.hexToBytes(infoElements.substring(separator + 1)))
    138                 .order(ByteOrder.LITTLE_ENDIAN);
    139 
    140         String ssid = null;
    141         byte[] ssidOctets = null;
    142         int stationCount = 0;
    143         int channelUtilization = 0;
    144         int capacity = 0;
    145 
    146         Ant ant = null;
    147         boolean internet = false;
    148         VenueNameElement.VenueGroup venueGroup = null;
    149         VenueNameElement.VenueType venueType = null;
    150         long hessid = 0L;
    151 
    152         int anqpOICount = 0;
    153         long[] roamingConsortiums = null;
    154 
    155         HSRelease hsRelease = null;
    156         int anqpDomainID = 0;       // No domain ID treated the same as a 0; unique info per AP.
    157 
    158         Long extendedCapabilities = null;
    159 
    160         int secondChanelOffset = 0;
    161         int channelMode = 0;
    162         int centerFreqIndex1 = 0;
    163         int centerFreqIndex2 = 0;
    164         boolean RTTResponder = false;
    165 
    166         RuntimeException exception = null;
    167 
    168         try {
    169             while (data.remaining() > 1) {
    170                 int eid = data.get() & Constants.BYTE_MASK;
    171                 int elementLength = data.get() & Constants.BYTE_MASK;
    172 
    173                 if (elementLength > data.remaining()) {
    174                     throw new IllegalArgumentException("Element length " + elementLength +
    175                             " exceeds payload length " + data.remaining() +
    176                             " @ " + data.position());
    177                 }
    178                 if (eid == 0 && elementLength == 0 && ssidOctets != null) {
    179                     // Don't overwrite SSID (eid 0) with trailing zero garbage
    180                     continue;
    181                 }
    182 
    183                 ByteBuffer element;
    184 
    185                 switch (eid) {
    186                     case EID_SSID:
    187                         ssidOctets = new byte[elementLength];
    188                         data.get(ssidOctets);
    189                         break;
    190                     case EID_BSSLoad:
    191                         if (elementLength != 5) {
    192                             throw new IllegalArgumentException("BSS Load element length is not 5: " +
    193                                     elementLength);
    194                         }
    195                         stationCount = data.getShort() & Constants.SHORT_MASK;
    196                         channelUtilization = data.get() & Constants.BYTE_MASK;
    197                         capacity = data.getShort() & Constants.SHORT_MASK;
    198                         break;
    199                     case EID_HT_OPERATION:
    200                         element = getAndAdvancePayload(data, elementLength);
    201                         int primary_channel = element.get();
    202                         secondChanelOffset = element.get() & 0x3;
    203                         break;
    204                     case EID_VHT_OPERATION:
    205                         element = getAndAdvancePayload(data, elementLength);
    206                         channelMode = element.get() & Constants.BYTE_MASK;
    207                         centerFreqIndex1 = element.get() & Constants.BYTE_MASK;
    208                         centerFreqIndex2 = element.get() & Constants.BYTE_MASK;
    209                         break;
    210                     case EID_Interworking:
    211                         int anOptions = data.get() & Constants.BYTE_MASK;
    212                         ant = Ant.values()[anOptions & 0x0f];
    213                         internet = (anOptions & 0x10) != 0;
    214                         // Len 1 none, 3 venue-info, 7 HESSID, 9 venue-info & HESSID
    215                         if (elementLength == 3 || elementLength == 9) {
    216                             try {
    217                                 ByteBuffer vinfo = data.duplicate();
    218                                 vinfo.limit(vinfo.position() + 2);
    219                                 VenueNameElement vne =
    220                                         new VenueNameElement(Constants.ANQPElementType.ANQPVenueName,
    221                                                 vinfo);
    222                                 venueGroup = vne.getGroup();
    223                                 venueType = vne.getType();
    224                                 data.getShort();
    225                             } catch (ProtocolException pe) {
    226                                 /*Cannot happen*/
    227                             }
    228                         } else if (elementLength != 1 && elementLength != 7) {
    229                             throw new IllegalArgumentException("Bad Interworking element length: " +
    230                                     elementLength);
    231                         }
    232                         if (elementLength == 7 || elementLength == 9) {
    233                             hessid = getInteger(data, ByteOrder.BIG_ENDIAN, 6);
    234                         }
    235                         break;
    236                     case EID_RoamingConsortium:
    237                         anqpOICount = data.get() & Constants.BYTE_MASK;
    238 
    239                         int oi12Length = data.get() & Constants.BYTE_MASK;
    240                         int oi1Length = oi12Length & Constants.NIBBLE_MASK;
    241                         int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK;
    242                         int oi3Length = elementLength - 2 - oi1Length - oi2Length;
    243                         int oiCount = 0;
    244                         if (oi1Length > 0) {
    245                             oiCount++;
    246                             if (oi2Length > 0) {
    247                                 oiCount++;
    248                                 if (oi3Length > 0) {
    249                                     oiCount++;
    250                                 }
    251                             }
    252                         }
    253                         roamingConsortiums = new long[oiCount];
    254                         if (oi1Length > 0 && roamingConsortiums.length > 0) {
    255                             roamingConsortiums[0] =
    256                                     getInteger(data, ByteOrder.BIG_ENDIAN, oi1Length);
    257                         }
    258                         if (oi2Length > 0 && roamingConsortiums.length > 1) {
    259                             roamingConsortiums[1] =
    260                                     getInteger(data, ByteOrder.BIG_ENDIAN, oi2Length);
    261                         }
    262                         if (oi3Length > 0 && roamingConsortiums.length > 2) {
    263                             roamingConsortiums[2] =
    264                                     getInteger(data, ByteOrder.BIG_ENDIAN, oi3Length);
    265                         }
    266                         break;
    267                     case EID_VSA:
    268                         element = getAndAdvancePayload(data, elementLength);
    269                         if (elementLength >= 5 && element.getInt() == Constants.HS20_FRAME_PREFIX) {
    270                             int hsConf = element.get() & Constants.BYTE_MASK;
    271                             switch ((hsConf >> 4) & Constants.NIBBLE_MASK) {
    272                                 case 0:
    273                                     hsRelease = HSRelease.R1;
    274                                     break;
    275                                 case 1:
    276                                     hsRelease = HSRelease.R2;
    277                                     break;
    278                                 default:
    279                                     hsRelease = HSRelease.Unknown;
    280                                     break;
    281                             }
    282                             if ((hsConf & ANQP_DOMID_BIT) != 0) {
    283                                 if (elementLength < 7) {
    284                                     throw new IllegalArgumentException(
    285                                             "HS20 indication element too short: " + elementLength);
    286                                 }
    287                                 anqpDomainID = element.getShort() & Constants.SHORT_MASK;
    288                             }
    289                         }
    290                         break;
    291                     case EID_ExtendedCaps:
    292                         element = data.duplicate();
    293                         extendedCapabilities =
    294                                 Constants.getInteger(data, ByteOrder.LITTLE_ENDIAN, elementLength);
    295 
    296                         int index = RTT_RESP_ENABLE_BIT / 8;
    297                         byte offset = RTT_RESP_ENABLE_BIT % 8;
    298 
    299                         if (elementLength < index + 1) {
    300                             RTTResponder = false;
    301                             element.position(element.position() + elementLength);
    302                             break;
    303                         }
    304 
    305                         element.position(element.position() + index);
    306 
    307                         RTTResponder = (element.get() & (0x1 << offset)) != 0;
    308                         break;
    309                     default:
    310                         data.position(data.position() + elementLength);
    311                         break;
    312                 }
    313             }
    314         }
    315         catch (IllegalArgumentException | BufferUnderflowException | ArrayIndexOutOfBoundsException e) {
    316             Log.d(Utils.hs2LogTag(getClass()), "Caught " + e);
    317             if (ssidOctets == null) {
    318                 throw new IllegalArgumentException("Malformed IE string (no SSID)", e);
    319             }
    320             exception = e;
    321         }
    322 
    323         if (ssidOctets != null) {
    324             boolean strictUTF8 = extendedCapabilities != null &&
    325                     ( extendedCapabilities & SSID_UTF8_BIT ) != 0;
    326 
    327             /*
    328              * Strict use of the "UTF-8 SSID" bit by APs appears to be spotty at best even if the
    329              * encoding truly is in UTF-8. An unconditional attempt to decode the SSID as UTF-8 is
    330              * therefore always made with a fall back to 8859-1 under normal circumstances.
    331              * If, however, a previous exception was detected and the UTF-8 bit is set, failure to
    332              * decode the SSID will be used as an indication that the whole frame is malformed and
    333              * an exception will be triggered.
    334              */
    335             CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
    336             try {
    337                 CharBuffer decoded = decoder.decode(ByteBuffer.wrap(ssidOctets));
    338                 ssid = decoded.toString();
    339             }
    340             catch (CharacterCodingException cce) {
    341                 ssid = null;
    342             }
    343 
    344             if (ssid == null) {
    345                 if (strictUTF8 && exception != null) {
    346                     throw new IllegalArgumentException("Failed to decode SSID in dubious IE string");
    347                 }
    348                 else {
    349                     ssid = new String(ssidOctets, StandardCharsets.ISO_8859_1);
    350                 }
    351             }
    352         }
    353 
    354         mSSID = ssid;
    355         mHESSID = hessid;
    356         mStationCount = stationCount;
    357         mChannelUtilization = channelUtilization;
    358         mCapacity = capacity;
    359         mAnt = ant;
    360         mInternet = internet;
    361         mVenueGroup = venueGroup;
    362         mVenueType = venueType;
    363         mHSRelease = hsRelease;
    364         mAnqpDomainID = anqpDomainID;
    365         mAnqpOICount = anqpOICount;
    366         mRoamingConsortiums = roamingConsortiums;
    367         mExtendedCapabilities = extendedCapabilities;
    368         mANQPElements = SupplicantBridge.parseANQPLines(anqpLines);
    369         //set up channel info
    370         mPrimaryFreq = freq;
    371 
    372         if (channelMode != 0) {
    373             // 80 or 160 MHz
    374             mChannelWidth = channelMode + 1;
    375             mCenterfreq0 = (centerFreqIndex1 - 36) * 5 + 5180;
    376             if(channelMode > 1) { //160MHz
    377                 mCenterfreq1 = (centerFreqIndex2 - 36) * 5 + 5180;
    378             } else {
    379                 mCenterfreq1 = 0;
    380             }
    381         } else {
    382             //20 or 40 MHz
    383             if (secondChanelOffset != 0) {//40MHz
    384                 mChannelWidth = 1;
    385                 if (secondChanelOffset == 1) {
    386                     mCenterfreq0 = mPrimaryFreq + 20;
    387                 } else if (secondChanelOffset == 3) {
    388                     mCenterfreq0 = mPrimaryFreq - 20;
    389                 } else {
    390                     mCenterfreq0 = 0;
    391                     Log.e(TAG,"Error on secondChanelOffset");
    392                 }
    393             } else {
    394                 mCenterfreq0 = 0;
    395                 mChannelWidth = 0;
    396             }
    397             mCenterfreq1 = 0;
    398         }
    399         m80211McRTTResponder = RTTResponder;
    400         if (VDBG) {
    401             Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq +
    402                     " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: " + mCenterfreq1 +
    403                     (m80211McRTTResponder ? "Support RTT reponder" : "Do not support RTT responder"));
    404         }
    405     }
    406 
    407     private static ByteBuffer getAndAdvancePayload(ByteBuffer data, int plLength) {
    408         ByteBuffer payload = data.duplicate().order(data.order());
    409         payload.limit(payload.position() + plLength);
    410         data.position(data.position() + plLength);
    411         return payload;
    412     }
    413 
    414     private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
    415         mSSID = base.mSSID;
    416         mBSSID = base.mBSSID;
    417         mHESSID = base.mHESSID;
    418         mStationCount = base.mStationCount;
    419         mChannelUtilization = base.mChannelUtilization;
    420         mCapacity = base.mCapacity;
    421         mAnt = base.mAnt;
    422         mInternet = base.mInternet;
    423         mVenueGroup = base.mVenueGroup;
    424         mVenueType = base.mVenueType;
    425         mHSRelease = base.mHSRelease;
    426         mAnqpDomainID = base.mAnqpDomainID;
    427         mAnqpOICount = base.mAnqpOICount;
    428         mRoamingConsortiums = base.mRoamingConsortiums;
    429         mExtendedCapabilities = base.mExtendedCapabilities;
    430         mANQPElements = anqpElements;
    431         mChannelWidth = base.mChannelWidth;
    432         mPrimaryFreq = base.mPrimaryFreq;
    433         mCenterfreq0 = base.mCenterfreq0;
    434         mCenterfreq1 = base.mCenterfreq1;
    435         m80211McRTTResponder = base.m80211McRTTResponder;
    436     }
    437 
    438     public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
    439         return new NetworkDetail(this, anqpElements);
    440     }
    441 
    442     private static long parseMac(String s) {
    443 
    444         long mac = 0;
    445         int count = 0;
    446         for (int n = 0; n < s.length(); n++) {
    447             int nibble = Utils.fromHex(s.charAt(n), true);
    448             if (nibble >= 0) {
    449                 mac = (mac << 4) | nibble;
    450                 count++;
    451             }
    452         }
    453         if (count < 12 || (count&1) == 1) {
    454             throw new IllegalArgumentException("Bad MAC address: '" + s + "'");
    455         }
    456         return mac;
    457     }
    458 
    459     public boolean has80211uInfo() {
    460         return mAnt != null || mRoamingConsortiums != null || mHSRelease != null;
    461     }
    462 
    463     public boolean hasInterworking() {
    464         return mAnt != null;
    465     }
    466 
    467     public String getSSID() {
    468         return mSSID;
    469     }
    470 
    471     public String getTrimmedSSID() {
    472         for (int n = 0; n < mSSID.length(); n++) {
    473             if (mSSID.charAt(n) != 0) {
    474                 return mSSID;
    475             }
    476         }
    477         return "";
    478     }
    479 
    480     public long getHESSID() {
    481         return mHESSID;
    482     }
    483 
    484     public long getBSSID() {
    485         return mBSSID;
    486     }
    487 
    488     public int getStationCount() {
    489         return mStationCount;
    490     }
    491 
    492     public int getChannelUtilization() {
    493         return mChannelUtilization;
    494     }
    495 
    496     public int getCapacity() {
    497         return mCapacity;
    498     }
    499 
    500     public boolean isInterworking() {
    501         return mAnt != null;
    502     }
    503 
    504     public Ant getAnt() {
    505         return mAnt;
    506     }
    507 
    508     public boolean isInternet() {
    509         return mInternet;
    510     }
    511 
    512     public VenueNameElement.VenueGroup getVenueGroup() {
    513         return mVenueGroup;
    514     }
    515 
    516     public VenueNameElement.VenueType getVenueType() {
    517         return mVenueType;
    518     }
    519 
    520     public HSRelease getHSRelease() {
    521         return mHSRelease;
    522     }
    523 
    524     public int getAnqpDomainID() {
    525         return mAnqpDomainID;
    526     }
    527 
    528     public int getAnqpOICount() {
    529         return mAnqpOICount;
    530     }
    531 
    532     public long[] getRoamingConsortiums() {
    533         return mRoamingConsortiums;
    534     }
    535 
    536     public Long getExtendedCapabilities() {
    537         return mExtendedCapabilities;
    538     }
    539 
    540     public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() {
    541         return mANQPElements;
    542     }
    543 
    544     public int getChannelWidth() {
    545         return mChannelWidth;
    546     }
    547 
    548     public int getCenterfreq0() {
    549         return mCenterfreq0;
    550     }
    551 
    552     public int getCenterfreq1() {
    553         return mCenterfreq1;
    554     }
    555 
    556     public boolean is80211McResponderSupport() {
    557         return m80211McRTTResponder;
    558     }
    559 
    560     public boolean isSSID_UTF8() {
    561         return mExtendedCapabilities != null && (mExtendedCapabilities & SSID_UTF8_BIT) != 0;
    562     }
    563 
    564     @Override
    565     public boolean equals(Object thatObject) {
    566         if (this == thatObject) {
    567             return true;
    568         }
    569         if (thatObject == null || getClass() != thatObject.getClass()) {
    570             return false;
    571         }
    572 
    573         NetworkDetail that = (NetworkDetail)thatObject;
    574 
    575         return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID();
    576     }
    577 
    578     @Override
    579     public int hashCode() {
    580         return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID;
    581     }
    582 
    583     @Override
    584     public String toString() {
    585         return String.format("NetworkInfo{mSSID='%s', mHESSID=%x, mBSSID=%x, mStationCount=%d, " +
    586                 "mChannelUtilization=%d, mCapacity=%d, mAnt=%s, mInternet=%s, " +
    587                 "mVenueGroup=%s, mVenueType=%s, mHSRelease=%s, mAnqpDomainID=%d, " +
    588                 "mAnqpOICount=%d, mRoamingConsortiums=%s}",
    589                 mSSID, mHESSID, mBSSID, mStationCount,
    590                 mChannelUtilization, mCapacity, mAnt, mInternet,
    591                 mVenueGroup, mVenueType, mHSRelease, mAnqpDomainID,
    592                 mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums));
    593     }
    594 
    595     public String toKeyString() {
    596         return mHESSID != 0 ?
    597             String.format("'%s':%012x (%012x)", mSSID, mBSSID, mHESSID) :
    598             String.format("'%s':%012x", mSSID, mBSSID);
    599     }
    600 
    601     public String getBSSIDString() {
    602         return toMACString(mBSSID);
    603     }
    604 
    605     public static String toMACString(long mac) {
    606         StringBuilder sb = new StringBuilder();
    607         boolean first = true;
    608         for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) {
    609             if (first) {
    610                 first = false;
    611             } else {
    612                 sb.append(':');
    613             }
    614             sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK));
    615         }
    616         return sb.toString();
    617     }
    618 
    619     private static final String IE = "ie=" +
    620             "000477696e67" +                // SSID wing
    621             "0b052a00cf611e" +              // BSS Load 42:207:7777
    622             "6b091e0a01610408621205" +      // internet:Experimental:Vehicular:Auto:hessid
    623             "6f0a0e530111112222222229" +    // 14:111111:2222222229
    624             "dd07506f9a10143a01";           // r2:314
    625 
    626     private static final String IE2 = "ie=000f4578616d706c65204e6574776f726b010882848b960c1218240301012a010432043048606c30140100000fac040100000fac040100000fac0100007f04000000806b091e07010203040506076c027f006f1001531122331020304050010203040506dd05506f9a1000";
    627 
    628     public static void main(String[] args) {
    629         ScanResult scanResult = new ScanResult();
    630         scanResult.SSID = "wing";
    631         scanResult.BSSID = "610408";
    632         NetworkDetail nwkDetail = new NetworkDetail(scanResult.BSSID, IE2, null, 0);
    633         System.out.println(nwkDetail);
    634     }
    635 }
    636