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