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