1package com.android.server.wifi.hotspot2;
2
3import android.net.wifi.ScanResult;
4import android.util.Log;
5
6import com.android.server.wifi.anqp.ANQPElement;
7import com.android.server.wifi.anqp.Constants;
8import com.android.server.wifi.anqp.VenueNameElement;
9
10import java.net.ProtocolException;
11import java.nio.BufferUnderflowException;
12import java.nio.ByteBuffer;
13import java.nio.ByteOrder;
14import java.nio.CharBuffer;
15import java.nio.charset.CharacterCodingException;
16import java.nio.charset.CharsetDecoder;
17import java.nio.charset.StandardCharsets;
18import java.util.List;
19import java.util.Map;
20
21import static com.android.server.wifi.anqp.Constants.BYTES_IN_EUI48;
22import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
23import static com.android.server.wifi.anqp.Constants.getInteger;
24
25public 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