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