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