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