NetworkDetail.java revision ac5ef7ed57cc198486d4c411f39ca89192ed6395
1package com.android.server.wifi.hotspot2; 2 3import android.net.wifi.ScanResult; 4 5import com.android.server.wifi.anqp.ANQPElement; 6import com.android.server.wifi.anqp.Constants; 7import com.android.server.wifi.anqp.VenueNameElement; 8 9import java.net.ProtocolException; 10import java.nio.ByteBuffer; 11import java.nio.ByteOrder; 12import java.nio.charset.Charset; 13import java.nio.charset.StandardCharsets; 14import java.util.List; 15import java.util.Map; 16 17import static com.android.server.wifi.anqp.Constants.BYTES_IN_EUI48; 18import static com.android.server.wifi.anqp.Constants.BYTE_MASK; 19import static com.android.server.wifi.anqp.Constants.getInteger; 20import android.util.Log; 21public class NetworkDetail { 22 23 private static final int EID_SSID = 0; 24 private static final int EID_BSSLoad = 11; 25 private static final int EID_HT_OPERATION = 61; 26 private static final int EID_VHT_OPERATION = 192; 27 private static final int EID_Interworking = 107; 28 private static final int EID_RoamingConsortium = 111; 29 private static final int EID_ExtendedCaps = 127; 30 private static final int EID_VSA = 221; 31 private static final int ANQP_DOMID_BIT = 0x04; 32 private static final int RTT_RESP_ENABLE_BIT = 70; 33 34 private static final long SSID_UTF8_BIT = 0x0001000000000000L; 35 36 // This should only be enabled locally for debugging purposes. 37 private static final boolean DBG = false; 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 if (DBG) Log.e(TAG,"IE Length is %d" + data.remaining()); 161 while (data.hasRemaining()) { 162 int eid = data.get() & Constants.BYTE_MASK; 163 int elementLength = data.get() & Constants.BYTE_MASK; 164 if (DBG) Log.e(TAG,"eid is:" + eid + " elementLength:" + elementLength); 165 if (elementLength > data.remaining()) { 166 throw new IllegalArgumentException("Length out of bounds: " + elementLength +" ," + data.remaining()); 167 } 168 169 switch (eid) { 170 case EID_SSID: 171 ssidOctets = new byte[elementLength]; 172 data.get(ssidOctets); 173 break; 174 case EID_BSSLoad: 175 if (elementLength != 5) { 176 throw new IllegalArgumentException("BSS Load element length is not 5: " + 177 elementLength); 178 } 179 stationCount = data.getShort() & Constants.SHORT_MASK; 180 channelUtilization = data.get() & Constants.BYTE_MASK; 181 capacity = data.getShort() & Constants.SHORT_MASK; 182 break; 183 case EID_HT_OPERATION: 184 int primary_channel = data.get(); 185 byte tmp = data.get(); 186 secondChanelOffset = tmp & 0x3; 187 data.position(data.position() + elementLength - 2); 188 if(DBG) { 189 Log.d(TAG, "primary_channel: " + primary_channel + " secondChanelOffset:" 190 + tmp); 191 } 192 break; 193 case EID_VHT_OPERATION: 194 channelMode = data.get() & Constants.BYTE_MASK; 195 centerFreqIndex1 = data.get() & Constants.BYTE_MASK; 196 centerFreqIndex2 = data.get() & Constants.BYTE_MASK; 197 data.position(data.position() + elementLength - 3); 198 if(DBG) { 199 Log.d(TAG, "channelMode :" + channelMode + " centerFreqIndex1: " + 200 centerFreqIndex1 + " centerFreqIndex2: " + centerFreqIndex1); 201 } 202 break; 203 case EID_Interworking: 204 int anOptions = data.get() & Constants.BYTE_MASK; 205 ant = Ant.values()[anOptions&0x0f]; 206 internet = ( anOptions & 0x10 ) != 0; 207 // Len 1 none, 3 venue-info, 7 HESSID, 9 venue-info & HESSID 208 if (elementLength == 3 || elementLength == 9) { 209 try { 210 ByteBuffer vinfo = data.duplicate(); 211 vinfo.limit(vinfo.position() + 2); 212 VenueNameElement vne = 213 new VenueNameElement(Constants.ANQPElementType.ANQPVenueName, 214 vinfo); 215 venueGroup = vne.getGroup(); 216 venueType = vne.getType(); 217 data.getShort(); 218 } 219 catch ( ProtocolException pe ) { 220 /*Cannot happen*/ 221 } 222 } 223 if (elementLength == 7 || elementLength == 9) { 224 hessid = getInteger(data, ByteOrder.BIG_ENDIAN, 6); 225 } 226 break; 227 case EID_RoamingConsortium: 228 anqpOICount = data.get() & Constants.BYTE_MASK; 229 230 int oi12Length = data.get() & Constants.BYTE_MASK; 231 int oi1Length = oi12Length & Constants.NIBBLE_MASK; 232 int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK; 233 int oi3Length = elementLength - 2 - oi1Length - oi2Length; 234 int oiCount = 0; 235 if (oi1Length > 0) { 236 oiCount++; 237 if (oi2Length > 0) { 238 oiCount++; 239 if (oi3Length > 0) { 240 oiCount++; 241 } 242 } 243 } 244 roamingConsortiums = new long[oiCount]; 245 if (oi1Length > 0 ) { 246 roamingConsortiums[0] = getInteger(data, ByteOrder.BIG_ENDIAN, oi1Length); 247 } 248 if (oi2Length > 0 ) { 249 roamingConsortiums[1] = getInteger(data, ByteOrder.BIG_ENDIAN, oi2Length); 250 } 251 if (oi3Length > 0 ) { 252 roamingConsortiums[2] = getInteger(data, ByteOrder.BIG_ENDIAN, oi3Length); 253 } 254 break; 255 case EID_VSA: 256 if (elementLength < 5) { 257 data.position(data.position() + elementLength); 258 } 259 else if (data.getInt() != Constants.HS20_FRAME_PREFIX) { 260 data.position(data.position() + elementLength - Constants.BYTES_IN_INT); 261 } 262 else { 263 int hsConf = data.get() & Constants.BYTE_MASK; 264 switch ((hsConf>>4) & Constants.NIBBLE_MASK) { 265 case 0: 266 hsRelease = HSRelease.R1; 267 break; 268 case 1: 269 hsRelease = HSRelease.R2; 270 break; 271 default: 272 hsRelease = HSRelease.Unknown; 273 break; 274 } 275 if ((hsConf & ANQP_DOMID_BIT) != 0) { 276 anqpDomainID = data.getShort() & Constants.SHORT_MASK; 277 } 278 } 279 break; 280 case EID_ExtendedCaps: 281 int position = data.position(); 282 extendedCapabilities = 283- Constants.getInteger(data, ByteOrder.LITTLE_ENDIAN, elementLength); 284 285 //recover 286 data.position(position); 287 int index = RTT_RESP_ENABLE_BIT / 8; 288 byte offset = RTT_RESP_ENABLE_BIT % 8; 289 290 if(elementLength < index + 1) { 291 RTTResponder = false; 292 data.position(data.position() + elementLength); 293 break; 294 } 295 296 data.position(data.position() + index); 297 elementLength -= index; 298 299 if ((data.get() & (0x1 << offset)) != 0) { 300 RTTResponder = true; 301 } else { 302 RTTResponder = false; 303 } 304 data.position(data.position()+ elementLength - 1); 305 break; 306 default: 307 data.position(data.position()+elementLength); 308 break; 309 } 310 } 311 312 if (ssidOctets != null) { 313 Charset encoding; 314 if (extendedCapabilities != null && (extendedCapabilities & SSID_UTF8_BIT) != 0) { 315 encoding = StandardCharsets.UTF_8; 316 } 317 else { 318 encoding = StandardCharsets.ISO_8859_1; 319 } 320 ssid = new String(ssidOctets, encoding); 321 } 322 323 mSSID = ssid; 324 mHESSID = hessid; 325 mStationCount = stationCount; 326 mChannelUtilization = channelUtilization; 327 mCapacity = capacity; 328 mAnt = ant; 329 mInternet = internet; 330 mVenueGroup = venueGroup; 331 mVenueType = venueType; 332 mHSRelease = hsRelease; 333 mAnqpDomainID = anqpDomainID; 334 mAnqpOICount = anqpOICount; 335 mRoamingConsortiums = roamingConsortiums; 336 mExtendedCapabilities = extendedCapabilities; 337 mANQPElements = SupplicantBridge.parseANQPLines(anqpLines); 338 //set up channel info 339 mPrimaryFreq = freq; 340 341 if (channelMode != 0) { 342 // 80 or 160 MHz 343 mChannelWidth = channelMode + 1; 344 mCenterfreq0 = (centerFreqIndex1 - 36) * 5 + 5180; 345 if(channelMode > 1) { //160MHz 346 mCenterfreq1 = (centerFreqIndex2 - 36) * 5 + 5180; 347 } else { 348 mCenterfreq1 = 0; 349 } 350 } else { 351 //20 or 40 MHz 352 if (secondChanelOffset != 0) {//40MHz 353 mChannelWidth = 1; 354 if (secondChanelOffset == 1) { 355 mCenterfreq0 = mPrimaryFreq + 20; 356 } else if (secondChanelOffset == 3) { 357 mCenterfreq0 = mPrimaryFreq - 20; 358 } else { 359 mCenterfreq0 = 0; 360 Log.e(TAG,"Error on secondChanelOffset"); 361 } 362 } else { 363 mCenterfreq0 = 0; 364 mChannelWidth = 0; 365 } 366 mCenterfreq1 = 0; 367 } 368 m80211McRTTResponder = RTTResponder; 369 if(DBG) { 370 Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq + 371 " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: " + mCenterfreq1 + 372 (m80211McRTTResponder ? "Support RTT reponder" : "Do not support RTT responder")); 373 } 374 } 375 376 private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 377 mSSID = base.mSSID; 378 mBSSID = base.mBSSID; 379 mHESSID = base.mHESSID; 380 mStationCount = base.mStationCount; 381 mChannelUtilization = base.mChannelUtilization; 382 mCapacity = base.mCapacity; 383 mAnt = base.mAnt; 384 mInternet = base.mInternet; 385 mVenueGroup = base.mVenueGroup; 386 mVenueType = base.mVenueType; 387 mHSRelease = base.mHSRelease; 388 mAnqpDomainID = base.mAnqpDomainID; 389 mAnqpOICount = base.mAnqpOICount; 390 mRoamingConsortiums = base.mRoamingConsortiums; 391 mExtendedCapabilities = base.mExtendedCapabilities; 392 mANQPElements = anqpElements; 393 mChannelWidth = base.mChannelWidth; 394 mPrimaryFreq = base.mPrimaryFreq; 395 mCenterfreq0 = base.mCenterfreq0; 396 mCenterfreq1 = base.mCenterfreq1; 397 m80211McRTTResponder = base.m80211McRTTResponder; 398 } 399 400 public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 401 return new NetworkDetail(this, anqpElements); 402 } 403 404 private static long parseMac(String s) { 405 406 long mac = 0; 407 int count = 0; 408 for (int n = 0; n < s.length(); n++) { 409 int nibble = Utils.fromHex(s.charAt(n), true); 410 if (nibble >= 0) { 411 mac = (mac << 4) | nibble; 412 count++; 413 } 414 } 415 if (count < 12 || (count&1) == 1) { 416 throw new IllegalArgumentException("Bad MAC address: '" + s + "'"); 417 } 418 return mac; 419 } 420 421 public boolean has80211uInfo() { 422 return mAnt != null || mRoamingConsortiums != null || mHSRelease != null; 423 } 424 425 public boolean hasInterworking() { 426 return mAnt != null; 427 } 428 429 public String getSSID() { 430 return mSSID; 431 } 432 433 public long getHESSID() { 434 return mHESSID; 435 } 436 437 public long getBSSID() { 438 return mBSSID; 439 } 440 441 public int getStationCount() { 442 return mStationCount; 443 } 444 445 public int getChannelUtilization() { 446 return mChannelUtilization; 447 } 448 449 public int getCapacity() { 450 return mCapacity; 451 } 452 453 public boolean isInterworking() { 454 return mAnt != null; 455 } 456 457 public Ant getAnt() { 458 return mAnt; 459 } 460 461 public boolean isInternet() { 462 return mInternet; 463 } 464 465 public VenueNameElement.VenueGroup getVenueGroup() { 466 return mVenueGroup; 467 } 468 469 public VenueNameElement.VenueType getVenueType() { 470 return mVenueType; 471 } 472 473 public HSRelease getHSRelease() { 474 return mHSRelease; 475 } 476 477 public int getAnqpDomainID() { 478 return mAnqpDomainID; 479 } 480 481 public int getAnqpOICount() { 482 return mAnqpOICount; 483 } 484 485 public long[] getRoamingConsortiums() { 486 return mRoamingConsortiums; 487 } 488 489 public Long getExtendedCapabilities() { 490 return mExtendedCapabilities; 491 } 492 493 public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() { 494 return mANQPElements; 495 } 496 497 public int getChannelWidth() { 498 return mChannelWidth; 499 } 500 501 public int getCenterfreq0() { 502 return mCenterfreq0; 503 } 504 505 public int getCenterfreq1() { 506 return mCenterfreq1; 507 } 508 509 public boolean is80211McResponderSupport() { 510 return m80211McRTTResponder; 511 } 512 513 public boolean isSSID_UTF8() { 514 return mExtendedCapabilities != null && (mExtendedCapabilities & SSID_UTF8_BIT) != 0; 515 } 516 517 @Override 518 public boolean equals(Object thatObject) { 519 if (this == thatObject) { 520 return true; 521 } 522 if (thatObject == null || getClass() != thatObject.getClass()) { 523 return false; 524 } 525 526 NetworkDetail that = (NetworkDetail)thatObject; 527 528 return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID(); 529 } 530 531 @Override 532 public int hashCode() { 533 return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID; 534 } 535 536 @Override 537 public String toString() { 538 return String.format("NetworkInfo{mSSID='%s', mHESSID=%x, mBSSID=%x, mStationCount=%d, " + 539 "mChannelUtilization=%d, mCapacity=%d, mAnt=%s, mInternet=%s, " + 540 "mVenueGroup=%s, mVenueType=%s, mHSRelease=%s, mAnqpDomainID=%d, " + 541 "mAnqpOICount=%d, mRoamingConsortiums=%s}", 542 mSSID, mHESSID, mBSSID, mStationCount, 543 mChannelUtilization, mCapacity, mAnt, mInternet, 544 mVenueGroup, mVenueType, mHSRelease, mAnqpDomainID, 545 mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums)); 546 } 547 548 public String toKeyString() { 549 return String.format("'%s':%s", mSSID, getBSSIDString()); 550 } 551 552 public String getBSSIDString() { 553 return toMACString(mBSSID); 554 } 555 556 public static String toMACString(long mac) { 557 StringBuilder sb = new StringBuilder(); 558 boolean first = true; 559 for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) { 560 if (first) { 561 first = false; 562 } else { 563 sb.append(':'); 564 } 565 sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK)); 566 } 567 return sb.toString(); 568 } 569 570 private static final String IE = "ie=" + 571 "000477696e67" + // SSID wing 572 "0b052a00cf611e" + // BSS Load 42:207:7777 573 "6b091e0a01610408621205" + // internet:Experimental:Vehicular:Auto:hessid 574 "6f0a0e530111112222222229" + // 14:111111:2222222229 575 "dd07506f9a10143a01"; // r2:314 576 577 private static final String IE2 = "ie=000f4578616d706c65204e6574776f726b010882848b960c1218240301012a010432043048606c30140100000fac040100000fac040100000fac0100007f04000000806b091e07010203040506076c027f006f1001531122331020304050010203040506dd05506f9a1000"; 578 579 public static void main(String[] args) { 580 ScanResult scanResult = new ScanResult(); 581 scanResult.SSID = "wing"; 582 scanResult.BSSID = "610408"; 583 NetworkDetail nwkDetail = new NetworkDetail(scanResult.BSSID, IE2, null, 0); 584 System.out.println(nwkDetail); 585 } 586} 587