1 package com.android.server.wifi.hotspot2; 2 3 import static com.android.server.wifi.anqp.Constants.BYTES_IN_EUI48; 4 import static com.android.server.wifi.anqp.Constants.BYTE_MASK; 5 6 import android.net.wifi.ScanResult; 7 import android.util.Log; 8 9 import com.android.server.wifi.anqp.ANQPElement; 10 import com.android.server.wifi.anqp.Constants; 11 import com.android.server.wifi.anqp.RawByteElement; 12 import com.android.server.wifi.anqp.VenueNameElement; 13 import com.android.server.wifi.util.InformationElementUtil; 14 15 import java.nio.BufferUnderflowException; 16 import java.nio.ByteBuffer; 17 import java.nio.CharBuffer; 18 import java.nio.charset.CharacterCodingException; 19 import java.nio.charset.CharsetDecoder; 20 import java.nio.charset.StandardCharsets; 21 import java.util.ArrayList; 22 import java.util.List; 23 import java.util.Map; 24 25 public class NetworkDetail { 26 27 //turn off when SHIP 28 private static final boolean DBG = true; 29 private static final boolean VDBG = false; 30 31 private static final String TAG = "NetworkDetail:"; 32 33 public enum Ant { 34 Private, 35 PrivateWithGuest, 36 ChargeablePublic, 37 FreePublic, 38 Personal, 39 EmergencyOnly, 40 Resvd6, 41 Resvd7, 42 Resvd8, 43 Resvd9, 44 Resvd10, 45 Resvd11, 46 Resvd12, 47 Resvd13, 48 TestOrExperimental, 49 Wildcard 50 } 51 52 public enum HSRelease { 53 R1, 54 R2, 55 Unknown 56 } 57 58 // General identifiers: 59 private final String mSSID; 60 private final long mHESSID; 61 private final long mBSSID; 62 // True if the SSID is potentially from a hidden network 63 private final boolean mIsHiddenSsid; 64 65 // BSS Load element: 66 private final int mStationCount; 67 private final int mChannelUtilization; 68 private final int mCapacity; 69 70 //channel detailed information 71 /* 72 * 0 -- 20 MHz 73 * 1 -- 40 MHz 74 * 2 -- 80 MHz 75 * 3 -- 160 MHz 76 * 4 -- 80 + 80 MHz 77 */ 78 private final int mChannelWidth; 79 private final int mPrimaryFreq; 80 private final int mCenterfreq0; 81 private final int mCenterfreq1; 82 83 /* 84 * 802.11 Standard (calculated from Capabilities and Supported Rates) 85 * 0 -- Unknown 86 * 1 -- 802.11a 87 * 2 -- 802.11b 88 * 3 -- 802.11g 89 * 4 -- 802.11n 90 * 7 -- 802.11ac 91 */ 92 private final int mWifiMode; 93 private final int mMaxRate; 94 95 /* 96 * From Interworking element: 97 * mAnt non null indicates the presence of Interworking, i.e. 802.11u 98 * mVenueGroup and mVenueType may be null if not present in the Interworking element. 99 */ 100 private final Ant mAnt; 101 private final boolean mInternet; 102 private final VenueNameElement.VenueGroup mVenueGroup; 103 private final VenueNameElement.VenueType mVenueType; 104 105 /* 106 * From HS20 Indication element: 107 * mHSRelease is null only if the HS20 Indication element was not present. 108 * mAnqpDomainID is set to -1 if not present in the element. 109 */ 110 private final HSRelease mHSRelease; 111 private final int mAnqpDomainID; 112 113 /* 114 * From beacon: 115 * mAnqpOICount is how many additional OIs are available through ANQP. 116 * mRoamingConsortiums is either null, if the element was not present, or is an array of 117 * 1, 2 or 3 longs in which the roaming consortium values occupy the LSBs. 118 */ 119 private final int mAnqpOICount; 120 private final long[] mRoamingConsortiums; 121 private int mDtimInterval = -1; 122 123 private final InformationElementUtil.ExtendedCapabilities mExtendedCapabilities; 124 125 private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements; 126 127 public NetworkDetail(String bssid, ScanResult.InformationElement[] infoElements, 128 List<String> anqpLines, int freq) { 129 if (infoElements == null) { 130 throw new IllegalArgumentException("Null information elements"); 131 } 132 133 mBSSID = Utils.parseMac(bssid); 134 135 String ssid = null; 136 boolean isHiddenSsid = false; 137 byte[] ssidOctets = null; 138 139 InformationElementUtil.BssLoad bssLoad = new InformationElementUtil.BssLoad(); 140 141 InformationElementUtil.Interworking interworking = 142 new InformationElementUtil.Interworking(); 143 144 InformationElementUtil.RoamingConsortium roamingConsortium = 145 new InformationElementUtil.RoamingConsortium(); 146 147 InformationElementUtil.Vsa vsa = new InformationElementUtil.Vsa(); 148 149 InformationElementUtil.HtOperation htOperation = new InformationElementUtil.HtOperation(); 150 InformationElementUtil.VhtOperation vhtOperation = 151 new InformationElementUtil.VhtOperation(); 152 153 InformationElementUtil.ExtendedCapabilities extendedCapabilities = 154 new InformationElementUtil.ExtendedCapabilities(); 155 156 InformationElementUtil.TrafficIndicationMap trafficIndicationMap = 157 new InformationElementUtil.TrafficIndicationMap(); 158 159 InformationElementUtil.SupportedRates supportedRates = 160 new InformationElementUtil.SupportedRates(); 161 InformationElementUtil.SupportedRates extendedSupportedRates = 162 new InformationElementUtil.SupportedRates(); 163 164 RuntimeException exception = null; 165 166 ArrayList<Integer> iesFound = new ArrayList<Integer>(); 167 try { 168 for (ScanResult.InformationElement ie : infoElements) { 169 iesFound.add(ie.id); 170 switch (ie.id) { 171 case ScanResult.InformationElement.EID_SSID: 172 ssidOctets = ie.bytes; 173 break; 174 case ScanResult.InformationElement.EID_BSS_LOAD: 175 bssLoad.from(ie); 176 break; 177 case ScanResult.InformationElement.EID_HT_OPERATION: 178 htOperation.from(ie); 179 break; 180 case ScanResult.InformationElement.EID_VHT_OPERATION: 181 vhtOperation.from(ie); 182 break; 183 case ScanResult.InformationElement.EID_INTERWORKING: 184 interworking.from(ie); 185 break; 186 case ScanResult.InformationElement.EID_ROAMING_CONSORTIUM: 187 roamingConsortium.from(ie); 188 break; 189 case ScanResult.InformationElement.EID_VSA: 190 vsa.from(ie); 191 break; 192 case ScanResult.InformationElement.EID_EXTENDED_CAPS: 193 extendedCapabilities.from(ie); 194 break; 195 case ScanResult.InformationElement.EID_TIM: 196 trafficIndicationMap.from(ie); 197 break; 198 case ScanResult.InformationElement.EID_SUPPORTED_RATES: 199 supportedRates.from(ie); 200 break; 201 case ScanResult.InformationElement.EID_EXTENDED_SUPPORTED_RATES: 202 extendedSupportedRates.from(ie); 203 break; 204 default: 205 break; 206 } 207 } 208 } 209 catch (IllegalArgumentException | BufferUnderflowException | ArrayIndexOutOfBoundsException e) { 210 Log.d(Utils.hs2LogTag(getClass()), "Caught " + e); 211 if (ssidOctets == null) { 212 throw new IllegalArgumentException("Malformed IE string (no SSID)", e); 213 } 214 exception = e; 215 } 216 if (ssidOctets != null) { 217 /* 218 * Strict use of the "UTF-8 SSID" bit by APs appears to be spotty at best even if the 219 * encoding truly is in UTF-8. An unconditional attempt to decode the SSID as UTF-8 is 220 * therefore always made with a fall back to 8859-1 under normal circumstances. 221 * If, however, a previous exception was detected and the UTF-8 bit is set, failure to 222 * decode the SSID will be used as an indication that the whole frame is malformed and 223 * an exception will be triggered. 224 */ 225 CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); 226 try { 227 CharBuffer decoded = decoder.decode(ByteBuffer.wrap(ssidOctets)); 228 ssid = decoded.toString(); 229 } 230 catch (CharacterCodingException cce) { 231 ssid = null; 232 } 233 234 if (ssid == null) { 235 if (extendedCapabilities.isStrictUtf8() && exception != null) { 236 throw new IllegalArgumentException("Failed to decode SSID in dubious IE string"); 237 } 238 else { 239 ssid = new String(ssidOctets, StandardCharsets.ISO_8859_1); 240 } 241 } 242 isHiddenSsid = true; 243 for (byte byteVal : ssidOctets) { 244 if (byteVal != 0) { 245 isHiddenSsid = false; 246 break; 247 } 248 } 249 } 250 251 mSSID = ssid; 252 mHESSID = interworking.hessid; 253 mIsHiddenSsid = isHiddenSsid; 254 mStationCount = bssLoad.stationCount; 255 mChannelUtilization = bssLoad.channelUtilization; 256 mCapacity = bssLoad.capacity; 257 mAnt = interworking.ant; 258 mInternet = interworking.internet; 259 mVenueGroup = interworking.venueGroup; 260 mVenueType = interworking.venueType; 261 mHSRelease = vsa.hsRelease; 262 mAnqpDomainID = vsa.anqpDomainID; 263 mAnqpOICount = roamingConsortium.anqpOICount; 264 mRoamingConsortiums = roamingConsortium.roamingConsortiums; 265 mExtendedCapabilities = extendedCapabilities; 266 mANQPElements = SupplicantBridge.parseANQPLines(anqpLines); 267 //set up channel info 268 mPrimaryFreq = freq; 269 270 if (vhtOperation.isValid()) { 271 // 80 or 160 MHz 272 mChannelWidth = vhtOperation.getChannelWidth(); 273 mCenterfreq0 = vhtOperation.getCenterFreq0(); 274 mCenterfreq1 = vhtOperation.getCenterFreq1(); 275 } else { 276 mChannelWidth = htOperation.getChannelWidth(); 277 mCenterfreq0 = htOperation.getCenterFreq0(mPrimaryFreq); 278 mCenterfreq1 = 0; 279 } 280 281 // If trafficIndicationMap is not valid, mDtimPeriod will be negative 282 if (trafficIndicationMap.isValid()) { 283 mDtimInterval = trafficIndicationMap.mDtimPeriod; 284 } 285 286 int maxRateA = 0; 287 int maxRateB = 0; 288 // If we got some Extended supported rates, consider them, if not default to 0 289 if (extendedSupportedRates.isValid()) { 290 // rates are sorted from smallest to largest in InformationElement 291 maxRateB = extendedSupportedRates.mRates.get(extendedSupportedRates.mRates.size() - 1); 292 } 293 // Only process the determination logic if we got a 'SupportedRates' 294 if (supportedRates.isValid()) { 295 maxRateA = supportedRates.mRates.get(supportedRates.mRates.size() - 1); 296 mMaxRate = maxRateA > maxRateB ? maxRateA : maxRateB; 297 mWifiMode = InformationElementUtil.WifiMode.determineMode(mPrimaryFreq, mMaxRate, 298 vhtOperation.isValid(), 299 iesFound.contains(ScanResult.InformationElement.EID_HT_OPERATION), 300 iesFound.contains(ScanResult.InformationElement.EID_ERP)); 301 } else { 302 mWifiMode = 0; 303 mMaxRate = 0; 304 Log.w("WifiMode", mSSID + ", Invalid SupportedRates!!!"); 305 } 306 if (VDBG) { 307 Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq 308 + " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: " + mCenterfreq1 309 + (extendedCapabilities.is80211McRTTResponder ? "Support RTT reponder" 310 : "Do not support RTT responder")); 311 Log.v("WifiMode", mSSID 312 + ", WifiMode: " + InformationElementUtil.WifiMode.toString(mWifiMode) 313 + ", Freq: " + mPrimaryFreq 314 + ", mMaxRate: " + mMaxRate 315 + ", VHT: " + String.valueOf(vhtOperation.isValid()) 316 + ", HT: " + String.valueOf( 317 iesFound.contains(ScanResult.InformationElement.EID_HT_OPERATION)) 318 + ", ERP: " + String.valueOf( 319 iesFound.contains(ScanResult.InformationElement.EID_ERP)) 320 + ", SupportedRates: " + supportedRates.toString() 321 + " ExtendedSupportedRates: " + extendedSupportedRates.toString()); 322 } 323 } 324 325 private static ByteBuffer getAndAdvancePayload(ByteBuffer data, int plLength) { 326 ByteBuffer payload = data.duplicate().order(data.order()); 327 payload.limit(payload.position() + plLength); 328 data.position(data.position() + plLength); 329 return payload; 330 } 331 332 private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 333 mSSID = base.mSSID; 334 mIsHiddenSsid = base.mIsHiddenSsid; 335 mBSSID = base.mBSSID; 336 mHESSID = base.mHESSID; 337 mStationCount = base.mStationCount; 338 mChannelUtilization = base.mChannelUtilization; 339 mCapacity = base.mCapacity; 340 mAnt = base.mAnt; 341 mInternet = base.mInternet; 342 mVenueGroup = base.mVenueGroup; 343 mVenueType = base.mVenueType; 344 mHSRelease = base.mHSRelease; 345 mAnqpDomainID = base.mAnqpDomainID; 346 mAnqpOICount = base.mAnqpOICount; 347 mRoamingConsortiums = base.mRoamingConsortiums; 348 mExtendedCapabilities = 349 new InformationElementUtil.ExtendedCapabilities(base.mExtendedCapabilities); 350 mANQPElements = anqpElements; 351 mChannelWidth = base.mChannelWidth; 352 mPrimaryFreq = base.mPrimaryFreq; 353 mCenterfreq0 = base.mCenterfreq0; 354 mCenterfreq1 = base.mCenterfreq1; 355 mDtimInterval = base.mDtimInterval; 356 mWifiMode = base.mWifiMode; 357 mMaxRate = base.mMaxRate; 358 } 359 360 public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 361 return new NetworkDetail(this, anqpElements); 362 } 363 364 public boolean queriable(List<Constants.ANQPElementType> queryElements) { 365 return mAnt != null && 366 (Constants.hasBaseANQPElements(queryElements) || 367 Constants.hasR2Elements(queryElements) && mHSRelease == HSRelease.R2); 368 } 369 370 public boolean has80211uInfo() { 371 return mAnt != null || mRoamingConsortiums != null || mHSRelease != null; 372 } 373 374 public boolean hasInterworking() { 375 return mAnt != null; 376 } 377 378 public String getSSID() { 379 return mSSID; 380 } 381 382 public String getTrimmedSSID() { 383 for (int n = 0; n < mSSID.length(); n++) { 384 if (mSSID.charAt(n) != 0) { 385 return mSSID; 386 } 387 } 388 return ""; 389 } 390 391 public long getHESSID() { 392 return mHESSID; 393 } 394 395 public long getBSSID() { 396 return mBSSID; 397 } 398 399 public int getStationCount() { 400 return mStationCount; 401 } 402 403 public int getChannelUtilization() { 404 return mChannelUtilization; 405 } 406 407 public int getCapacity() { 408 return mCapacity; 409 } 410 411 public boolean isInterworking() { 412 return mAnt != null; 413 } 414 415 public Ant getAnt() { 416 return mAnt; 417 } 418 419 public boolean isInternet() { 420 return mInternet; 421 } 422 423 public VenueNameElement.VenueGroup getVenueGroup() { 424 return mVenueGroup; 425 } 426 427 public VenueNameElement.VenueType getVenueType() { 428 return mVenueType; 429 } 430 431 public HSRelease getHSRelease() { 432 return mHSRelease; 433 } 434 435 public int getAnqpDomainID() { 436 return mAnqpDomainID; 437 } 438 439 public byte[] getOsuProviders() { 440 if (mANQPElements == null) { 441 return null; 442 } 443 ANQPElement osuProviders = mANQPElements.get(Constants.ANQPElementType.HSOSUProviders); 444 return osuProviders != null ? ((RawByteElement) osuProviders).getPayload() : null; 445 } 446 447 public int getAnqpOICount() { 448 return mAnqpOICount; 449 } 450 451 public long[] getRoamingConsortiums() { 452 return mRoamingConsortiums; 453 } 454 455 public Long getExtendedCapabilities() { 456 return mExtendedCapabilities.extendedCapabilities; 457 } 458 459 public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() { 460 return mANQPElements; 461 } 462 463 public int getChannelWidth() { 464 return mChannelWidth; 465 } 466 467 public int getCenterfreq0() { 468 return mCenterfreq0; 469 } 470 471 public int getCenterfreq1() { 472 return mCenterfreq1; 473 } 474 475 public int getWifiMode() { 476 return mWifiMode; 477 } 478 479 public int getDtimInterval() { 480 return mDtimInterval; 481 } 482 483 public boolean is80211McResponderSupport() { 484 return mExtendedCapabilities.is80211McRTTResponder; 485 } 486 487 public boolean isSSID_UTF8() { 488 return mExtendedCapabilities.isStrictUtf8(); 489 } 490 491 @Override 492 public boolean equals(Object thatObject) { 493 if (this == thatObject) { 494 return true; 495 } 496 if (thatObject == null || getClass() != thatObject.getClass()) { 497 return false; 498 } 499 500 NetworkDetail that = (NetworkDetail)thatObject; 501 502 return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID(); 503 } 504 505 @Override 506 public int hashCode() { 507 return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID; 508 } 509 510 @Override 511 public String toString() { 512 return String.format("NetworkInfo{SSID='%s', HESSID=%x, BSSID=%x, StationCount=%d, " + 513 "ChannelUtilization=%d, Capacity=%d, Ant=%s, Internet=%s, " + 514 "VenueGroup=%s, VenueType=%s, HSRelease=%s, AnqpDomainID=%d, " + 515 "AnqpOICount=%d, RoamingConsortiums=%s}", 516 mSSID, mHESSID, mBSSID, mStationCount, 517 mChannelUtilization, mCapacity, mAnt, mInternet, 518 mVenueGroup, mVenueType, mHSRelease, mAnqpDomainID, 519 mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums)); 520 } 521 522 public String toKeyString() { 523 return mHESSID != 0 ? 524 String.format("'%s':%012x (%012x)", mSSID, mBSSID, mHESSID) : 525 String.format("'%s':%012x", mSSID, mBSSID); 526 } 527 528 public String getBSSIDString() { 529 return toMACString(mBSSID); 530 } 531 532 /** 533 * Evaluates the ScanResult this NetworkDetail is built from 534 * returns true if built from a Beacon Frame 535 * returns false if built from a Probe Response 536 */ 537 public boolean isBeaconFrame() { 538 // Beacon frames have a 'Traffic Indication Map' Information element 539 // Probe Responses do not. This is indicated by a DTIM period > 0 540 return mDtimInterval > 0; 541 } 542 543 /** 544 * Evaluates the ScanResult this NetworkDetail is built from 545 * returns true if built from a hidden Beacon Frame 546 * returns false if not hidden or not a Beacon 547 */ 548 public boolean isHiddenBeaconFrame() { 549 // Hidden networks are not 80211 standard, but it is common for a hidden network beacon 550 // frame to either send zero-value bytes as the SSID, or to send no bytes at all. 551 return isBeaconFrame() && mIsHiddenSsid; 552 } 553 554 public static String toMACString(long mac) { 555 StringBuilder sb = new StringBuilder(); 556 boolean first = true; 557 for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) { 558 if (first) { 559 first = false; 560 } else { 561 sb.append(':'); 562 } 563 sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK)); 564 } 565 return sb.toString(); 566 } 567 } 568