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