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