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