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 NetworkDetail(String bssid, ScanResult.InformationElement[] infoElements, List<String> anqpLines, int freq)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.getRoamingConsortiums(); 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 } 297 if (DBG) { 298 Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq 299 + " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: " + mCenterfreq1 300 + (extendedCapabilities.is80211McRTTResponder() ? "Support RTT responder" 301 : "Do not support RTT responder")); 302 Log.v("WifiMode", mSSID 303 + ", WifiMode: " + InformationElementUtil.WifiMode.toString(mWifiMode) 304 + ", Freq: " + mPrimaryFreq 305 + ", mMaxRate: " + mMaxRate 306 + ", VHT: " + String.valueOf(vhtOperation.isValid()) 307 + ", HT: " + String.valueOf( 308 iesFound.contains(ScanResult.InformationElement.EID_HT_OPERATION)) 309 + ", ERP: " + String.valueOf( 310 iesFound.contains(ScanResult.InformationElement.EID_ERP)) 311 + ", SupportedRates: " + supportedRates.toString() 312 + " ExtendedSupportedRates: " + extendedSupportedRates.toString()); 313 } 314 } 315 getAndAdvancePayload(ByteBuffer data, int plLength)316 private static ByteBuffer getAndAdvancePayload(ByteBuffer data, int plLength) { 317 ByteBuffer payload = data.duplicate().order(data.order()); 318 payload.limit(payload.position() + plLength); 319 data.position(data.position() + plLength); 320 return payload; 321 } 322 NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements)323 private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 324 mSSID = base.mSSID; 325 mIsHiddenSsid = base.mIsHiddenSsid; 326 mBSSID = base.mBSSID; 327 mHESSID = base.mHESSID; 328 mStationCount = base.mStationCount; 329 mChannelUtilization = base.mChannelUtilization; 330 mCapacity = base.mCapacity; 331 mAnt = base.mAnt; 332 mInternet = base.mInternet; 333 mHSRelease = base.mHSRelease; 334 mAnqpDomainID = base.mAnqpDomainID; 335 mAnqpOICount = base.mAnqpOICount; 336 mRoamingConsortiums = base.mRoamingConsortiums; 337 mExtendedCapabilities = 338 new InformationElementUtil.ExtendedCapabilities(base.mExtendedCapabilities); 339 mANQPElements = anqpElements; 340 mChannelWidth = base.mChannelWidth; 341 mPrimaryFreq = base.mPrimaryFreq; 342 mCenterfreq0 = base.mCenterfreq0; 343 mCenterfreq1 = base.mCenterfreq1; 344 mDtimInterval = base.mDtimInterval; 345 mWifiMode = base.mWifiMode; 346 mMaxRate = base.mMaxRate; 347 } 348 complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements)349 public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 350 return new NetworkDetail(this, anqpElements); 351 } 352 queriable(List<Constants.ANQPElementType> queryElements)353 public boolean queriable(List<Constants.ANQPElementType> queryElements) { 354 return mAnt != null && 355 (Constants.hasBaseANQPElements(queryElements) || 356 Constants.hasR2Elements(queryElements) && mHSRelease == HSRelease.R2); 357 } 358 has80211uInfo()359 public boolean has80211uInfo() { 360 return mAnt != null || mRoamingConsortiums != null || mHSRelease != null; 361 } 362 hasInterworking()363 public boolean hasInterworking() { 364 return mAnt != null; 365 } 366 getSSID()367 public String getSSID() { 368 return mSSID; 369 } 370 getTrimmedSSID()371 public String getTrimmedSSID() { 372 if (mSSID != null) { 373 for (int n = 0; n < mSSID.length(); n++) { 374 if (mSSID.charAt(n) != 0) { 375 return mSSID; 376 } 377 } 378 } 379 return ""; 380 } 381 getHESSID()382 public long getHESSID() { 383 return mHESSID; 384 } 385 getBSSID()386 public long getBSSID() { 387 return mBSSID; 388 } 389 getStationCount()390 public int getStationCount() { 391 return mStationCount; 392 } 393 getChannelUtilization()394 public int getChannelUtilization() { 395 return mChannelUtilization; 396 } 397 getCapacity()398 public int getCapacity() { 399 return mCapacity; 400 } 401 isInterworking()402 public boolean isInterworking() { 403 return mAnt != null; 404 } 405 getAnt()406 public Ant getAnt() { 407 return mAnt; 408 } 409 isInternet()410 public boolean isInternet() { 411 return mInternet; 412 } 413 getHSRelease()414 public HSRelease getHSRelease() { 415 return mHSRelease; 416 } 417 getAnqpDomainID()418 public int getAnqpDomainID() { 419 return mAnqpDomainID; 420 } 421 getOsuProviders()422 public byte[] getOsuProviders() { 423 if (mANQPElements == null) { 424 return null; 425 } 426 ANQPElement osuProviders = mANQPElements.get(Constants.ANQPElementType.HSOSUProviders); 427 return osuProviders != null ? ((RawByteElement) osuProviders).getPayload() : null; 428 } 429 getAnqpOICount()430 public int getAnqpOICount() { 431 return mAnqpOICount; 432 } 433 getRoamingConsortiums()434 public long[] getRoamingConsortiums() { 435 return mRoamingConsortiums; 436 } 437 getANQPElements()438 public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() { 439 return mANQPElements; 440 } 441 getChannelWidth()442 public int getChannelWidth() { 443 return mChannelWidth; 444 } 445 getCenterfreq0()446 public int getCenterfreq0() { 447 return mCenterfreq0; 448 } 449 getCenterfreq1()450 public int getCenterfreq1() { 451 return mCenterfreq1; 452 } 453 getWifiMode()454 public int getWifiMode() { 455 return mWifiMode; 456 } 457 getDtimInterval()458 public int getDtimInterval() { 459 return mDtimInterval; 460 } 461 is80211McResponderSupport()462 public boolean is80211McResponderSupport() { 463 return mExtendedCapabilities.is80211McRTTResponder(); 464 } 465 isSSID_UTF8()466 public boolean isSSID_UTF8() { 467 return mExtendedCapabilities.isStrictUtf8(); 468 } 469 470 @Override equals(Object thatObject)471 public boolean equals(Object thatObject) { 472 if (this == thatObject) { 473 return true; 474 } 475 if (thatObject == null || getClass() != thatObject.getClass()) { 476 return false; 477 } 478 479 NetworkDetail that = (NetworkDetail)thatObject; 480 481 return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID(); 482 } 483 484 @Override hashCode()485 public int hashCode() { 486 return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID; 487 } 488 489 @Override toString()490 public String toString() { 491 return String.format("NetworkInfo{SSID='%s', HESSID=%x, BSSID=%x, StationCount=%d, " + 492 "ChannelUtilization=%d, Capacity=%d, Ant=%s, Internet=%s, " + 493 "HSRelease=%s, AnqpDomainID=%d, " + 494 "AnqpOICount=%d, RoamingConsortiums=%s}", 495 mSSID, mHESSID, mBSSID, mStationCount, 496 mChannelUtilization, mCapacity, mAnt, mInternet, 497 mHSRelease, mAnqpDomainID, 498 mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums)); 499 } 500 toKeyString()501 public String toKeyString() { 502 return mHESSID != 0 ? 503 String.format("'%s':%012x (%012x)", mSSID, mBSSID, mHESSID) : 504 String.format("'%s':%012x", mSSID, mBSSID); 505 } 506 getBSSIDString()507 public String getBSSIDString() { 508 return toMACString(mBSSID); 509 } 510 511 /** 512 * Evaluates the ScanResult this NetworkDetail is built from 513 * returns true if built from a Beacon Frame 514 * returns false if built from a Probe Response 515 */ isBeaconFrame()516 public boolean isBeaconFrame() { 517 // Beacon frames have a 'Traffic Indication Map' Information element 518 // Probe Responses do not. This is indicated by a DTIM period > 0 519 return mDtimInterval > 0; 520 } 521 522 /** 523 * Evaluates the ScanResult this NetworkDetail is built from 524 * returns true if built from a hidden Beacon Frame 525 * returns false if not hidden or not a Beacon 526 */ isHiddenBeaconFrame()527 public boolean isHiddenBeaconFrame() { 528 // Hidden networks are not 80211 standard, but it is common for a hidden network beacon 529 // frame to either send zero-value bytes as the SSID, or to send no bytes at all. 530 return isBeaconFrame() && mIsHiddenSsid; 531 } 532 toMACString(long mac)533 public static String toMACString(long mac) { 534 StringBuilder sb = new StringBuilder(); 535 boolean first = true; 536 for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) { 537 if (first) { 538 first = false; 539 } else { 540 sb.append(':'); 541 } 542 sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK)); 543 } 544 return sb.toString(); 545 } 546 } 547