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