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