NetworkDetail.java revision f9f7c4ceed78d380a78c2d79a2af3d0cf473e065
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 if (DBG) Log.e(TAG,"IE Length is %d" + data.remaining()); 167 RuntimeException exception = null; 168 169 try { 170 while (data.hasRemaining()) { 171 int eid = data.get() & Constants.BYTE_MASK; 172 int elementLength = data.get() & Constants.BYTE_MASK; 173 174 if (elementLength > data.remaining()) { 175 throw new IllegalArgumentException("Element length " + elementLength + 176 " exceeds payload length " + data.remaining() + 177 " @ " + data.position()); 178 } 179 180 ByteBuffer element; 181 182 switch (eid) { 183 case EID_SSID: 184 ssidOctets = new byte[elementLength]; 185 data.get(ssidOctets); 186 break; 187 case EID_BSSLoad: 188 if (elementLength != 5) { 189 throw new IllegalArgumentException("BSS Load element length is not 5: " + 190 elementLength); 191 } 192 stationCount = data.getShort() & Constants.SHORT_MASK; 193 channelUtilization = data.get() & Constants.BYTE_MASK; 194 capacity = data.getShort() & Constants.SHORT_MASK; 195 break; 196 case EID_HT_OPERATION: 197 element = getAndAdvancePayload(data, elementLength); 198 int primary_channel = element.get(); 199 secondChanelOffset = element.get() & 0x3; 200 break; 201 case EID_VHT_OPERATION: 202 element = getAndAdvancePayload(data, elementLength); 203 channelMode = element.get() & Constants.BYTE_MASK; 204 centerFreqIndex1 = element.get() & Constants.BYTE_MASK; 205 centerFreqIndex2 = element.get() & Constants.BYTE_MASK; 206 break; 207 case EID_Interworking: 208 int anOptions = data.get() & Constants.BYTE_MASK; 209 ant = Ant.values()[anOptions & 0x0f]; 210 internet = (anOptions & 0x10) != 0; 211 // Len 1 none, 3 venue-info, 7 HESSID, 9 venue-info & HESSID 212 if (elementLength == 3 || elementLength == 9) { 213 try { 214 ByteBuffer vinfo = data.duplicate(); 215 vinfo.limit(vinfo.position() + 2); 216 VenueNameElement vne = 217 new VenueNameElement(Constants.ANQPElementType.ANQPVenueName, 218 vinfo); 219 venueGroup = vne.getGroup(); 220 venueType = vne.getType(); 221 data.getShort(); 222 } catch (ProtocolException pe) { 223 /*Cannot happen*/ 224 } 225 } else if (elementLength != 1 && elementLength != 7) { 226 throw new IllegalArgumentException("Bad Interworking element length: " + 227 elementLength); 228 } 229 if (elementLength == 7 || elementLength == 9) { 230 hessid = getInteger(data, ByteOrder.BIG_ENDIAN, 6); 231 } 232 break; 233 case EID_RoamingConsortium: 234 anqpOICount = data.get() & Constants.BYTE_MASK; 235 236 int oi12Length = data.get() & Constants.BYTE_MASK; 237 int oi1Length = oi12Length & Constants.NIBBLE_MASK; 238 int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK; 239 int oi3Length = elementLength - 2 - oi1Length - oi2Length; 240 int oiCount = 0; 241 if (oi1Length > 0) { 242 oiCount++; 243 if (oi2Length > 0) { 244 oiCount++; 245 if (oi3Length > 0) { 246 oiCount++; 247 } 248 } 249 } 250 roamingConsortiums = new long[oiCount]; 251 if (oi1Length > 0) { 252 roamingConsortiums[0] = 253 getInteger(data, ByteOrder.BIG_ENDIAN, oi1Length); 254 } 255 if (oi2Length > 0) { 256 roamingConsortiums[1] = 257 getInteger(data, ByteOrder.BIG_ENDIAN, oi2Length); 258 } 259 if (oi3Length > 0) { 260 roamingConsortiums[2] = 261 getInteger(data, ByteOrder.BIG_ENDIAN, oi3Length); 262 } 263 break; 264 case EID_VSA: 265 element = getAndAdvancePayload(data, elementLength); 266 if (elementLength >= 5 && element.getInt() == Constants.HS20_FRAME_PREFIX) { 267 int hsConf = element.get() & Constants.BYTE_MASK; 268 switch ((hsConf >> 4) & Constants.NIBBLE_MASK) { 269 case 0: 270 hsRelease = HSRelease.R1; 271 break; 272 case 1: 273 hsRelease = HSRelease.R2; 274 break; 275 default: 276 hsRelease = HSRelease.Unknown; 277 break; 278 } 279 if ((hsConf & ANQP_DOMID_BIT) != 0) { 280 if (elementLength < 7) { 281 throw new IllegalArgumentException( 282 "HS20 indication element too short: " + elementLength); 283 } 284 anqpDomainID = element.getShort() & Constants.SHORT_MASK; 285 } 286 } 287 break; 288 case EID_ExtendedCaps: 289 element = data.duplicate(); 290 extendedCapabilities = 291 Constants.getInteger(data, ByteOrder.LITTLE_ENDIAN, elementLength); 292 293 int index = RTT_RESP_ENABLE_BIT / 8; 294 byte offset = RTT_RESP_ENABLE_BIT % 8; 295 296 if (elementLength < index + 1) { 297 RTTResponder = false; 298 element.position(element.position() + elementLength); 299 break; 300 } 301 302 element.position(element.position() + index); 303 304 RTTResponder = (element.get() & (0x1 << offset)) != 0; 305 break; 306 default: 307 data.position(data.position() + elementLength); 308 break; 309 } 310 } 311 } 312 catch (IllegalArgumentException iae) { 313 Log.d("HS2J", "Caught " + iae); 314 if (ssidOctets == null) { 315 throw iae; 316 } 317 exception = iae; 318 } 319 320 if (ssidOctets != null) { 321 Charset encoding; 322 if (extendedCapabilities != null && (extendedCapabilities & SSID_UTF8_BIT) != 0) { 323 encoding = StandardCharsets.UTF_8; 324 } 325 else { 326 encoding = StandardCharsets.ISO_8859_1; 327 } 328 329 if (exception == null) { 330 ssid = new String(ssidOctets, encoding); 331 } 332 else { 333 // Apply strict checking if there were previous errors: 334 CharsetDecoder decoder = encoding.newDecoder(); 335 try { 336 CharBuffer decoded = decoder.decode(ByteBuffer.wrap(ssidOctets)); 337 ssid = decoded.toString(); 338 } 339 catch (CharacterCodingException cce) { 340 throw exception; 341 } 342 } 343 } 344 345 mSSID = ssid; 346 mHESSID = hessid; 347 mStationCount = stationCount; 348 mChannelUtilization = channelUtilization; 349 mCapacity = capacity; 350 mAnt = ant; 351 mInternet = internet; 352 mVenueGroup = venueGroup; 353 mVenueType = venueType; 354 mHSRelease = hsRelease; 355 mAnqpDomainID = anqpDomainID; 356 mAnqpOICount = anqpOICount; 357 mRoamingConsortiums = roamingConsortiums; 358 mExtendedCapabilities = extendedCapabilities; 359 mANQPElements = SupplicantBridge.parseANQPLines(anqpLines); 360 //set up channel info 361 mPrimaryFreq = freq; 362 363 if (channelMode != 0) { 364 // 80 or 160 MHz 365 mChannelWidth = channelMode + 1; 366 mCenterfreq0 = (centerFreqIndex1 - 36) * 5 + 5180; 367 if(channelMode > 1) { //160MHz 368 mCenterfreq1 = (centerFreqIndex2 - 36) * 5 + 5180; 369 } else { 370 mCenterfreq1 = 0; 371 } 372 } else { 373 //20 or 40 MHz 374 if (secondChanelOffset != 0) {//40MHz 375 mChannelWidth = 1; 376 if (secondChanelOffset == 1) { 377 mCenterfreq0 = mPrimaryFreq + 20; 378 } else if (secondChanelOffset == 3) { 379 mCenterfreq0 = mPrimaryFreq - 20; 380 } else { 381 mCenterfreq0 = 0; 382 Log.e(TAG,"Error on secondChanelOffset"); 383 } 384 } else { 385 mCenterfreq0 = 0; 386 mChannelWidth = 0; 387 } 388 mCenterfreq1 = 0; 389 } 390 m80211McRTTResponder = RTTResponder; 391 if (VDBG) { 392 Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq + 393 " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: " + mCenterfreq1 + 394 (m80211McRTTResponder ? "Support RTT reponder" : "Do not support RTT responder")); 395 } 396 } 397 398 private static ByteBuffer getAndAdvancePayload(ByteBuffer data, int plLength) { 399 ByteBuffer payload = data.duplicate().order(data.order()); 400 payload.limit(payload.position() + plLength); 401 data.position(data.position() + plLength); 402 return payload; 403 } 404 405 private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 406 mSSID = base.mSSID; 407 mBSSID = base.mBSSID; 408 mHESSID = base.mHESSID; 409 mStationCount = base.mStationCount; 410 mChannelUtilization = base.mChannelUtilization; 411 mCapacity = base.mCapacity; 412 mAnt = base.mAnt; 413 mInternet = base.mInternet; 414 mVenueGroup = base.mVenueGroup; 415 mVenueType = base.mVenueType; 416 mHSRelease = base.mHSRelease; 417 mAnqpDomainID = base.mAnqpDomainID; 418 mAnqpOICount = base.mAnqpOICount; 419 mRoamingConsortiums = base.mRoamingConsortiums; 420 mExtendedCapabilities = base.mExtendedCapabilities; 421 mANQPElements = anqpElements; 422 mChannelWidth = base.mChannelWidth; 423 mPrimaryFreq = base.mPrimaryFreq; 424 mCenterfreq0 = base.mCenterfreq0; 425 mCenterfreq1 = base.mCenterfreq1; 426 m80211McRTTResponder = base.m80211McRTTResponder; 427 } 428 429 public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 430 return new NetworkDetail(this, anqpElements); 431 } 432 433 private static long parseMac(String s) { 434 435 long mac = 0; 436 int count = 0; 437 for (int n = 0; n < s.length(); n++) { 438 int nibble = Utils.fromHex(s.charAt(n), true); 439 if (nibble >= 0) { 440 mac = (mac << 4) | nibble; 441 count++; 442 } 443 } 444 if (count < 12 || (count&1) == 1) { 445 throw new IllegalArgumentException("Bad MAC address: '" + s + "'"); 446 } 447 return mac; 448 } 449 450 public boolean has80211uInfo() { 451 return mAnt != null || mRoamingConsortiums != null || mHSRelease != null; 452 } 453 454 public boolean hasInterworking() { 455 return mAnt != null; 456 } 457 458 public String getSSID() { 459 return mSSID; 460 } 461 462 public long getHESSID() { 463 return mHESSID; 464 } 465 466 public long getBSSID() { 467 return mBSSID; 468 } 469 470 public int getStationCount() { 471 return mStationCount; 472 } 473 474 public int getChannelUtilization() { 475 return mChannelUtilization; 476 } 477 478 public int getCapacity() { 479 return mCapacity; 480 } 481 482 public boolean isInterworking() { 483 return mAnt != null; 484 } 485 486 public Ant getAnt() { 487 return mAnt; 488 } 489 490 public boolean isInternet() { 491 return mInternet; 492 } 493 494 public VenueNameElement.VenueGroup getVenueGroup() { 495 return mVenueGroup; 496 } 497 498 public VenueNameElement.VenueType getVenueType() { 499 return mVenueType; 500 } 501 502 public HSRelease getHSRelease() { 503 return mHSRelease; 504 } 505 506 public int getAnqpDomainID() { 507 return mAnqpDomainID; 508 } 509 510 public int getAnqpOICount() { 511 return mAnqpOICount; 512 } 513 514 public long[] getRoamingConsortiums() { 515 return mRoamingConsortiums; 516 } 517 518 public Long getExtendedCapabilities() { 519 return mExtendedCapabilities; 520 } 521 522 public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() { 523 return mANQPElements; 524 } 525 526 public int getChannelWidth() { 527 return mChannelWidth; 528 } 529 530 public int getCenterfreq0() { 531 return mCenterfreq0; 532 } 533 534 public int getCenterfreq1() { 535 return mCenterfreq1; 536 } 537 538 public boolean is80211McResponderSupport() { 539 return m80211McRTTResponder; 540 } 541 542 public boolean isSSID_UTF8() { 543 return mExtendedCapabilities != null && (mExtendedCapabilities & SSID_UTF8_BIT) != 0; 544 } 545 546 @Override 547 public boolean equals(Object thatObject) { 548 if (this == thatObject) { 549 return true; 550 } 551 if (thatObject == null || getClass() != thatObject.getClass()) { 552 return false; 553 } 554 555 NetworkDetail that = (NetworkDetail)thatObject; 556 557 return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID(); 558 } 559 560 @Override 561 public int hashCode() { 562 return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID; 563 } 564 565 @Override 566 public String toString() { 567 return String.format("NetworkInfo{mSSID='%s', mHESSID=%x, mBSSID=%x, mStationCount=%d, " + 568 "mChannelUtilization=%d, mCapacity=%d, mAnt=%s, mInternet=%s, " + 569 "mVenueGroup=%s, mVenueType=%s, mHSRelease=%s, mAnqpDomainID=%d, " + 570 "mAnqpOICount=%d, mRoamingConsortiums=%s}", 571 mSSID, mHESSID, mBSSID, mStationCount, 572 mChannelUtilization, mCapacity, mAnt, mInternet, 573 mVenueGroup, mVenueType, mHSRelease, mAnqpDomainID, 574 mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums)); 575 } 576 577 public String toKeyString() { 578 return String.format("'%s':%s", mSSID, getBSSIDString()); 579 } 580 581 public String getBSSIDString() { 582 return toMACString(mBSSID); 583 } 584 585 public static String toMACString(long mac) { 586 StringBuilder sb = new StringBuilder(); 587 boolean first = true; 588 for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) { 589 if (first) { 590 first = false; 591 } else { 592 sb.append(':'); 593 } 594 sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK)); 595 } 596 return sb.toString(); 597 } 598 599 private static final String IE = "ie=" + 600 "000477696e67" + // SSID wing 601 "0b052a00cf611e" + // BSS Load 42:207:7777 602 "6b091e0a01610408621205" + // internet:Experimental:Vehicular:Auto:hessid 603 "6f0a0e530111112222222229" + // 14:111111:2222222229 604 "dd07506f9a10143a01"; // r2:314 605 606 private static final String IE2 = "ie=000f4578616d706c65204e6574776f726b010882848b960c1218240301012a010432043048606c30140100000fac040100000fac040100000fac0100007f04000000806b091e07010203040506076c027f006f1001531122331020304050010203040506dd05506f9a1000"; 607 608 public static void main(String[] args) { 609 ScanResult scanResult = new ScanResult(); 610 scanResult.SSID = "wing"; 611 scanResult.BSSID = "610408"; 612 NetworkDetail nwkDetail = new NetworkDetail(scanResult.BSSID, IE2, null, 0); 613 System.out.println(nwkDetail); 614 } 615} 616