NetworkDetail.java revision 398823d45a240ff90ff2ffab3ff4a8b8646f24c9
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.ByteBuffer; 12import java.nio.ByteOrder; 13import java.nio.CharBuffer; 14import java.nio.charset.CharacterCodingException; 15import java.nio.charset.Charset; 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 = 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 iae) { 312 Log.d("HS2J", "Caught " + iae); 313 if (ssidOctets == null) { 314 throw iae; 315 } 316 exception = iae; 317 } 318 319 if (ssidOctets != null) { 320 Charset encoding; 321 if (extendedCapabilities != null && (extendedCapabilities & SSID_UTF8_BIT) != 0) { 322 encoding = StandardCharsets.UTF_8; 323 } 324 else { 325 encoding = StandardCharsets.ISO_8859_1; 326 } 327 328 if (exception == null) { 329 ssid = new String(ssidOctets, encoding); 330 } 331 else { 332 // Apply strict checking if there were previous errors: 333 CharsetDecoder decoder = encoding.newDecoder(); 334 try { 335 CharBuffer decoded = decoder.decode(ByteBuffer.wrap(ssidOctets)); 336 ssid = decoded.toString(); 337 } 338 catch (CharacterCodingException cce) { 339 throw exception; 340 } 341 } 342 } 343 344 mSSID = ssid; 345 mHESSID = hessid; 346 mStationCount = stationCount; 347 mChannelUtilization = channelUtilization; 348 mCapacity = capacity; 349 mAnt = ant; 350 mInternet = internet; 351 mVenueGroup = venueGroup; 352 mVenueType = venueType; 353 mHSRelease = hsRelease; 354 mAnqpDomainID = anqpDomainID; 355 mAnqpOICount = anqpOICount; 356 mRoamingConsortiums = roamingConsortiums; 357 mExtendedCapabilities = extendedCapabilities; 358 mANQPElements = SupplicantBridge.parseANQPLines(anqpLines); 359 //set up channel info 360 mPrimaryFreq = freq; 361 362 if (channelMode != 0) { 363 // 80 or 160 MHz 364 mChannelWidth = channelMode + 1; 365 mCenterfreq0 = (centerFreqIndex1 - 36) * 5 + 5180; 366 if(channelMode > 1) { //160MHz 367 mCenterfreq1 = (centerFreqIndex2 - 36) * 5 + 5180; 368 } else { 369 mCenterfreq1 = 0; 370 } 371 } else { 372 //20 or 40 MHz 373 if (secondChanelOffset != 0) {//40MHz 374 mChannelWidth = 1; 375 if (secondChanelOffset == 1) { 376 mCenterfreq0 = mPrimaryFreq + 20; 377 } else if (secondChanelOffset == 3) { 378 mCenterfreq0 = mPrimaryFreq - 20; 379 } else { 380 mCenterfreq0 = 0; 381 Log.e(TAG,"Error on secondChanelOffset"); 382 } 383 } else { 384 mCenterfreq0 = 0; 385 mChannelWidth = 0; 386 } 387 mCenterfreq1 = 0; 388 } 389 m80211McRTTResponder = RTTResponder; 390 if (VDBG) { 391 Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq + 392 " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: " + mCenterfreq1 + 393 (m80211McRTTResponder ? "Support RTT reponder" : "Do not support RTT responder")); 394 } 395 } 396 397 private static ByteBuffer getAndAdvancePayload(ByteBuffer data, int plLength) { 398 ByteBuffer payload = data.duplicate().order(data.order()); 399 payload.limit(payload.position() + plLength); 400 data.position(data.position() + plLength); 401 return payload; 402 } 403 404 private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 405 mSSID = base.mSSID; 406 mBSSID = base.mBSSID; 407 mHESSID = base.mHESSID; 408 mStationCount = base.mStationCount; 409 mChannelUtilization = base.mChannelUtilization; 410 mCapacity = base.mCapacity; 411 mAnt = base.mAnt; 412 mInternet = base.mInternet; 413 mVenueGroup = base.mVenueGroup; 414 mVenueType = base.mVenueType; 415 mHSRelease = base.mHSRelease; 416 mAnqpDomainID = base.mAnqpDomainID; 417 mAnqpOICount = base.mAnqpOICount; 418 mRoamingConsortiums = base.mRoamingConsortiums; 419 mExtendedCapabilities = base.mExtendedCapabilities; 420 mANQPElements = anqpElements; 421 mChannelWidth = base.mChannelWidth; 422 mPrimaryFreq = base.mPrimaryFreq; 423 mCenterfreq0 = base.mCenterfreq0; 424 mCenterfreq1 = base.mCenterfreq1; 425 m80211McRTTResponder = base.m80211McRTTResponder; 426 } 427 428 public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 429 return new NetworkDetail(this, anqpElements); 430 } 431 432 private static long parseMac(String s) { 433 434 long mac = 0; 435 int count = 0; 436 for (int n = 0; n < s.length(); n++) { 437 int nibble = Utils.fromHex(s.charAt(n), true); 438 if (nibble >= 0) { 439 mac = (mac << 4) | nibble; 440 count++; 441 } 442 } 443 if (count < 12 || (count&1) == 1) { 444 throw new IllegalArgumentException("Bad MAC address: '" + s + "'"); 445 } 446 return mac; 447 } 448 449 public boolean has80211uInfo() { 450 return mAnt != null || mRoamingConsortiums != null || mHSRelease != null; 451 } 452 453 public boolean hasInterworking() { 454 return mAnt != null; 455 } 456 457 public String getSSID() { 458 return mSSID; 459 } 460 461 public long getHESSID() { 462 return mHESSID; 463 } 464 465 public long getBSSID() { 466 return mBSSID; 467 } 468 469 public int getStationCount() { 470 return mStationCount; 471 } 472 473 public int getChannelUtilization() { 474 return mChannelUtilization; 475 } 476 477 public int getCapacity() { 478 return mCapacity; 479 } 480 481 public boolean isInterworking() { 482 return mAnt != null; 483 } 484 485 public Ant getAnt() { 486 return mAnt; 487 } 488 489 public boolean isInternet() { 490 return mInternet; 491 } 492 493 public VenueNameElement.VenueGroup getVenueGroup() { 494 return mVenueGroup; 495 } 496 497 public VenueNameElement.VenueType getVenueType() { 498 return mVenueType; 499 } 500 501 public HSRelease getHSRelease() { 502 return mHSRelease; 503 } 504 505 public int getAnqpDomainID() { 506 return mAnqpDomainID; 507 } 508 509 public int getAnqpOICount() { 510 return mAnqpOICount; 511 } 512 513 public long[] getRoamingConsortiums() { 514 return mRoamingConsortiums; 515 } 516 517 public Long getExtendedCapabilities() { 518 return mExtendedCapabilities; 519 } 520 521 public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() { 522 return mANQPElements; 523 } 524 525 public int getChannelWidth() { 526 return mChannelWidth; 527 } 528 529 public int getCenterfreq0() { 530 return mCenterfreq0; 531 } 532 533 public int getCenterfreq1() { 534 return mCenterfreq1; 535 } 536 537 public boolean is80211McResponderSupport() { 538 return m80211McRTTResponder; 539 } 540 541 public boolean isSSID_UTF8() { 542 return mExtendedCapabilities != null && (mExtendedCapabilities & SSID_UTF8_BIT) != 0; 543 } 544 545 @Override 546 public boolean equals(Object thatObject) { 547 if (this == thatObject) { 548 return true; 549 } 550 if (thatObject == null || getClass() != thatObject.getClass()) { 551 return false; 552 } 553 554 NetworkDetail that = (NetworkDetail)thatObject; 555 556 return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID(); 557 } 558 559 @Override 560 public int hashCode() { 561 return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID; 562 } 563 564 @Override 565 public String toString() { 566 return String.format("NetworkInfo{mSSID='%s', mHESSID=%x, mBSSID=%x, mStationCount=%d, " + 567 "mChannelUtilization=%d, mCapacity=%d, mAnt=%s, mInternet=%s, " + 568 "mVenueGroup=%s, mVenueType=%s, mHSRelease=%s, mAnqpDomainID=%d, " + 569 "mAnqpOICount=%d, mRoamingConsortiums=%s}", 570 mSSID, mHESSID, mBSSID, mStationCount, 571 mChannelUtilization, mCapacity, mAnt, mInternet, 572 mVenueGroup, mVenueType, mHSRelease, mAnqpDomainID, 573 mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums)); 574 } 575 576 public String toKeyString() { 577 return String.format("'%s':%s", mSSID, getBSSIDString()); 578 } 579 580 public String getBSSIDString() { 581 return toMACString(mBSSID); 582 } 583 584 public static String toMACString(long mac) { 585 StringBuilder sb = new StringBuilder(); 586 boolean first = true; 587 for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) { 588 if (first) { 589 first = false; 590 } else { 591 sb.append(':'); 592 } 593 sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK)); 594 } 595 return sb.toString(); 596 } 597 598 private static final String IE = "ie=" + 599 "000477696e67" + // SSID wing 600 "0b052a00cf611e" + // BSS Load 42:207:7777 601 "6b091e0a01610408621205" + // internet:Experimental:Vehicular:Auto:hessid 602 "6f0a0e530111112222222229" + // 14:111111:2222222229 603 "dd07506f9a10143a01"; // r2:314 604 605 private static final String IE2 = "ie=000f4578616d706c65204e6574776f726b010882848b960c1218240301012a010432043048606c30140100000fac040100000fac040100000fac0100007f04000000806b091e07010203040506076c027f006f1001531122331020304050010203040506dd05506f9a1000"; 606 607 public static void main(String[] args) { 608 ScanResult scanResult = new ScanResult(); 609 scanResult.SSID = "wing"; 610 scanResult.BSSID = "610408"; 611 NetworkDetail nwkDetail = new NetworkDetail(scanResult.BSSID, IE2, null, 0); 612 System.out.println(nwkDetail); 613 } 614} 615