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