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