NetworkDetail.java revision 207af567531a0813a9c8651d28bc4bda0ea22088
1package com.android.server.wifi.hotspot2; 2 3import android.net.wifi.ScanResult; 4import android.util.Log; 5 6import com.android.server.wifi.anqp.ANQPElement; 7import com.android.server.wifi.anqp.Constants; 8import com.android.server.wifi.anqp.VenueNameElement; 9 10import java.net.ProtocolException; 11import java.nio.ByteBuffer; 12import java.nio.ByteOrder; 13import java.nio.charset.Charset; 14import java.nio.charset.StandardCharsets; 15import java.util.List; 16import java.util.Map; 17 18import static com.android.server.wifi.anqp.Constants.BYTES_IN_EUI48; 19import static com.android.server.wifi.anqp.Constants.BYTE_MASK; 20import static com.android.server.wifi.anqp.Constants.getInteger; 21 22public class NetworkDetail { 23 24 private static final int EID_SSID = 0; 25 private static final int EID_BSSLoad = 11; 26 private static final int EID_HT_OPERATION = 61; 27 private static final int EID_VHT_OPERATION = 192; 28 private static final int EID_Interworking = 107; 29 private static final int EID_RoamingConsortium = 111; 30 private static final int EID_ExtendedCaps = 127; 31 private static final int EID_VSA = 221; 32 private static final int ANQP_DOMID_BIT = 0x04; 33 private static final int RTT_RESP_ENABLE_BIT = 70; 34 35 private static final long SSID_UTF8_BIT = 0x0001000000000000L; 36 //turn off when SHIP 37 private static final boolean DBG = true; 38 private static final String TAG = "NetworkDetail"; 39 40 public enum Ant { 41 Private, 42 PrivateWithGuest, 43 ChargeablePublic, 44 FreePublic, 45 Personal, 46 EmergencyOnly, 47 Resvd6, 48 Resvd7, 49 Resvd8, 50 Resvd9, 51 Resvd10, 52 Resvd11, 53 Resvd12, 54 Resvd13, 55 TestOrExperimental, 56 Wildcard 57 } 58 59 public enum HSRelease { 60 R1, 61 R2, 62 Unknown 63 } 64 65 // General identifiers: 66 private final String mSSID; 67 private final long mHESSID; 68 private final long mBSSID; 69 70 // BSS Load element: 71 private final int mStationCount; 72 private final int mChannelUtilization; 73 private final int mCapacity; 74 75 //channel detailed information 76 /* 77 * 0 -- 20 MHz 78 * 1 -- 40 MHz 79 * 2 -- 80 MHz 80 * 3 -- 160 MHz 81 * 4 -- 80 + 80 MHz 82 */ 83 private final int mChannelWidth; 84 private final int mPrimaryFreq; 85 private final int mCenterfreq0; 86 private final int mCenterfreq1; 87 private final boolean m80211McRTTResponder; 88 /* 89 * From Interworking element: 90 * mAnt non null indicates the presence of Interworking, i.e. 802.11u 91 * mVenueGroup and mVenueType may be null if not present in the Interworking element. 92 */ 93 private final Ant mAnt; 94 private final boolean mInternet; 95 private final VenueNameElement.VenueGroup mVenueGroup; 96 private final VenueNameElement.VenueType mVenueType; 97 98 /* 99 * From HS20 Indication element: 100 * mHSRelease is null only if the HS20 Indication element was not present. 101 * mAnqpDomainID is set to -1 if not present in the element. 102 */ 103 private final HSRelease mHSRelease; 104 private final int mAnqpDomainID; 105 106 /* 107 * From beacon: 108 * mAnqpOICount is how many additional OIs are available through ANQP. 109 * mRoamingConsortiums is either null, if the element was not present, or is an array of 110 * 1, 2 or 3 longs in which the roaming consortium values occupy the LSBs. 111 */ 112 private final int mAnqpOICount; 113 private final long[] mRoamingConsortiums; 114 115 private final Long mExtendedCapabilities; 116 117 private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements; 118 119 public NetworkDetail(String bssid, String infoElements, List<String> anqpLines, int freq) { 120 121 if (infoElements == null) { 122 throw new IllegalArgumentException("Null information element string"); 123 } 124 int separator = infoElements.indexOf('='); 125 if (separator<0) { 126 throw new IllegalArgumentException("No element separator"); 127 } 128 129 mBSSID = parseMac(bssid); 130 131 ByteBuffer data = ByteBuffer.wrap(Utils.hexToBytes(infoElements.substring(separator + 1))) 132 .order(ByteOrder.LITTLE_ENDIAN); 133 134 String ssid = null; 135 byte[] ssidOctets = null; 136 int stationCount = 0; 137 int channelUtilization = 0; 138 int capacity = 0; 139 140 Ant ant = null; 141 boolean internet = false; 142 VenueNameElement.VenueGroup venueGroup = null; 143 VenueNameElement.VenueType venueType = null; 144 long hessid = 0L; 145 146 int anqpOICount = 0; 147 long[] roamingConsortiums = null; 148 149 HSRelease hsRelease = null; 150 int anqpDomainID = 0; // No domain ID treated the same as a 0; unique info per AP. 151 152 Long extendedCapabilities = null; 153 154 int secondChanelOffset = 0; 155 int channelMode = 0; 156 int centerFreqIndex1 = 0; 157 int centerFreqIndex2 = 0; 158 boolean RTTResponder = false; 159 160 while (data.hasRemaining()) { 161 int eid = data.get() & Constants.BYTE_MASK; 162 int elementLength = data.get() & Constants.BYTE_MASK; 163 164 if (elementLength > data.remaining()) { 165 Log.e(TAG, "EID " + eid + " too long: " + elementLength + "/" + data.remaining() + 166 " data=" + infoElements.substring(separator + 1)); 167 break; 168 } 169 170 switch (eid) { 171 case EID_SSID: 172 ssidOctets = new byte[elementLength]; 173 data.get(ssidOctets); 174 break; 175 case EID_BSSLoad: 176 if (elementLength != 5) { 177 throw new IllegalArgumentException("BSS Load element length is not 5: " + 178 elementLength); 179 } 180 stationCount = data.getShort() & Constants.SHORT_MASK; 181 channelUtilization = data.get() & Constants.BYTE_MASK; 182 capacity = data.getShort() & Constants.SHORT_MASK; 183 break; 184 case EID_HT_OPERATION: 185 int primary_channel = data.get(); 186 byte tmp = data.get(); 187 secondChanelOffset = tmp & 0x3; 188 data.position(data.position() + elementLength - 2); 189 break; 190 case EID_VHT_OPERATION: 191 channelMode = data.get() & Constants.BYTE_MASK; 192 centerFreqIndex1 = data.get() & Constants.BYTE_MASK; 193 centerFreqIndex2 = data.get() & Constants.BYTE_MASK; 194 data.position(data.position() + elementLength - 3); 195 break; 196 case EID_Interworking: 197 int anOptions = data.get() & Constants.BYTE_MASK; 198 ant = Ant.values()[anOptions&0x0f]; 199 internet = ( anOptions & 0x10 ) != 0; 200 // Len 1 none, 3 venue-info, 7 HESSID, 9 venue-info & HESSID 201 if (elementLength == 3 || elementLength == 9) { 202 try { 203 ByteBuffer vinfo = data.duplicate(); 204 vinfo.limit(vinfo.position() + 2); 205 VenueNameElement vne = 206 new VenueNameElement(Constants.ANQPElementType.ANQPVenueName, 207 vinfo); 208 venueGroup = vne.getGroup(); 209 venueType = vne.getType(); 210 data.getShort(); 211 } 212 catch ( ProtocolException pe ) { 213 /*Cannot happen*/ 214 } 215 } 216 if (elementLength == 7 || elementLength == 9) { 217 hessid = getInteger(data, ByteOrder.BIG_ENDIAN, 6); 218 } 219 break; 220 case EID_RoamingConsortium: 221 anqpOICount = data.get() & Constants.BYTE_MASK; 222 223 int oi12Length = data.get() & Constants.BYTE_MASK; 224 int oi1Length = oi12Length & Constants.NIBBLE_MASK; 225 int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK; 226 int oi3Length = elementLength - 2 - oi1Length - oi2Length; 227 int oiCount = 0; 228 if (oi1Length > 0) { 229 oiCount++; 230 if (oi2Length > 0) { 231 oiCount++; 232 if (oi3Length > 0) { 233 oiCount++; 234 } 235 } 236 } 237 roamingConsortiums = new long[oiCount]; 238 if (oi1Length > 0 ) { 239 roamingConsortiums[0] = getInteger(data, ByteOrder.BIG_ENDIAN, oi1Length); 240 } 241 if (oi2Length > 0 ) { 242 roamingConsortiums[1] = getInteger(data, ByteOrder.BIG_ENDIAN, oi2Length); 243 } 244 if (oi3Length > 0 ) { 245 roamingConsortiums[2] = getInteger(data, ByteOrder.BIG_ENDIAN, oi3Length); 246 } 247 break; 248 case EID_VSA: 249 if (elementLength < 5) { 250 data.position(data.position() + elementLength); 251 } 252 else if (data.getInt() != Constants.HS20_FRAME_PREFIX) { 253 data.position(data.position() + elementLength - Constants.BYTES_IN_INT); 254 } 255 else { 256 int hsConf = data.get() & Constants.BYTE_MASK; 257 switch ((hsConf>>4) & Constants.NIBBLE_MASK) { 258 case 0: 259 hsRelease = HSRelease.R1; 260 break; 261 case 1: 262 hsRelease = HSRelease.R2; 263 break; 264 default: 265 hsRelease = HSRelease.Unknown; 266 break; 267 } 268 if ((hsConf & ANQP_DOMID_BIT) != 0) { 269 anqpDomainID = data.getShort() & Constants.SHORT_MASK; 270 } 271 } 272 break; 273 case EID_ExtendedCaps: 274 int position = data.position(); 275 extendedCapabilities = 276- Constants.getInteger(data, ByteOrder.LITTLE_ENDIAN, elementLength); 277 278 //recover 279 data.position(position); 280 int index = RTT_RESP_ENABLE_BIT / 8; 281 byte offset = RTT_RESP_ENABLE_BIT % 8; 282 283 if(elementLength < index + 1) { 284 RTTResponder = false; 285 data.position(data.position() + elementLength); 286 break; 287 } 288 289 data.position(data.position() + index); 290 elementLength -= index; 291 292 if ((data.get() & (0x1 << offset)) != 0) { 293 RTTResponder = true; 294 } else { 295 RTTResponder = false; 296 } 297 data.position(data.position()+ elementLength - 1); 298 break; 299 default: 300 data.position(data.position()+elementLength); 301 break; 302 } 303 } 304 305 if (ssidOctets != null) { 306 Charset encoding; 307 if (extendedCapabilities != null && (extendedCapabilities & SSID_UTF8_BIT) != 0) { 308 encoding = StandardCharsets.UTF_8; 309 } 310 else { 311 encoding = StandardCharsets.ISO_8859_1; 312 } 313 ssid = new String(ssidOctets, encoding); 314 } 315 316 mSSID = ssid; 317 mHESSID = hessid; 318 mStationCount = stationCount; 319 mChannelUtilization = channelUtilization; 320 mCapacity = capacity; 321 mAnt = ant; 322 mInternet = internet; 323 mVenueGroup = venueGroup; 324 mVenueType = venueType; 325 mHSRelease = hsRelease; 326 mAnqpDomainID = anqpDomainID; 327 mAnqpOICount = anqpOICount; 328 mRoamingConsortiums = roamingConsortiums; 329 mExtendedCapabilities = extendedCapabilities; 330 mANQPElements = SupplicantBridge.parseANQPLines(anqpLines); 331 //set up channel info 332 mPrimaryFreq = freq; 333 334 if (channelMode != 0) { 335 // 80 or 160 MHz 336 mChannelWidth = channelMode + 1; 337 mCenterfreq0 = (centerFreqIndex1 - 36) * 5 + 5180; 338 if(channelMode > 1) { //160MHz 339 mCenterfreq1 = (centerFreqIndex2 - 36) * 5 + 5180; 340 } else { 341 mCenterfreq1 = 0; 342 } 343 } else { 344 //20 or 40 MHz 345 if (secondChanelOffset != 0) {//40MHz 346 mChannelWidth = 1; 347 if (secondChanelOffset == 1) { 348 mCenterfreq0 = mPrimaryFreq + 20; 349 } else if (secondChanelOffset == 3) { 350 mCenterfreq0 = mPrimaryFreq - 20; 351 } else { 352 mCenterfreq0 = 0; 353 Log.e(TAG,"Error on secondChanelOffset"); 354 } 355 } else { 356 mCenterfreq0 = 0; 357 mChannelWidth = 0; 358 } 359 mCenterfreq1 = 0; 360 } 361 m80211McRTTResponder = RTTResponder; 362 } 363 364 private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 365 mSSID = base.mSSID; 366 mBSSID = base.mBSSID; 367 mHESSID = base.mHESSID; 368 mStationCount = base.mStationCount; 369 mChannelUtilization = base.mChannelUtilization; 370 mCapacity = base.mCapacity; 371 mAnt = base.mAnt; 372 mInternet = base.mInternet; 373 mVenueGroup = base.mVenueGroup; 374 mVenueType = base.mVenueType; 375 mHSRelease = base.mHSRelease; 376 mAnqpDomainID = base.mAnqpDomainID; 377 mAnqpOICount = base.mAnqpOICount; 378 mRoamingConsortiums = base.mRoamingConsortiums; 379 mExtendedCapabilities = base.mExtendedCapabilities; 380 mANQPElements = anqpElements; 381 mChannelWidth = base.mChannelWidth; 382 mPrimaryFreq = base.mPrimaryFreq; 383 mCenterfreq0 = base.mCenterfreq0; 384 mCenterfreq1 = base.mCenterfreq1; 385 m80211McRTTResponder = base.m80211McRTTResponder; 386 } 387 388 public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 389 return new NetworkDetail(this, anqpElements); 390 } 391 392 private static long parseMac(String s) { 393 394 long mac = 0; 395 int count = 0; 396 for (int n = 0; n < s.length(); n++) { 397 int nibble = Utils.fromHex(s.charAt(n), true); 398 if (nibble >= 0) { 399 mac = (mac << 4) | nibble; 400 count++; 401 } 402 } 403 if (count < 12 || (count&1) == 1) { 404 throw new IllegalArgumentException("Bad MAC address: '" + s + "'"); 405 } 406 return mac; 407 } 408 409 public boolean has80211uInfo() { 410 return mAnt != null || mRoamingConsortiums != null || mHSRelease != null; 411 } 412 413 public boolean hasInterworking() { 414 return mAnt != null; 415 } 416 417 public String getSSID() { 418 return mSSID; 419 } 420 421 public long getHESSID() { 422 return mHESSID; 423 } 424 425 public long getBSSID() { 426 return mBSSID; 427 } 428 429 public int getStationCount() { 430 return mStationCount; 431 } 432 433 public int getChannelUtilization() { 434 return mChannelUtilization; 435 } 436 437 public int getCapacity() { 438 return mCapacity; 439 } 440 441 public boolean isInterworking() { 442 return mAnt != null; 443 } 444 445 public Ant getAnt() { 446 return mAnt; 447 } 448 449 public boolean isInternet() { 450 return mInternet; 451 } 452 453 public VenueNameElement.VenueGroup getVenueGroup() { 454 return mVenueGroup; 455 } 456 457 public VenueNameElement.VenueType getVenueType() { 458 return mVenueType; 459 } 460 461 public HSRelease getHSRelease() { 462 return mHSRelease; 463 } 464 465 public int getAnqpDomainID() { 466 return mAnqpDomainID; 467 } 468 469 public int getAnqpOICount() { 470 return mAnqpOICount; 471 } 472 473 public long[] getRoamingConsortiums() { 474 return mRoamingConsortiums; 475 } 476 477 public Long getExtendedCapabilities() { 478 return mExtendedCapabilities; 479 } 480 481 public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() { 482 return mANQPElements; 483 } 484 485 public int getChannelWidth() { 486 return mChannelWidth; 487 } 488 489 public int getCenterfreq0() { 490 return mCenterfreq0; 491 } 492 493 public int getCenterfreq1() { 494 return mCenterfreq1; 495 } 496 497 public boolean is80211McResponderSupport() { 498 return m80211McRTTResponder; 499 } 500 501 public boolean isSSID_UTF8() { 502 return mExtendedCapabilities != null && (mExtendedCapabilities & SSID_UTF8_BIT) != 0; 503 } 504 505 @Override 506 public boolean equals(Object thatObject) { 507 if (this == thatObject) { 508 return true; 509 } 510 if (thatObject == null || getClass() != thatObject.getClass()) { 511 return false; 512 } 513 514 NetworkDetail that = (NetworkDetail)thatObject; 515 516 return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID(); 517 } 518 519 @Override 520 public int hashCode() { 521 return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID; 522 } 523 524 @Override 525 public String toString() { 526 return String.format("NetworkInfo{mSSID='%s', mHESSID=%x, mBSSID=%x, mStationCount=%d, " + 527 "mChannelUtilization=%d, mCapacity=%d, mAnt=%s, mInternet=%s, " + 528 "mVenueGroup=%s, mVenueType=%s, mHSRelease=%s, mAnqpDomainID=%d, " + 529 "mAnqpOICount=%d, mRoamingConsortiums=%s}", 530 mSSID, mHESSID, mBSSID, mStationCount, 531 mChannelUtilization, mCapacity, mAnt, mInternet, 532 mVenueGroup, mVenueType, mHSRelease, mAnqpDomainID, 533 mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums)); 534 } 535 536 public String toKeyString() { 537 return String.format("'%s':%s", mSSID, getBSSIDString()); 538 } 539 540 public String getBSSIDString() { 541 return toMACString(mBSSID); 542 } 543 544 public static String toMACString(long mac) { 545 StringBuilder sb = new StringBuilder(); 546 boolean first = true; 547 for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) { 548 if (first) { 549 first = false; 550 } else { 551 sb.append(':'); 552 } 553 sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK)); 554 } 555 return sb.toString(); 556 } 557 558 private static final String IE = "ie=" + 559 "000477696e67" + // SSID wing 560 "0b052a00cf611e" + // BSS Load 42:207:7777 561 "6b091e0a01610408621205" + // internet:Experimental:Vehicular:Auto:hessid 562 "6f0a0e530111112222222229" + // 14:111111:2222222229 563 "dd07506f9a10143a01"; // r2:314 564 565 private static final String IE2 = "ie=000f4578616d706c65204e6574776f726b010882848b960c1218240301012a010432043048606c30140100000fac040100000fac040100000fac0100007f04000000806b091e07010203040506076c027f006f1001531122331020304050010203040506dd05506f9a1000"; 566 567 public static void main(String[] args) { 568 ScanResult scanResult = new ScanResult(); 569 scanResult.SSID = "wing"; 570 scanResult.BSSID = "610408"; 571 NetworkDetail nwkDetail = new NetworkDetail(scanResult.BSSID, IE2, null, 0); 572 System.out.println(nwkDetail); 573 } 574} 575