NetworkDetail.java revision ef1567e413c9ed5f5c4fdb9e354861632f7b2f87
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; 20 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_Interworking = 107; 26 private static final int EID_RoamingConsortium = 111; 27 private static final int EID_ExtendedCaps = 127; 28 private static final int EID_VSA = 221; 29 private static final int ANQP_DOMID_BIT = 0x04; 30 31 private static final long SSID_UTF8_BIT = 0x0001000000000000L; 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 /* 69 * From Interworking element: 70 * mAnt non null indicates the presence of Interworking, i.e. 802.11u 71 * mVenueGroup and mVenueType may be null if not present in the Interworking element. 72 */ 73 private final Ant mAnt; 74 private final boolean mInternet; 75 private final VenueNameElement.VenueGroup mVenueGroup; 76 private final VenueNameElement.VenueType mVenueType; 77 78 /* 79 * From HS20 Indication element: 80 * mHSRelease is null only if the HS20 Indication element was not present. 81 * mAnqpDomainID is set to -1 if not present in the element. 82 */ 83 private final HSRelease mHSRelease; 84 private final int mAnqpDomainID; 85 86 /* 87 * From beacon: 88 * mAnqpOICount is how many additional OIs are available through ANQP. 89 * mRoamingConsortiums is either null, if the element was not present, or is an array of 90 * 1, 2 or 3 longs in which the roaming consortium values occupy the LSBs. 91 */ 92 private final int mAnqpOICount; 93 private final long[] mRoamingConsortiums; 94 95 private final Long mExtendedCapabilities; 96 97 private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements; 98 99 public NetworkDetail(String bssid, String infoElements, List<String> anqpLines) { 100 101 if (infoElements == null) { 102 throw new IllegalArgumentException("Null information element string"); 103 } 104 int separator = infoElements.indexOf('='); 105 if (separator<0) { 106 throw new IllegalArgumentException("No element separator"); 107 } 108 109 mBSSID = parseMac(bssid); 110 111 ByteBuffer data = ByteBuffer.wrap(Utils.hexToBytes(infoElements.substring(separator + 1))) 112 .order(ByteOrder.LITTLE_ENDIAN); 113 114 String ssid = null; 115 byte[] ssidOctets = null; 116 int stationCount = 0; 117 int channelUtilization = 0; 118 int capacity = 0; 119 120 Ant ant = null; 121 boolean internet = false; 122 VenueNameElement.VenueGroup venueGroup = null; 123 VenueNameElement.VenueType venueType = null; 124 long hessid = 0L; 125 126 int anqpOICount = 0; 127 long[] roamingConsortiums = null; 128 129 HSRelease hsRelease = null; 130 int anqpDomainID = 0; // No domain ID treated the same as a 0; unique info per AP. 131 132 Long extendedCapabilities = null; 133 134 while (data.hasRemaining()) { 135 int eid = data.get() & Constants.BYTE_MASK; 136 int elementLength = data.get() & Constants.BYTE_MASK; 137 if (elementLength > data.remaining()) { 138 throw new IllegalArgumentException("Length out of bounds: " + elementLength); 139 } 140 141 switch (eid) { 142 case EID_SSID: 143 ssidOctets = new byte[elementLength]; 144 data.get(ssidOctets); 145 break; 146 case EID_BSSLoad: 147 if (elementLength != 5) { 148 throw new IllegalArgumentException("BSS Load element length is not 5: " + 149 elementLength); 150 } 151 stationCount = data.getShort() & Constants.SHORT_MASK; 152 channelUtilization = data.get() & Constants.BYTE_MASK; 153 capacity = data.getShort() & Constants.SHORT_MASK; 154 break; 155 case EID_Interworking: 156 int anOptions = data.get() & Constants.BYTE_MASK; 157 ant = Ant.values()[anOptions&0x0f]; 158 internet = ( anOptions & 0x10 ) != 0; 159 // Len 1 none, 3 venue-info, 7 HESSID, 9 venue-info & HESSID 160 if (elementLength == 3 || elementLength == 9) { 161 try { 162 ByteBuffer vinfo = data.duplicate(); 163 vinfo.limit(vinfo.position() + 2); 164 VenueNameElement vne = 165 new VenueNameElement(Constants.ANQPElementType.ANQPVenueName, 166 vinfo); 167 venueGroup = vne.getGroup(); 168 venueType = vne.getType(); 169 data.getShort(); 170 } 171 catch ( ProtocolException pe ) { 172 /*Cannot happen*/ 173 } 174 } 175 if (elementLength == 7 || elementLength == 9) { 176 hessid = getInteger(data, ByteOrder.BIG_ENDIAN, 6); 177 } 178 break; 179 case EID_RoamingConsortium: 180 anqpOICount = data.get() & Constants.BYTE_MASK; 181 182 int oi12Length = data.get() & Constants.BYTE_MASK; 183 int oi1Length = oi12Length & Constants.NIBBLE_MASK; 184 int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK; 185 int oi3Length = elementLength - 2 - oi1Length - oi2Length; 186 int oiCount = 0; 187 if (oi1Length > 0) { 188 oiCount++; 189 if (oi2Length > 0) { 190 oiCount++; 191 if (oi3Length > 0) { 192 oiCount++; 193 } 194 } 195 } 196 roamingConsortiums = new long[oiCount]; 197 if (oi1Length > 0 ) { 198 roamingConsortiums[0] = getInteger(data, ByteOrder.BIG_ENDIAN, oi1Length); 199 } 200 if (oi2Length > 0 ) { 201 roamingConsortiums[1] = getInteger(data, ByteOrder.BIG_ENDIAN, oi2Length); 202 } 203 if (oi3Length > 0 ) { 204 roamingConsortiums[2] = getInteger(data, ByteOrder.BIG_ENDIAN, oi3Length); 205 } 206 break; 207 case EID_VSA: 208 if (elementLength < 5) { 209 data.position(data.position() + elementLength); 210 } 211 else if (data.getInt() != Constants.HS20_FRAME_PREFIX) { 212 data.position(data.position() + elementLength - Constants.BYTES_IN_INT); 213 } 214 else { 215 int hsConf = data.get() & Constants.BYTE_MASK; 216 switch ((hsConf>>4) & Constants.NIBBLE_MASK) { 217 case 0: 218 hsRelease = HSRelease.R1; 219 break; 220 case 1: 221 hsRelease = HSRelease.R2; 222 break; 223 default: 224 hsRelease = HSRelease.Unknown; 225 break; 226 } 227 if ((hsConf & ANQP_DOMID_BIT) != 0) { 228 anqpDomainID = data.getShort() & Constants.SHORT_MASK; 229 } 230 } 231 break; 232 case EID_ExtendedCaps: 233 extendedCapabilities = 234 Constants.getInteger(data, ByteOrder.LITTLE_ENDIAN, elementLength); 235 break; 236 default: 237 data.position(data.position()+elementLength); 238 break; 239 } 240 } 241 242 if (ssidOctets != null) { 243 Charset encoding; 244 if (extendedCapabilities != null && (extendedCapabilities & SSID_UTF8_BIT) != 0) { 245 encoding = StandardCharsets.UTF_8; 246 } 247 else { 248 encoding = StandardCharsets.ISO_8859_1; 249 } 250 ssid = new String(ssidOctets, encoding); 251 } 252 253 mSSID = ssid; 254 mHESSID = hessid; 255 mStationCount = stationCount; 256 mChannelUtilization = channelUtilization; 257 mCapacity = capacity; 258 mAnt = ant; 259 mInternet = internet; 260 mVenueGroup = venueGroup; 261 mVenueType = venueType; 262 mHSRelease = hsRelease; 263 mAnqpDomainID = anqpDomainID; 264 mAnqpOICount = anqpOICount; 265 mRoamingConsortiums = roamingConsortiums; 266 mExtendedCapabilities = extendedCapabilities; 267 mANQPElements = SupplicantBridge.parseANQPLines(anqpLines); 268 } 269 270 private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 271 mSSID = base.mSSID; 272 mBSSID = base.mBSSID; 273 mHESSID = base.mHESSID; 274 mStationCount = base.mStationCount; 275 mChannelUtilization = base.mChannelUtilization; 276 mCapacity = base.mCapacity; 277 mAnt = base.mAnt; 278 mInternet = base.mInternet; 279 mVenueGroup = base.mVenueGroup; 280 mVenueType = base.mVenueType; 281 mHSRelease = base.mHSRelease; 282 mAnqpDomainID = base.mAnqpDomainID; 283 mAnqpOICount = base.mAnqpOICount; 284 mRoamingConsortiums = base.mRoamingConsortiums; 285 mExtendedCapabilities = base.mExtendedCapabilities; 286 mANQPElements = anqpElements; 287 } 288 289 public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 290 return new NetworkDetail(this, anqpElements); 291 } 292 293 private static long parseMac(String s) { 294 295 long mac = 0; 296 int count = 0; 297 for (int n = 0; n < s.length(); n++) { 298 int nibble = Utils.fromHex(s.charAt(n), true); 299 if (nibble >= 0) { 300 mac = (mac << 4) | nibble; 301 count++; 302 } 303 } 304 if (count < 12 || (count&1) == 1) { 305 throw new IllegalArgumentException("Bad MAC address: '" + s + "'"); 306 } 307 return mac; 308 } 309 310 public boolean has80211uInfo() { 311 return mAnt != null || mRoamingConsortiums != null || mHSRelease != null; 312 } 313 314 public boolean hasInterworking() { 315 return mAnt != null; 316 } 317 318 public String getSSID() { 319 return mSSID; 320 } 321 322 public long getHESSID() { 323 return mHESSID; 324 } 325 326 public long getBSSID() { 327 return mBSSID; 328 } 329 330 public int getStationCount() { 331 return mStationCount; 332 } 333 334 public int getChannelUtilization() { 335 return mChannelUtilization; 336 } 337 338 public int getCapacity() { 339 return mCapacity; 340 } 341 342 public boolean isInterworking() { 343 return mAnt != null; 344 } 345 346 public Ant getAnt() { 347 return mAnt; 348 } 349 350 public boolean isInternet() { 351 return mInternet; 352 } 353 354 public VenueNameElement.VenueGroup getVenueGroup() { 355 return mVenueGroup; 356 } 357 358 public VenueNameElement.VenueType getVenueType() { 359 return mVenueType; 360 } 361 362 public HSRelease getHSRelease() { 363 return mHSRelease; 364 } 365 366 public int getAnqpDomainID() { 367 return mAnqpDomainID; 368 } 369 370 public int getAnqpOICount() { 371 return mAnqpOICount; 372 } 373 374 public long[] getRoamingConsortiums() { 375 return mRoamingConsortiums; 376 } 377 378 public Long getExtendedCapabilities() { 379 return mExtendedCapabilities; 380 } 381 382 public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() { 383 return mANQPElements; 384 } 385 386 public boolean isSSID_UTF8() { 387 return mExtendedCapabilities != null && (mExtendedCapabilities & SSID_UTF8_BIT) != 0; 388 } 389 390 @Override 391 public boolean equals(Object thatObject) { 392 if (this == thatObject) { 393 return true; 394 } 395 if (thatObject == null || getClass() != thatObject.getClass()) { 396 return false; 397 } 398 399 NetworkDetail that = (NetworkDetail)thatObject; 400 401 return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID(); 402 } 403 404 @Override 405 public int hashCode() { 406 return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID; 407 } 408 409 @Override 410 public String toString() { 411 return String.format("NetworkInfo{mSSID='%s', mHESSID=%x, mBSSID=%x, mStationCount=%d, " + 412 "mChannelUtilization=%d, mCapacity=%d, mAnt=%s, mInternet=%s, " + 413 "mVenueGroup=%s, mVenueType=%s, mHSRelease=%s, mAnqpDomainID=%d, " + 414 "mAnqpOICount=%d, mRoamingConsortiums=%s}", 415 mSSID, mHESSID, mBSSID, mStationCount, 416 mChannelUtilization, mCapacity, mAnt, mInternet, 417 mVenueGroup, mVenueType, mHSRelease, mAnqpDomainID, 418 mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums)); 419 } 420 421 public String toKeyString() { 422 return String.format("'%s':%s", mSSID, getBSSIDString()); 423 } 424 425 public String getBSSIDString() { 426 return toMACString(mBSSID); 427 } 428 429 public static String toMACString(long mac) { 430 StringBuilder sb = new StringBuilder(); 431 boolean first = true; 432 for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) { 433 if (first) { 434 first = false; 435 } else { 436 sb.append(':'); 437 } 438 sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK)); 439 } 440 return sb.toString(); 441 } 442 443 private static final String IE = "ie=" + 444 "000477696e67" + // SSID wing 445 "0b052a00cf611e" + // BSS Load 42:207:7777 446 "6b091e0a01610408621205" + // internet:Experimental:Vehicular:Auto:hessid 447 "6f0a0e530111112222222229" + // 14:111111:2222222229 448 "dd07506f9a10143a01"; // r2:314 449 450 private static final String IE2 = "ie=000f4578616d706c65204e6574776f726b010882848b960c1218240301012a010432043048606c30140100000fac040100000fac040100000fac0100007f04000000806b091e07010203040506076c027f006f1001531122331020304050010203040506dd05506f9a1000"; 451 452 public static void main(String[] args) { 453 ScanResult scanResult = new ScanResult(); 454 scanResult.SSID = "wing"; 455 scanResult.BSSID = "610408"; 456 NetworkDetail nwkDetail = new NetworkDetail(scanResult.BSSID, IE2, null); 457 System.out.println(nwkDetail); 458 } 459} 460