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