• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 == 7 || ie.bytes.length == 9) {
251                 hessid = ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 6);
252             }
253         }
254     }
255 
256     public static class RoamingConsortium {
257         public int anqpOICount = 0;
258         public long[] roamingConsortiums = null;
259 
from(InformationElement ie)260         public void from(InformationElement ie) {
261             if (ie.id != InformationElement.EID_ROAMING_CONSORTIUM) {
262                 throw new IllegalArgumentException("Element id is not ROAMING_CONSORTIUM, : "
263                         + ie.id);
264             }
265             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
266             anqpOICount = data.get() & Constants.BYTE_MASK;
267 
268             int oi12Length = data.get() & Constants.BYTE_MASK;
269             int oi1Length = oi12Length & Constants.NIBBLE_MASK;
270             int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK;
271             int oi3Length = ie.bytes.length - 2 - oi1Length - oi2Length;
272             int oiCount = 0;
273             if (oi1Length > 0) {
274                 oiCount++;
275                 if (oi2Length > 0) {
276                     oiCount++;
277                     if (oi3Length > 0) {
278                         oiCount++;
279                     }
280                 }
281             }
282             roamingConsortiums = new long[oiCount];
283             if (oi1Length > 0 && roamingConsortiums.length > 0) {
284                 roamingConsortiums[0] =
285                         ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi1Length);
286             }
287             if (oi2Length > 0 && roamingConsortiums.length > 1) {
288                 roamingConsortiums[1] =
289                         ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi2Length);
290             }
291             if (oi3Length > 0 && roamingConsortiums.length > 2) {
292                 roamingConsortiums[2] =
293                         ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi3Length);
294             }
295         }
296     }
297 
298     public static class Vsa {
299         private static final int ANQP_DOMID_BIT = 0x04;
300 
301         public NetworkDetail.HSRelease hsRelease = null;
302         public int anqpDomainID = 0;    // No domain ID treated the same as a 0; unique info per AP.
303 
from(InformationElement ie)304         public void from(InformationElement ie) {
305             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
306             if (ie.bytes.length >= 5 && data.getInt() == Constants.HS20_FRAME_PREFIX) {
307                 int hsConf = data.get() & Constants.BYTE_MASK;
308                 switch ((hsConf >> 4) & Constants.NIBBLE_MASK) {
309                     case 0:
310                         hsRelease = NetworkDetail.HSRelease.R1;
311                         break;
312                     case 1:
313                         hsRelease = NetworkDetail.HSRelease.R2;
314                         break;
315                     default:
316                         hsRelease = NetworkDetail.HSRelease.Unknown;
317                         break;
318                 }
319                 if ((hsConf & ANQP_DOMID_BIT) != 0) {
320                     if (ie.bytes.length < 7) {
321                         throw new IllegalArgumentException(
322                                 "HS20 indication element too short: " + ie.bytes.length);
323                     }
324                     anqpDomainID = data.getShort() & Constants.SHORT_MASK;
325                 }
326             }
327         }
328     }
329 
330     /**
331      * This IE contained a bit field indicating the capabilities being advertised by the STA.
332      * The size of the bit field (number of bytes) is indicated by the |Length| field in the IE.
333      *
334      * Refer to Section 8.4.2.29 in IEEE 802.11-2012 Spec for capability associated with each
335      * bit.
336      *
337      * Here is the wire format of this IE:
338      * | Element ID | Length | Capabilities |
339      *       1           1          n
340      */
341     public static class ExtendedCapabilities {
342         private static final int RTT_RESP_ENABLE_BIT = 70;
343         private static final int SSID_UTF8_BIT = 48;
344 
345         public BitSet capabilitiesBitSet;
346 
347         /**
348          * @return true if SSID should be interpreted using UTF-8 encoding
349          */
isStrictUtf8()350         public boolean isStrictUtf8() {
351             return capabilitiesBitSet.get(SSID_UTF8_BIT);
352         }
353 
354         /**
355          * @return true if 802.11 MC RTT Response is enabled
356          */
is80211McRTTResponder()357         public boolean is80211McRTTResponder() {
358             return capabilitiesBitSet.get(RTT_RESP_ENABLE_BIT);
359         }
360 
ExtendedCapabilities()361         public ExtendedCapabilities() {
362             capabilitiesBitSet = new BitSet();
363         }
364 
ExtendedCapabilities(ExtendedCapabilities other)365         public ExtendedCapabilities(ExtendedCapabilities other) {
366             capabilitiesBitSet = other.capabilitiesBitSet;
367         }
368 
369         /**
370          * Parse an ExtendedCapabilities from the IE containing raw bytes.
371          *
372          * @param ie The Information element data
373          */
from(InformationElement ie)374         public void from(InformationElement ie) {
375             capabilitiesBitSet = BitSet.valueOf(ie.bytes);
376         }
377     }
378 
379     /**
380      * parse beacon to build the capabilities
381      *
382      * This class is used to build the capabilities string of the scan results coming
383      * from HAL. It parses the ieee beacon's capability field, WPA and RSNE IE as per spec,
384      * and builds the ScanResult.capabilities String in a way that mirrors the values returned
385      * by wpa_supplicant.
386      */
387     public static class Capabilities {
388         private static final int CAP_ESS_BIT_OFFSET = 0;
389         private static final int CAP_PRIVACY_BIT_OFFSET = 4;
390 
391         private static final int WPA_VENDOR_OUI_TYPE_ONE = 0x01f25000;
392         private static final int WPS_VENDOR_OUI_TYPE = 0x04f25000;
393         private static final short WPA_VENDOR_OUI_VERSION = 0x0001;
394         private static final short RSNE_VERSION = 0x0001;
395 
396         private static final int WPA_AKM_EAP = 0x01f25000;
397         private static final int WPA_AKM_PSK = 0x02f25000;
398 
399         private static final int WPA2_AKM_EAP = 0x01ac0f00;
400         private static final int WPA2_AKM_PSK = 0x02ac0f00;
401         private static final int WPA2_AKM_FT_EAP = 0x03ac0f00;
402         private static final int WPA2_AKM_FT_PSK = 0x04ac0f00;
403         private static final int WPA2_AKM_EAP_SHA256 = 0x05ac0f00;
404         private static final int WPA2_AKM_PSK_SHA256 = 0x06ac0f00;
405 
406         private static final int WPA_CIPHER_NONE = 0x00f25000;
407         private static final int WPA_CIPHER_TKIP = 0x02f25000;
408         private static final int WPA_CIPHER_CCMP = 0x04f25000;
409 
410         private static final int RSN_CIPHER_NONE = 0x00ac0f00;
411         private static final int RSN_CIPHER_TKIP = 0x02ac0f00;
412         private static final int RSN_CIPHER_CCMP = 0x04ac0f00;
413         private static final int RSN_CIPHER_NO_GROUP_ADDRESSED = 0x07ac0f00;
414 
415         public ArrayList<Integer> protocol;
416         public ArrayList<ArrayList<Integer>> keyManagement;
417         public ArrayList<ArrayList<Integer>> pairwiseCipher;
418         public ArrayList<Integer> groupCipher;
419         public boolean isESS;
420         public boolean isPrivacy;
421         public boolean isWPS;
422 
Capabilities()423         public Capabilities() {
424         }
425 
426         // RSNE format (size unit: byte)
427         //
428         // | Element ID | Length | Version | Group Data Cipher Suite |
429         //      1           1         2                 4
430         // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
431         //              2                            4 * m
432         // | AKM Suite Count | AKM Suite List | RSN Capabilities |
433         //          2               4 * n               2
434         // | PMKID Count | PMKID List | Group Management Cipher Suite |
435         //        2          16 * s                 4
436         //
437         // Note: InformationElement.bytes has 'Element ID' and 'Length'
438         //       stripped off already
parseRsnElement(InformationElement ie)439         private void parseRsnElement(InformationElement ie) {
440             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
441 
442             try {
443                 // version
444                 if (buf.getShort() != RSNE_VERSION) {
445                     // incorrect version
446                     return;
447                 }
448 
449                 // found the RSNE IE, hence start building the capability string
450                 protocol.add(ScanResult.PROTOCOL_WPA2);
451 
452                 // group data cipher suite
453                 groupCipher.add(parseRsnCipher(buf.getInt()));
454 
455                 // pairwise cipher suite count
456                 short cipherCount = buf.getShort();
457                 ArrayList<Integer> rsnPairwiseCipher = new ArrayList<>();
458                 // pairwise cipher suite list
459                 for (int i = 0; i < cipherCount; i++) {
460                     rsnPairwiseCipher.add(parseRsnCipher(buf.getInt()));
461                 }
462                 pairwiseCipher.add(rsnPairwiseCipher);
463 
464                 // AKM
465                 // AKM suite count
466                 short akmCount = buf.getShort();
467                 ArrayList<Integer> rsnKeyManagement = new ArrayList<>();
468 
469                 for (int i = 0; i < akmCount; i++) {
470                     int akm = buf.getInt();
471                     switch (akm) {
472                         case WPA2_AKM_EAP:
473                             rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP);
474                             break;
475                         case WPA2_AKM_PSK:
476                             rsnKeyManagement.add(ScanResult.KEY_MGMT_PSK);
477                             break;
478                         case WPA2_AKM_FT_EAP:
479                             rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_EAP);
480                             break;
481                         case WPA2_AKM_FT_PSK:
482                             rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_PSK);
483                             break;
484                         case WPA2_AKM_EAP_SHA256:
485                             rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP_SHA256);
486                             break;
487                         case WPA2_AKM_PSK_SHA256:
488                             rsnKeyManagement.add(ScanResult.KEY_MGMT_PSK_SHA256);
489                             break;
490                         default:
491                             // do nothing
492                             break;
493                     }
494                 }
495                 // Default AKM
496                 if (rsnKeyManagement.isEmpty()) {
497                     rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP);
498                 }
499                 keyManagement.add(rsnKeyManagement);
500             } catch (BufferUnderflowException e) {
501                 Log.e("IE_Capabilities", "Couldn't parse RSNE, buffer underflow");
502             }
503         }
504 
parseWpaCipher(int cipher)505         private static int parseWpaCipher(int cipher) {
506             switch (cipher) {
507                 case WPA_CIPHER_NONE:
508                     return ScanResult.CIPHER_NONE;
509                 case WPA_CIPHER_TKIP:
510                     return ScanResult.CIPHER_TKIP;
511                 case WPA_CIPHER_CCMP:
512                     return ScanResult.CIPHER_CCMP;
513                 default:
514                     Log.w("IE_Capabilities", "Unknown WPA cipher suite: "
515                             + Integer.toHexString(cipher));
516                     return ScanResult.CIPHER_NONE;
517             }
518         }
519 
parseRsnCipher(int cipher)520         private static int parseRsnCipher(int cipher) {
521             switch (cipher) {
522                 case RSN_CIPHER_NONE:
523                     return ScanResult.CIPHER_NONE;
524                 case RSN_CIPHER_TKIP:
525                     return ScanResult.CIPHER_TKIP;
526                 case RSN_CIPHER_CCMP:
527                     return ScanResult.CIPHER_CCMP;
528                 case RSN_CIPHER_NO_GROUP_ADDRESSED:
529                     return ScanResult.CIPHER_NO_GROUP_ADDRESSED;
530                 default:
531                     Log.w("IE_Capabilities", "Unknown RSN cipher suite: "
532                             + Integer.toHexString(cipher));
533                     return ScanResult.CIPHER_NONE;
534             }
535         }
536 
isWpsElement(InformationElement ie)537         private static boolean isWpsElement(InformationElement ie) {
538             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
539             try {
540                 // WPS OUI and type
541                 return (buf.getInt() == WPS_VENDOR_OUI_TYPE);
542             } catch (BufferUnderflowException e) {
543                 Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
544                 return false;
545             }
546         }
547 
isWpaOneElement(InformationElement ie)548         private static boolean isWpaOneElement(InformationElement ie) {
549             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
550 
551             try {
552                 // WPA OUI and type
553                 return (buf.getInt() == WPA_VENDOR_OUI_TYPE_ONE);
554             } catch (BufferUnderflowException e) {
555                 Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
556                 return false;
557             }
558         }
559 
560         // WPA type 1 format (size unit: byte)
561         //
562         // | Element ID | Length | OUI | Type | Version |
563         //      1           1       3     1        2
564         // | Group Data Cipher Suite |
565         //             4
566         // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
567         //              2                            4 * m
568         // | AKM Suite Count | AKM Suite List |
569         //          2               4 * n
570         //
571         // Note: InformationElement.bytes has 'Element ID' and 'Length'
572         //       stripped off already
573         //
parseWpaOneElement(InformationElement ie)574         private void parseWpaOneElement(InformationElement ie) {
575             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
576 
577             try {
578                 // skip WPA OUI and type parsing. isWpaOneElement() should have
579                 // been called for verification before we reach here.
580                 buf.getInt();
581 
582                 // version
583                 if (buf.getShort() != WPA_VENDOR_OUI_VERSION)  {
584                     // incorrect version
585                     return;
586                 }
587 
588                 // start building the string
589                 protocol.add(ScanResult.PROTOCOL_WPA);
590 
591                 // group data cipher suite
592                 groupCipher.add(parseWpaCipher(buf.getInt()));
593 
594                 // pairwise cipher suite count
595                 short cipherCount = buf.getShort();
596                 ArrayList<Integer> wpaPairwiseCipher = new ArrayList<>();
597                 // pairwise chipher suite list
598                 for (int i = 0; i < cipherCount; i++) {
599                     wpaPairwiseCipher.add(parseWpaCipher(buf.getInt()));
600                 }
601                 pairwiseCipher.add(wpaPairwiseCipher);
602 
603                 // AKM
604                 // AKM suite count
605                 short akmCount = buf.getShort();
606                 ArrayList<Integer> wpaKeyManagement = new ArrayList<>();
607 
608                 // AKM suite list
609                 for (int i = 0; i < akmCount; i++) {
610                     int akm = buf.getInt();
611                     switch (akm) {
612                         case WPA_AKM_EAP:
613                             wpaKeyManagement.add(ScanResult.KEY_MGMT_EAP);
614                             break;
615                         case WPA_AKM_PSK:
616                             wpaKeyManagement.add(ScanResult.KEY_MGMT_PSK);
617                             break;
618                         default:
619                             // do nothing
620                             break;
621                     }
622                 }
623                 // Default AKM
624                 if (wpaKeyManagement.isEmpty()) {
625                     wpaKeyManagement.add(ScanResult.KEY_MGMT_EAP);
626                 }
627                 keyManagement.add(wpaKeyManagement);
628             } catch (BufferUnderflowException e) {
629                 Log.e("IE_Capabilities", "Couldn't parse type 1 WPA, buffer underflow");
630             }
631         }
632 
633         /**
634          * Parse the Information Element and the 16-bit Capability Information field
635          * to build the InformationElemmentUtil.capabilities object.
636          *
637          * @param ies -- Information Element array
638          * @param beaconCap -- 16-bit Beacon Capability Information field
639          */
640 
from(InformationElement[] ies, BitSet beaconCap)641         public void from(InformationElement[] ies, BitSet beaconCap) {
642             protocol = new ArrayList<Integer>();
643             keyManagement = new ArrayList<ArrayList<Integer>>();
644             groupCipher = new ArrayList<Integer>();
645             pairwiseCipher = new ArrayList<ArrayList<Integer>>();
646 
647             if (ies == null || beaconCap == null) {
648                 return;
649             }
650             isESS = beaconCap.get(CAP_ESS_BIT_OFFSET);
651             isPrivacy = beaconCap.get(CAP_PRIVACY_BIT_OFFSET);
652             for (InformationElement ie : ies) {
653                 if (ie.id == InformationElement.EID_RSN) {
654                     parseRsnElement(ie);
655                 }
656 
657                 if (ie.id == InformationElement.EID_VSA) {
658                     if (isWpaOneElement(ie)) {
659                         parseWpaOneElement(ie);
660                     }
661                     if (isWpsElement(ie)) {
662                         // TODO(b/62134557): parse WPS IE to provide finer granularity information.
663                         isWPS = true;
664                     }
665                 }
666             }
667         }
668 
protocolToString(int protocol)669         private String protocolToString(int protocol) {
670             switch (protocol) {
671                 case ScanResult.PROTOCOL_NONE:
672                     return "None";
673                 case ScanResult.PROTOCOL_WPA:
674                     return "WPA";
675                 case ScanResult.PROTOCOL_WPA2:
676                     return "WPA2";
677                 default:
678                     return "?";
679             }
680         }
681 
keyManagementToString(int akm)682         private String keyManagementToString(int akm) {
683             switch (akm) {
684                 case ScanResult.KEY_MGMT_NONE:
685                     return "None";
686                 case ScanResult.KEY_MGMT_PSK:
687                     return "PSK";
688                 case ScanResult.KEY_MGMT_EAP:
689                     return "EAP";
690                 case ScanResult.KEY_MGMT_FT_EAP:
691                     return "FT/EAP";
692                 case ScanResult.KEY_MGMT_FT_PSK:
693                     return "FT/PSK";
694                 case ScanResult.KEY_MGMT_EAP_SHA256:
695                     return "EAP-SHA256";
696                 case ScanResult.KEY_MGMT_PSK_SHA256:
697                     return "PSK-SHA256";
698                 default:
699                     return "?";
700             }
701         }
702 
cipherToString(int cipher)703         private String cipherToString(int cipher) {
704             switch (cipher) {
705                 case ScanResult.CIPHER_NONE:
706                     return "None";
707                 case ScanResult.CIPHER_CCMP:
708                     return "CCMP";
709                 case ScanResult.CIPHER_TKIP:
710                     return "TKIP";
711                 default:
712                     return "?";
713             }
714         }
715 
716         /**
717          * Build the ScanResult.capabilities String.
718          *
719          * @return security string that mirrors what wpa_supplicant generates
720          */
generateCapabilitiesString()721         public String generateCapabilitiesString() {
722             String capabilities = "";
723             // private Beacon without an RSNE or WPA IE, hence WEP0
724             boolean isWEP = (protocol.isEmpty()) && isPrivacy;
725 
726             if (isWEP) {
727                 capabilities += "[WEP]";
728             }
729             for (int i = 0; i < protocol.size(); i++) {
730                 capabilities += "[" + protocolToString(protocol.get(i));
731                 if (i < keyManagement.size()) {
732                     for (int j = 0; j < keyManagement.get(i).size(); j++) {
733                         capabilities += ((j == 0) ? "-" : "+")
734                                 + keyManagementToString(keyManagement.get(i).get(j));
735                     }
736                 }
737                 if (i < pairwiseCipher.size()) {
738                     for (int j = 0; j < pairwiseCipher.get(i).size(); j++) {
739                         capabilities += ((j == 0) ? "-" : "+")
740                                 + cipherToString(pairwiseCipher.get(i).get(j));
741                     }
742                 }
743                 capabilities += "]";
744             }
745             if (isESS) {
746                 capabilities += "[ESS]";
747             }
748             if (isWPS) {
749                 capabilities += "[WPS]";
750             }
751 
752             return capabilities;
753         }
754     }
755 
756     /**
757      * Parser for the Traffic Indication Map (TIM) Information Element (EID 5). This element will
758      * only be present in scan results that are derived from a Beacon Frame, not from the more
759      * plentiful probe responses. Call 'isValid()' after parsing, to ensure the results are correct.
760      */
761     public static class TrafficIndicationMap {
762         private static final int MAX_TIM_LENGTH = 254;
763         private boolean mValid = false;
764         public int mLength = 0;
765         public int mDtimCount = -1;
766         //Negative DTIM Period means no TIM element was given this frame.
767         public int mDtimPeriod = -1;
768         public int mBitmapControl = 0;
769 
770         /**
771          * Is this a valid TIM information element.
772          */
isValid()773         public boolean isValid() {
774             return mValid;
775         }
776 
777         // Traffic Indication Map format (size unit: byte)
778         //
779         //| ElementID | Length | DTIM Count | DTIM Period | BitmapControl | Partial Virtual Bitmap |
780         //      1          1          1            1               1                1 - 251
781         //
782         // Note: InformationElement.bytes has 'Element ID' and 'Length'
783         //       stripped off already
784         //
from(InformationElement ie)785         public void from(InformationElement ie) {
786             mValid = false;
787             if (ie == null || ie.bytes == null) return;
788             mLength = ie.bytes.length;
789             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
790             try {
791                 mDtimCount = data.get() & Constants.BYTE_MASK;
792                 mDtimPeriod = data.get() & Constants.BYTE_MASK;
793                 mBitmapControl = data.get() & Constants.BYTE_MASK;
794                 //A valid TIM element must have atleast one more byte
795                 data.get();
796             } catch (BufferUnderflowException e) {
797                 return;
798             }
799             if (mLength <= MAX_TIM_LENGTH && mDtimPeriod > 0) {
800                 mValid = true;
801             }
802         }
803     }
804 
805     /**
806      * This util class determines the 802.11 standard (a/b/g/n/ac) being used
807      */
808     public static class WifiMode {
809         public static final int MODE_UNDEFINED = 0; // Unknown/undefined
810         public static final int MODE_11A = 1;       // 802.11a
811         public static final int MODE_11B = 2;       // 802.11b
812         public static final int MODE_11G = 3;       // 802.11g
813         public static final int MODE_11N = 4;       // 802.11n
814         public static final int MODE_11AC = 5;      // 802.11ac
815         //<TODO> add support for 802.11ad and be more selective instead of defaulting to 11A
816 
817         /**
818          * Use frequency, max supported rate, and the existence of VHT, HT & ERP fields in scan
819          * scan result to determine the 802.11 Wifi standard being used.
820          */
determineMode(int frequency, int maxRate, boolean foundVht, boolean foundHt, boolean foundErp)821         public static int determineMode(int frequency, int maxRate, boolean foundVht,
822                 boolean foundHt, boolean foundErp) {
823             if (foundVht) {
824                 return MODE_11AC;
825             } else if (foundHt) {
826                 return MODE_11N;
827             } else if (foundErp) {
828                 return MODE_11G;
829             } else if (frequency < 3000) {
830                 if (maxRate < 24000000) {
831                     return MODE_11B;
832                 } else {
833                     return MODE_11G;
834                 }
835             } else {
836                 return MODE_11A;
837             }
838         }
839 
840         /**
841          * Map the wifiMode integer to its type, and output as String MODE_11<A/B/G/N/AC>
842          */
toString(int mode)843         public static String toString(int mode) {
844             switch(mode) {
845                 case MODE_11A:
846                     return "MODE_11A";
847                 case MODE_11B:
848                     return "MODE_11B";
849                 case MODE_11G:
850                     return "MODE_11G";
851                 case MODE_11N:
852                     return "MODE_11N";
853                 case MODE_11AC:
854                     return "MODE_11AC";
855                 default:
856                     return "MODE_UNDEFINED";
857             }
858         }
859     }
860 
861     /**
862      * Parser for both the Supported Rates & Extended Supported Rates Information Elements
863      */
864     public static class SupportedRates {
865         public static final int MASK = 0x7F; // 0111 1111
866         public boolean mValid = false;
867         public ArrayList<Integer> mRates;
868 
SupportedRates()869         public SupportedRates() {
870             mRates = new ArrayList<Integer>();
871         }
872 
873         /**
874          * Is this a valid Supported Rates information element.
875          */
isValid()876         public boolean isValid() {
877             return mValid;
878         }
879 
880         /**
881          * get the Rate in bits/s from associated byteval
882          */
getRateFromByte(int byteVal)883         public static int getRateFromByte(int byteVal) {
884             byteVal &= MASK;
885             switch(byteVal) {
886                 case 2:
887                     return 1000000;
888                 case 4:
889                     return 2000000;
890                 case 11:
891                     return 5500000;
892                 case 12:
893                     return 6000000;
894                 case 18:
895                     return 9000000;
896                 case 22:
897                     return 11000000;
898                 case 24:
899                     return 12000000;
900                 case 36:
901                     return 18000000;
902                 case 44:
903                     return 22000000;
904                 case 48:
905                     return 24000000;
906                 case 66:
907                     return 33000000;
908                 case 72:
909                     return 36000000;
910                 case 96:
911                     return 48000000;
912                 case 108:
913                     return 54000000;
914                 default:
915                     //ERROR UNKNOWN RATE
916                     return -1;
917             }
918         }
919 
920         // Supported Rates format (size unit: byte)
921         //
922         //| ElementID | Length | Supported Rates  [7 Little Endian Info bits - 1 Flag bit]
923         //      1          1          1 - 8
924         //
925         // Note: InformationElement.bytes has 'Element ID' and 'Length'
926         //       stripped off already
927         //
from(InformationElement ie)928         public void from(InformationElement ie) {
929             mValid = false;
930             if (ie == null || ie.bytes == null || ie.bytes.length > 8 || ie.bytes.length < 1)  {
931                 return;
932             }
933             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
934             try {
935                 for (int i = 0; i < ie.bytes.length; i++) {
936                     int rate = getRateFromByte(data.get());
937                     if (rate > 0) {
938                         mRates.add(rate);
939                     } else {
940                         return;
941                     }
942                 }
943             } catch (BufferUnderflowException e) {
944                 return;
945             }
946             mValid = true;
947             return;
948         }
949 
950         /**
951          * Lists the rates in a human readable string
952          */
toString()953         public String toString() {
954             StringBuilder sbuf = new StringBuilder();
955             for (Integer rate : mRates) {
956                 sbuf.append(String.format("%.1f", (double) rate / 1000000) + ", ");
957             }
958             return sbuf.toString();
959         }
960     }
961 }
962