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