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