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  */
16 package com.android.server.wifi.util;
17 
18 import static com.android.server.wifi.anqp.Constants.getInteger;
19 
20 import android.net.wifi.ScanResult.InformationElement;
21 import android.util.Log;
22 
23 import com.android.server.wifi.anqp.Constants;
24 import com.android.server.wifi.anqp.VenueNameElement;
25 import com.android.server.wifi.hotspot2.NetworkDetail;
26 
27 import java.net.ProtocolException;
28 import java.nio.BufferUnderflowException;
29 import java.nio.ByteBuffer;
30 import java.nio.ByteOrder;
31 import java.util.ArrayList;
32 import java.util.BitSet;
33 
34 public class InformationElementUtil {
35 
parseInformationElements(byte[] bytes)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 
from(InformationElement ie)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 
getChannelWidth()92         public int getChannelWidth() {
93             if (secondChannelOffset != 0) {
94                 return 1;
95             } else {
96                 return 0;
97             }
98         }
99 
getCenterFreq0(int primaryFrequency)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 
from(InformationElement ie)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 
isValid()129         public boolean isValid() {
130             return channelMode != 0;
131         }
132 
getChannelWidth()133         public int getChannelWidth() {
134             return channelMode + 1;
135         }
136 
getCenterFreq0()137         public int getCenterFreq0() {
138             //convert channel index to frequency in MHz, channel 36 is 5180MHz
139             return (centerFreqIndex1 - 36) * 5 + 5180;
140         }
141 
getCenterFreq1()142         public int getCenterFreq1() {
143             if (channelMode > 1) { //160MHz
144                 return (centerFreqIndex2 - 36) * 5 + 5180;
145             } else {
146                 return 0;
147             }
148         }
149 
from(InformationElement ie)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 
from(InformationElement ie)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 
from(InformationElement ie)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 
from(InformationElement ie)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 
ExtendedCapabilities()278         public ExtendedCapabilities() {
279         }
280 
ExtendedCapabilities(ExtendedCapabilities other)281         public ExtendedCapabilities(ExtendedCapabilities other) {
282             extendedCapabilities = other.extendedCapabilities;
283             is80211McRTTResponder = other.is80211McRTTResponder;
284         }
285 
isStrictUtf8()286         public boolean isStrictUtf8() {
287             return extendedCapabilities != null && (extendedCapabilities & SSID_UTF8_BIT) != 0;
288         }
289 
from(InformationElement ie)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 
Capabilities()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
parseRsnElement(InformationElement ie)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 
isWpaOneElement(InformationElement ie)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         //
parseWpaOneElement(InformationElement ie)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          */
buildCapabilities(InformationElement[] ies, BitSet beaconCap)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          */
isValid()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         //
from(InformationElement ie)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          */
determineMode(int frequency, int maxRate, boolean foundVht, boolean foundHt, boolean foundErp)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          */
toString(int mode)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 
SupportedRates()673         public SupportedRates() {
674             mRates = new ArrayList<Integer>();
675         }
676 
677         /**
678          * Is this a valid Supported Rates information element.
679          */
isValid()680         public boolean isValid() {
681             return mValid;
682         }
683 
684         /**
685          * get the Rate in bits/s from associated byteval
686          */
getRateFromByte(int byteVal)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         //
from(InformationElement ie)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          */
toString()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