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