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
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