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