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