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