1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.server.wifi.util;
17 
18 import android.net.wifi.ScanResult;
19 import android.net.wifi.ScanResult.InformationElement;
20 import android.util.Log;
21 
22 import com.android.server.wifi.ByteBufferReader;
23 import com.android.server.wifi.hotspot2.NetworkDetail;
24 import com.android.server.wifi.hotspot2.anqp.Constants;
25 
26 import java.nio.BufferUnderflowException;
27 import java.nio.ByteBuffer;
28 import java.nio.ByteOrder;
29 import java.util.ArrayList;
30 import java.util.BitSet;
31 
32 public class InformationElementUtil {
33     private static final String TAG = "InformationElementUtil";
34 
parseInformationElements(byte[] bytes)35     public static InformationElement[] parseInformationElements(byte[] bytes) {
36         if (bytes == null) {
37             return new InformationElement[0];
38         }
39         ByteBuffer data = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
40 
41         ArrayList<InformationElement> infoElements = new ArrayList<>();
42         boolean found_ssid = false;
43         while (data.remaining() > 1) {
44             int eid = data.get() & Constants.BYTE_MASK;
45             int elementLength = data.get() & Constants.BYTE_MASK;
46 
47             if (elementLength > data.remaining() || (eid == InformationElement.EID_SSID
48                     && found_ssid)) {
49                 // APs often pad the data with bytes that happen to match that of the EID_SSID
50                 // marker.  This is not due to a known issue for APs to incorrectly send the SSID
51                 // name multiple times.
52                 break;
53             }
54             if (eid == InformationElement.EID_SSID) {
55                 found_ssid = true;
56             }
57 
58             InformationElement ie = new InformationElement();
59             ie.id = eid;
60             ie.bytes = new byte[elementLength];
61             data.get(ie.bytes);
62             infoElements.add(ie);
63         }
64         return infoElements.toArray(new InformationElement[infoElements.size()]);
65     }
66 
67     /**
68      * Parse and retrieve the Roaming Consortium Information Element from the list of IEs.
69      *
70      * @param ies List of IEs to retrieve from
71      * @return {@link RoamingConsortium}
72      */
getRoamingConsortiumIE(InformationElement[] ies)73     public static RoamingConsortium getRoamingConsortiumIE(InformationElement[] ies) {
74         RoamingConsortium roamingConsortium = new RoamingConsortium();
75         if (ies != null) {
76             for (InformationElement ie : ies) {
77                 if (ie.id == InformationElement.EID_ROAMING_CONSORTIUM) {
78                     try {
79                         roamingConsortium.from(ie);
80                     } catch (RuntimeException e) {
81                         Log.e(TAG, "Failed to parse Roaming Consortium IE: " + e.getMessage());
82                     }
83                 }
84             }
85         }
86         return roamingConsortium;
87     }
88 
89     /**
90      * Parse and retrieve the Hotspot 2.0 Vendor Specific Information Element from the list of IEs.
91      *
92      * @param ies List of IEs to retrieve from
93      * @return {@link Vsa}
94      */
getHS2VendorSpecificIE(InformationElement[] ies)95     public static Vsa getHS2VendorSpecificIE(InformationElement[] ies) {
96         Vsa vsa = new Vsa();
97         if (ies != null) {
98             for (InformationElement ie : ies) {
99                 if (ie.id == InformationElement.EID_VSA) {
100                     try {
101                         vsa.from(ie);
102                     } catch (RuntimeException e) {
103                         Log.e(TAG, "Failed to parse Vendor Specific IE: " + e.getMessage());
104                     }
105                 }
106             }
107         }
108         return vsa;
109     }
110 
111     /**
112      * Parse and retrieve the Interworking information element from the list of IEs.
113      *
114      * @param ies List of IEs to retrieve from
115      * @return {@link Interworking}
116      */
getInterworkingIE(InformationElement[] ies)117     public static Interworking getInterworkingIE(InformationElement[] ies) {
118         Interworking interworking = new Interworking();
119         if (ies != null) {
120             for (InformationElement ie : ies) {
121                 if (ie.id == InformationElement.EID_INTERWORKING) {
122                     try {
123                         interworking.from(ie);
124                     } catch (RuntimeException e) {
125                         Log.e(TAG, "Failed to parse Interworking IE: " + e.getMessage());
126                     }
127                 }
128             }
129         }
130         return interworking;
131     }
132 
133     public static class BssLoad {
134         public int stationCount = 0;
135         public int channelUtilization = 0;
136         public int capacity = 0;
137 
from(InformationElement ie)138         public void from(InformationElement ie) {
139             if (ie.id != InformationElement.EID_BSS_LOAD) {
140                 throw new IllegalArgumentException("Element id is not BSS_LOAD, : " + ie.id);
141             }
142             if (ie.bytes.length != 5) {
143                 throw new IllegalArgumentException("BSS Load element length is not 5: "
144                                                    + ie.bytes.length);
145             }
146             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
147             stationCount = data.getShort() & Constants.SHORT_MASK;
148             channelUtilization = data.get() & Constants.BYTE_MASK;
149             capacity = data.getShort() & Constants.SHORT_MASK;
150         }
151     }
152 
153     public static class HtOperation {
154         public int secondChannelOffset = 0;
155 
getChannelWidth()156         public int getChannelWidth() {
157             if (secondChannelOffset != 0) {
158                 return 1;
159             } else {
160                 return 0;
161             }
162         }
163 
getCenterFreq0(int primaryFrequency)164         public int getCenterFreq0(int primaryFrequency) {
165             //40 MHz
166             if (secondChannelOffset != 0) {
167                 if (secondChannelOffset == 1) {
168                     return primaryFrequency + 10;
169                 } else if (secondChannelOffset == 3) {
170                     return primaryFrequency - 10;
171                 } else {
172                     Log.e("HtOperation", "Error on secondChannelOffset: " + secondChannelOffset);
173                     return 0;
174                 }
175             } else {
176                 return 0;
177             }
178         }
179 
from(InformationElement ie)180         public void from(InformationElement ie) {
181             if (ie.id != InformationElement.EID_HT_OPERATION) {
182                 throw new IllegalArgumentException("Element id is not HT_OPERATION, : " + ie.id);
183             }
184             secondChannelOffset = ie.bytes[1] & 0x3;
185         }
186     }
187 
188     public static class VhtOperation {
189         public int channelMode = 0;
190         public int centerFreqIndex1 = 0;
191         public int centerFreqIndex2 = 0;
192 
isValid()193         public boolean isValid() {
194             return channelMode != 0;
195         }
196 
getChannelWidth()197         public int getChannelWidth() {
198             return channelMode + 1;
199         }
200 
getCenterFreq0()201         public int getCenterFreq0() {
202             //convert channel index to frequency in MHz, channel 36 is 5180MHz
203             return (centerFreqIndex1 - 36) * 5 + 5180;
204         }
205 
getCenterFreq1()206         public int getCenterFreq1() {
207             if (channelMode > 1) { //160MHz
208                 return (centerFreqIndex2 - 36) * 5 + 5180;
209             } else {
210                 return 0;
211             }
212         }
213 
from(InformationElement ie)214         public void from(InformationElement ie) {
215             if (ie.id != InformationElement.EID_VHT_OPERATION) {
216                 throw new IllegalArgumentException("Element id is not VHT_OPERATION, : " + ie.id);
217             }
218             channelMode = ie.bytes[0] & Constants.BYTE_MASK;
219             centerFreqIndex1 = ie.bytes[1] & Constants.BYTE_MASK;
220             centerFreqIndex2 = ie.bytes[2] & Constants.BYTE_MASK;
221         }
222     }
223 
224     public static class Interworking {
225         public NetworkDetail.Ant ant = null;
226         public boolean internet = false;
227         public long hessid = 0L;
228 
from(InformationElement ie)229         public void from(InformationElement ie) {
230             if (ie.id != InformationElement.EID_INTERWORKING) {
231                 throw new IllegalArgumentException("Element id is not INTERWORKING, : " + ie.id);
232             }
233             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
234             int anOptions = data.get() & Constants.BYTE_MASK;
235             ant = NetworkDetail.Ant.values()[anOptions & 0x0f];
236             internet = (anOptions & 0x10) != 0;
237             // There are only three possible lengths for the Interworking IE:
238             // Len 1: Access Network Options only
239             // Len 3: Access Network Options & Venue Info
240             // Len 7: Access Network Options & HESSID
241             // Len 9: Access Network Options, Venue Info, & HESSID
242             if (ie.bytes.length != 1
243                     && ie.bytes.length != 3
244                     && ie.bytes.length != 7
245                     && ie.bytes.length != 9) {
246                 throw new IllegalArgumentException(
247                         "Bad Interworking element length: " + ie.bytes.length);
248             }
249 
250             if (ie.bytes.length == 3 || ie.bytes.length == 9) {
251                 int venueInfo = (int) ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 2);
252             }
253 
254             if (ie.bytes.length == 7 || ie.bytes.length == 9) {
255                 hessid = ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 6);
256             }
257         }
258     }
259 
260     public static class RoamingConsortium {
261         public int anqpOICount = 0;
262 
263         private long[] roamingConsortiums = null;
264 
getRoamingConsortiums()265         public long[] getRoamingConsortiums() {
266             return roamingConsortiums;
267         }
268 
from(InformationElement ie)269         public void from(InformationElement ie) {
270             if (ie.id != InformationElement.EID_ROAMING_CONSORTIUM) {
271                 throw new IllegalArgumentException("Element id is not ROAMING_CONSORTIUM, : "
272                         + ie.id);
273             }
274             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
275             anqpOICount = data.get() & Constants.BYTE_MASK;
276 
277             int oi12Length = data.get() & Constants.BYTE_MASK;
278             int oi1Length = oi12Length & Constants.NIBBLE_MASK;
279             int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK;
280             int oi3Length = ie.bytes.length - 2 - oi1Length - oi2Length;
281             int oiCount = 0;
282             if (oi1Length > 0) {
283                 oiCount++;
284                 if (oi2Length > 0) {
285                     oiCount++;
286                     if (oi3Length > 0) {
287                         oiCount++;
288                     }
289                 }
290             }
291             roamingConsortiums = new long[oiCount];
292             if (oi1Length > 0 && roamingConsortiums.length > 0) {
293                 roamingConsortiums[0] =
294                         ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi1Length);
295             }
296             if (oi2Length > 0 && roamingConsortiums.length > 1) {
297                 roamingConsortiums[1] =
298                         ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi2Length);
299             }
300             if (oi3Length > 0 && roamingConsortiums.length > 2) {
301                 roamingConsortiums[2] =
302                         ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi3Length);
303             }
304         }
305     }
306 
307     public static class Vsa {
308         private static final int ANQP_DOMID_BIT = 0x04;
309 
310         public NetworkDetail.HSRelease hsRelease = null;
311         public int anqpDomainID = 0;    // No domain ID treated the same as a 0; unique info per AP.
312 
from(InformationElement ie)313         public void from(InformationElement ie) {
314             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
315             if (ie.bytes.length >= 5 && data.getInt() == Constants.HS20_FRAME_PREFIX) {
316                 int hsConf = data.get() & Constants.BYTE_MASK;
317                 switch ((hsConf >> 4) & Constants.NIBBLE_MASK) {
318                     case 0:
319                         hsRelease = NetworkDetail.HSRelease.R1;
320                         break;
321                     case 1:
322                         hsRelease = NetworkDetail.HSRelease.R2;
323                         break;
324                     default:
325                         hsRelease = NetworkDetail.HSRelease.Unknown;
326                         break;
327                 }
328                 if ((hsConf & ANQP_DOMID_BIT) != 0) {
329                     if (ie.bytes.length < 7) {
330                         throw new IllegalArgumentException(
331                                 "HS20 indication element too short: " + ie.bytes.length);
332                     }
333                     anqpDomainID = data.getShort() & Constants.SHORT_MASK;
334                 }
335             }
336         }
337     }
338 
339     /**
340      * This IE contained a bit field indicating the capabilities being advertised by the STA.
341      * The size of the bit field (number of bytes) is indicated by the |Length| field in the IE.
342      *
343      * Refer to Section 8.4.2.29 in IEEE 802.11-2012 Spec for capability associated with each
344      * bit.
345      *
346      * Here is the wire format of this IE:
347      * | Element ID | Length | Capabilities |
348      *       1           1          n
349      */
350     public static class ExtendedCapabilities {
351         private static final int RTT_RESP_ENABLE_BIT = 70;
352         private static final int SSID_UTF8_BIT = 48;
353 
354         public BitSet capabilitiesBitSet;
355 
356         /**
357          * @return true if SSID should be interpreted using UTF-8 encoding
358          */
isStrictUtf8()359         public boolean isStrictUtf8() {
360             return capabilitiesBitSet.get(SSID_UTF8_BIT);
361         }
362 
363         /**
364          * @return true if 802.11 MC RTT Response is enabled
365          */
is80211McRTTResponder()366         public boolean is80211McRTTResponder() {
367             return capabilitiesBitSet.get(RTT_RESP_ENABLE_BIT);
368         }
369 
ExtendedCapabilities()370         public ExtendedCapabilities() {
371             capabilitiesBitSet = new BitSet();
372         }
373 
ExtendedCapabilities(ExtendedCapabilities other)374         public ExtendedCapabilities(ExtendedCapabilities other) {
375             capabilitiesBitSet = other.capabilitiesBitSet;
376         }
377 
378         /**
379          * Parse an ExtendedCapabilities from the IE containing raw bytes.
380          *
381          * @param ie The Information element data
382          */
from(InformationElement ie)383         public void from(InformationElement ie) {
384             capabilitiesBitSet = BitSet.valueOf(ie.bytes);
385         }
386     }
387 
388     /**
389      * parse beacon to build the capabilities
390      *
391      * This class is used to build the capabilities string of the scan results coming
392      * from HAL. It parses the ieee beacon's capability field, WPA and RSNE IE as per spec,
393      * and builds the ScanResult.capabilities String in a way that mirrors the values returned
394      * by wpa_supplicant.
395      */
396     public static class Capabilities {
397         private static final int CAP_ESS_BIT_OFFSET = 0;
398         private static final int CAP_PRIVACY_BIT_OFFSET = 4;
399 
400         private static final int WPA_VENDOR_OUI_TYPE_ONE = 0x01f25000;
401         private static final int WPS_VENDOR_OUI_TYPE = 0x04f25000;
402         private static final short WPA_VENDOR_OUI_VERSION = 0x0001;
403         private static final short RSNE_VERSION = 0x0001;
404 
405         private static final int WPA_AKM_EAP = 0x01f25000;
406         private static final int WPA_AKM_PSK = 0x02f25000;
407 
408         private static final int WPA2_AKM_EAP = 0x01ac0f00;
409         private static final int WPA2_AKM_PSK = 0x02ac0f00;
410         private static final int WPA2_AKM_FT_EAP = 0x03ac0f00;
411         private static final int WPA2_AKM_FT_PSK = 0x04ac0f00;
412         private static final int WPA2_AKM_EAP_SHA256 = 0x05ac0f00;
413         private static final int WPA2_AKM_PSK_SHA256 = 0x06ac0f00;
414 
415         private static final int WPA_CIPHER_NONE = 0x00f25000;
416         private static final int WPA_CIPHER_TKIP = 0x02f25000;
417         private static final int WPA_CIPHER_CCMP = 0x04f25000;
418 
419         private static final int RSN_CIPHER_NONE = 0x00ac0f00;
420         private static final int RSN_CIPHER_TKIP = 0x02ac0f00;
421         private static final int RSN_CIPHER_CCMP = 0x04ac0f00;
422         private static final int RSN_CIPHER_NO_GROUP_ADDRESSED = 0x07ac0f00;
423 
424         public ArrayList<Integer> protocol;
425         public ArrayList<ArrayList<Integer>> keyManagement;
426         public ArrayList<ArrayList<Integer>> pairwiseCipher;
427         public ArrayList<Integer> groupCipher;
428         public boolean isESS;
429         public boolean isPrivacy;
430         public boolean isWPS;
431 
Capabilities()432         public Capabilities() {
433         }
434 
435         // RSNE format (size unit: byte)
436         //
437         // | Element ID | Length | Version | Group Data Cipher Suite |
438         //      1           1         2                 4
439         // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
440         //              2                            4 * m
441         // | AKM Suite Count | AKM Suite List | RSN Capabilities |
442         //          2               4 * n               2
443         // | PMKID Count | PMKID List | Group Management Cipher Suite |
444         //        2          16 * s                 4
445         //
446         // Note: InformationElement.bytes has 'Element ID' and 'Length'
447         //       stripped off already
parseRsnElement(InformationElement ie)448         private void parseRsnElement(InformationElement ie) {
449             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
450 
451             try {
452                 // version
453                 if (buf.getShort() != RSNE_VERSION) {
454                     // incorrect version
455                     return;
456                 }
457 
458                 // found the RSNE IE, hence start building the capability string
459                 protocol.add(ScanResult.PROTOCOL_WPA2);
460 
461                 // group data cipher suite
462                 groupCipher.add(parseRsnCipher(buf.getInt()));
463 
464                 // pairwise cipher suite count
465                 short cipherCount = buf.getShort();
466                 ArrayList<Integer> rsnPairwiseCipher = new ArrayList<>();
467                 // pairwise cipher suite list
468                 for (int i = 0; i < cipherCount; i++) {
469                     rsnPairwiseCipher.add(parseRsnCipher(buf.getInt()));
470                 }
471                 pairwiseCipher.add(rsnPairwiseCipher);
472 
473                 // AKM
474                 // AKM suite count
475                 short akmCount = buf.getShort();
476                 ArrayList<Integer> rsnKeyManagement = new ArrayList<>();
477 
478                 for (int i = 0; i < akmCount; i++) {
479                     int akm = buf.getInt();
480                     switch (akm) {
481                         case WPA2_AKM_EAP:
482                             rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP);
483                             break;
484                         case WPA2_AKM_PSK:
485                             rsnKeyManagement.add(ScanResult.KEY_MGMT_PSK);
486                             break;
487                         case WPA2_AKM_FT_EAP:
488                             rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_EAP);
489                             break;
490                         case WPA2_AKM_FT_PSK:
491                             rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_PSK);
492                             break;
493                         case WPA2_AKM_EAP_SHA256:
494                             rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP_SHA256);
495                             break;
496                         case WPA2_AKM_PSK_SHA256:
497                             rsnKeyManagement.add(ScanResult.KEY_MGMT_PSK_SHA256);
498                             break;
499                         default:
500                             // do nothing
501                             break;
502                     }
503                 }
504                 // Default AKM
505                 if (rsnKeyManagement.isEmpty()) {
506                     rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP);
507                 }
508                 keyManagement.add(rsnKeyManagement);
509             } catch (BufferUnderflowException e) {
510                 Log.e("IE_Capabilities", "Couldn't parse RSNE, buffer underflow");
511             }
512         }
513 
parseWpaCipher(int cipher)514         private static int parseWpaCipher(int cipher) {
515             switch (cipher) {
516                 case WPA_CIPHER_NONE:
517                     return ScanResult.CIPHER_NONE;
518                 case WPA_CIPHER_TKIP:
519                     return ScanResult.CIPHER_TKIP;
520                 case WPA_CIPHER_CCMP:
521                     return ScanResult.CIPHER_CCMP;
522                 default:
523                     Log.w("IE_Capabilities", "Unknown WPA cipher suite: "
524                             + Integer.toHexString(cipher));
525                     return ScanResult.CIPHER_NONE;
526             }
527         }
528 
parseRsnCipher(int cipher)529         private static int parseRsnCipher(int cipher) {
530             switch (cipher) {
531                 case RSN_CIPHER_NONE:
532                     return ScanResult.CIPHER_NONE;
533                 case RSN_CIPHER_TKIP:
534                     return ScanResult.CIPHER_TKIP;
535                 case RSN_CIPHER_CCMP:
536                     return ScanResult.CIPHER_CCMP;
537                 case RSN_CIPHER_NO_GROUP_ADDRESSED:
538                     return ScanResult.CIPHER_NO_GROUP_ADDRESSED;
539                 default:
540                     Log.w("IE_Capabilities", "Unknown RSN cipher suite: "
541                             + Integer.toHexString(cipher));
542                     return ScanResult.CIPHER_NONE;
543             }
544         }
545 
isWpsElement(InformationElement ie)546         private static boolean isWpsElement(InformationElement ie) {
547             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
548             try {
549                 // WPS OUI and type
550                 return (buf.getInt() == WPS_VENDOR_OUI_TYPE);
551             } catch (BufferUnderflowException e) {
552                 Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
553                 return false;
554             }
555         }
556 
isWpaOneElement(InformationElement ie)557         private static boolean isWpaOneElement(InformationElement ie) {
558             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
559 
560             try {
561                 // WPA OUI and type
562                 return (buf.getInt() == WPA_VENDOR_OUI_TYPE_ONE);
563             } catch (BufferUnderflowException e) {
564                 Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
565                 return false;
566             }
567         }
568 
569         // WPA type 1 format (size unit: byte)
570         //
571         // | Element ID | Length | OUI | Type | Version |
572         //      1           1       3     1        2
573         // | Group Data Cipher Suite |
574         //             4
575         // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
576         //              2                            4 * m
577         // | AKM Suite Count | AKM Suite List |
578         //          2               4 * n
579         //
580         // Note: InformationElement.bytes has 'Element ID' and 'Length'
581         //       stripped off already
582         //
parseWpaOneElement(InformationElement ie)583         private void parseWpaOneElement(InformationElement ie) {
584             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
585 
586             try {
587                 // skip WPA OUI and type parsing. isWpaOneElement() should have
588                 // been called for verification before we reach here.
589                 buf.getInt();
590 
591                 // version
592                 if (buf.getShort() != WPA_VENDOR_OUI_VERSION)  {
593                     // incorrect version
594                     return;
595                 }
596 
597                 // start building the string
598                 protocol.add(ScanResult.PROTOCOL_WPA);
599 
600                 // group data cipher suite
601                 groupCipher.add(parseWpaCipher(buf.getInt()));
602 
603                 // pairwise cipher suite count
604                 short cipherCount = buf.getShort();
605                 ArrayList<Integer> wpaPairwiseCipher = new ArrayList<>();
606                 // pairwise chipher suite list
607                 for (int i = 0; i < cipherCount; i++) {
608                     wpaPairwiseCipher.add(parseWpaCipher(buf.getInt()));
609                 }
610                 pairwiseCipher.add(wpaPairwiseCipher);
611 
612                 // AKM
613                 // AKM suite count
614                 short akmCount = buf.getShort();
615                 ArrayList<Integer> wpaKeyManagement = new ArrayList<>();
616 
617                 // AKM suite list
618                 for (int i = 0; i < akmCount; i++) {
619                     int akm = buf.getInt();
620                     switch (akm) {
621                         case WPA_AKM_EAP:
622                             wpaKeyManagement.add(ScanResult.KEY_MGMT_EAP);
623                             break;
624                         case WPA_AKM_PSK:
625                             wpaKeyManagement.add(ScanResult.KEY_MGMT_PSK);
626                             break;
627                         default:
628                             // do nothing
629                             break;
630                     }
631                 }
632                 // Default AKM
633                 if (wpaKeyManagement.isEmpty()) {
634                     wpaKeyManagement.add(ScanResult.KEY_MGMT_EAP);
635                 }
636                 keyManagement.add(wpaKeyManagement);
637             } catch (BufferUnderflowException e) {
638                 Log.e("IE_Capabilities", "Couldn't parse type 1 WPA, buffer underflow");
639             }
640         }
641 
642         /**
643          * Parse the Information Element and the 16-bit Capability Information field
644          * to build the InformationElemmentUtil.capabilities object.
645          *
646          * @param ies -- Information Element array
647          * @param beaconCap -- 16-bit Beacon Capability Information field
648          */
649 
from(InformationElement[] ies, BitSet beaconCap)650         public void from(InformationElement[] ies, BitSet beaconCap) {
651             protocol = new ArrayList<Integer>();
652             keyManagement = new ArrayList<ArrayList<Integer>>();
653             groupCipher = new ArrayList<Integer>();
654             pairwiseCipher = new ArrayList<ArrayList<Integer>>();
655 
656             if (ies == null || beaconCap == null) {
657                 return;
658             }
659             isESS = beaconCap.get(CAP_ESS_BIT_OFFSET);
660             isPrivacy = beaconCap.get(CAP_PRIVACY_BIT_OFFSET);
661             for (InformationElement ie : ies) {
662                 if (ie.id == InformationElement.EID_RSN) {
663                     parseRsnElement(ie);
664                 }
665 
666                 if (ie.id == InformationElement.EID_VSA) {
667                     if (isWpaOneElement(ie)) {
668                         parseWpaOneElement(ie);
669                     }
670                     if (isWpsElement(ie)) {
671                         // TODO(b/62134557): parse WPS IE to provide finer granularity information.
672                         isWPS = true;
673                     }
674                 }
675             }
676         }
677 
protocolToString(int protocol)678         private String protocolToString(int protocol) {
679             switch (protocol) {
680                 case ScanResult.PROTOCOL_NONE:
681                     return "None";
682                 case ScanResult.PROTOCOL_WPA:
683                     return "WPA";
684                 case ScanResult.PROTOCOL_WPA2:
685                     return "WPA2";
686                 default:
687                     return "?";
688             }
689         }
690 
keyManagementToString(int akm)691         private String keyManagementToString(int akm) {
692             switch (akm) {
693                 case ScanResult.KEY_MGMT_NONE:
694                     return "None";
695                 case ScanResult.KEY_MGMT_PSK:
696                     return "PSK";
697                 case ScanResult.KEY_MGMT_EAP:
698                     return "EAP";
699                 case ScanResult.KEY_MGMT_FT_EAP:
700                     return "FT/EAP";
701                 case ScanResult.KEY_MGMT_FT_PSK:
702                     return "FT/PSK";
703                 case ScanResult.KEY_MGMT_EAP_SHA256:
704                     return "EAP-SHA256";
705                 case ScanResult.KEY_MGMT_PSK_SHA256:
706                     return "PSK-SHA256";
707                 default:
708                     return "?";
709             }
710         }
711 
cipherToString(int cipher)712         private String cipherToString(int cipher) {
713             switch (cipher) {
714                 case ScanResult.CIPHER_NONE:
715                     return "None";
716                 case ScanResult.CIPHER_CCMP:
717                     return "CCMP";
718                 case ScanResult.CIPHER_TKIP:
719                     return "TKIP";
720                 default:
721                     return "?";
722             }
723         }
724 
725         /**
726          * Build the ScanResult.capabilities String.
727          *
728          * @return security string that mirrors what wpa_supplicant generates
729          */
generateCapabilitiesString()730         public String generateCapabilitiesString() {
731             String capabilities = "";
732             // private Beacon without an RSNE or WPA IE, hence WEP0
733             boolean isWEP = (protocol.isEmpty()) && isPrivacy;
734 
735             if (isWEP) {
736                 capabilities += "[WEP]";
737             }
738             for (int i = 0; i < protocol.size(); i++) {
739                 capabilities += "[" + protocolToString(protocol.get(i));
740                 if (i < keyManagement.size()) {
741                     for (int j = 0; j < keyManagement.get(i).size(); j++) {
742                         capabilities += ((j == 0) ? "-" : "+")
743                                 + keyManagementToString(keyManagement.get(i).get(j));
744                     }
745                 }
746                 if (i < pairwiseCipher.size()) {
747                     for (int j = 0; j < pairwiseCipher.get(i).size(); j++) {
748                         capabilities += ((j == 0) ? "-" : "+")
749                                 + cipherToString(pairwiseCipher.get(i).get(j));
750                     }
751                 }
752                 capabilities += "]";
753             }
754             if (isESS) {
755                 capabilities += "[ESS]";
756             }
757             if (isWPS) {
758                 capabilities += "[WPS]";
759             }
760 
761             return capabilities;
762         }
763     }
764 
765     /**
766      * Parser for the Traffic Indication Map (TIM) Information Element (EID 5). This element will
767      * only be present in scan results that are derived from a Beacon Frame, not from the more
768      * plentiful probe responses. Call 'isValid()' after parsing, to ensure the results are correct.
769      */
770     public static class TrafficIndicationMap {
771         private static final int MAX_TIM_LENGTH = 254;
772         private boolean mValid = false;
773         public int mLength = 0;
774         public int mDtimCount = -1;
775         //Negative DTIM Period means no TIM element was given this frame.
776         public int mDtimPeriod = -1;
777         public int mBitmapControl = 0;
778 
779         /**
780          * Is this a valid TIM information element.
781          */
isValid()782         public boolean isValid() {
783             return mValid;
784         }
785 
786         // Traffic Indication Map format (size unit: byte)
787         //
788         //| ElementID | Length | DTIM Count | DTIM Period | BitmapControl | Partial Virtual Bitmap |
789         //      1          1          1            1               1                1 - 251
790         //
791         // Note: InformationElement.bytes has 'Element ID' and 'Length'
792         //       stripped off already
793         //
from(InformationElement ie)794         public void from(InformationElement ie) {
795             mValid = false;
796             if (ie == null || ie.bytes == null) return;
797             mLength = ie.bytes.length;
798             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
799             try {
800                 mDtimCount = data.get() & Constants.BYTE_MASK;
801                 mDtimPeriod = data.get() & Constants.BYTE_MASK;
802                 mBitmapControl = data.get() & Constants.BYTE_MASK;
803                 //A valid TIM element must have atleast one more byte
804                 data.get();
805             } catch (BufferUnderflowException e) {
806                 return;
807             }
808             if (mLength <= MAX_TIM_LENGTH && mDtimPeriod > 0) {
809                 mValid = true;
810             }
811         }
812     }
813 
814     /**
815      * This util class determines the 802.11 standard (a/b/g/n/ac) being used
816      */
817     public static class WifiMode {
818         public static final int MODE_UNDEFINED = 0; // Unknown/undefined
819         public static final int MODE_11A = 1;       // 802.11a
820         public static final int MODE_11B = 2;       // 802.11b
821         public static final int MODE_11G = 3;       // 802.11g
822         public static final int MODE_11N = 4;       // 802.11n
823         public static final int MODE_11AC = 5;      // 802.11ac
824         //<TODO> add support for 802.11ad and be more selective instead of defaulting to 11A
825 
826         /**
827          * Use frequency, max supported rate, and the existence of VHT, HT & ERP fields in scan
828          * scan result to determine the 802.11 Wifi standard being used.
829          */
determineMode(int frequency, int maxRate, boolean foundVht, boolean foundHt, boolean foundErp)830         public static int determineMode(int frequency, int maxRate, boolean foundVht,
831                 boolean foundHt, boolean foundErp) {
832             if (foundVht) {
833                 return MODE_11AC;
834             } else if (foundHt) {
835                 return MODE_11N;
836             } else if (foundErp) {
837                 return MODE_11G;
838             } else if (frequency < 3000) {
839                 if (maxRate < 24000000) {
840                     return MODE_11B;
841                 } else {
842                     return MODE_11G;
843                 }
844             } else {
845                 return MODE_11A;
846             }
847         }
848 
849         /**
850          * Map the wifiMode integer to its type, and output as String MODE_11<A/B/G/N/AC>
851          */
toString(int mode)852         public static String toString(int mode) {
853             switch(mode) {
854                 case MODE_11A:
855                     return "MODE_11A";
856                 case MODE_11B:
857                     return "MODE_11B";
858                 case MODE_11G:
859                     return "MODE_11G";
860                 case MODE_11N:
861                     return "MODE_11N";
862                 case MODE_11AC:
863                     return "MODE_11AC";
864                 default:
865                     return "MODE_UNDEFINED";
866             }
867         }
868     }
869 
870     /**
871      * Parser for both the Supported Rates & Extended Supported Rates Information Elements
872      */
873     public static class SupportedRates {
874         public static final int MASK = 0x7F; // 0111 1111
875         public boolean mValid = false;
876         public ArrayList<Integer> mRates;
877 
SupportedRates()878         public SupportedRates() {
879             mRates = new ArrayList<Integer>();
880         }
881 
882         /**
883          * Is this a valid Supported Rates information element.
884          */
isValid()885         public boolean isValid() {
886             return mValid;
887         }
888 
889         /**
890          * get the Rate in bits/s from associated byteval
891          */
getRateFromByte(int byteVal)892         public static int getRateFromByte(int byteVal) {
893             byteVal &= MASK;
894             switch(byteVal) {
895                 case 2:
896                     return 1000000;
897                 case 4:
898                     return 2000000;
899                 case 11:
900                     return 5500000;
901                 case 12:
902                     return 6000000;
903                 case 18:
904                     return 9000000;
905                 case 22:
906                     return 11000000;
907                 case 24:
908                     return 12000000;
909                 case 36:
910                     return 18000000;
911                 case 44:
912                     return 22000000;
913                 case 48:
914                     return 24000000;
915                 case 66:
916                     return 33000000;
917                 case 72:
918                     return 36000000;
919                 case 96:
920                     return 48000000;
921                 case 108:
922                     return 54000000;
923                 default:
924                     //ERROR UNKNOWN RATE
925                     return -1;
926             }
927         }
928 
929         // Supported Rates format (size unit: byte)
930         //
931         //| ElementID | Length | Supported Rates  [7 Little Endian Info bits - 1 Flag bit]
932         //      1          1          1 - 8
933         //
934         // Note: InformationElement.bytes has 'Element ID' and 'Length'
935         //       stripped off already
936         //
from(InformationElement ie)937         public void from(InformationElement ie) {
938             mValid = false;
939             if (ie == null || ie.bytes == null || ie.bytes.length > 8 || ie.bytes.length < 1)  {
940                 return;
941             }
942             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
943             try {
944                 for (int i = 0; i < ie.bytes.length; i++) {
945                     int rate = getRateFromByte(data.get());
946                     if (rate > 0) {
947                         mRates.add(rate);
948                     } else {
949                         return;
950                     }
951                 }
952             } catch (BufferUnderflowException e) {
953                 return;
954             }
955             mValid = true;
956             return;
957         }
958 
959         /**
960          * Lists the rates in a human readable string
961          */
toString()962         public String toString() {
963             StringBuilder sbuf = new StringBuilder();
964             for (Integer rate : mRates) {
965                 sbuf.append(String.format("%.1f", (double) rate / 1000000) + ", ");
966             }
967             return sbuf.toString();
968         }
969     }
970 }
971