InformationElementUtil.java revision f7364b4baea06722022b7c6519ace0556481cc06
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.server.wifi.util;
17
18import android.net.wifi.ScanResult;
19import android.net.wifi.ScanResult.InformationElement;
20import android.util.Log;
21
22import com.android.server.wifi.ByteBufferReader;
23import com.android.server.wifi.hotspot2.NetworkDetail;
24import com.android.server.wifi.hotspot2.anqp.Constants;
25
26import java.nio.BufferUnderflowException;
27import java.nio.ByteBuffer;
28import java.nio.ByteOrder;
29import java.util.ArrayList;
30import java.util.BitSet;
31
32public class InformationElementUtil {
33    private static final String TAG = "InformationElementUtil";
34
35    public static InformationElement[] parseInformationElements(byte[] bytes) {
36        if (bytes == null) {
37            return new InformationElement[0];
38        }
39        ByteBuffer data = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
40
41        ArrayList<InformationElement> infoElements = new ArrayList<>();
42        boolean found_ssid = false;
43        while (data.remaining() > 1) {
44            int eid = data.get() & Constants.BYTE_MASK;
45            int elementLength = data.get() & Constants.BYTE_MASK;
46
47            if (elementLength > data.remaining() || (eid == InformationElement.EID_SSID
48                    && found_ssid)) {
49                // APs often pad the data with bytes that happen to match that of the EID_SSID
50                // marker.  This is not due to a known issue for APs to incorrectly send the SSID
51                // name multiple times.
52                break;
53            }
54            if (eid == InformationElement.EID_SSID) {
55                found_ssid = true;
56            }
57
58            InformationElement ie = new InformationElement();
59            ie.id = eid;
60            ie.bytes = new byte[elementLength];
61            data.get(ie.bytes);
62            infoElements.add(ie);
63        }
64        return infoElements.toArray(new InformationElement[infoElements.size()]);
65    }
66
67    /**
68     * Parse and retrieve the Roaming Consortium Information Element from the list of IEs.
69     *
70     * @param ies List of IEs to retrieve from
71     * @return {@link RoamingConsortium}
72     */
73    public static RoamingConsortium getRoamingConsortiumIE(InformationElement[] ies) {
74        RoamingConsortium roamingConsortium = new RoamingConsortium();
75        if (ies != null) {
76            for (InformationElement ie : ies) {
77                if (ie.id == InformationElement.EID_ROAMING_CONSORTIUM) {
78                    try {
79                        roamingConsortium.from(ie);
80                    } catch (RuntimeException e) {
81                        Log.e(TAG, "Failed to parse Roaming Consortium IE: " + e.getMessage());
82                    }
83                }
84            }
85        }
86        return roamingConsortium;
87    }
88
89    /**
90     * Parse and retrieve the Hotspot 2.0 Vendor Specific Information Element from the list of IEs.
91     *
92     * @param ies List of IEs to retrieve from
93     * @return {@link Vsa}
94     */
95    public static Vsa getHS2VendorSpecificIE(InformationElement[] ies) {
96        Vsa vsa = new Vsa();
97        if (ies != null) {
98            for (InformationElement ie : ies) {
99                if (ie.id == InformationElement.EID_VSA) {
100                    try {
101                        vsa.from(ie);
102                    } catch (RuntimeException e) {
103                        Log.e(TAG, "Failed to parse Vendor Specific IE: " + e.getMessage());
104                    }
105                }
106            }
107        }
108        return vsa;
109    }
110
111    /**
112     * Parse and retrieve the Interworking information element from the list of IEs.
113     *
114     * @param ies List of IEs to retrieve from
115     * @return {@link Interworking}
116     */
117    public static Interworking getInterworkingIE(InformationElement[] ies) {
118        Interworking interworking = new Interworking();
119        if (ies != null) {
120            for (InformationElement ie : ies) {
121                if (ie.id == InformationElement.EID_INTERWORKING) {
122                    try {
123                        interworking.from(ie);
124                    } catch (RuntimeException e) {
125                        Log.e(TAG, "Failed to parse Interworking IE: " + e.getMessage());
126                    }
127                }
128            }
129        }
130        return interworking;
131    }
132
133    public static class BssLoad {
134        public int stationCount = 0;
135        public int channelUtilization = 0;
136        public int capacity = 0;
137
138        public void from(InformationElement ie) {
139            if (ie.id != InformationElement.EID_BSS_LOAD) {
140                throw new IllegalArgumentException("Element id is not BSS_LOAD, : " + ie.id);
141            }
142            if (ie.bytes.length != 5) {
143                throw new IllegalArgumentException("BSS Load element length is not 5: "
144                                                   + ie.bytes.length);
145            }
146            ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
147            stationCount = data.getShort() & Constants.SHORT_MASK;
148            channelUtilization = data.get() & Constants.BYTE_MASK;
149            capacity = data.getShort() & Constants.SHORT_MASK;
150        }
151    }
152
153    public static class HtOperation {
154        public int secondChannelOffset = 0;
155
156        public int getChannelWidth() {
157            if (secondChannelOffset != 0) {
158                return 1;
159            } else {
160                return 0;
161            }
162        }
163
164        public int getCenterFreq0(int primaryFrequency) {
165            //40 MHz
166            if (secondChannelOffset != 0) {
167                if (secondChannelOffset == 1) {
168                    return primaryFrequency + 10;
169                } else if (secondChannelOffset == 3) {
170                    return primaryFrequency - 10;
171                } else {
172                    Log.e("HtOperation", "Error on secondChannelOffset: " + secondChannelOffset);
173                    return 0;
174                }
175            } else {
176                return 0;
177            }
178        }
179
180        public void from(InformationElement ie) {
181            if (ie.id != InformationElement.EID_HT_OPERATION) {
182                throw new IllegalArgumentException("Element id is not HT_OPERATION, : " + ie.id);
183            }
184            secondChannelOffset = ie.bytes[1] & 0x3;
185        }
186    }
187
188    public static class VhtOperation {
189        public int channelMode = 0;
190        public int centerFreqIndex1 = 0;
191        public int centerFreqIndex2 = 0;
192
193        public boolean isValid() {
194            return channelMode != 0;
195        }
196
197        public int getChannelWidth() {
198            return channelMode + 1;
199        }
200
201        public int getCenterFreq0() {
202            //convert channel index to frequency in MHz, channel 36 is 5180MHz
203            return (centerFreqIndex1 - 36) * 5 + 5180;
204        }
205
206        public int getCenterFreq1() {
207            if (channelMode > 1) { //160MHz
208                return (centerFreqIndex2 - 36) * 5 + 5180;
209            } else {
210                return 0;
211            }
212        }
213
214        public void from(InformationElement ie) {
215            if (ie.id != InformationElement.EID_VHT_OPERATION) {
216                throw new IllegalArgumentException("Element id is not VHT_OPERATION, : " + ie.id);
217            }
218            channelMode = ie.bytes[0] & Constants.BYTE_MASK;
219            centerFreqIndex1 = ie.bytes[1] & Constants.BYTE_MASK;
220            centerFreqIndex2 = ie.bytes[2] & Constants.BYTE_MASK;
221        }
222    }
223
224    public static class Interworking {
225        public NetworkDetail.Ant ant = null;
226        public boolean internet = false;
227        public long hessid = 0L;
228
229        public void from(InformationElement ie) {
230            if (ie.id != InformationElement.EID_INTERWORKING) {
231                throw new IllegalArgumentException("Element id is not INTERWORKING, : " + ie.id);
232            }
233            ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
234            int anOptions = data.get() & Constants.BYTE_MASK;
235            ant = NetworkDetail.Ant.values()[anOptions & 0x0f];
236            internet = (anOptions & 0x10) != 0;
237            // There are only three possible lengths for the Interworking IE:
238            // Len 1: Access Network Options only
239            // Len 3: Access Network Options & Venue Info
240            // Len 7: Access Network Options & HESSID
241            // Len 9: Access Network Options, Venue Info, & HESSID
242            if (ie.bytes.length != 1
243                    && ie.bytes.length != 3
244                    && ie.bytes.length != 7
245                    && ie.bytes.length != 9) {
246                throw new IllegalArgumentException(
247                        "Bad Interworking element length: " + ie.bytes.length);
248            }
249
250            if (ie.bytes.length == 7 || ie.bytes.length == 9) {
251                hessid = ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 6);
252            }
253        }
254    }
255
256    public static class RoamingConsortium {
257        public int anqpOICount = 0;
258        public long[] roamingConsortiums = null;
259
260        public void from(InformationElement ie) {
261            if (ie.id != InformationElement.EID_ROAMING_CONSORTIUM) {
262                throw new IllegalArgumentException("Element id is not ROAMING_CONSORTIUM, : "
263                        + ie.id);
264            }
265            ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
266            anqpOICount = data.get() & Constants.BYTE_MASK;
267
268            int oi12Length = data.get() & Constants.BYTE_MASK;
269            int oi1Length = oi12Length & Constants.NIBBLE_MASK;
270            int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK;
271            int oi3Length = ie.bytes.length - 2 - oi1Length - oi2Length;
272            int oiCount = 0;
273            if (oi1Length > 0) {
274                oiCount++;
275                if (oi2Length > 0) {
276                    oiCount++;
277                    if (oi3Length > 0) {
278                        oiCount++;
279                    }
280                }
281            }
282            roamingConsortiums = new long[oiCount];
283            if (oi1Length > 0 && roamingConsortiums.length > 0) {
284                roamingConsortiums[0] =
285                        ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi1Length);
286            }
287            if (oi2Length > 0 && roamingConsortiums.length > 1) {
288                roamingConsortiums[1] =
289                        ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi2Length);
290            }
291            if (oi3Length > 0 && roamingConsortiums.length > 2) {
292                roamingConsortiums[2] =
293                        ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi3Length);
294            }
295        }
296    }
297
298    public static class Vsa {
299        private static final int ANQP_DOMID_BIT = 0x04;
300
301        public NetworkDetail.HSRelease hsRelease = null;
302        public int anqpDomainID = 0;    // No domain ID treated the same as a 0; unique info per AP.
303
304        public void from(InformationElement ie) {
305            ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
306            if (ie.bytes.length >= 5 && data.getInt() == Constants.HS20_FRAME_PREFIX) {
307                int hsConf = data.get() & Constants.BYTE_MASK;
308                switch ((hsConf >> 4) & Constants.NIBBLE_MASK) {
309                    case 0:
310                        hsRelease = NetworkDetail.HSRelease.R1;
311                        break;
312                    case 1:
313                        hsRelease = NetworkDetail.HSRelease.R2;
314                        break;
315                    default:
316                        hsRelease = NetworkDetail.HSRelease.Unknown;
317                        break;
318                }
319                if ((hsConf & ANQP_DOMID_BIT) != 0) {
320                    if (ie.bytes.length < 7) {
321                        throw new IllegalArgumentException(
322                                "HS20 indication element too short: " + ie.bytes.length);
323                    }
324                    anqpDomainID = data.getShort() & Constants.SHORT_MASK;
325                }
326            }
327        }
328    }
329
330    /**
331     * This IE contained a bit field indicating the capabilities being advertised by the STA.
332     * The size of the bit field (number of bytes) is indicated by the |Length| field in the IE.
333     *
334     * Refer to Section 8.4.2.29 in IEEE 802.11-2012 Spec for capability associated with each
335     * bit.
336     *
337     * Here is the wire format of this IE:
338     * | Element ID | Length | Capabilities |
339     *       1           1          n
340     */
341    public static class ExtendedCapabilities {
342        private static final int RTT_RESP_ENABLE_BIT = 70;
343        private static final int SSID_UTF8_BIT = 48;
344
345        public BitSet capabilitiesBitSet;
346
347        /**
348         * @return true if SSID should be interpreted using UTF-8 encoding
349         */
350        public boolean isStrictUtf8() {
351            return capabilitiesBitSet.get(SSID_UTF8_BIT);
352        }
353
354        /**
355         * @return true if 802.11 MC RTT Response is enabled
356         */
357        public boolean is80211McRTTResponder() {
358            return capabilitiesBitSet.get(RTT_RESP_ENABLE_BIT);
359        }
360
361        public ExtendedCapabilities() {
362            capabilitiesBitSet = new BitSet();
363        }
364
365        public ExtendedCapabilities(ExtendedCapabilities other) {
366            capabilitiesBitSet = other.capabilitiesBitSet;
367        }
368
369        /**
370         * Parse an ExtendedCapabilities from the IE containing raw bytes.
371         *
372         * @param ie The Information element data
373         */
374        public void from(InformationElement ie) {
375            capabilitiesBitSet = BitSet.valueOf(ie.bytes);
376        }
377    }
378
379    /**
380     * parse beacon to build the capabilities
381     *
382     * This class is used to build the capabilities string of the scan results coming
383     * from HAL. It parses the ieee beacon's capability field, WPA and RSNE IE as per spec,
384     * and builds the ScanResult.capabilities String in a way that mirrors the values returned
385     * by wpa_supplicant.
386     */
387    public static class Capabilities {
388        private static final int CAP_ESS_BIT_OFFSET = 0;
389        private static final int CAP_PRIVACY_BIT_OFFSET = 4;
390
391        private static final int WPA_VENDOR_OUI_TYPE_ONE = 0x01f25000;
392        private static final int WPS_VENDOR_OUI_TYPE = 0x04f25000;
393        private static final short WPA_VENDOR_OUI_VERSION = 0x0001;
394        private static final short RSNE_VERSION = 0x0001;
395
396        private static final int WPA_AKM_EAP = 0x01f25000;
397        private static final int WPA_AKM_PSK = 0x02f25000;
398
399        private static final int WPA2_AKM_EAP = 0x01ac0f00;
400        private static final int WPA2_AKM_PSK = 0x02ac0f00;
401        private static final int WPA2_AKM_FT_EAP = 0x03ac0f00;
402        private static final int WPA2_AKM_FT_PSK = 0x04ac0f00;
403        private static final int WPA2_AKM_EAP_SHA256 = 0x05ac0f00;
404        private static final int WPA2_AKM_PSK_SHA256 = 0x06ac0f00;
405
406        private static final int WPA_CIPHER_NONE = 0x00f25000;
407        private static final int WPA_CIPHER_TKIP = 0x02f25000;
408        private static final int WPA_CIPHER_CCMP = 0x04f25000;
409
410        private static final int RSN_CIPHER_NONE = 0x00ac0f00;
411        private static final int RSN_CIPHER_TKIP = 0x02ac0f00;
412        private static final int RSN_CIPHER_CCMP = 0x04ac0f00;
413        private static final int RSN_CIPHER_NO_GROUP_ADDRESSED = 0x07ac0f00;
414
415        public ArrayList<Integer> protocol;
416        public ArrayList<ArrayList<Integer>> keyManagement;
417        public ArrayList<ArrayList<Integer>> pairwiseCipher;
418        public ArrayList<Integer> groupCipher;
419        public boolean isESS;
420        public boolean isPrivacy;
421        public boolean isWPS;
422
423        public Capabilities() {
424        }
425
426        // RSNE format (size unit: byte)
427        //
428        // | Element ID | Length | Version | Group Data Cipher Suite |
429        //      1           1         2                 4
430        // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
431        //              2                            4 * m
432        // | AKM Suite Count | AKM Suite List | RSN Capabilities |
433        //          2               4 * n               2
434        // | PMKID Count | PMKID List | Group Management Cipher Suite |
435        //        2          16 * s                 4
436        //
437        // Note: InformationElement.bytes has 'Element ID' and 'Length'
438        //       stripped off already
439        private void parseRsnElement(InformationElement ie) {
440            ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
441
442            try {
443                // version
444                if (buf.getShort() != RSNE_VERSION) {
445                    // incorrect version
446                    return;
447                }
448
449                // found the RSNE IE, hence start building the capability string
450                protocol.add(ScanResult.PROTOCOL_WPA2);
451
452                // group data cipher suite
453                groupCipher.add(parseRsnCipher(buf.getInt()));
454
455                // pairwise cipher suite count
456                short cipherCount = buf.getShort();
457                ArrayList<Integer> rsnPairwiseCipher = new ArrayList<>();
458                // pairwise cipher suite list
459                for (int i = 0; i < cipherCount; i++) {
460                    rsnPairwiseCipher.add(parseRsnCipher(buf.getInt()));
461                }
462                pairwiseCipher.add(rsnPairwiseCipher);
463
464                // AKM
465                // AKM suite count
466                short akmCount = buf.getShort();
467                ArrayList<Integer> rsnKeyManagement = new ArrayList<>();
468
469                for (int i = 0; i < akmCount; i++) {
470                    int akm = buf.getInt();
471                    switch (akm) {
472                        case WPA2_AKM_EAP:
473                            rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP);
474                            break;
475                        case WPA2_AKM_PSK:
476                            rsnKeyManagement.add(ScanResult.KEY_MGMT_PSK);
477                            break;
478                        case WPA2_AKM_FT_EAP:
479                            rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_EAP);
480                            break;
481                        case WPA2_AKM_FT_PSK:
482                            rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_PSK);
483                            break;
484                        case WPA2_AKM_EAP_SHA256:
485                            rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP_SHA256);
486                            break;
487                        case WPA2_AKM_PSK_SHA256:
488                            rsnKeyManagement.add(ScanResult.KEY_MGMT_PSK_SHA256);
489                            break;
490                        default:
491                            // do nothing
492                            break;
493                    }
494                }
495                // Default AKM
496                if (rsnKeyManagement.isEmpty()) {
497                    rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP);
498                }
499                keyManagement.add(rsnKeyManagement);
500            } catch (BufferUnderflowException e) {
501                Log.e("IE_Capabilities", "Couldn't parse RSNE, buffer underflow");
502            }
503        }
504
505        private static int parseWpaCipher(int cipher) {
506            switch (cipher) {
507                case WPA_CIPHER_NONE:
508                    return ScanResult.CIPHER_NONE;
509                case WPA_CIPHER_TKIP:
510                    return ScanResult.CIPHER_TKIP;
511                case WPA_CIPHER_CCMP:
512                    return ScanResult.CIPHER_CCMP;
513                default:
514                    Log.w("IE_Capabilities", "Unknown WPA cipher suite: "
515                            + Integer.toHexString(cipher));
516                    return ScanResult.CIPHER_NONE;
517            }
518        }
519
520        private static int parseRsnCipher(int cipher) {
521            switch (cipher) {
522                case RSN_CIPHER_NONE:
523                    return ScanResult.CIPHER_NONE;
524                case RSN_CIPHER_TKIP:
525                    return ScanResult.CIPHER_TKIP;
526                case RSN_CIPHER_CCMP:
527                    return ScanResult.CIPHER_CCMP;
528                case RSN_CIPHER_NO_GROUP_ADDRESSED:
529                    return ScanResult.CIPHER_NO_GROUP_ADDRESSED;
530                default:
531                    Log.w("IE_Capabilities", "Unknown RSN cipher suite: "
532                            + Integer.toHexString(cipher));
533                    return ScanResult.CIPHER_NONE;
534            }
535        }
536
537        private static boolean isWpsElement(InformationElement ie) {
538            ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
539            try {
540                // WPS OUI and type
541                return (buf.getInt() == WPS_VENDOR_OUI_TYPE);
542            } catch (BufferUnderflowException e) {
543                Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
544                return false;
545            }
546        }
547
548        private static boolean isWpaOneElement(InformationElement ie) {
549            ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
550
551            try {
552                // WPA OUI and type
553                return (buf.getInt() == WPA_VENDOR_OUI_TYPE_ONE);
554            } catch (BufferUnderflowException e) {
555                Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
556                return false;
557            }
558        }
559
560        // WPA type 1 format (size unit: byte)
561        //
562        // | Element ID | Length | OUI | Type | Version |
563        //      1           1       3     1        2
564        // | Group Data Cipher Suite |
565        //             4
566        // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
567        //              2                            4 * m
568        // | AKM Suite Count | AKM Suite List |
569        //          2               4 * n
570        //
571        // Note: InformationElement.bytes has 'Element ID' and 'Length'
572        //       stripped off already
573        //
574        private void parseWpaOneElement(InformationElement ie) {
575            ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
576
577            try {
578                // skip WPA OUI and type parsing. isWpaOneElement() should have
579                // been called for verification before we reach here.
580                buf.getInt();
581
582                // version
583                if (buf.getShort() != WPA_VENDOR_OUI_VERSION)  {
584                    // incorrect version
585                    return;
586                }
587
588                // start building the string
589                protocol.add(ScanResult.PROTOCOL_WPA);
590
591                // group data cipher suite
592                groupCipher.add(parseWpaCipher(buf.getInt()));
593
594                // pairwise cipher suite count
595                short cipherCount = buf.getShort();
596                ArrayList<Integer> wpaPairwiseCipher = new ArrayList<>();
597                // pairwise chipher suite list
598                for (int i = 0; i < cipherCount; i++) {
599                    wpaPairwiseCipher.add(parseWpaCipher(buf.getInt()));
600                }
601                pairwiseCipher.add(wpaPairwiseCipher);
602
603                // AKM
604                // AKM suite count
605                short akmCount = buf.getShort();
606                ArrayList<Integer> wpaKeyManagement = new ArrayList<>();
607
608                // AKM suite list
609                for (int i = 0; i < akmCount; i++) {
610                    int akm = buf.getInt();
611                    switch (akm) {
612                        case WPA_AKM_EAP:
613                            wpaKeyManagement.add(ScanResult.KEY_MGMT_EAP);
614                            break;
615                        case WPA_AKM_PSK:
616                            wpaKeyManagement.add(ScanResult.KEY_MGMT_PSK);
617                            break;
618                        default:
619                            // do nothing
620                            break;
621                    }
622                }
623                // Default AKM
624                if (wpaKeyManagement.isEmpty()) {
625                    wpaKeyManagement.add(ScanResult.KEY_MGMT_EAP);
626                }
627                keyManagement.add(wpaKeyManagement);
628            } catch (BufferUnderflowException e) {
629                Log.e("IE_Capabilities", "Couldn't parse type 1 WPA, buffer underflow");
630            }
631        }
632
633        /**
634         * Parse the Information Element and the 16-bit Capability Information field
635         * to build the InformationElemmentUtil.capabilities object.
636         *
637         * @param ies -- Information Element array
638         * @param beaconCap -- 16-bit Beacon Capability Information field
639         */
640
641        public void from(InformationElement[] ies, BitSet beaconCap) {
642            protocol = new ArrayList<Integer>();
643            keyManagement = new ArrayList<ArrayList<Integer>>();
644            groupCipher = new ArrayList<Integer>();
645            pairwiseCipher = new ArrayList<ArrayList<Integer>>();
646
647            if (ies == null || beaconCap == null) {
648                return;
649            }
650            isESS = beaconCap.get(CAP_ESS_BIT_OFFSET);
651            isPrivacy = beaconCap.get(CAP_PRIVACY_BIT_OFFSET);
652            for (InformationElement ie : ies) {
653                if (ie.id == InformationElement.EID_RSN) {
654                    parseRsnElement(ie);
655                }
656
657                if (ie.id == InformationElement.EID_VSA) {
658                    if (isWpaOneElement(ie)) {
659                        parseWpaOneElement(ie);
660                    }
661                    if (isWpsElement(ie)) {
662                        // TODO(b/62134557): parse WPS IE to provide finer granularity information.
663                        isWPS = true;
664                    }
665                }
666            }
667        }
668
669        private String protocolToString(int protocol) {
670            switch (protocol) {
671                case ScanResult.PROTOCOL_NONE:
672                    return "None";
673                case ScanResult.PROTOCOL_WPA:
674                    return "WPA";
675                case ScanResult.PROTOCOL_WPA2:
676                    return "WPA2";
677                default:
678                    return "?";
679            }
680        }
681
682        private String keyManagementToString(int akm) {
683            switch (akm) {
684                case ScanResult.KEY_MGMT_NONE:
685                    return "None";
686                case ScanResult.KEY_MGMT_PSK:
687                    return "PSK";
688                case ScanResult.KEY_MGMT_EAP:
689                    return "EAP";
690                case ScanResult.KEY_MGMT_FT_EAP:
691                    return "FT/EAP";
692                case ScanResult.KEY_MGMT_FT_PSK:
693                    return "FT/PSK";
694                case ScanResult.KEY_MGMT_EAP_SHA256:
695                    return "EAP-SHA256";
696                case ScanResult.KEY_MGMT_PSK_SHA256:
697                    return "PSK-SHA256";
698                default:
699                    return "?";
700            }
701        }
702
703        private String cipherToString(int cipher) {
704            switch (cipher) {
705                case ScanResult.CIPHER_NONE:
706                    return "None";
707                case ScanResult.CIPHER_CCMP:
708                    return "CCMP";
709                case ScanResult.CIPHER_TKIP:
710                    return "TKIP";
711                default:
712                    return "?";
713            }
714        }
715
716        /**
717         * Build the ScanResult.capabilities String.
718         *
719         * @return security string that mirrors what wpa_supplicant generates
720         */
721        public String generateCapabilitiesString() {
722            String capabilities = "";
723            // private Beacon without an RSNE or WPA IE, hence WEP0
724            boolean isWEP = (protocol.isEmpty()) && isPrivacy;
725
726            if (isWEP) {
727                capabilities += "[WEP]";
728            }
729            for (int i = 0; i < protocol.size(); i++) {
730                capabilities += "[" +  protocolToString(protocol.get(i));
731                for (int j = 0; j < keyManagement.get(i).size(); j++) {
732                    capabilities += ((j == 0) ? "-" : "+")
733                        + keyManagementToString(keyManagement.get(i).get(j));
734                }
735                for (int j = 0; j < pairwiseCipher.get(i).size(); j++) {
736                    capabilities += ((j == 0) ? "-" : "+")
737                            + cipherToString(pairwiseCipher.get(i).get(j));
738                }
739                capabilities += "]";
740            }
741            if (isESS) {
742                capabilities += "[ESS]";
743            }
744            if (isWPS) {
745                capabilities += "[WPS]";
746            }
747
748            return capabilities;
749        }
750    }
751
752    /**
753     * Parser for the Traffic Indication Map (TIM) Information Element (EID 5). This element will
754     * only be present in scan results that are derived from a Beacon Frame, not from the more
755     * plentiful probe responses. Call 'isValid()' after parsing, to ensure the results are correct.
756     */
757    public static class TrafficIndicationMap {
758        private static final int MAX_TIM_LENGTH = 254;
759        private boolean mValid = false;
760        public int mLength = 0;
761        public int mDtimCount = -1;
762        //Negative DTIM Period means no TIM element was given this frame.
763        public int mDtimPeriod = -1;
764        public int mBitmapControl = 0;
765
766        /**
767         * Is this a valid TIM information element.
768         */
769        public boolean isValid() {
770            return mValid;
771        }
772
773        // Traffic Indication Map format (size unit: byte)
774        //
775        //| ElementID | Length | DTIM Count | DTIM Period | BitmapControl | Partial Virtual Bitmap |
776        //      1          1          1            1               1                1 - 251
777        //
778        // Note: InformationElement.bytes has 'Element ID' and 'Length'
779        //       stripped off already
780        //
781        public void from(InformationElement ie) {
782            mValid = false;
783            if (ie == null || ie.bytes == null) return;
784            mLength = ie.bytes.length;
785            ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
786            try {
787                mDtimCount = data.get() & Constants.BYTE_MASK;
788                mDtimPeriod = data.get() & Constants.BYTE_MASK;
789                mBitmapControl = data.get() & Constants.BYTE_MASK;
790                //A valid TIM element must have atleast one more byte
791                data.get();
792            } catch (BufferUnderflowException e) {
793                return;
794            }
795            if (mLength <= MAX_TIM_LENGTH && mDtimPeriod > 0) {
796                mValid = true;
797            }
798        }
799    }
800
801    /**
802     * This util class determines the 802.11 standard (a/b/g/n/ac) being used
803     */
804    public static class WifiMode {
805        public static final int MODE_UNDEFINED = 0; // Unknown/undefined
806        public static final int MODE_11A = 1;       // 802.11a
807        public static final int MODE_11B = 2;       // 802.11b
808        public static final int MODE_11G = 3;       // 802.11g
809        public static final int MODE_11N = 4;       // 802.11n
810        public static final int MODE_11AC = 5;      // 802.11ac
811        //<TODO> add support for 802.11ad and be more selective instead of defaulting to 11A
812
813        /**
814         * Use frequency, max supported rate, and the existence of VHT, HT & ERP fields in scan
815         * scan result to determine the 802.11 Wifi standard being used.
816         */
817        public static int determineMode(int frequency, int maxRate, boolean foundVht,
818                boolean foundHt, boolean foundErp) {
819            if (foundVht) {
820                return MODE_11AC;
821            } else if (foundHt) {
822                return MODE_11N;
823            } else if (foundErp) {
824                return MODE_11G;
825            } else if (frequency < 3000) {
826                if (maxRate < 24000000) {
827                    return MODE_11B;
828                } else {
829                    return MODE_11G;
830                }
831            } else {
832                return MODE_11A;
833            }
834        }
835
836        /**
837         * Map the wifiMode integer to its type, and output as String MODE_11<A/B/G/N/AC>
838         */
839        public static String toString(int mode) {
840            switch(mode) {
841                case MODE_11A:
842                    return "MODE_11A";
843                case MODE_11B:
844                    return "MODE_11B";
845                case MODE_11G:
846                    return "MODE_11G";
847                case MODE_11N:
848                    return "MODE_11N";
849                case MODE_11AC:
850                    return "MODE_11AC";
851                default:
852                    return "MODE_UNDEFINED";
853            }
854        }
855    }
856
857    /**
858     * Parser for both the Supported Rates & Extended Supported Rates Information Elements
859     */
860    public static class SupportedRates {
861        public static final int MASK = 0x7F; // 0111 1111
862        public boolean mValid = false;
863        public ArrayList<Integer> mRates;
864
865        public SupportedRates() {
866            mRates = new ArrayList<Integer>();
867        }
868
869        /**
870         * Is this a valid Supported Rates information element.
871         */
872        public boolean isValid() {
873            return mValid;
874        }
875
876        /**
877         * get the Rate in bits/s from associated byteval
878         */
879        public static int getRateFromByte(int byteVal) {
880            byteVal &= MASK;
881            switch(byteVal) {
882                case 2:
883                    return 1000000;
884                case 4:
885                    return 2000000;
886                case 11:
887                    return 5500000;
888                case 12:
889                    return 6000000;
890                case 18:
891                    return 9000000;
892                case 22:
893                    return 11000000;
894                case 24:
895                    return 12000000;
896                case 36:
897                    return 18000000;
898                case 44:
899                    return 22000000;
900                case 48:
901                    return 24000000;
902                case 66:
903                    return 33000000;
904                case 72:
905                    return 36000000;
906                case 96:
907                    return 48000000;
908                case 108:
909                    return 54000000;
910                default:
911                    //ERROR UNKNOWN RATE
912                    return -1;
913            }
914        }
915
916        // Supported Rates format (size unit: byte)
917        //
918        //| ElementID | Length | Supported Rates  [7 Little Endian Info bits - 1 Flag bit]
919        //      1          1          1 - 8
920        //
921        // Note: InformationElement.bytes has 'Element ID' and 'Length'
922        //       stripped off already
923        //
924        public void from(InformationElement ie) {
925            mValid = false;
926            if (ie == null || ie.bytes == null || ie.bytes.length > 8 || ie.bytes.length < 1)  {
927                return;
928            }
929            ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
930            try {
931                for (int i = 0; i < ie.bytes.length; i++) {
932                    int rate = getRateFromByte(data.get());
933                    if (rate > 0) {
934                        mRates.add(rate);
935                    } else {
936                        return;
937                    }
938                }
939            } catch (BufferUnderflowException e) {
940                return;
941            }
942            mValid = true;
943            return;
944        }
945
946        /**
947         * Lists the rates in a human readable string
948         */
949        public String toString() {
950            StringBuilder sbuf = new StringBuilder();
951            for (Integer rate : mRates) {
952                sbuf.append(String.format("%.1f", (double) rate / 1000000) + ", ");
953            }
954            return sbuf.toString();
955        }
956    }
957}
958