NetworkDetail.java revision ef1567e413c9ed5f5c4fdb9e354861632f7b2f87
1package com.android.server.wifi.hotspot2;
2
3import android.net.wifi.ScanResult;
4
5import com.android.server.wifi.anqp.ANQPElement;
6import com.android.server.wifi.anqp.Constants;
7import com.android.server.wifi.anqp.VenueNameElement;
8
9import java.net.ProtocolException;
10import java.nio.ByteBuffer;
11import java.nio.ByteOrder;
12import java.nio.charset.Charset;
13import java.nio.charset.StandardCharsets;
14import java.util.List;
15import java.util.Map;
16
17import static com.android.server.wifi.anqp.Constants.BYTES_IN_EUI48;
18import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
19import static com.android.server.wifi.anqp.Constants.getInteger;
20
21public class NetworkDetail {
22
23    private static final int EID_SSID = 0;
24    private static final int EID_BSSLoad = 11;
25    private static final int EID_Interworking = 107;
26    private static final int EID_RoamingConsortium = 111;
27    private static final int EID_ExtendedCaps = 127;
28    private static final int EID_VSA = 221;
29    private static final int ANQP_DOMID_BIT = 0x04;
30
31    private static final long SSID_UTF8_BIT = 0x0001000000000000L;
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    /*
69     * From Interworking element:
70     * mAnt non null indicates the presence of Interworking, i.e. 802.11u
71     * mVenueGroup and mVenueType may be null if not present in the Interworking element.
72     */
73    private final Ant mAnt;
74    private final boolean mInternet;
75    private final VenueNameElement.VenueGroup mVenueGroup;
76    private final VenueNameElement.VenueType mVenueType;
77
78    /*
79     * From HS20 Indication element:
80     * mHSRelease is null only if the HS20 Indication element was not present.
81     * mAnqpDomainID is set to -1 if not present in the element.
82     */
83    private final HSRelease mHSRelease;
84    private final int mAnqpDomainID;
85
86    /*
87     * From beacon:
88     * mAnqpOICount is how many additional OIs are available through ANQP.
89     * mRoamingConsortiums is either null, if the element was not present, or is an array of
90     * 1, 2 or 3 longs in which the roaming consortium values occupy the LSBs.
91     */
92    private final int mAnqpOICount;
93    private final long[] mRoamingConsortiums;
94
95    private final Long mExtendedCapabilities;
96
97    private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements;
98
99    public NetworkDetail(String bssid, String infoElements, List<String> anqpLines) {
100
101        if (infoElements == null) {
102            throw new IllegalArgumentException("Null information element string");
103        }
104        int separator = infoElements.indexOf('=');
105        if (separator<0) {
106            throw new IllegalArgumentException("No element separator");
107        }
108
109        mBSSID = parseMac(bssid);
110
111        ByteBuffer data = ByteBuffer.wrap(Utils.hexToBytes(infoElements.substring(separator + 1)))
112                .order(ByteOrder.LITTLE_ENDIAN);
113
114        String ssid = null;
115        byte[] ssidOctets = null;
116        int stationCount = 0;
117        int channelUtilization = 0;
118        int capacity = 0;
119
120        Ant ant = null;
121        boolean internet = false;
122        VenueNameElement.VenueGroup venueGroup = null;
123        VenueNameElement.VenueType venueType = null;
124        long hessid = 0L;
125
126        int anqpOICount = 0;
127        long[] roamingConsortiums = null;
128
129        HSRelease hsRelease = null;
130        int anqpDomainID = 0;       // No domain ID treated the same as a 0; unique info per AP.
131
132        Long extendedCapabilities = null;
133
134        while (data.hasRemaining()) {
135            int eid = data.get() & Constants.BYTE_MASK;
136            int elementLength = data.get() & Constants.BYTE_MASK;
137            if (elementLength > data.remaining()) {
138                throw new IllegalArgumentException("Length out of bounds: " + elementLength);
139            }
140
141            switch (eid) {
142                case EID_SSID:
143                    ssidOctets = new byte[elementLength];
144                    data.get(ssidOctets);
145                    break;
146                case EID_BSSLoad:
147                    if (elementLength != 5) {
148                        throw new IllegalArgumentException("BSS Load element length is not 5: " +
149                                elementLength);
150                    }
151                    stationCount = data.getShort() & Constants.SHORT_MASK;
152                    channelUtilization = data.get() & Constants.BYTE_MASK;
153                    capacity = data.getShort() & Constants.SHORT_MASK;
154                    break;
155                case EID_Interworking:
156                    int anOptions = data.get() & Constants.BYTE_MASK;
157                    ant = Ant.values()[anOptions&0x0f];
158                    internet = ( anOptions & 0x10 ) != 0;
159                    // Len 1 none, 3 venue-info, 7 HESSID, 9 venue-info & HESSID
160                    if (elementLength == 3 || elementLength == 9) {
161                        try {
162                            ByteBuffer vinfo = data.duplicate();
163                            vinfo.limit(vinfo.position() + 2);
164                            VenueNameElement vne =
165                                    new VenueNameElement(Constants.ANQPElementType.ANQPVenueName,
166                                            vinfo);
167                            venueGroup = vne.getGroup();
168                            venueType = vne.getType();
169                            data.getShort();
170                        }
171                        catch ( ProtocolException pe ) {
172                            /*Cannot happen*/
173                        }
174                    }
175                    if (elementLength == 7 || elementLength == 9) {
176                        hessid = getInteger(data, ByteOrder.BIG_ENDIAN, 6);
177                    }
178                    break;
179                case EID_RoamingConsortium:
180                    anqpOICount = data.get() & Constants.BYTE_MASK;
181
182                    int oi12Length = data.get() & Constants.BYTE_MASK;
183                    int oi1Length = oi12Length & Constants.NIBBLE_MASK;
184                    int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK;
185                    int oi3Length = elementLength - 2 - oi1Length - oi2Length;
186                    int oiCount = 0;
187                    if (oi1Length > 0) {
188                        oiCount++;
189                        if (oi2Length > 0) {
190                            oiCount++;
191                            if (oi3Length > 0) {
192                                oiCount++;
193                            }
194                        }
195                    }
196                    roamingConsortiums = new long[oiCount];
197                    if (oi1Length > 0 ) {
198                        roamingConsortiums[0] = getInteger(data, ByteOrder.BIG_ENDIAN, oi1Length);
199                    }
200                    if (oi2Length > 0 ) {
201                        roamingConsortiums[1] = getInteger(data, ByteOrder.BIG_ENDIAN, oi2Length);
202                    }
203                    if (oi3Length > 0 ) {
204                        roamingConsortiums[2] = getInteger(data, ByteOrder.BIG_ENDIAN, oi3Length);
205                    }
206                    break;
207                case EID_VSA:
208                    if (elementLength < 5) {
209                        data.position(data.position() + elementLength);
210                    }
211                    else if (data.getInt() != Constants.HS20_FRAME_PREFIX) {
212                        data.position(data.position() + elementLength - Constants.BYTES_IN_INT);
213                    }
214                    else {
215                        int hsConf = data.get() & Constants.BYTE_MASK;
216                        switch ((hsConf>>4) & Constants.NIBBLE_MASK) {
217                            case 0:
218                                hsRelease = HSRelease.R1;
219                                break;
220                            case 1:
221                                hsRelease = HSRelease.R2;
222                                break;
223                            default:
224                                hsRelease = HSRelease.Unknown;
225                                break;
226                        }
227                        if ((hsConf & ANQP_DOMID_BIT) != 0) {
228                            anqpDomainID = data.getShort() & Constants.SHORT_MASK;
229                        }
230                    }
231                    break;
232                case EID_ExtendedCaps:
233                    extendedCapabilities =
234                            Constants.getInteger(data, ByteOrder.LITTLE_ENDIAN, elementLength);
235                    break;
236                default:
237                    data.position(data.position()+elementLength);
238                    break;
239            }
240        }
241
242        if (ssidOctets != null) {
243            Charset encoding;
244            if (extendedCapabilities != null && (extendedCapabilities & SSID_UTF8_BIT) != 0) {
245                encoding = StandardCharsets.UTF_8;
246            }
247            else {
248                encoding = StandardCharsets.ISO_8859_1;
249            }
250            ssid = new String(ssidOctets, encoding);
251        }
252
253        mSSID = ssid;
254        mHESSID = hessid;
255        mStationCount = stationCount;
256        mChannelUtilization = channelUtilization;
257        mCapacity = capacity;
258        mAnt = ant;
259        mInternet = internet;
260        mVenueGroup = venueGroup;
261        mVenueType = venueType;
262        mHSRelease = hsRelease;
263        mAnqpDomainID = anqpDomainID;
264        mAnqpOICount = anqpOICount;
265        mRoamingConsortiums = roamingConsortiums;
266        mExtendedCapabilities = extendedCapabilities;
267        mANQPElements = SupplicantBridge.parseANQPLines(anqpLines);
268    }
269
270    private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
271        mSSID = base.mSSID;
272        mBSSID = base.mBSSID;
273        mHESSID = base.mHESSID;
274        mStationCount = base.mStationCount;
275        mChannelUtilization = base.mChannelUtilization;
276        mCapacity = base.mCapacity;
277        mAnt = base.mAnt;
278        mInternet = base.mInternet;
279        mVenueGroup = base.mVenueGroup;
280        mVenueType = base.mVenueType;
281        mHSRelease = base.mHSRelease;
282        mAnqpDomainID = base.mAnqpDomainID;
283        mAnqpOICount = base.mAnqpOICount;
284        mRoamingConsortiums = base.mRoamingConsortiums;
285        mExtendedCapabilities = base.mExtendedCapabilities;
286        mANQPElements = anqpElements;
287    }
288
289    public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
290        return new NetworkDetail(this, anqpElements);
291    }
292
293    private static long parseMac(String s) {
294
295        long mac = 0;
296        int count = 0;
297        for (int n = 0; n < s.length(); n++) {
298            int nibble = Utils.fromHex(s.charAt(n), true);
299            if (nibble >= 0) {
300                mac = (mac << 4) | nibble;
301                count++;
302            }
303        }
304        if (count < 12 || (count&1) == 1) {
305            throw new IllegalArgumentException("Bad MAC address: '" + s + "'");
306        }
307        return mac;
308    }
309
310    public boolean has80211uInfo() {
311        return mAnt != null || mRoamingConsortiums != null || mHSRelease != null;
312    }
313
314    public boolean hasInterworking() {
315        return mAnt != null;
316    }
317
318    public String getSSID() {
319        return mSSID;
320    }
321
322    public long getHESSID() {
323        return mHESSID;
324    }
325
326    public long getBSSID() {
327        return mBSSID;
328    }
329
330    public int getStationCount() {
331        return mStationCount;
332    }
333
334    public int getChannelUtilization() {
335        return mChannelUtilization;
336    }
337
338    public int getCapacity() {
339        return mCapacity;
340    }
341
342    public boolean isInterworking() {
343        return mAnt != null;
344    }
345
346    public Ant getAnt() {
347        return mAnt;
348    }
349
350    public boolean isInternet() {
351        return mInternet;
352    }
353
354    public VenueNameElement.VenueGroup getVenueGroup() {
355        return mVenueGroup;
356    }
357
358    public VenueNameElement.VenueType getVenueType() {
359        return mVenueType;
360    }
361
362    public HSRelease getHSRelease() {
363        return mHSRelease;
364    }
365
366    public int getAnqpDomainID() {
367        return mAnqpDomainID;
368    }
369
370    public int getAnqpOICount() {
371        return mAnqpOICount;
372    }
373
374    public long[] getRoamingConsortiums() {
375        return mRoamingConsortiums;
376    }
377
378    public Long getExtendedCapabilities() {
379        return mExtendedCapabilities;
380    }
381
382    public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() {
383        return mANQPElements;
384    }
385
386    public boolean isSSID_UTF8() {
387        return mExtendedCapabilities != null && (mExtendedCapabilities & SSID_UTF8_BIT) != 0;
388    }
389
390    @Override
391    public boolean equals(Object thatObject) {
392        if (this == thatObject) {
393            return true;
394        }
395        if (thatObject == null || getClass() != thatObject.getClass()) {
396            return false;
397        }
398
399        NetworkDetail that = (NetworkDetail)thatObject;
400
401        return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID();
402    }
403
404    @Override
405    public int hashCode() {
406        return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID;
407    }
408
409    @Override
410    public String toString() {
411        return String.format("NetworkInfo{mSSID='%s', mHESSID=%x, mBSSID=%x, mStationCount=%d, " +
412                "mChannelUtilization=%d, mCapacity=%d, mAnt=%s, mInternet=%s, " +
413                "mVenueGroup=%s, mVenueType=%s, mHSRelease=%s, mAnqpDomainID=%d, " +
414                "mAnqpOICount=%d, mRoamingConsortiums=%s}",
415                mSSID, mHESSID, mBSSID, mStationCount,
416                mChannelUtilization, mCapacity, mAnt, mInternet,
417                mVenueGroup, mVenueType, mHSRelease, mAnqpDomainID,
418                mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums));
419    }
420
421    public String toKeyString() {
422        return String.format("'%s':%s", mSSID, getBSSIDString());
423    }
424
425    public String getBSSIDString() {
426        return toMACString(mBSSID);
427    }
428
429    public static String toMACString(long mac) {
430        StringBuilder sb = new StringBuilder();
431        boolean first = true;
432        for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) {
433            if (first) {
434                first = false;
435            } else {
436                sb.append(':');
437            }
438            sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK));
439        }
440        return sb.toString();
441    }
442
443    private static final String IE = "ie=" +
444            "000477696e67" +                // SSID wing
445            "0b052a00cf611e" +              // BSS Load 42:207:7777
446            "6b091e0a01610408621205" +      // internet:Experimental:Vehicular:Auto:hessid
447            "6f0a0e530111112222222229" +    // 14:111111:2222222229
448            "dd07506f9a10143a01";           // r2:314
449
450    private static final String IE2 = "ie=000f4578616d706c65204e6574776f726b010882848b960c1218240301012a010432043048606c30140100000fac040100000fac040100000fac0100007f04000000806b091e07010203040506076c027f006f1001531122331020304050010203040506dd05506f9a1000";
451
452    public static void main(String[] args) {
453        ScanResult scanResult = new ScanResult();
454        scanResult.SSID = "wing";
455        scanResult.BSSID = "610408";
456        NetworkDetail nwkDetail = new NetworkDetail(scanResult.BSSID, IE2, null);
457        System.out.println(nwkDetail);
458    }
459}
460