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