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