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