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.anqp.VenueNameElement;
13import com.android.server.wifi.util.InformationElementUtil;
14
15import java.nio.BufferUnderflowException;
16import java.nio.ByteBuffer;
17import java.nio.CharBuffer;
18import java.nio.charset.CharacterCodingException;
19import java.nio.charset.CharsetDecoder;
20import java.nio.charset.StandardCharsets;
21import java.util.ArrayList;
22import java.util.List;
23import java.util.Map;
24
25public 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