NetworkDetail.java revision 2e814680f4dd27a5f825afab189843582235cedc
1package com.android.server.wifi.hotspot2;
2
3import android.net.wifi.ScanResult;
4import android.util.Log;
5
6import com.android.server.wifi.anqp.ANQPElement;
7
8import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
9import static com.android.server.wifi.anqp.Constants.BYTES_IN_EUI48;
10
11import com.android.server.wifi.anqp.Constants;
12import com.android.server.wifi.anqp.RawByteElement;
13import com.android.server.wifi.anqp.VenueNameElement;
14import com.android.server.wifi.util.InformationElementUtil;
15
16import java.nio.BufferUnderflowException;
17import java.nio.ByteBuffer;
18import java.nio.CharBuffer;
19import java.nio.charset.CharacterCodingException;
20import java.nio.charset.CharsetDecoder;
21import java.nio.charset.StandardCharsets;
22import java.util.List;
23import java.util.Map;
24
25public class NetworkDetail {
26
27    //turn off when SHIP
28    private static final boolean DBG = true;
29    private static final boolean VDBG = false;
30
31    private static final String TAG = "NetworkDetail:";
32
33    public enum Ant {
34        Private,
35        PrivateWithGuest,
36        ChargeablePublic,
37        FreePublic,
38        Personal,
39        EmergencyOnly,
40        Resvd6,
41        Resvd7,
42        Resvd8,
43        Resvd9,
44        Resvd10,
45        Resvd11,
46        Resvd12,
47        Resvd13,
48        TestOrExperimental,
49        Wildcard
50    }
51
52    public enum HSRelease {
53        R1,
54        R2,
55        Unknown
56    }
57
58    // General identifiers:
59    private final String mSSID;
60    private final long mHESSID;
61    private final long mBSSID;
62
63    // BSS Load element:
64    private final int mStationCount;
65    private final int mChannelUtilization;
66    private final int mCapacity;
67
68    //channel detailed information
69   /*
70    * 0 -- 20 MHz
71    * 1 -- 40 MHz
72    * 2 -- 80 MHz
73    * 3 -- 160 MHz
74    * 4 -- 80 + 80 MHz
75    */
76    private final int mChannelWidth;
77    private final int mPrimaryFreq;
78    private final int mCenterfreq0;
79    private final int mCenterfreq1;
80    /*
81     * From Interworking element:
82     * mAnt non null indicates the presence of Interworking, i.e. 802.11u
83     * mVenueGroup and mVenueType may be null if not present in the Interworking element.
84     */
85    private final Ant mAnt;
86    private final boolean mInternet;
87    private final VenueNameElement.VenueGroup mVenueGroup;
88    private final VenueNameElement.VenueType mVenueType;
89
90    /*
91     * From HS20 Indication element:
92     * mHSRelease is null only if the HS20 Indication element was not present.
93     * mAnqpDomainID is set to -1 if not present in the element.
94     */
95    private final HSRelease mHSRelease;
96    private final int mAnqpDomainID;
97
98    /*
99     * From beacon:
100     * mAnqpOICount is how many additional OIs are available through ANQP.
101     * mRoamingConsortiums is either null, if the element was not present, or is an array of
102     * 1, 2 or 3 longs in which the roaming consortium values occupy the LSBs.
103     */
104    private final int mAnqpOICount;
105    private final long[] mRoamingConsortiums;
106
107    private final InformationElementUtil.ExtendedCapabilities mExtendedCapabilities;
108
109    private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements;
110
111    public NetworkDetail(String bssid, ScanResult.InformationElement[] infoElements,
112            List<String> anqpLines, int freq) {
113        if (infoElements == null) {
114            throw new IllegalArgumentException("Null information elements");
115        }
116
117        mBSSID = Utils.parseMac(bssid);
118
119        String ssid = null;
120        byte[] ssidOctets = null;
121
122        InformationElementUtil.BssLoad bssLoad = new InformationElementUtil.BssLoad();
123
124        InformationElementUtil.Interworking interworking =
125                new InformationElementUtil.Interworking();
126
127        InformationElementUtil.RoamingConsortium roamingConsortium =
128                new InformationElementUtil.RoamingConsortium();
129
130        InformationElementUtil.Vsa vsa = new InformationElementUtil.Vsa();
131
132        InformationElementUtil.HtOperation htOperation = new InformationElementUtil.HtOperation();
133        InformationElementUtil.VhtOperation vhtOperation =
134                new InformationElementUtil.VhtOperation();
135
136        InformationElementUtil.ExtendedCapabilities extendedCapabilities =
137                new InformationElementUtil.ExtendedCapabilities();
138
139        RuntimeException exception = null;
140
141        try {
142            for (ScanResult.InformationElement ie : infoElements) {
143                switch (ie.id) {
144                    case ScanResult.InformationElement.EID_SSID:
145                        ssidOctets = ie.bytes;
146                        break;
147                    case ScanResult.InformationElement.EID_BSS_LOAD:
148                        bssLoad.from(ie);
149                        break;
150                    case ScanResult.InformationElement.EID_HT_OPERATION:
151                        htOperation.from(ie);
152                        break;
153                    case ScanResult.InformationElement.EID_VHT_OPERATION:
154                        vhtOperation.from(ie);
155                        break;
156                    case ScanResult.InformationElement.EID_INTERWORKING:
157                        interworking.from(ie);
158                        break;
159                    case ScanResult.InformationElement.EID_ROAMING_CONSORTIUM:
160                        roamingConsortium.from(ie);
161                        break;
162                    case ScanResult.InformationElement.EID_VSA:
163                        vsa.from(ie);
164                        break;
165                    case ScanResult.InformationElement.EID_EXTENDED_CAPS:
166                        extendedCapabilities.from(ie);
167                        break;
168                    default:
169                        break;
170                }
171            }
172        }
173        catch (IllegalArgumentException | BufferUnderflowException | ArrayIndexOutOfBoundsException e) {
174            Log.d(Utils.hs2LogTag(getClass()), "Caught " + e);
175            if (ssidOctets == null) {
176                throw new IllegalArgumentException("Malformed IE string (no SSID)", e);
177            }
178            exception = e;
179        }
180
181        if (ssidOctets != null) {
182            /*
183             * Strict use of the "UTF-8 SSID" bit by APs appears to be spotty at best even if the
184             * encoding truly is in UTF-8. An unconditional attempt to decode the SSID as UTF-8 is
185             * therefore always made with a fall back to 8859-1 under normal circumstances.
186             * If, however, a previous exception was detected and the UTF-8 bit is set, failure to
187             * decode the SSID will be used as an indication that the whole frame is malformed and
188             * an exception will be triggered.
189             */
190            CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
191            try {
192                CharBuffer decoded = decoder.decode(ByteBuffer.wrap(ssidOctets));
193                ssid = decoded.toString();
194            }
195            catch (CharacterCodingException cce) {
196                ssid = null;
197            }
198
199            if (ssid == null) {
200                if (extendedCapabilities.isStrictUtf8() && exception != null) {
201                    throw new IllegalArgumentException("Failed to decode SSID in dubious IE string");
202                }
203                else {
204                    ssid = new String(ssidOctets, StandardCharsets.ISO_8859_1);
205                }
206            }
207        }
208
209        mSSID = ssid;
210        mHESSID = interworking.hessid;
211        mStationCount = bssLoad.stationCount;
212        mChannelUtilization = bssLoad.channelUtilization;
213        mCapacity = bssLoad.capacity;
214        mAnt = interworking.ant;
215        mInternet = interworking.internet;
216        mVenueGroup = interworking.venueGroup;
217        mVenueType = interworking.venueType;
218        mHSRelease = vsa.hsRelease;
219        mAnqpDomainID = vsa.anqpDomainID;
220        mAnqpOICount = roamingConsortium.anqpOICount;
221        mRoamingConsortiums = roamingConsortium.roamingConsortiums;
222        mExtendedCapabilities = extendedCapabilities;
223        mANQPElements = SupplicantBridge.parseANQPLines(anqpLines);
224        //set up channel info
225        mPrimaryFreq = freq;
226
227        if (vhtOperation.isValid()) {
228            // 80 or 160 MHz
229            mChannelWidth = vhtOperation.getChannelWidth();
230            mCenterfreq0 = vhtOperation.getCenterFreq0();
231            mCenterfreq1 = vhtOperation.getCenterFreq1();
232        } else {
233            mChannelWidth = htOperation.getChannelWidth();
234            mCenterfreq0 = htOperation.getCenterFreq0(mPrimaryFreq);
235            mCenterfreq1  = 0;
236        }
237        if (VDBG) {
238            Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq +
239                    " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: " + mCenterfreq1 +
240                    (extendedCapabilities.is80211McRTTResponder ? "Support RTT reponder" :
241                    "Do not support RTT responder"));
242        }
243    }
244
245    private static ByteBuffer getAndAdvancePayload(ByteBuffer data, int plLength) {
246        ByteBuffer payload = data.duplicate().order(data.order());
247        payload.limit(payload.position() + plLength);
248        data.position(data.position() + plLength);
249        return payload;
250    }
251
252    private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
253        mSSID = base.mSSID;
254        mBSSID = base.mBSSID;
255        mHESSID = base.mHESSID;
256        mStationCount = base.mStationCount;
257        mChannelUtilization = base.mChannelUtilization;
258        mCapacity = base.mCapacity;
259        mAnt = base.mAnt;
260        mInternet = base.mInternet;
261        mVenueGroup = base.mVenueGroup;
262        mVenueType = base.mVenueType;
263        mHSRelease = base.mHSRelease;
264        mAnqpDomainID = base.mAnqpDomainID;
265        mAnqpOICount = base.mAnqpOICount;
266        mRoamingConsortiums = base.mRoamingConsortiums;
267        mExtendedCapabilities =
268                new InformationElementUtil.ExtendedCapabilities(base.mExtendedCapabilities);
269        mANQPElements = anqpElements;
270        mChannelWidth = base.mChannelWidth;
271        mPrimaryFreq = base.mPrimaryFreq;
272        mCenterfreq0 = base.mCenterfreq0;
273        mCenterfreq1 = base.mCenterfreq1;
274    }
275
276    public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
277        return new NetworkDetail(this, anqpElements);
278    }
279
280    public boolean queriable(List<Constants.ANQPElementType> queryElements) {
281        return mAnt != null &&
282                (Constants.hasBaseANQPElements(queryElements) ||
283                 Constants.hasR2Elements(queryElements) && mHSRelease == HSRelease.R2);
284    }
285
286    public boolean has80211uInfo() {
287        return mAnt != null || mRoamingConsortiums != null || mHSRelease != null;
288    }
289
290    public boolean hasInterworking() {
291        return mAnt != null;
292    }
293
294    public String getSSID() {
295        return mSSID;
296    }
297
298    public String getTrimmedSSID() {
299        for (int n = 0; n < mSSID.length(); n++) {
300            if (mSSID.charAt(n) != 0) {
301                return mSSID;
302            }
303        }
304        return "";
305    }
306
307    public long getHESSID() {
308        return mHESSID;
309    }
310
311    public long getBSSID() {
312        return mBSSID;
313    }
314
315    public int getStationCount() {
316        return mStationCount;
317    }
318
319    public int getChannelUtilization() {
320        return mChannelUtilization;
321    }
322
323    public int getCapacity() {
324        return mCapacity;
325    }
326
327    public boolean isInterworking() {
328        return mAnt != null;
329    }
330
331    public Ant getAnt() {
332        return mAnt;
333    }
334
335    public boolean isInternet() {
336        return mInternet;
337    }
338
339    public VenueNameElement.VenueGroup getVenueGroup() {
340        return mVenueGroup;
341    }
342
343    public VenueNameElement.VenueType getVenueType() {
344        return mVenueType;
345    }
346
347    public HSRelease getHSRelease() {
348        return mHSRelease;
349    }
350
351    public int getAnqpDomainID() {
352        return mAnqpDomainID;
353    }
354
355    public byte[] getOsuProviders() {
356        if (mANQPElements == null) {
357            return null;
358        }
359        ANQPElement osuProviders = mANQPElements.get(Constants.ANQPElementType.HSOSUProviders);
360        return osuProviders != null ? ((RawByteElement) osuProviders).getPayload() : null;
361    }
362
363    public int getAnqpOICount() {
364        return mAnqpOICount;
365    }
366
367    public long[] getRoamingConsortiums() {
368        return mRoamingConsortiums;
369    }
370
371    public Long getExtendedCapabilities() {
372        return mExtendedCapabilities.extendedCapabilities;
373    }
374
375    public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() {
376        return mANQPElements;
377    }
378
379    public int getChannelWidth() {
380        return mChannelWidth;
381    }
382
383    public int getCenterfreq0() {
384        return mCenterfreq0;
385    }
386
387    public int getCenterfreq1() {
388        return mCenterfreq1;
389    }
390
391    public boolean is80211McResponderSupport() {
392        return mExtendedCapabilities.is80211McRTTResponder;
393    }
394
395    public boolean isSSID_UTF8() {
396        return mExtendedCapabilities.isStrictUtf8();
397    }
398
399    @Override
400    public boolean equals(Object thatObject) {
401        if (this == thatObject) {
402            return true;
403        }
404        if (thatObject == null || getClass() != thatObject.getClass()) {
405            return false;
406        }
407
408        NetworkDetail that = (NetworkDetail)thatObject;
409
410        return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID();
411    }
412
413    @Override
414    public int hashCode() {
415        return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID;
416    }
417
418    @Override
419    public String toString() {
420        return String.format("NetworkInfo{SSID='%s', HESSID=%x, BSSID=%x, StationCount=%d, " +
421                "ChannelUtilization=%d, Capacity=%d, Ant=%s, Internet=%s, " +
422                "VenueGroup=%s, VenueType=%s, HSRelease=%s, AnqpDomainID=%d, " +
423                "AnqpOICount=%d, RoamingConsortiums=%s}",
424                mSSID, mHESSID, mBSSID, mStationCount,
425                mChannelUtilization, mCapacity, mAnt, mInternet,
426                mVenueGroup, mVenueType, mHSRelease, mAnqpDomainID,
427                mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums));
428    }
429
430    public String toKeyString() {
431        return mHESSID != 0 ?
432            String.format("'%s':%012x (%012x)", mSSID, mBSSID, mHESSID) :
433            String.format("'%s':%012x", mSSID, mBSSID);
434    }
435
436    public String getBSSIDString() {
437        return toMACString(mBSSID);
438    }
439
440    public static String toMACString(long mac) {
441        StringBuilder sb = new StringBuilder();
442        boolean first = true;
443        for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) {
444            if (first) {
445                first = false;
446            } else {
447                sb.append(':');
448            }
449            sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK));
450        }
451        return sb.toString();
452    }
453
454}
455