NetworkDetail.java revision 5bee0e4616e2f8025d60cbfe3eec3e274a68a452
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.BufferUnderflowException;
12import java.nio.ByteBuffer;
13import java.nio.ByteOrder;
14import java.nio.CharBuffer;
15import java.nio.charset.CharacterCodingException;
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 = Utils.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 | BufferUnderflowException e) {
312            Log.d(Utils.hs2LogTag(getClass()), "Caught " + e);
313            if (ssidOctets == null) {
314                throw new IllegalArgumentException("Malformed IE string (no SSID)", e);
315            }
316            exception = e;
317        }
318
319        if (ssidOctets != null) {
320            boolean strictUTF8 = extendedCapabilities != null &&
321                    ( extendedCapabilities & SSID_UTF8_BIT ) != 0;
322
323            /*
324             * Strict use of the "UTF-8 SSID" bit by APs appears to be spotty at best even if the
325             * encoding truly is in UTF-8. An unconditional attempt to decode the SSID as UTF-8 is
326             * therefore always made with a fall back to 8859-1 under normal circumstances.
327             * If, however, a previous exception was detected and the UTF-8 bit is set, failure to
328             * decode the SSID will be used as an indication that the whole frame is malformed and
329             * an exception will be triggered.
330             */
331            CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
332            try {
333                CharBuffer decoded = decoder.decode(ByteBuffer.wrap(ssidOctets));
334                ssid = decoded.toString();
335            }
336            catch (CharacterCodingException cce) {
337                ssid = null;
338            }
339
340            if (ssid == null) {
341                if (strictUTF8 && exception != null) {
342                    throw new IllegalArgumentException("Failed to decode SSID in dubious IE string");
343                }
344                else {
345                    ssid = new String(ssidOctets, StandardCharsets.ISO_8859_1);
346                }
347            }
348        }
349
350        mSSID = ssid;
351        mHESSID = hessid;
352        mStationCount = stationCount;
353        mChannelUtilization = channelUtilization;
354        mCapacity = capacity;
355        mAnt = ant;
356        mInternet = internet;
357        mVenueGroup = venueGroup;
358        mVenueType = venueType;
359        mHSRelease = hsRelease;
360        mAnqpDomainID = anqpDomainID;
361        mAnqpOICount = anqpOICount;
362        mRoamingConsortiums = roamingConsortiums;
363        mExtendedCapabilities = extendedCapabilities;
364        mANQPElements = SupplicantBridge.parseANQPLines(anqpLines);
365        //set up channel info
366        mPrimaryFreq = freq;
367
368        if (channelMode != 0) {
369            // 80 or 160 MHz
370            mChannelWidth = channelMode + 1;
371            mCenterfreq0 = (centerFreqIndex1 - 36) * 5 + 5180;
372            if(channelMode > 1) { //160MHz
373                mCenterfreq1 = (centerFreqIndex2 - 36) * 5 + 5180;
374            } else {
375                mCenterfreq1 = 0;
376            }
377        } else {
378            //20 or 40 MHz
379            if (secondChanelOffset != 0) {//40MHz
380                mChannelWidth = 1;
381                if (secondChanelOffset == 1) {
382                    mCenterfreq0 = mPrimaryFreq + 20;
383                } else if (secondChanelOffset == 3) {
384                    mCenterfreq0 = mPrimaryFreq - 20;
385                } else {
386                    mCenterfreq0 = 0;
387                    Log.e(TAG,"Error on secondChanelOffset");
388                }
389            } else {
390                mCenterfreq0 = 0;
391                mChannelWidth = 0;
392            }
393            mCenterfreq1 = 0;
394        }
395        m80211McRTTResponder = RTTResponder;
396        if (VDBG) {
397            Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq +
398                    " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: " + mCenterfreq1 +
399                    (m80211McRTTResponder ? "Support RTT reponder" : "Do not support RTT responder"));
400        }
401    }
402
403    private static ByteBuffer getAndAdvancePayload(ByteBuffer data, int plLength) {
404        ByteBuffer payload = data.duplicate().order(data.order());
405        payload.limit(payload.position() + plLength);
406        data.position(data.position() + plLength);
407        return payload;
408    }
409
410    private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
411        mSSID = base.mSSID;
412        mBSSID = base.mBSSID;
413        mHESSID = base.mHESSID;
414        mStationCount = base.mStationCount;
415        mChannelUtilization = base.mChannelUtilization;
416        mCapacity = base.mCapacity;
417        mAnt = base.mAnt;
418        mInternet = base.mInternet;
419        mVenueGroup = base.mVenueGroup;
420        mVenueType = base.mVenueType;
421        mHSRelease = base.mHSRelease;
422        mAnqpDomainID = base.mAnqpDomainID;
423        mAnqpOICount = base.mAnqpOICount;
424        mRoamingConsortiums = base.mRoamingConsortiums;
425        mExtendedCapabilities = base.mExtendedCapabilities;
426        mANQPElements = anqpElements;
427        mChannelWidth = base.mChannelWidth;
428        mPrimaryFreq = base.mPrimaryFreq;
429        mCenterfreq0 = base.mCenterfreq0;
430        mCenterfreq1 = base.mCenterfreq1;
431        m80211McRTTResponder = base.m80211McRTTResponder;
432    }
433
434    public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
435        return new NetworkDetail(this, anqpElements);
436    }
437
438    public boolean hasInterworking() {
439        return mAnt != null;
440    }
441
442    public String getSSID() {
443        return mSSID;
444    }
445
446    public String getTrimmedSSID() {
447        for (int n = 0; n < mSSID.length(); n++) {
448            if (mSSID.charAt(n) != 0) {
449                return mSSID;
450            }
451        }
452        return "";
453    }
454
455    public long getHESSID() {
456        return mHESSID;
457    }
458
459    public long getBSSID() {
460        return mBSSID;
461    }
462
463    public int getStationCount() {
464        return mStationCount;
465    }
466
467    public int getChannelUtilization() {
468        return mChannelUtilization;
469    }
470
471    public int getCapacity() {
472        return mCapacity;
473    }
474
475    public boolean isInterworking() {
476        return mAnt != null;
477    }
478
479    public Ant getAnt() {
480        return mAnt;
481    }
482
483    public boolean isInternet() {
484        return mInternet;
485    }
486
487    public VenueNameElement.VenueGroup getVenueGroup() {
488        return mVenueGroup;
489    }
490
491    public VenueNameElement.VenueType getVenueType() {
492        return mVenueType;
493    }
494
495    public HSRelease getHSRelease() {
496        return mHSRelease;
497    }
498
499    public int getAnqpDomainID() {
500        return mAnqpDomainID;
501    }
502
503    public int getAnqpOICount() {
504        return mAnqpOICount;
505    }
506
507    public long[] getRoamingConsortiums() {
508        return mRoamingConsortiums;
509    }
510
511    public Long getExtendedCapabilities() {
512        return mExtendedCapabilities;
513    }
514
515    public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() {
516        return mANQPElements;
517    }
518
519    public int getChannelWidth() {
520        return mChannelWidth;
521    }
522
523    public int getCenterfreq0() {
524        return mCenterfreq0;
525    }
526
527    public int getCenterfreq1() {
528        return mCenterfreq1;
529    }
530
531    public boolean is80211McResponderSupport() {
532        return m80211McRTTResponder;
533    }
534
535    public boolean isSSID_UTF8() {
536        return mExtendedCapabilities != null && (mExtendedCapabilities & SSID_UTF8_BIT) != 0;
537    }
538
539    @Override
540    public boolean equals(Object thatObject) {
541        if (this == thatObject) {
542            return true;
543        }
544        if (thatObject == null || getClass() != thatObject.getClass()) {
545            return false;
546        }
547
548        NetworkDetail that = (NetworkDetail)thatObject;
549
550        return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID();
551    }
552
553    @Override
554    public int hashCode() {
555        return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID;
556    }
557
558    @Override
559    public String toString() {
560        return String.format("NetworkInfo{mSSID='%s', mHESSID=%x, mBSSID=%x, mStationCount=%d, " +
561                "mChannelUtilization=%d, mCapacity=%d, mAnt=%s, mInternet=%s, " +
562                "mVenueGroup=%s, mVenueType=%s, mHSRelease=%s, mAnqpDomainID=%d, " +
563                "mAnqpOICount=%d, mRoamingConsortiums=%s}",
564                mSSID, mHESSID, mBSSID, mStationCount,
565                mChannelUtilization, mCapacity, mAnt, mInternet,
566                mVenueGroup, mVenueType, mHSRelease, mAnqpDomainID,
567                mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums));
568    }
569
570    public String toKeyString() {
571        return mHESSID != 0 ?
572            String.format("'%s':%s (%012x)", mSSID, getBSSIDString(), mHESSID) :
573            String.format("'%s':%s", mSSID, getBSSIDString());
574    }
575
576    public String getBSSIDString() {
577        return toMACString(mBSSID);
578    }
579
580    public static String toMACString(long mac) {
581        StringBuilder sb = new StringBuilder();
582        boolean first = true;
583        for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) {
584            if (first) {
585                first = false;
586            } else {
587                sb.append(':');
588            }
589            sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK));
590        }
591        return sb.toString();
592    }
593
594    private static final String IE = "ie=" +
595            "000477696e67" +                // SSID wing
596            "0b052a00cf611e" +              // BSS Load 42:207:7777
597            "6b091e0a01610408621205" +      // internet:Experimental:Vehicular:Auto:hessid
598            "6f0a0e530111112222222229" +    // 14:111111:2222222229
599            "dd07506f9a10143a01";           // r2:314
600
601    private static final String IE2 = "ie=000f4578616d706c65204e6574776f726b010882848b960c1218240301012a010432043048606c30140100000fac040100000fac040100000fac0100007f04000000806b091e07010203040506076c027f006f1001531122331020304050010203040506dd05506f9a1000";
602
603    public static void main(String[] args) {
604        ScanResult scanResult = new ScanResult();
605        scanResult.SSID = "wing";
606        scanResult.BSSID = "610408";
607        NetworkDetail nwkDetail = new NetworkDetail(scanResult.BSSID, IE2, null, 0);
608        System.out.println(nwkDetail);
609    }
610}
611