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