1 package com.android.server.wifi.hotspot2; 2 3 import static com.android.server.wifi.hotspot2.anqp.Constants.BYTES_IN_EUI48; 4 import static com.android.server.wifi.hotspot2.anqp.Constants.BYTE_MASK; 5 6 import android.net.wifi.ScanResult; 7 import android.util.Log; 8 9 import com.android.server.wifi.hotspot2.anqp.ANQPElement; 10 import com.android.server.wifi.hotspot2.anqp.Constants; 11 import com.android.server.wifi.hotspot2.anqp.RawByteElement; 12 import com.android.server.wifi.util.InformationElementUtil; 13 14 import java.nio.BufferUnderflowException; 15 import java.nio.ByteBuffer; 16 import java.nio.CharBuffer; 17 import java.nio.charset.CharacterCodingException; 18 import java.nio.charset.CharsetDecoder; 19 import java.nio.charset.StandardCharsets; 20 import java.util.ArrayList; 21 import java.util.List; 22 import java.util.Map; 23 24 public 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 = null; 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 responder" 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 if (mSSID != null) { 374 for (int n = 0; n < mSSID.length(); n++) { 375 if (mSSID.charAt(n) != 0) { 376 return mSSID; 377 } 378 } 379 } 380 return ""; 381 } 382 383 public long getHESSID() { 384 return mHESSID; 385 } 386 387 public long getBSSID() { 388 return mBSSID; 389 } 390 391 public int getStationCount() { 392 return mStationCount; 393 } 394 395 public int getChannelUtilization() { 396 return mChannelUtilization; 397 } 398 399 public int getCapacity() { 400 return mCapacity; 401 } 402 403 public boolean isInterworking() { 404 return mAnt != null; 405 } 406 407 public Ant getAnt() { 408 return mAnt; 409 } 410 411 public boolean isInternet() { 412 return mInternet; 413 } 414 415 public HSRelease getHSRelease() { 416 return mHSRelease; 417 } 418 419 public int getAnqpDomainID() { 420 return mAnqpDomainID; 421 } 422 423 public byte[] getOsuProviders() { 424 if (mANQPElements == null) { 425 return null; 426 } 427 ANQPElement osuProviders = mANQPElements.get(Constants.ANQPElementType.HSOSUProviders); 428 return osuProviders != null ? ((RawByteElement) osuProviders).getPayload() : null; 429 } 430 431 public int getAnqpOICount() { 432 return mAnqpOICount; 433 } 434 435 public long[] getRoamingConsortiums() { 436 return mRoamingConsortiums; 437 } 438 439 public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() { 440 return mANQPElements; 441 } 442 443 public int getChannelWidth() { 444 return mChannelWidth; 445 } 446 447 public int getCenterfreq0() { 448 return mCenterfreq0; 449 } 450 451 public int getCenterfreq1() { 452 return mCenterfreq1; 453 } 454 455 public int getWifiMode() { 456 return mWifiMode; 457 } 458 459 public int getDtimInterval() { 460 return mDtimInterval; 461 } 462 463 public boolean is80211McResponderSupport() { 464 return mExtendedCapabilities.is80211McRTTResponder(); 465 } 466 467 public boolean isSSID_UTF8() { 468 return mExtendedCapabilities.isStrictUtf8(); 469 } 470 471 @Override 472 public boolean equals(Object thatObject) { 473 if (this == thatObject) { 474 return true; 475 } 476 if (thatObject == null || getClass() != thatObject.getClass()) { 477 return false; 478 } 479 480 NetworkDetail that = (NetworkDetail)thatObject; 481 482 return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID(); 483 } 484 485 @Override 486 public int hashCode() { 487 return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID; 488 } 489 490 @Override 491 public String toString() { 492 return String.format("NetworkInfo{SSID='%s', HESSID=%x, BSSID=%x, StationCount=%d, " + 493 "ChannelUtilization=%d, Capacity=%d, Ant=%s, Internet=%s, " + 494 "HSRelease=%s, AnqpDomainID=%d, " + 495 "AnqpOICount=%d, RoamingConsortiums=%s}", 496 mSSID, mHESSID, mBSSID, mStationCount, 497 mChannelUtilization, mCapacity, mAnt, mInternet, 498 mHSRelease, mAnqpDomainID, 499 mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums)); 500 } 501 502 public String toKeyString() { 503 return mHESSID != 0 ? 504 String.format("'%s':%012x (%012x)", mSSID, mBSSID, mHESSID) : 505 String.format("'%s':%012x", mSSID, mBSSID); 506 } 507 508 public String getBSSIDString() { 509 return toMACString(mBSSID); 510 } 511 512 /** 513 * Evaluates the ScanResult this NetworkDetail is built from 514 * returns true if built from a Beacon Frame 515 * returns false if built from a Probe Response 516 */ 517 public boolean isBeaconFrame() { 518 // Beacon frames have a 'Traffic Indication Map' Information element 519 // Probe Responses do not. This is indicated by a DTIM period > 0 520 return mDtimInterval > 0; 521 } 522 523 /** 524 * Evaluates the ScanResult this NetworkDetail is built from 525 * returns true if built from a hidden Beacon Frame 526 * returns false if not hidden or not a Beacon 527 */ 528 public boolean isHiddenBeaconFrame() { 529 // Hidden networks are not 80211 standard, but it is common for a hidden network beacon 530 // frame to either send zero-value bytes as the SSID, or to send no bytes at all. 531 return isBeaconFrame() && mIsHiddenSsid; 532 } 533 534 public static String toMACString(long mac) { 535 StringBuilder sb = new StringBuilder(); 536 boolean first = true; 537 for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) { 538 if (first) { 539 first = false; 540 } else { 541 sb.append(':'); 542 } 543 sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK)); 544 } 545 return sb.toString(); 546 } 547 } 548