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