NetworkDetail.java revision 5bee0e4616e2f8025d60cbfe3eec3e274a68a452
1package com.android.server.wifi.hotspot2; 2 3import android.net.wifi.ScanResult; 4import android.util.Log; 5 6import com.android.server.wifi.anqp.ANQPElement; 7import com.android.server.wifi.anqp.Constants; 8import com.android.server.wifi.anqp.VenueNameElement; 9 10import java.net.ProtocolException; 11import java.nio.BufferUnderflowException; 12import java.nio.ByteBuffer; 13import java.nio.ByteOrder; 14import java.nio.CharBuffer; 15import java.nio.charset.CharacterCodingException; 16import java.nio.charset.CharsetDecoder; 17import java.nio.charset.StandardCharsets; 18import java.util.List; 19import java.util.Map; 20 21import static com.android.server.wifi.anqp.Constants.BYTES_IN_EUI48; 22import static com.android.server.wifi.anqp.Constants.BYTE_MASK; 23import static com.android.server.wifi.anqp.Constants.getInteger; 24 25public class NetworkDetail { 26 27 private static final int EID_SSID = 0; 28 private static final int EID_BSSLoad = 11; 29 private static final int EID_HT_OPERATION = 61; 30 private static final int EID_VHT_OPERATION = 192; 31 private static final int EID_Interworking = 107; 32 private static final int EID_RoamingConsortium = 111; 33 private static final int EID_ExtendedCaps = 127; 34 private static final int EID_VSA = 221; 35 36 private static final int ANQP_DOMID_BIT = 0x04; 37 private static final int RTT_RESP_ENABLE_BIT = 70; 38 39 private static final long SSID_UTF8_BIT = 0x0001000000000000L; 40 //turn off when SHIP 41 private static final boolean DBG = true; 42 private static final boolean VDBG = false; 43 44 private static final String TAG = "NetworkDetail:"; 45 46 public enum Ant { 47 Private, 48 PrivateWithGuest, 49 ChargeablePublic, 50 FreePublic, 51 Personal, 52 EmergencyOnly, 53 Resvd6, 54 Resvd7, 55 Resvd8, 56 Resvd9, 57 Resvd10, 58 Resvd11, 59 Resvd12, 60 Resvd13, 61 TestOrExperimental, 62 Wildcard 63 } 64 65 public enum HSRelease { 66 R1, 67 R2, 68 Unknown 69 } 70 71 // General identifiers: 72 private final String mSSID; 73 private final long mHESSID; 74 private final long mBSSID; 75 76 // BSS Load element: 77 private final int mStationCount; 78 private final int mChannelUtilization; 79 private final int mCapacity; 80 81 //channel detailed information 82 /* 83 * 0 -- 20 MHz 84 * 1 -- 40 MHz 85 * 2 -- 80 MHz 86 * 3 -- 160 MHz 87 * 4 -- 80 + 80 MHz 88 */ 89 private final int mChannelWidth; 90 private final int mPrimaryFreq; 91 private final int mCenterfreq0; 92 private final int mCenterfreq1; 93 private final boolean m80211McRTTResponder; 94 /* 95 * From Interworking element: 96 * mAnt non null indicates the presence of Interworking, i.e. 802.11u 97 * mVenueGroup and mVenueType may be null if not present in the Interworking element. 98 */ 99 private final Ant mAnt; 100 private final boolean mInternet; 101 private final VenueNameElement.VenueGroup mVenueGroup; 102 private final VenueNameElement.VenueType mVenueType; 103 104 /* 105 * From HS20 Indication element: 106 * mHSRelease is null only if the HS20 Indication element was not present. 107 * mAnqpDomainID is set to -1 if not present in the element. 108 */ 109 private final HSRelease mHSRelease; 110 private final int mAnqpDomainID; 111 112 /* 113 * From beacon: 114 * mAnqpOICount is how many additional OIs are available through ANQP. 115 * mRoamingConsortiums is either null, if the element was not present, or is an array of 116 * 1, 2 or 3 longs in which the roaming consortium values occupy the LSBs. 117 */ 118 private final int mAnqpOICount; 119 private final long[] mRoamingConsortiums; 120 121 private final Long mExtendedCapabilities; 122 123 private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements; 124 125 public NetworkDetail(String bssid, String infoElements, List<String> anqpLines, int freq) { 126 127 if (infoElements == null) { 128 throw new IllegalArgumentException("Null information element string"); 129 } 130 int separator = infoElements.indexOf('='); 131 if (separator<0) { 132 throw new IllegalArgumentException("No element separator"); 133 } 134 135 mBSSID = Utils.parseMac(bssid); 136 137 ByteBuffer data = ByteBuffer.wrap(Utils.hexToBytes(infoElements.substring(separator + 1))) 138 .order(ByteOrder.LITTLE_ENDIAN); 139 140 String ssid = null; 141 byte[] ssidOctets = null; 142 int stationCount = 0; 143 int channelUtilization = 0; 144 int capacity = 0; 145 146 Ant ant = null; 147 boolean internet = false; 148 VenueNameElement.VenueGroup venueGroup = null; 149 VenueNameElement.VenueType venueType = null; 150 long hessid = 0L; 151 152 int anqpOICount = 0; 153 long[] roamingConsortiums = null; 154 155 HSRelease hsRelease = null; 156 int anqpDomainID = 0; // No domain ID treated the same as a 0; unique info per AP. 157 158 Long extendedCapabilities = null; 159 160 int secondChanelOffset = 0; 161 int channelMode = 0; 162 int centerFreqIndex1 = 0; 163 int centerFreqIndex2 = 0; 164 boolean RTTResponder = false; 165 166 RuntimeException exception = null; 167 168 try { 169 while (data.hasRemaining()) { 170 int eid = data.get() & Constants.BYTE_MASK; 171 int elementLength = data.get() & Constants.BYTE_MASK; 172 173 if (elementLength > data.remaining()) { 174 throw new IllegalArgumentException("Element length " + elementLength + 175 " exceeds payload length " + data.remaining() + 176 " @ " + data.position()); 177 } 178 179 ByteBuffer element; 180 181 switch (eid) { 182 case EID_SSID: 183 ssidOctets = new byte[elementLength]; 184 data.get(ssidOctets); 185 break; 186 case EID_BSSLoad: 187 if (elementLength != 5) { 188 throw new IllegalArgumentException("BSS Load element length is not 5: " + 189 elementLength); 190 } 191 stationCount = data.getShort() & Constants.SHORT_MASK; 192 channelUtilization = data.get() & Constants.BYTE_MASK; 193 capacity = data.getShort() & Constants.SHORT_MASK; 194 break; 195 case EID_HT_OPERATION: 196 element = getAndAdvancePayload(data, elementLength); 197 int primary_channel = element.get(); 198 secondChanelOffset = element.get() & 0x3; 199 break; 200 case EID_VHT_OPERATION: 201 element = getAndAdvancePayload(data, elementLength); 202 channelMode = element.get() & Constants.BYTE_MASK; 203 centerFreqIndex1 = element.get() & Constants.BYTE_MASK; 204 centerFreqIndex2 = element.get() & Constants.BYTE_MASK; 205 break; 206 case EID_Interworking: 207 int anOptions = data.get() & Constants.BYTE_MASK; 208 ant = Ant.values()[anOptions & 0x0f]; 209 internet = (anOptions & 0x10) != 0; 210 // Len 1 none, 3 venue-info, 7 HESSID, 9 venue-info & HESSID 211 if (elementLength == 3 || elementLength == 9) { 212 try { 213 ByteBuffer vinfo = data.duplicate(); 214 vinfo.limit(vinfo.position() + 2); 215 VenueNameElement vne = 216 new VenueNameElement(Constants.ANQPElementType.ANQPVenueName, 217 vinfo); 218 venueGroup = vne.getGroup(); 219 venueType = vne.getType(); 220 data.getShort(); 221 } catch (ProtocolException pe) { 222 /*Cannot happen*/ 223 } 224 } else if (elementLength != 1 && elementLength != 7) { 225 throw new IllegalArgumentException("Bad Interworking element length: " + 226 elementLength); 227 } 228 if (elementLength == 7 || elementLength == 9) { 229 hessid = getInteger(data, ByteOrder.BIG_ENDIAN, 6); 230 } 231 break; 232 case EID_RoamingConsortium: 233 anqpOICount = data.get() & Constants.BYTE_MASK; 234 235 int oi12Length = data.get() & Constants.BYTE_MASK; 236 int oi1Length = oi12Length & Constants.NIBBLE_MASK; 237 int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK; 238 int oi3Length = elementLength - 2 - oi1Length - oi2Length; 239 int oiCount = 0; 240 if (oi1Length > 0) { 241 oiCount++; 242 if (oi2Length > 0) { 243 oiCount++; 244 if (oi3Length > 0) { 245 oiCount++; 246 } 247 } 248 } 249 roamingConsortiums = new long[oiCount]; 250 if (oi1Length > 0) { 251 roamingConsortiums[0] = 252 getInteger(data, ByteOrder.BIG_ENDIAN, oi1Length); 253 } 254 if (oi2Length > 0) { 255 roamingConsortiums[1] = 256 getInteger(data, ByteOrder.BIG_ENDIAN, oi2Length); 257 } 258 if (oi3Length > 0) { 259 roamingConsortiums[2] = 260 getInteger(data, ByteOrder.BIG_ENDIAN, oi3Length); 261 } 262 break; 263 case EID_VSA: 264 element = getAndAdvancePayload(data, elementLength); 265 if (elementLength >= 5 && element.getInt() == Constants.HS20_FRAME_PREFIX) { 266 int hsConf = element.get() & Constants.BYTE_MASK; 267 switch ((hsConf >> 4) & Constants.NIBBLE_MASK) { 268 case 0: 269 hsRelease = HSRelease.R1; 270 break; 271 case 1: 272 hsRelease = HSRelease.R2; 273 break; 274 default: 275 hsRelease = HSRelease.Unknown; 276 break; 277 } 278 if ((hsConf & ANQP_DOMID_BIT) != 0) { 279 if (elementLength < 7) { 280 throw new IllegalArgumentException( 281 "HS20 indication element too short: " + elementLength); 282 } 283 anqpDomainID = element.getShort() & Constants.SHORT_MASK; 284 } 285 } 286 break; 287 case EID_ExtendedCaps: 288 element = data.duplicate(); 289 extendedCapabilities = 290 Constants.getInteger(data, ByteOrder.LITTLE_ENDIAN, elementLength); 291 292 int index = RTT_RESP_ENABLE_BIT / 8; 293 byte offset = RTT_RESP_ENABLE_BIT % 8; 294 295 if (elementLength < index + 1) { 296 RTTResponder = false; 297 element.position(element.position() + elementLength); 298 break; 299 } 300 301 element.position(element.position() + index); 302 303 RTTResponder = (element.get() & (0x1 << offset)) != 0; 304 break; 305 default: 306 data.position(data.position() + elementLength); 307 break; 308 } 309 } 310 } 311 catch (IllegalArgumentException | BufferUnderflowException e) { 312 Log.d(Utils.hs2LogTag(getClass()), "Caught " + e); 313 if (ssidOctets == null) { 314 throw new IllegalArgumentException("Malformed IE string (no SSID)", e); 315 } 316 exception = e; 317 } 318 319 if (ssidOctets != null) { 320 boolean strictUTF8 = extendedCapabilities != null && 321 ( extendedCapabilities & SSID_UTF8_BIT ) != 0; 322 323 /* 324 * Strict use of the "UTF-8 SSID" bit by APs appears to be spotty at best even if the 325 * encoding truly is in UTF-8. An unconditional attempt to decode the SSID as UTF-8 is 326 * therefore always made with a fall back to 8859-1 under normal circumstances. 327 * If, however, a previous exception was detected and the UTF-8 bit is set, failure to 328 * decode the SSID will be used as an indication that the whole frame is malformed and 329 * an exception will be triggered. 330 */ 331 CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); 332 try { 333 CharBuffer decoded = decoder.decode(ByteBuffer.wrap(ssidOctets)); 334 ssid = decoded.toString(); 335 } 336 catch (CharacterCodingException cce) { 337 ssid = null; 338 } 339 340 if (ssid == null) { 341 if (strictUTF8 && exception != null) { 342 throw new IllegalArgumentException("Failed to decode SSID in dubious IE string"); 343 } 344 else { 345 ssid = new String(ssidOctets, StandardCharsets.ISO_8859_1); 346 } 347 } 348 } 349 350 mSSID = ssid; 351 mHESSID = hessid; 352 mStationCount = stationCount; 353 mChannelUtilization = channelUtilization; 354 mCapacity = capacity; 355 mAnt = ant; 356 mInternet = internet; 357 mVenueGroup = venueGroup; 358 mVenueType = venueType; 359 mHSRelease = hsRelease; 360 mAnqpDomainID = anqpDomainID; 361 mAnqpOICount = anqpOICount; 362 mRoamingConsortiums = roamingConsortiums; 363 mExtendedCapabilities = extendedCapabilities; 364 mANQPElements = SupplicantBridge.parseANQPLines(anqpLines); 365 //set up channel info 366 mPrimaryFreq = freq; 367 368 if (channelMode != 0) { 369 // 80 or 160 MHz 370 mChannelWidth = channelMode + 1; 371 mCenterfreq0 = (centerFreqIndex1 - 36) * 5 + 5180; 372 if(channelMode > 1) { //160MHz 373 mCenterfreq1 = (centerFreqIndex2 - 36) * 5 + 5180; 374 } else { 375 mCenterfreq1 = 0; 376 } 377 } else { 378 //20 or 40 MHz 379 if (secondChanelOffset != 0) {//40MHz 380 mChannelWidth = 1; 381 if (secondChanelOffset == 1) { 382 mCenterfreq0 = mPrimaryFreq + 20; 383 } else if (secondChanelOffset == 3) { 384 mCenterfreq0 = mPrimaryFreq - 20; 385 } else { 386 mCenterfreq0 = 0; 387 Log.e(TAG,"Error on secondChanelOffset"); 388 } 389 } else { 390 mCenterfreq0 = 0; 391 mChannelWidth = 0; 392 } 393 mCenterfreq1 = 0; 394 } 395 m80211McRTTResponder = RTTResponder; 396 if (VDBG) { 397 Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq + 398 " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: " + mCenterfreq1 + 399 (m80211McRTTResponder ? "Support RTT reponder" : "Do not support RTT responder")); 400 } 401 } 402 403 private static ByteBuffer getAndAdvancePayload(ByteBuffer data, int plLength) { 404 ByteBuffer payload = data.duplicate().order(data.order()); 405 payload.limit(payload.position() + plLength); 406 data.position(data.position() + plLength); 407 return payload; 408 } 409 410 private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 411 mSSID = base.mSSID; 412 mBSSID = base.mBSSID; 413 mHESSID = base.mHESSID; 414 mStationCount = base.mStationCount; 415 mChannelUtilization = base.mChannelUtilization; 416 mCapacity = base.mCapacity; 417 mAnt = base.mAnt; 418 mInternet = base.mInternet; 419 mVenueGroup = base.mVenueGroup; 420 mVenueType = base.mVenueType; 421 mHSRelease = base.mHSRelease; 422 mAnqpDomainID = base.mAnqpDomainID; 423 mAnqpOICount = base.mAnqpOICount; 424 mRoamingConsortiums = base.mRoamingConsortiums; 425 mExtendedCapabilities = base.mExtendedCapabilities; 426 mANQPElements = anqpElements; 427 mChannelWidth = base.mChannelWidth; 428 mPrimaryFreq = base.mPrimaryFreq; 429 mCenterfreq0 = base.mCenterfreq0; 430 mCenterfreq1 = base.mCenterfreq1; 431 m80211McRTTResponder = base.m80211McRTTResponder; 432 } 433 434 public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 435 return new NetworkDetail(this, anqpElements); 436 } 437 438 public boolean hasInterworking() { 439 return mAnt != null; 440 } 441 442 public String getSSID() { 443 return mSSID; 444 } 445 446 public String getTrimmedSSID() { 447 for (int n = 0; n < mSSID.length(); n++) { 448 if (mSSID.charAt(n) != 0) { 449 return mSSID; 450 } 451 } 452 return ""; 453 } 454 455 public long getHESSID() { 456 return mHESSID; 457 } 458 459 public long getBSSID() { 460 return mBSSID; 461 } 462 463 public int getStationCount() { 464 return mStationCount; 465 } 466 467 public int getChannelUtilization() { 468 return mChannelUtilization; 469 } 470 471 public int getCapacity() { 472 return mCapacity; 473 } 474 475 public boolean isInterworking() { 476 return mAnt != null; 477 } 478 479 public Ant getAnt() { 480 return mAnt; 481 } 482 483 public boolean isInternet() { 484 return mInternet; 485 } 486 487 public VenueNameElement.VenueGroup getVenueGroup() { 488 return mVenueGroup; 489 } 490 491 public VenueNameElement.VenueType getVenueType() { 492 return mVenueType; 493 } 494 495 public HSRelease getHSRelease() { 496 return mHSRelease; 497 } 498 499 public int getAnqpDomainID() { 500 return mAnqpDomainID; 501 } 502 503 public int getAnqpOICount() { 504 return mAnqpOICount; 505 } 506 507 public long[] getRoamingConsortiums() { 508 return mRoamingConsortiums; 509 } 510 511 public Long getExtendedCapabilities() { 512 return mExtendedCapabilities; 513 } 514 515 public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() { 516 return mANQPElements; 517 } 518 519 public int getChannelWidth() { 520 return mChannelWidth; 521 } 522 523 public int getCenterfreq0() { 524 return mCenterfreq0; 525 } 526 527 public int getCenterfreq1() { 528 return mCenterfreq1; 529 } 530 531 public boolean is80211McResponderSupport() { 532 return m80211McRTTResponder; 533 } 534 535 public boolean isSSID_UTF8() { 536 return mExtendedCapabilities != null && (mExtendedCapabilities & SSID_UTF8_BIT) != 0; 537 } 538 539 @Override 540 public boolean equals(Object thatObject) { 541 if (this == thatObject) { 542 return true; 543 } 544 if (thatObject == null || getClass() != thatObject.getClass()) { 545 return false; 546 } 547 548 NetworkDetail that = (NetworkDetail)thatObject; 549 550 return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID(); 551 } 552 553 @Override 554 public int hashCode() { 555 return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID; 556 } 557 558 @Override 559 public String toString() { 560 return String.format("NetworkInfo{mSSID='%s', mHESSID=%x, mBSSID=%x, mStationCount=%d, " + 561 "mChannelUtilization=%d, mCapacity=%d, mAnt=%s, mInternet=%s, " + 562 "mVenueGroup=%s, mVenueType=%s, mHSRelease=%s, mAnqpDomainID=%d, " + 563 "mAnqpOICount=%d, mRoamingConsortiums=%s}", 564 mSSID, mHESSID, mBSSID, mStationCount, 565 mChannelUtilization, mCapacity, mAnt, mInternet, 566 mVenueGroup, mVenueType, mHSRelease, mAnqpDomainID, 567 mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums)); 568 } 569 570 public String toKeyString() { 571 return mHESSID != 0 ? 572 String.format("'%s':%s (%012x)", mSSID, getBSSIDString(), mHESSID) : 573 String.format("'%s':%s", mSSID, getBSSIDString()); 574 } 575 576 public String getBSSIDString() { 577 return toMACString(mBSSID); 578 } 579 580 public static String toMACString(long mac) { 581 StringBuilder sb = new StringBuilder(); 582 boolean first = true; 583 for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) { 584 if (first) { 585 first = false; 586 } else { 587 sb.append(':'); 588 } 589 sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK)); 590 } 591 return sb.toString(); 592 } 593 594 private static final String IE = "ie=" + 595 "000477696e67" + // SSID wing 596 "0b052a00cf611e" + // BSS Load 42:207:7777 597 "6b091e0a01610408621205" + // internet:Experimental:Vehicular:Auto:hessid 598 "6f0a0e530111112222222229" + // 14:111111:2222222229 599 "dd07506f9a10143a01"; // r2:314 600 601 private static final String IE2 = "ie=000f4578616d706c65204e6574776f726b010882848b960c1218240301012a010432043048606c30140100000fac040100000fac040100000fac0100007f04000000806b091e07010203040506076c027f006f1001531122331020304050010203040506dd05506f9a1000"; 602 603 public static void main(String[] args) { 604 ScanResult scanResult = new ScanResult(); 605 scanResult.SSID = "wing"; 606 scanResult.BSSID = "610408"; 607 NetworkDetail nwkDetail = new NetworkDetail(scanResult.BSSID, IE2, null, 0); 608 System.out.println(nwkDetail); 609 } 610} 611