InformationElementUtil.java revision 3571366ac36c70746b9f013ec2b54482861c9292
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.anqp.VenueNameElement; 25import com.android.server.wifi.hotspot2.NetworkDetail; 26 27import java.net.ProtocolException; 28import java.nio.BufferUnderflowException; 29import java.nio.ByteBuffer; 30import java.nio.ByteOrder; 31import java.util.ArrayList; 32import java.util.BitSet; 33 34public class InformationElementUtil { 35 36 public static InformationElement[] parseInformationElements(byte[] bytes) { 37 if (bytes == null) { 38 return new InformationElement[0]; 39 } 40 ByteBuffer data = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); 41 42 ArrayList<InformationElement> infoElements = new ArrayList<>(); 43 boolean found_ssid = false; 44 while (data.remaining() > 1) { 45 int eid = data.get() & Constants.BYTE_MASK; 46 int elementLength = data.get() & Constants.BYTE_MASK; 47 48 if (elementLength > data.remaining() || (eid == InformationElement.EID_SSID 49 && found_ssid)) { 50 // APs often pad the data with bytes that happen to match that of the EID_SSID 51 // marker. This is not due to a known issue for APs to incorrectly send the SSID 52 // name multiple times. 53 break; 54 } 55 if (eid == InformationElement.EID_SSID) { 56 found_ssid = true; 57 } 58 59 InformationElement ie = new InformationElement(); 60 ie.id = eid; 61 ie.bytes = new byte[elementLength]; 62 data.get(ie.bytes); 63 infoElements.add(ie); 64 } 65 return infoElements.toArray(new InformationElement[infoElements.size()]); 66 } 67 68 69 public static class BssLoad { 70 public int stationCount = 0; 71 public int channelUtilization = 0; 72 public int capacity = 0; 73 74 public void from(InformationElement ie) { 75 if (ie.id != InformationElement.EID_BSS_LOAD) { 76 throw new IllegalArgumentException("Element id is not BSS_LOAD, : " + ie.id); 77 } 78 if (ie.bytes.length != 5) { 79 throw new IllegalArgumentException("BSS Load element length is not 5: " 80 + ie.bytes.length); 81 } 82 ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN); 83 stationCount = data.getShort() & Constants.SHORT_MASK; 84 channelUtilization = data.get() & Constants.BYTE_MASK; 85 capacity = data.getShort() & Constants.SHORT_MASK; 86 } 87 } 88 89 public static class HtOperation { 90 public int secondChannelOffset = 0; 91 92 public int getChannelWidth() { 93 if (secondChannelOffset != 0) { 94 return 1; 95 } else { 96 return 0; 97 } 98 } 99 100 public int getCenterFreq0(int primaryFrequency) { 101 //40 MHz 102 if (secondChannelOffset != 0) { 103 if (secondChannelOffset == 1) { 104 return primaryFrequency + 10; 105 } else if (secondChannelOffset == 3) { 106 return primaryFrequency - 10; 107 } else { 108 Log.e("HtOperation", "Error on secondChannelOffset: " + secondChannelOffset); 109 return 0; 110 } 111 } else { 112 return 0; 113 } 114 } 115 116 public void from(InformationElement ie) { 117 if (ie.id != InformationElement.EID_HT_OPERATION) { 118 throw new IllegalArgumentException("Element id is not HT_OPERATION, : " + ie.id); 119 } 120 secondChannelOffset = ie.bytes[1] & 0x3; 121 } 122 } 123 124 public static class VhtOperation { 125 public int channelMode = 0; 126 public int centerFreqIndex1 = 0; 127 public int centerFreqIndex2 = 0; 128 129 public boolean isValid() { 130 return channelMode != 0; 131 } 132 133 public int getChannelWidth() { 134 return channelMode + 1; 135 } 136 137 public int getCenterFreq0() { 138 //convert channel index to frequency in MHz, channel 36 is 5180MHz 139 return (centerFreqIndex1 - 36) * 5 + 5180; 140 } 141 142 public int getCenterFreq1() { 143 if (channelMode > 1) { //160MHz 144 return (centerFreqIndex2 - 36) * 5 + 5180; 145 } else { 146 return 0; 147 } 148 } 149 150 public void from(InformationElement ie) { 151 if (ie.id != InformationElement.EID_VHT_OPERATION) { 152 throw new IllegalArgumentException("Element id is not VHT_OPERATION, : " + ie.id); 153 } 154 channelMode = ie.bytes[0] & Constants.BYTE_MASK; 155 centerFreqIndex1 = ie.bytes[1] & Constants.BYTE_MASK; 156 centerFreqIndex2 = ie.bytes[2] & Constants.BYTE_MASK; 157 } 158 } 159 160 public static class Interworking { 161 public NetworkDetail.Ant ant = null; 162 public boolean internet = false; 163 public VenueNameElement.VenueGroup venueGroup = null; 164 public VenueNameElement.VenueType venueType = null; 165 public long hessid = 0L; 166 167 public void from(InformationElement ie) { 168 if (ie.id != InformationElement.EID_INTERWORKING) { 169 throw new IllegalArgumentException("Element id is not INTERWORKING, : " + ie.id); 170 } 171 ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN); 172 int anOptions = data.get() & Constants.BYTE_MASK; 173 ant = NetworkDetail.Ant.values()[anOptions & 0x0f]; 174 internet = (anOptions & 0x10) != 0; 175 // Len 1 none, 3 venue-info, 7 HESSID, 9 venue-info & HESSID 176 if (ie.bytes.length == 3 || ie.bytes.length == 9) { 177 try { 178 ByteBuffer vinfo = data.duplicate(); 179 vinfo.limit(vinfo.position() + 2); 180 VenueNameElement vne = new VenueNameElement( 181 Constants.ANQPElementType.ANQPVenueName, vinfo); 182 venueGroup = vne.getGroup(); 183 venueType = vne.getType(); 184 } catch (ProtocolException pe) { 185 /*Cannot happen*/ 186 } 187 } else if (ie.bytes.length != 1 && ie.bytes.length != 7) { 188 throw new IllegalArgumentException("Bad Interworking element length: " 189 + ie.bytes.length); 190 } 191 if (ie.bytes.length == 7 || ie.bytes.length == 9) { 192 hessid = getInteger(data, ByteOrder.BIG_ENDIAN, 6); 193 } 194 } 195 } 196 197 public static class RoamingConsortium { 198 public int anqpOICount = 0; 199 public long[] roamingConsortiums = null; 200 201 public void from(InformationElement ie) { 202 if (ie.id != InformationElement.EID_ROAMING_CONSORTIUM) { 203 throw new IllegalArgumentException("Element id is not ROAMING_CONSORTIUM, : " 204 + ie.id); 205 } 206 ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN); 207 anqpOICount = data.get() & Constants.BYTE_MASK; 208 209 int oi12Length = data.get() & Constants.BYTE_MASK; 210 int oi1Length = oi12Length & Constants.NIBBLE_MASK; 211 int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK; 212 int oi3Length = ie.bytes.length - 2 - oi1Length - oi2Length; 213 int oiCount = 0; 214 if (oi1Length > 0) { 215 oiCount++; 216 if (oi2Length > 0) { 217 oiCount++; 218 if (oi3Length > 0) { 219 oiCount++; 220 } 221 } 222 } 223 roamingConsortiums = new long[oiCount]; 224 if (oi1Length > 0 && roamingConsortiums.length > 0) { 225 roamingConsortiums[0] = 226 getInteger(data, ByteOrder.BIG_ENDIAN, oi1Length); 227 } 228 if (oi2Length > 0 && roamingConsortiums.length > 1) { 229 roamingConsortiums[1] = 230 getInteger(data, ByteOrder.BIG_ENDIAN, oi2Length); 231 } 232 if (oi3Length > 0 && roamingConsortiums.length > 2) { 233 roamingConsortiums[2] = 234 getInteger(data, ByteOrder.BIG_ENDIAN, oi3Length); 235 } 236 } 237 } 238 239 public static class Vsa { 240 private static final int ANQP_DOMID_BIT = 0x04; 241 242 public NetworkDetail.HSRelease hsRelease = null; 243 public int anqpDomainID = 0; // No domain ID treated the same as a 0; unique info per AP. 244 245 public void from(InformationElement ie) { 246 ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN); 247 if (ie.bytes.length >= 5 && data.getInt() == Constants.HS20_FRAME_PREFIX) { 248 int hsConf = data.get() & Constants.BYTE_MASK; 249 switch ((hsConf >> 4) & Constants.NIBBLE_MASK) { 250 case 0: 251 hsRelease = NetworkDetail.HSRelease.R1; 252 break; 253 case 1: 254 hsRelease = NetworkDetail.HSRelease.R2; 255 break; 256 default: 257 hsRelease = NetworkDetail.HSRelease.Unknown; 258 break; 259 } 260 if ((hsConf & ANQP_DOMID_BIT) != 0) { 261 if (ie.bytes.length < 7) { 262 throw new IllegalArgumentException( 263 "HS20 indication element too short: " + ie.bytes.length); 264 } 265 anqpDomainID = data.getShort() & Constants.SHORT_MASK; 266 } 267 } 268 } 269 } 270 271 public static class ExtendedCapabilities { 272 private static final int RTT_RESP_ENABLE_BIT = 70; 273 private static final long SSID_UTF8_BIT = 0x0001000000000000L; 274 275 public Long extendedCapabilities = null; 276 public boolean is80211McRTTResponder = false; 277 278 public ExtendedCapabilities() { 279 } 280 281 public ExtendedCapabilities(ExtendedCapabilities other) { 282 extendedCapabilities = other.extendedCapabilities; 283 is80211McRTTResponder = other.is80211McRTTResponder; 284 } 285 286 public boolean isStrictUtf8() { 287 return extendedCapabilities != null && (extendedCapabilities & SSID_UTF8_BIT) != 0; 288 } 289 290 public void from(InformationElement ie) { 291 ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN); 292 extendedCapabilities = 293 Constants.getInteger(data, ByteOrder.LITTLE_ENDIAN, ie.bytes.length); 294 295 int index = RTT_RESP_ENABLE_BIT / 8; 296 byte offset = RTT_RESP_ENABLE_BIT % 8; 297 if (ie.bytes.length < index + 1) { 298 is80211McRTTResponder = false; 299 } else { 300 is80211McRTTResponder = (ie.bytes[index] & ((byte) 0x1 << offset)) != 0; 301 } 302 } 303 } 304 305 /** 306 * parse beacon to build the capabilities 307 * 308 * This class is used to build the capabilities string of the scan results coming 309 * from HAL. It parses the ieee beacon's capability field, WPA and RSNE IE as per spec, 310 * and builds the ScanResult.capabilities String in a way that mirrors the values returned 311 * by wpa_supplicant. 312 */ 313 public static class Capabilities { 314 private static final int CAP_PRIVACY_BIT_OFFSET = 4; 315 316 private static final int WPA_VENDOR_OUI_TYPE_ONE = 0x01f25000; 317 private static final short WPA_VENDOR_OUI_VERSION = 0x0001; 318 private static final short RSNE_VERSION = 0x0001; 319 320 private static final int WPA_AKM_EAP = 0x01f25000; 321 private static final int WPA_AKM_PSK = 0x02f25000; 322 323 private static final int WPA2_AKM_EAP = 0x01ac0f00; 324 private static final int WPA2_AKM_PSK = 0x02ac0f00; 325 private static final int WPA2_AKM_FT_EAP = 0x03ac0f00; 326 private static final int WPA2_AKM_FT_PSK = 0x04ac0f00; 327 private static final int WPA2_AKM_EAP_SHA256 = 0x05ac0f00; 328 private static final int WPA2_AKM_PSK_SHA256 = 0x06ac0f00; 329 330 public Capabilities() { 331 } 332 333 // RSNE format (size unit: byte) 334 // 335 // | Element ID | Length | Version | Group Data Cipher Suite | 336 // 1 1 2 4 337 // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List | 338 // 2 4 * m 339 // | AKM Suite Count | AKM Suite List | RSN Capabilities | 340 // 2 4 * n 2 341 // | PMKID Count | PMKID List | Group Management Cipher Suite | 342 // 2 16 * s 4 343 // 344 // Note: InformationElement.bytes has 'Element ID' and 'Length' 345 // stripped off already 346 private static String parseRsnElement(InformationElement ie) { 347 ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN); 348 349 try { 350 // version 351 if (buf.getShort() != RSNE_VERSION) { 352 // incorrect version 353 return null; 354 } 355 356 // group data cipher suite 357 // here we simply advance the buffer position 358 buf.getInt(); 359 360 // found the RSNE IE, hence start building the capability string 361 String security = "[WPA2"; 362 363 // pairwise cipher suite count 364 short cipherCount = buf.getShort(); 365 366 // pairwise cipher suite list 367 for (int i = 0; i < cipherCount; i++) { 368 // here we simply advance the buffer position 369 buf.getInt(); 370 } 371 372 // AKM 373 // AKM suite count 374 short akmCount = buf.getShort(); 375 376 // parse AKM suite list 377 if (akmCount == 0) { 378 security += "-EAP"; //default AKM 379 } 380 boolean found = false; 381 for (int i = 0; i < akmCount; i++) { 382 int akm = buf.getInt(); 383 switch (akm) { 384 case WPA2_AKM_EAP: 385 security += (found ? "+" : "-") + "EAP"; 386 found = true; 387 break; 388 case WPA2_AKM_PSK: 389 security += (found ? "+" : "-") + "PSK"; 390 found = true; 391 break; 392 case WPA2_AKM_FT_EAP: 393 security += (found ? "+" : "-") + "FT/EAP"; 394 found = true; 395 break; 396 case WPA2_AKM_FT_PSK: 397 security += (found ? "+" : "-") + "FT/PSK"; 398 found = true; 399 break; 400 case WPA2_AKM_EAP_SHA256: 401 security += (found ? "+" : "-") + "EAP-SHA256"; 402 found = true; 403 break; 404 case WPA2_AKM_PSK_SHA256: 405 security += (found ? "+" : "-") + "PSK-SHA256"; 406 found = true; 407 break; 408 default: 409 // do nothing 410 break; 411 } 412 } 413 414 // we parsed what we want at this point 415 security += "]"; 416 return security; 417 } catch (BufferUnderflowException e) { 418 Log.e("IE_Capabilities", "Couldn't parse RSNE, buffer underflow"); 419 return null; 420 } 421 } 422 423 private static boolean isWpaOneElement(InformationElement ie) { 424 ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN); 425 426 try { 427 // WPA OUI and type 428 return (buf.getInt() == WPA_VENDOR_OUI_TYPE_ONE); 429 } catch (BufferUnderflowException e) { 430 Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow"); 431 return false; 432 } 433 } 434 435 // WPA type 1 format (size unit: byte) 436 // 437 // | Element ID | Length | OUI | Type | Version | 438 // 1 1 3 1 2 439 // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List | 440 // 2 4 * m 441 // | AKM Suite Count | AKM Suite List | 442 // 2 4 * n 443 // 444 // Note: InformationElement.bytes has 'Element ID' and 'Length' 445 // stripped off already 446 // 447 private static String parseWpaOneElement(InformationElement ie) { 448 ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN); 449 450 try { 451 // skip WPA OUI and type parsing. isWpaOneElement() should have 452 // been called for verification before we reach here. 453 buf.getInt(); 454 455 // start building the string 456 String security = "[WPA"; 457 458 // version 459 if (buf.getShort() != WPA_VENDOR_OUI_VERSION) { 460 // incorrect version 461 return null; 462 } 463 464 // group data cipher suite 465 // here we simply advance buffer position 466 buf.getInt(); 467 468 // pairwise cipher suite count 469 short cipherCount = buf.getShort(); 470 471 // pairwise chipher suite list 472 for (int i = 0; i < cipherCount; i++) { 473 // here we simply advance buffer position 474 buf.getInt(); 475 } 476 477 // AKM 478 // AKM suite count 479 short akmCount = buf.getShort(); 480 481 // AKM suite list 482 if (akmCount == 0) { 483 security += "-EAP"; //default AKM 484 } 485 boolean found = false; 486 for (int i = 0; i < akmCount; i++) { 487 int akm = buf.getInt(); 488 switch (akm) { 489 case WPA_AKM_EAP: 490 security += (found ? "+" : "-") + "EAP"; 491 found = true; 492 break; 493 case WPA_AKM_PSK: 494 security += (found ? "+" : "-") + "PSK"; 495 found = true; 496 break; 497 default: 498 // do nothing 499 break; 500 } 501 } 502 503 // we parsed what we want at this point 504 security += "]"; 505 return security; 506 } catch (BufferUnderflowException e) { 507 Log.e("IE_Capabilities", "Couldn't parse type 1 WPA, buffer underflow"); 508 return null; 509 } 510 } 511 512 /** 513 * Parse the Information Element and the 16-bit Capability Information field 514 * to build the ScanResult.capabilities String. 515 * 516 * @param ies -- Information Element array 517 * @param beaconCap -- 16-bit Beacon Capability Information field 518 * @return security string that mirrors what wpa_supplicant generates 519 */ 520 public static String buildCapabilities(InformationElement[] ies, BitSet beaconCap) { 521 String capabilities = ""; 522 boolean rsneFound = false; 523 boolean wpaFound = false; 524 525 if (ies == null || beaconCap == null) { 526 return capabilities; 527 } 528 529 boolean privacy = beaconCap.get(CAP_PRIVACY_BIT_OFFSET); 530 531 for (InformationElement ie : ies) { 532 if (ie.id == InformationElement.EID_RSN) { 533 rsneFound = true; 534 capabilities += parseRsnElement(ie); 535 } 536 537 if (ie.id == InformationElement.EID_VSA) { 538 if (isWpaOneElement(ie)) { 539 wpaFound = true; 540 capabilities += parseWpaOneElement(ie); 541 } 542 } 543 } 544 545 if (!rsneFound && !wpaFound && privacy) { 546 //private Beacon without an RSNE or WPA IE, hence WEP0 547 capabilities += "[WEP]"; 548 } 549 550 return capabilities; 551 } 552 } 553} 554