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.hardware.wifi.WifiBand;
19 import android.net.MacAddress;
20 import android.net.wifi.MloLink;
21 import android.net.wifi.ScanResult;
22 import android.net.wifi.ScanResult.InformationElement;
23 import android.net.wifi.WifiAnnotations.Cipher;
24 import android.net.wifi.WifiAnnotations.KeyMgmt;
25 import android.net.wifi.WifiAnnotations.Protocol;
26 import android.net.wifi.WifiScanner;
27 import android.net.wifi.nl80211.NativeScanResult;
28 import android.net.wifi.nl80211.WifiNl80211Manager;
29 import android.net.wifi.util.HexEncoding;
30 import android.util.Log;
31 import android.util.SparseIntArray;
32 
33 import com.android.server.wifi.ByteBufferReader;
34 import com.android.server.wifi.MboOceConstants;
35 import com.android.server.wifi.hotspot2.NetworkDetail;
36 import com.android.server.wifi.hotspot2.anqp.Constants;
37 
38 import java.io.ByteArrayOutputStream;
39 import java.io.IOException;
40 import java.nio.BufferUnderflowException;
41 import java.nio.ByteBuffer;
42 import java.nio.ByteOrder;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.BitSet;
46 import java.util.List;
47 import java.util.Locale;
48 
49 public class InformationElementUtil {
50     private static final String TAG = "InformationElementUtil";
51     private static final boolean DBG = false;
52 
53     /**
54      * 6 GHz Access Point type as encoded in the Regulatory Info subfield in the Control field of
55      * the 6 GHz Operation Information field of the HE Operation element.
56      * Reference E.2.7 6 GHz band (IEEE Std 802.11ax-2021).
57      */
58     public enum ApType6GHz {
59         AP_TYPE_6GHZ_UNKNOWN,
60         AP_TYPE_6GHZ_INDOOR,
61         AP_TYPE_6GHZ_STANDARD_POWER,
62     }
63 
64     /**
65      * Defragment Element class
66      *
67      * IEEE Std 802.11™‐2020, Section: 10.28.11 Element fragmentation describes a fragmented sub
68      * element as,
69      *    | SubEID | Len | Data | FragId | Len | Data | FragId | Len| Data ...
70      * Octets: 1     1     255     1        1     255     1       1     m
71      * Values: eid   255         fid       255           fid      m
72      *
73      */
74     private static class DefragmentElement {
75         /** Defagmented element bytes */
76         public byte[] bytes;
77         /** Bytes read to defragment the fragmented element */
78         public int bytesRead = 0;
79 
80         public static final int FRAG_MAX_LEN = 255;
81         public static final int FRAGMENT_ELEMENT_EID = 242;
82 
DefragmentElement(byte[] bytes, int start, int eid, int fid)83         DefragmentElement(byte[] bytes, int start, int eid, int fid) {
84             if (bytes == null) return;
85             ByteArrayOutputStream defrag = new ByteArrayOutputStream();
86             ByteBuffer element = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
87             element.position(start);
88             try {
89                 if ((element.get() & Constants.BYTE_MASK) != eid) return;
90                 // Add EID, 255 as the parser expects the element header.
91                 defrag.write(eid);
92                 defrag.write(255);
93                 int fragLen, fragId;
94                 do {
95                     fragLen = element.get() & Constants.BYTE_MASK;
96                     byte[] b = new byte[fragLen];
97                     element.get(b);
98                     defrag.write(b);
99                     // Mark the position to undo the extra read.
100                     element.mark();
101                     if (element.remaining() <= 0) break;
102                     fragId = element.get() & Constants.BYTE_MASK;
103                 } while (fragLen == FRAG_MAX_LEN && fragId == fid);
104                 // Reset the extra get.
105                 element.reset();
106             } catch (IOException e) {
107                 if (DBG) {
108                     Log.w(TAG, "Failed to defragment sub element: " + e.getMessage());
109                 }
110                 return;
111             } catch (IndexOutOfBoundsException e) {
112                 if (DBG) {
113                     Log.e(TAG, "Failed to defragment sub element: " + e.getMessage());
114                 }
115                 return;
116             } catch (BufferUnderflowException e) {
117                 if (DBG) {
118                     Log.w(TAG, "Failed to defragment sub element: " + e.getMessage());
119                 }
120                 return;
121             }
122 
123             this.bytes = defrag.toByteArray();
124             bytesRead = element.position() - start;
125         }
126     }
127 
128     /** Converts InformationElement to hex string */
toHexString(InformationElement e)129     public static String toHexString(InformationElement e) {
130         StringBuilder sb = new StringBuilder();
131         sb.append(HexEncoding.encode(new byte[]{(byte) e.id}));
132         if (e.id == InformationElement.EID_EXTENSION_PRESENT) {
133             sb.append(HexEncoding.encode(new byte[]{(byte) e.idExt}));
134         }
135         sb.append(HexEncoding.encode(new byte[]{(byte) e.bytes.length}));
136         sb.append(HexEncoding.encode(e.bytes));
137         return sb.toString();
138     }
139 
140     /** Parses information elements from hex string */
parseInformationElements(String data)141     public static InformationElement[] parseInformationElements(String data) {
142         if (data == null) {
143             return new InformationElement[0];
144         }
145         return parseInformationElements(HexEncoding.decode(data));
146     }
147 
isFragmentable(int eid, int eidExt)148     private static boolean isFragmentable(int eid, int eidExt) {
149         // Refer IEE802.11BE D2.3, Section 9.4.2 Elements
150         return ((eid == InformationElement.EID_EXTENSION_PRESENT)
151                 && (eidExt == InformationElement.EID_EXT_MULTI_LINK));
152     }
153 
parseInformationElements(byte[] bytes)154     public static InformationElement[] parseInformationElements(byte[] bytes) {
155         if (bytes == null) {
156             return new InformationElement[0];
157         }
158         ByteBuffer data = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
159 
160         ArrayList<InformationElement> infoElements = new ArrayList<>();
161         boolean found_ssid = false;
162         while (data.remaining() > 1) {
163             // Mark the start of the data
164             data.mark();
165             int eid = data.get() & Constants.BYTE_MASK;
166             int eidExt = 0;
167             int elementLength = data.get() & Constants.BYTE_MASK;
168             DefragmentElement defrag = null;
169 
170             if (elementLength > data.remaining() || (eid == InformationElement.EID_SSID
171                     && found_ssid)) {
172                 // APs often pad the data with bytes that happen to match that of the EID_SSID
173                 // marker.  This is not due to a known issue for APs to incorrectly send the SSID
174                 // name multiple times.
175                 break;
176             }
177             if (eid == InformationElement.EID_SSID) {
178                 found_ssid = true;
179             } else if (eid == InformationElement.EID_EXTENSION_PRESENT) {
180                 if (elementLength == 0) {
181                     // Malformed IE, skipping
182                     break;
183                 }
184                 eidExt = data.get() & Constants.BYTE_MASK;
185                 if (isFragmentable(eid, eidExt)
186                         && elementLength == DefragmentElement.FRAG_MAX_LEN) {
187                     // Fragmented IE. Reset the position to head to defragment.
188                     data.reset();
189                     defrag =
190                             new DefragmentElement(
191                                     bytes,
192                                     data.position(),
193                                     eid,
194                                     DefragmentElement.FRAGMENT_ELEMENT_EID);
195                 }
196                 elementLength--;
197             }
198 
199             InformationElement ie = new InformationElement();
200             ie.id = eid;
201             ie.idExt = eidExt;
202             if (defrag != null) {
203                 if (defrag.bytesRead == 0) {
204                     // Malformed IE skipping
205                     break;
206                 }
207                 // Skip first three bytes: eid, len, eidExt as it is already processed.
208                 ie.bytes = Arrays.copyOfRange(defrag.bytes, 3, defrag.bytes.length);
209                 int newPosition = data.position() + defrag.bytesRead;
210                 data.position(newPosition);
211             } else {
212                 ie.bytes = new byte[elementLength];
213                 data.get(ie.bytes);
214             }
215             infoElements.add(ie);
216         }
217         return infoElements.toArray(new InformationElement[infoElements.size()]);
218     }
219 
220     /**
221      * Parse and retrieve the Roaming Consortium Information Element from the list of IEs.
222      *
223      * @param ies List of IEs to retrieve from
224      * @return {@link RoamingConsortium}
225      */
getRoamingConsortiumIE(InformationElement[] ies)226     public static RoamingConsortium getRoamingConsortiumIE(InformationElement[] ies) {
227         RoamingConsortium roamingConsortium = new RoamingConsortium();
228         if (ies != null) {
229             for (InformationElement ie : ies) {
230                 if (ie.id == InformationElement.EID_ROAMING_CONSORTIUM) {
231                     try {
232                         roamingConsortium.from(ie);
233                     } catch (RuntimeException e) {
234                         Log.e(TAG, "Failed to parse Roaming Consortium IE: " + e.getMessage());
235                     }
236                 }
237             }
238         }
239         return roamingConsortium;
240     }
241 
242     /**
243      * Parse and retrieve the Hotspot 2.0 Vendor Specific Information Element from the list of IEs.
244      *
245      * @param ies List of IEs to retrieve from
246      * @return {@link Vsa}
247      */
getHS2VendorSpecificIE(InformationElement[] ies)248     public static Vsa getHS2VendorSpecificIE(InformationElement[] ies) {
249         Vsa vsa = new Vsa();
250         if (ies != null) {
251             for (InformationElement ie : ies) {
252                 if (ie.id == InformationElement.EID_VSA) {
253                     try {
254                         vsa.from(ie);
255                     } catch (RuntimeException e) {
256                         Log.e(TAG, "Failed to parse Vendor Specific IE: " + e.getMessage());
257                     }
258                 }
259             }
260         }
261         return vsa;
262     }
263 
264     /**
265      * Parse and retrieve all Vendor Specific Information Elements from the list of IEs.
266      *
267      * @param ies List of IEs to retrieve from
268      * @return List of {@link Vsa}
269      */
getVendorSpecificIE(InformationElement[] ies)270     public static List<Vsa> getVendorSpecificIE(InformationElement[] ies) {
271         List<Vsa> vsas = new ArrayList<>();
272         if (ies != null) {
273             for (InformationElement ie : ies) {
274                 if (ie.id == InformationElement.EID_VSA) {
275                     try {
276                         Vsa vsa = new Vsa();
277                         vsa.from(ie);
278                         vsas.add(vsa);
279                     } catch (RuntimeException e) {
280                         Log.e(TAG, "Failed to parse Vendor Specific IE: " + e.getMessage());
281                     }
282                 }
283             }
284         }
285         return vsas;
286     }
287 
288     /**
289      * Parse and retrieve the Interworking information element from the list of IEs.
290      *
291      * @param ies List of IEs to retrieve from
292      * @return {@link Interworking}
293      */
getInterworkingIE(InformationElement[] ies)294     public static Interworking getInterworkingIE(InformationElement[] ies) {
295         Interworking interworking = new Interworking();
296         if (ies != null) {
297             for (InformationElement ie : ies) {
298                 if (ie.id == InformationElement.EID_INTERWORKING) {
299                     try {
300                         interworking.from(ie);
301                     } catch (RuntimeException e) {
302                         Log.e(TAG, "Failed to parse Interworking IE: " + e.getMessage());
303                     }
304                 }
305             }
306         }
307         return interworking;
308     }
309 
310     public static class BssLoad {
311         public static final int INVALID = -1;
312         public static final int MAX_CHANNEL_UTILIZATION = 255;
313         public static final int MIN_CHANNEL_UTILIZATION = 0;
314         public static final int CHANNEL_UTILIZATION_SCALE = 256;
315         public int stationCount = INVALID;
316         public int channelUtilization = INVALID;
317         public int capacity = INVALID;
318 
from(InformationElement ie)319         public void from(InformationElement ie) {
320             if (ie.id != InformationElement.EID_BSS_LOAD) {
321                 throw new IllegalArgumentException("Element id is not BSS_LOAD, : " + ie.id);
322             }
323             if (ie.bytes.length != 5) {
324                 throw new IllegalArgumentException("BSS Load element length is not 5: "
325                                                    + ie.bytes.length);
326             }
327             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
328             stationCount = data.getShort() & Constants.SHORT_MASK;
329             channelUtilization = data.get() & Constants.BYTE_MASK;
330             capacity = data.getShort() & Constants.SHORT_MASK;
331         }
332     }
333 
334     /**
335      * Rnr: represents the Reduced Neighbor Report (RNR) IE
336      * As described by IEEE 802.11 Specification Section 9.4.2.170
337      */
338     public static class Rnr {
339         private static final int TBTT_INFO_COUNT_OFFSET = 0;
340         private static final int TBTT_INFO_COUNT_MASK = 0xF0;
341         private static final int TBTT_INFO_COUNT_SHIFT = 4;
342         private static final int TBTT_INFO_LENGTH_OFFSET = 1;
343         private static final int TBTT_INFO_OP_CLASS_OFFSET = 2;
344         private static final int TBTT_INFO_CHANNEL_OFFSET = 3;
345         private static final int TBTT_INFO_SET_START_OFFSET = 4;
346         private static final int MLD_ID_START_OFFSET = 0;
347         private static final int LINK_ID_START_OFFSET = 1;
348         private static final int LINK_ID_MASK = 0x0F;
349 
350         private boolean mPresent = false;
351         private List<MloLink> mAffiliatedMloLinks = new ArrayList<>();
352 
353         /**
354          * Returns whether the RNR Information Element is present.
355          */
isPresent()356         public boolean isPresent() {
357             return mPresent;
358         }
359 
360         /**
361          * Returns the list of the affiliated MLO links
362          */
getAffiliatedMloLinks()363         public List<MloLink> getAffiliatedMloLinks() {
364             return mAffiliatedMloLinks;
365         }
366 
367         /**
368          * Parse RNR Operation IE
369          *
370          * RNR format as described in IEEE 802.11 specs, Section 9.4.2.170
371          *
372          *              | ElementID | Length | Neighbor AP Information Fields |
373          * Octets:            1          1             variable
374          *
375          *
376          * Where Neighbor AP Information Fields is one or more Neighbor AP Information Field as,
377          *
378          *               | Header | Operating Class | Channel | TBTT Information Set |
379          * Octets:            2            1            1           variable
380          *
381          *
382          * The Header subfield is described as follows,
383          *
384          *            | Type  | Filtered AP | Reserved | Count | Length |
385          * Bits:         2          1           1          4       8
386          *
387          *
388          * Information Set is one or more TBTT Information fields, which is described as,
389          *
390          *         | Offset | BSSID  | Short-SSID | BSS Params | 20MHz PSD | MLD Params|
391          * Octets:     1      0 or 6    0 or 4        0 or 1      0 or 1      0 or 3
392          *
393          *
394          * The MLD Params are described as,
395          *       | MLD ID | Link ID | BSS Change Count | Reserved |
396          * Bits:     8        4              8              4
397          *
398          * Note: InformationElement.bytes has 'Element ID' and 'Length'
399          *       stripped off already
400          *
401          */
from(InformationElement ie)402         public void from(InformationElement ie) {
403             if (ie.id != InformationElement.EID_RNR) {
404                 throw new IllegalArgumentException("Element id is not RNR");
405             }
406 
407             int startOffset = 0;
408             while (ie.bytes.length > startOffset + TBTT_INFO_SET_START_OFFSET) {
409                 int tbttInfoCount =
410                         ie.bytes[startOffset + TBTT_INFO_COUNT_OFFSET] & TBTT_INFO_COUNT_MASK;
411                 tbttInfoCount >>= TBTT_INFO_COUNT_SHIFT;
412                 tbttInfoCount++;
413 
414                 int tbttInfoLen =
415                         ie.bytes[startOffset + TBTT_INFO_LENGTH_OFFSET] & Constants.BYTE_MASK;
416                 int tbttInfoStartOffset = startOffset + TBTT_INFO_SET_START_OFFSET;
417 
418                 // Only handle TBTT info with MLD Info
419                 if (tbttInfoLen == 4 || tbttInfoLen >= 16) {
420                     // Make sure length allows for this TBTT Info
421                     if (ie.bytes.length < startOffset + TBTT_INFO_SET_START_OFFSET
422                             + tbttInfoLen * tbttInfoCount) {
423                         if (DBG) {
424                             Log.w(TAG, "Invalid RNR len, not enough for TBTT Info: "
425                                     + ie.bytes.length + "/" + tbttInfoLen + "/" + tbttInfoCount);
426                         }
427                         // Skipping parsing of the IE
428                         return;
429                     }
430 
431                     int mldStartOffset;
432                     int bssidOffset;
433 
434                     if (tbttInfoLen == 4) {
435                         mldStartOffset = 1;
436                         bssidOffset = -1;
437                     } else {
438                         mldStartOffset = 13;
439                         bssidOffset = 1;
440                     }
441 
442                     int opClass = ie.bytes[startOffset + TBTT_INFO_OP_CLASS_OFFSET]
443                             & Constants.BYTE_MASK;
444                     int channel = ie.bytes[startOffset + TBTT_INFO_CHANNEL_OFFSET]
445                             & Constants.BYTE_MASK;
446                     int band = ScanResult.getBandFromOpClass(opClass, channel);
447                     if (band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
448                         if (DBG) {
449                             Log.w(TAG, "Invalid op class/channel in RNR TBTT Info: "
450                                     + opClass + "/" + channel);
451                         }
452                         // Skipping parsing of the IE
453                         return;
454                     }
455                     for (int i = 0; i < tbttInfoCount; i++) {
456                         int mldId = ie.bytes[tbttInfoStartOffset + mldStartOffset
457                                 + MLD_ID_START_OFFSET] & Constants.BYTE_MASK;
458                         if (mldId == 0) {
459                             //This is an affiliated link
460                             int linkId = ie.bytes[tbttInfoStartOffset + mldStartOffset
461                                     + LINK_ID_START_OFFSET] & LINK_ID_MASK;
462                             MloLink link = new MloLink();
463                             link.setLinkId(linkId);
464                             link.setBand(band);
465                             link.setChannel(channel);
466                             if (bssidOffset != -1) {
467                                 int macAddressStart = tbttInfoStartOffset + bssidOffset;
468                                 link.setApMacAddress(MacAddress.fromBytes(
469                                         Arrays.copyOfRange(ie.bytes,
470                                                 macAddressStart, macAddressStart + 6)));
471                             }
472 
473                             mAffiliatedMloLinks.add(link);
474                         }
475                         tbttInfoStartOffset += tbttInfoLen;
476                     }
477                 }
478 
479                 startOffset += TBTT_INFO_SET_START_OFFSET + (tbttInfoCount * tbttInfoLen);
480             }
481 
482             // Done with parsing
483             mPresent = true;
484         }
485     }
486 
487     public static class HtOperation {
488         private static final int HT_OPERATION_IE_LEN = 22;
489         private boolean mPresent = false;
490         private int mSecondChannelOffset = 0;
491 
492         /**
493          * returns if HT Operation IE present in the message.
494          */
isPresent()495         public boolean isPresent() {
496             return mPresent;
497         }
498 
499         /**
500          * Returns channel width if it is 20 or 40MHz
501          * Results will be invalid if channel width greater than 40MHz
502          * So caller should only call this method if VHT Operation IE is not present,
503          * or if VhtOperation.getChannelWidth() returns ScanResult.UNSPECIFIED.
504          */
getChannelWidth()505         public int getChannelWidth() {
506             if (mSecondChannelOffset != 0) {
507                 return ScanResult.CHANNEL_WIDTH_40MHZ;
508             } else {
509                 return ScanResult.CHANNEL_WIDTH_20MHZ;
510             }
511         }
512 
513         /**
514          * Returns channel Center frequency (for 20/40 MHz channels only)
515          * Results will be invalid for larger channel width,
516          * So, caller should only call this method if VHT Operation IE is not present,
517          * or if VhtOperation.getChannelWidth() returns ScanResult.UNSPECIFIED.
518          */
getCenterFreq0(int primaryFrequency)519         public int getCenterFreq0(int primaryFrequency) {
520             if (mSecondChannelOffset != 0) {
521                 //40 MHz
522                 if (mSecondChannelOffset == 1) {
523                     return primaryFrequency + 10;
524                 } else if (mSecondChannelOffset == 3) {
525                     return primaryFrequency - 10;
526                 } else {
527                     Log.e("HtOperation", "Error on secondChannelOffset: " + mSecondChannelOffset);
528                     return 0;
529                 }
530             } else {
531                 //20 MHz
532                 return primaryFrequency;
533             }
534         }
535 
536         /**
537          * Parse the HT Operation IE to read the fields of interest.
538          */
from(InformationElement ie)539         public void from(InformationElement ie) {
540             if (ie.id != InformationElement.EID_HT_OPERATION) {
541                 throw new IllegalArgumentException("Element id is not HT_OPERATION, : " + ie.id);
542             }
543             if (ie.bytes.length < HT_OPERATION_IE_LEN) {
544                 throw new IllegalArgumentException("Invalid HT_OPERATION len: " + ie.bytes.length);
545             }
546             mPresent = true;
547             mSecondChannelOffset = ie.bytes[1] & 0x3;
548         }
549     }
550 
551     public static class VhtOperation {
552         private static final int VHT_OPERATION_IE_LEN = 5;
553         private boolean mPresent = false;
554         private int mChannelMode = 0;
555         private int mCenterFreqIndex1 = 0;
556         private int mCenterFreqIndex2 = 0;
557 
558         /**
559          * returns if VHT Operation IE present in the message.
560          */
isPresent()561         public boolean isPresent() {
562             return mPresent;
563         }
564 
565         /**
566          * Returns channel width if it is above 40MHz,
567          * otherwise, returns {@link ScanResult.UNSPECIFIED} to indicate that
568          * channel width should be obtained from the HT Operation IE via
569          * HtOperation.getChannelWidth().
570          */
getChannelWidth()571         public int getChannelWidth() {
572             if (mChannelMode == 0) {
573                 // 20 or 40MHz
574                 return ScanResult.UNSPECIFIED;
575             } else if (mCenterFreqIndex2 == 0) {
576                 // No secondary channel
577                 return ScanResult.CHANNEL_WIDTH_80MHZ;
578             } else if (Math.abs(mCenterFreqIndex2 - mCenterFreqIndex1) == 8) {
579                 // Primary and secondary channels adjacent
580                 return ScanResult.CHANNEL_WIDTH_160MHZ;
581             } else {
582                 // Primary and secondary channels not adjacent
583                 return ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ;
584             }
585         }
586 
587         /**
588          * Returns center frequency of primary channel (if channel width greater than 40MHz),
589          * otherwise, it returns zero to indicate that center frequency should be obtained from
590          * the HT Operation IE via HtOperation.getCenterFreq0().
591          */
getCenterFreq0()592         public int getCenterFreq0() {
593             if (mCenterFreqIndex1 == 0 || mChannelMode == 0) {
594                 return 0;
595             } else {
596                 return ScanResult.convertChannelToFrequencyMhzIfSupported(mCenterFreqIndex1,
597                         WifiScanner.WIFI_BAND_5_GHZ);
598             }
599         }
600 
601          /**
602          * Returns center frequency of secondary channel if exists (channel width greater than
603          * 40MHz), otherwise, it returns zero.
604          * Note that the secondary channel center frequency only applies to 80+80 or 160 MHz
605          * channels.
606          */
getCenterFreq1()607         public int getCenterFreq1() {
608             if (mCenterFreqIndex2 == 0 || mChannelMode == 0) {
609                 return 0;
610             } else {
611                 return ScanResult.convertChannelToFrequencyMhzIfSupported(mCenterFreqIndex2,
612                         WifiScanner.WIFI_BAND_5_GHZ);
613             }
614         }
615 
616         /**
617          * Parse the VHT Operation IE to read the fields of interest.
618          */
from(InformationElement ie)619         public void from(InformationElement ie) {
620             if (ie.id != InformationElement.EID_VHT_OPERATION) {
621                 throw new IllegalArgumentException("Element id is not VHT_OPERATION, : " + ie.id);
622             }
623             if (ie.bytes.length < VHT_OPERATION_IE_LEN) {
624                 throw new IllegalArgumentException("Invalid VHT_OPERATION len: " + ie.bytes.length);
625             }
626             mPresent = true;
627             mChannelMode = ie.bytes[0] & Constants.BYTE_MASK;
628             mCenterFreqIndex1 = ie.bytes[1] & Constants.BYTE_MASK;
629             mCenterFreqIndex2 = ie.bytes[2] & Constants.BYTE_MASK;
630         }
631     }
632 
633     /**
634      * HeOperation: represents the HE Operation IE
635      */
636     public static class HeOperation {
637 
638         private static final int HE_OPERATION_BASIC_LENGTH = 6;
639         private static final int TWT_REQUIRED_MASK = 0x08;
640         private static final int VHT_OPERATION_INFO_PRESENT_MASK = 0x40;
641         private static final int HE_6GHZ_INFO_PRESENT_MASK = 0x02;
642         private static final int HE_6GHZ_CH_WIDTH_MASK = 0x03;
643         private static final int HE_6GHZ_REG_INFO_MASK = 0x38;
644         private static final int HE_6GHZ_REG_INFO_SHIFT = 3;
645         private static final int CO_HOSTED_BSS_PRESENT_MASK = 0x80;
646         private static final int VHT_OPERATION_INFO_START_INDEX = 6;
647         private static final int HE_BW_80_80_160 = 3;
648 
649         private boolean mPresent = false;
650         private boolean mTwtRequired = false;
651         private boolean mVhtInfoPresent = false;
652         private boolean m6GhzInfoPresent = false;
653         private int mChannelWidth;
654         private ApType6GHz mApType6GHz = ApType6GHz.AP_TYPE_6GHZ_UNKNOWN;
655         private int mPrimaryChannel;
656         private int mCenterFreqSeg0;
657         private int mCenterFreqSeg1;
658         private InformationElement mVhtInfo = null;
659 
660         /**
661          * Returns whether the HE Information Element is present.
662          */
isPresent()663         public boolean isPresent() {
664             return mPresent;
665         }
666 
667         /**
668          * Return whether the AP requires HE stations to participate either in individual TWT
669          * agreements or Broadcast TWT operation. Reference 9.4.2.249 HE Operation element (IEEE
670          * Std 802.11ax™-2021).
671          **/
isTwtRequired()672         public boolean isTwtRequired() {
673             return mTwtRequired;
674         }
675 
676         /**
677          * Return 6Ghz AP type as defined in {@link ApType6GHz}
678          **/
getApType6GHz()679         public ApType6GHz getApType6GHz() {
680             return mApType6GHz;
681         }
682         /**
683          * Returns whether VHT Information field is present.
684          */
isVhtInfoPresent()685         public boolean isVhtInfoPresent() {
686             return mVhtInfoPresent;
687         }
688 
689         /**
690          * Returns the VHT Information Element if it exists
691          * otherwise, return null.
692          */
getVhtInfoElement()693         public InformationElement getVhtInfoElement() {
694             return mVhtInfo;
695         }
696 
697         /**
698          * Returns whether the 6GHz information field is present.
699          */
is6GhzInfoPresent()700         public boolean is6GhzInfoPresent() {
701             return m6GhzInfoPresent;
702         }
703 
704         /**
705          * Returns the Channel BW
706          * Only applicable to 6GHz band
707          */
getChannelWidth()708         public int getChannelWidth() {
709             if (!m6GhzInfoPresent) {
710                 return ScanResult.UNSPECIFIED;
711             } else if (mChannelWidth == 0) {
712                 return ScanResult.CHANNEL_WIDTH_20MHZ;
713             } else if (mChannelWidth == 1) {
714                 return ScanResult.CHANNEL_WIDTH_40MHZ;
715             } else if (mChannelWidth == 2) {
716                 return ScanResult.CHANNEL_WIDTH_80MHZ;
717             } else if (Math.abs(mCenterFreqSeg1 - mCenterFreqSeg0) == 8) {
718                 return ScanResult.CHANNEL_WIDTH_160MHZ;
719             } else {
720                 return ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ;
721             }
722         }
723 
724         /**
725          * Returns the primary channel frequency
726          * Only applicable for 6GHz channels
727          */
getPrimaryFreq()728         public int getPrimaryFreq() {
729             return ScanResult.convertChannelToFrequencyMhzIfSupported(mPrimaryChannel,
730                         WifiScanner.WIFI_BAND_6_GHZ);
731         }
732 
733         /**
734          * Returns the center frequency for the primary channel
735          * Only applicable to 6GHz channels
736          */
getCenterFreq0()737         public int getCenterFreq0() {
738             if (m6GhzInfoPresent) {
739                 if (mCenterFreqSeg0 == 0) {
740                     return 0;
741                 } else {
742                     return ScanResult.convertChannelToFrequencyMhzIfSupported(mCenterFreqSeg0,
743                             WifiScanner.WIFI_BAND_6_GHZ);
744                 }
745             } else {
746                 return 0;
747             }
748         }
749 
750         /**
751          * Returns the center frequency for the secondary channel
752          * Only applicable to 6GHz channels
753          */
getCenterFreq1()754         public int getCenterFreq1() {
755             if (m6GhzInfoPresent) {
756                 if (mCenterFreqSeg1 == 0) {
757                     return 0;
758                 } else {
759                     return ScanResult.convertChannelToFrequencyMhzIfSupported(mCenterFreqSeg1,
760                             WifiScanner.WIFI_BAND_6_GHZ);
761                 }
762             } else {
763                 return 0;
764             }
765         }
766 
767         /** Parse HE Operation IE */
from(InformationElement ie)768         public void from(InformationElement ie) {
769             if (ie.id != InformationElement.EID_EXTENSION_PRESENT
770                     || ie.idExt != InformationElement.EID_EXT_HE_OPERATION) {
771                 throw new IllegalArgumentException("Element id is not HE_OPERATION");
772             }
773 
774             // Make sure the byte array length is at least the fixed size
775             if (ie.bytes.length < HE_OPERATION_BASIC_LENGTH) {
776                 if (DBG) {
777                     Log.w(TAG, "Invalid HE_OPERATION len: " + ie.bytes.length);
778                 }
779                 // Skipping parsing of the IE
780                 return;
781             }
782 
783             mTwtRequired = (ie.bytes[0] & TWT_REQUIRED_MASK) != 0;
784             mVhtInfoPresent = (ie.bytes[1] & VHT_OPERATION_INFO_PRESENT_MASK) != 0;
785             m6GhzInfoPresent = (ie.bytes[2] & HE_6GHZ_INFO_PRESENT_MASK) != 0;
786             boolean coHostedBssPresent = (ie.bytes[1] & CO_HOSTED_BSS_PRESENT_MASK) != 0;
787             int expectedLen = HE_OPERATION_BASIC_LENGTH + (mVhtInfoPresent ? 3 : 0)
788                     + (coHostedBssPresent ? 1 : 0) + (m6GhzInfoPresent ? 5 : 0);
789 
790             // Make sure the byte array length is at least fitting the known parameters
791             if (ie.bytes.length < expectedLen) {
792                 if (DBG) {
793                     Log.w(TAG, "Invalid HE_OPERATION len: " + ie.bytes.length);
794                 }
795                 // Skipping parsing of the IE
796                 return;
797             }
798 
799             // Passed all checks, IE is ready for decoding
800             mPresent = true;
801 
802             if (mVhtInfoPresent) {
803                 mVhtInfo = new InformationElement();
804                 mVhtInfo.id = InformationElement.EID_VHT_OPERATION;
805                 mVhtInfo.bytes = new byte[5];
806                 System.arraycopy(ie.bytes, VHT_OPERATION_INFO_START_INDEX, mVhtInfo.bytes, 0, 3);
807             }
808 
809             if (m6GhzInfoPresent) {
810                 int startIndx = VHT_OPERATION_INFO_START_INDEX + (mVhtInfoPresent ? 3 : 0)
811                         + (coHostedBssPresent ? 1 : 0);
812 
813                 mChannelWidth = ie.bytes[startIndx + 1] & HE_6GHZ_CH_WIDTH_MASK;
814                 int regInfo = (ie.bytes[startIndx + 1] & HE_6GHZ_REG_INFO_MASK)
815                         >> HE_6GHZ_REG_INFO_SHIFT;
816                 if (regInfo == 0) {
817                     mApType6GHz = ApType6GHz.AP_TYPE_6GHZ_INDOOR;
818                 } else if (regInfo == 1) {
819                     mApType6GHz = ApType6GHz.AP_TYPE_6GHZ_STANDARD_POWER;
820                 }
821                 mPrimaryChannel = ie.bytes[startIndx] & Constants.BYTE_MASK;
822                 mCenterFreqSeg0 = ie.bytes[startIndx + 2] & Constants.BYTE_MASK;
823                 mCenterFreqSeg1 = ie.bytes[startIndx + 3] & Constants.BYTE_MASK;
824             }
825         }
826     }
827 
828     /**
829      * EhtOperation: represents the EHT Operation IE
830      */
831     public static class EhtOperation {
832         private static final int EHT_OPERATION_BASIC_LENGTH = 5;
833         private static final int EHT_OPERATION_INFO_PRESENT_MASK = 0x01;
834         private static final int DISABLED_SUBCHANNEL_BITMAP_PRESENT_MASK = 0x02;
835         private static final int EHT_OPERATION_INFO_START_INDEX = EHT_OPERATION_BASIC_LENGTH;
836         private static final int DISABLED_SUBCHANNEL_BITMAP_START_INDEX =
837                 EHT_OPERATION_INFO_START_INDEX + 3;
838         private static final int CHANNEL_WIDTH_INDEX = EHT_OPERATION_INFO_START_INDEX + 0;
839         private static final int CHANNEL_WIDTH_MASK = 0xF;
840         private static final int CHANNEL_CENTER_FREQ_SEG0_INDEX =
841                 EHT_OPERATION_INFO_START_INDEX + 1;
842         private static final int CHANNEL_CENTER_FREQ_SEG_MASK = 0xFF;
843         private static final int CHANNEL_CENTER_FREQ_SEG1_INDEX =
844                 EHT_OPERATION_INFO_START_INDEX + 2;
845 
846         private boolean mPresent = false;
847         private boolean mEhtOperationInfoPresent = false;
848         private boolean mDisabledSubchannelBitmapPresent = false;
849         private byte[] mDisabledSubchannelBitmap;
850         private int mChannelWidth;
851         private int mCenterFreqSeg0;
852         private int mCenterFreqSeg1;
853 
854         /**
855          * Returns whether the EHT Information Element is present.
856          */
isPresent()857         public boolean isPresent() {
858             return mPresent;
859         }
860 
861         /**
862          * Returns whether EHT Operation Information field is present.
863          * Reference 9.4.2.311 EHT Operation element (IEEEStd 802.11be™ Draft2.0).
864          */
isEhtOperationInfoPresent()865         public boolean isEhtOperationInfoPresent() {
866             return mEhtOperationInfoPresent;
867         }
868 
869         /**
870          * Returns whether the Disabled Subchannel Bitmap field is present.
871          */
isDisabledSubchannelBitmapPresent()872         public boolean isDisabledSubchannelBitmapPresent() {
873             return mDisabledSubchannelBitmapPresent;
874         }
875 
876         /**
877          * Returns the Disabled Subchannel Bitmap field if it exists. Otherwise, returns null.
878          */
getDisabledSubchannelBitmap()879         public byte[] getDisabledSubchannelBitmap() {
880             return mDisabledSubchannelBitmap;
881         }
882 
883         /**
884          * @return  Channel width if EHT Operation Information Present.
885          */
getChannelWidth()886         public int getChannelWidth() {
887             /*
888              * Channel width in EHT operation Info is set,
889              *      0 for 20 MHz EHT BSS bandwidth.
890              *      1 for 40 MHz EHT BSS bandwidth.
891              *      2 for 80 MHz EHT BSS bandwidth.
892              *      3 for 160 MHz EHT BSS bandwidth.
893              *      4 for 320 MHz EHT BSS bandwidth.
894              *      Values in the ranges 5 to 7 are reserved.
895              */
896             switch(mChannelWidth) {
897                 case 0: return ScanResult.CHANNEL_WIDTH_20MHZ;
898                 case 1: return ScanResult.CHANNEL_WIDTH_40MHZ;
899                 case 2: return ScanResult.CHANNEL_WIDTH_80MHZ;
900                 case 3: return ScanResult.CHANNEL_WIDTH_160MHZ;
901                 case 4: return ScanResult.CHANNEL_WIDTH_320MHZ;
902                 default:
903                     return  ScanResult.UNSPECIFIED;
904             }
905         }
906 
907         /**
908          * Returns Channel Center Frequency Segment 0 (CCFS0).
909          *
910          * - For 20, 40 or 80 MHz BSS bandwidth, indicates the channel center frequency for the
911          *   20, 40 or 80 MHz channel on which the EHT BSS operates.
912          * - For 160 MHz BSS bandwidth, indicates the channel center frequency of the primary 80
913          *   MHz channel.
914          * - For 320 MHz BSS bandwidth, indicates the channel center frequency of the primary 160
915          *   MHz channel.
916          *
917          * @param band Operating band
918          * @return Center frequency.
919          */
getCenterFreq0(@canResult.WifiBand int band)920         public int getCenterFreq0(@ScanResult.WifiBand int band) {
921             if (mCenterFreqSeg0 == 0 || band == WifiBand.BAND_UNSPECIFIED) {
922                 return ScanResult.UNSPECIFIED;
923             }
924             return ScanResult.convertChannelToFrequencyMhzIfSupported(mCenterFreqSeg0, band);
925         }
926 
927         /**
928          * Returns Channel Center Frequency Segment 1 (CCFS1)
929          *
930          * - For a 20, 40 or 80 MHz BSS bandwidth, returns {@link ScanResult#UNSPECIFIED} .
931          * - For a 160 MHz BSS bandwidth, returns the channel center frequency of the 160 MHz
932          *   channel on which the EHT BSS operates.
933          * - For a 320 MHz BSS bandwidth, returns the channel center frequency of the 320 MHz
934          *   channel on which the EHT BSS operates
935          *
936          * @param band Operating band
937          * @return Center frequency.
938          */
getCenterFreq1(@canResult.WifiBand int band)939         public int getCenterFreq1(@ScanResult.WifiBand int band) {
940             if (mCenterFreqSeg1 == 0 || band == WifiBand.BAND_UNSPECIFIED) {
941                 return ScanResult.UNSPECIFIED;
942             }
943             return ScanResult.convertChannelToFrequencyMhzIfSupported(mCenterFreqSeg1, band);
944         }
945 
946         /**
947          * Parse EHT Operation IE
948          */
from(InformationElement ie)949         public void from(InformationElement ie) {
950             if (ie.id != InformationElement.EID_EXTENSION_PRESENT
951                     || ie.idExt != InformationElement.EID_EXT_EHT_OPERATION) {
952                 throw new IllegalArgumentException("Element id is not EHT_OPERATION");
953             }
954             // Make sure the byte array length is at least the fixed size
955             if (ie.bytes.length < EHT_OPERATION_BASIC_LENGTH) {
956                 if (DBG) {
957                     Log.w(TAG, "Invalid EHT_OPERATION IE len: " + ie.bytes.length);
958                 }
959                 // Skipping parsing of the IE
960                 return;
961             }
962 
963             mEhtOperationInfoPresent = (ie.bytes[0] & EHT_OPERATION_INFO_PRESENT_MASK) != 0;
964             mDisabledSubchannelBitmapPresent =
965                     (ie.bytes[0] & DISABLED_SUBCHANNEL_BITMAP_PRESENT_MASK) != 0;
966             int expectedLen = EHT_OPERATION_BASIC_LENGTH + (mEhtOperationInfoPresent ? (
967                     mDisabledSubchannelBitmapPresent ? 5 : 3) : 0);
968             // Make sure the byte array length is at least fitting the known parameters
969             if (ie.bytes.length < expectedLen) {
970                 if (DBG) {
971                     Log.w(TAG, "Invalid EHT_OPERATION info len: " + ie.bytes.length);
972                 }
973                 // Skipping parsing of the IE
974                 return;
975             }
976             mPresent = true;
977 
978             if (mEhtOperationInfoPresent) {
979                 mChannelWidth = ie.bytes[CHANNEL_WIDTH_INDEX] & CHANNEL_WIDTH_MASK;
980                 mCenterFreqSeg0 =
981                         ie.bytes[CHANNEL_CENTER_FREQ_SEG0_INDEX] & CHANNEL_CENTER_FREQ_SEG_MASK;
982                 mCenterFreqSeg1 =
983                         ie.bytes[CHANNEL_CENTER_FREQ_SEG1_INDEX] & CHANNEL_CENTER_FREQ_SEG_MASK;
984             }
985 
986             if (mDisabledSubchannelBitmapPresent) {
987                 mDisabledSubchannelBitmap = new byte[2];
988                 System.arraycopy(ie.bytes, DISABLED_SUBCHANNEL_BITMAP_START_INDEX,
989                         mDisabledSubchannelBitmap, 0, 2);
990             }
991 
992             //TODO put more functionality for parsing the IE
993         }
994     }
995 
996     /**
997      * HtCapabilities: represents the HT Capabilities IE
998      */
999     public static class HtCapabilities {
1000         private int mMaxNumberSpatialStreams  = 1;
1001         private boolean mPresent = false;
1002         /** Returns whether HT Capabilities IE is present */
isPresent()1003         public boolean isPresent() {
1004             return mPresent;
1005         }
1006         /**
1007          * Returns max number of spatial streams if HT Capabilities IE is found and parsed,
1008          * or 1 otherwise
1009          */
getMaxNumberSpatialStreams()1010         public int getMaxNumberSpatialStreams() {
1011             return mMaxNumberSpatialStreams;
1012         }
1013 
1014         /** Parse HT Capabilities IE */
from(InformationElement ie)1015         public void from(InformationElement ie) {
1016             if (ie.id != InformationElement.EID_HT_CAPABILITIES) {
1017                 throw new IllegalArgumentException("Element id is not HT_CAPABILITIES: " + ie.id);
1018             }
1019             if (ie.bytes.length < 26) {
1020                 if (DBG) {
1021                     Log.w(TAG, "Invalid HtCapabilities len: " + ie.bytes.length);
1022                 }
1023                 return;
1024             }
1025             int stream1 = ie.bytes[3] & Constants.BYTE_MASK;
1026             int stream2 = ie.bytes[4] & Constants.BYTE_MASK;
1027             int stream3 = ie.bytes[5] & Constants.BYTE_MASK;
1028             int stream4 = ie.bytes[6] & Constants.BYTE_MASK;
1029             if (DBG) {
1030                 Log.d(TAG, "HT Rx MCS set4: " + Integer.toHexString(stream4));
1031                 Log.d(TAG, "HT Rx MCS set3: " + Integer.toHexString(stream3));
1032                 Log.d(TAG, "HT Rx MCS set2: " + Integer.toHexString(stream2));
1033                 Log.d(TAG, "HT Rx MCS set1: " + Integer.toHexString(stream1));
1034             }
1035             mMaxNumberSpatialStreams = (stream4 > 0) ? 4 :
1036                     ((stream3 > 0) ? 3 :
1037                     ((stream2 > 0) ? 2 : 1));
1038             mPresent = true;
1039         }
1040     }
1041 
1042     /**
1043      * VhtCapabilities: represents the VHT Capabilities IE
1044      */
1045     public static class VhtCapabilities {
1046         private int mMaxNumberSpatialStreams = 1;
1047         private boolean mPresent = false;
1048         /** Returns whether VHT Capabilities IE is present */
isPresent()1049         public boolean isPresent() {
1050             return mPresent;
1051         }
1052         /**
1053          * Returns max number of spatial streams if VHT Capabilities IE is found and parsed,
1054          * or 1 otherwise
1055          */
getMaxNumberSpatialStreams()1056         public int getMaxNumberSpatialStreams() {
1057             return mMaxNumberSpatialStreams;
1058         }
1059         /** Parse VHT Capabilities IE */
from(InformationElement ie)1060         public void from(InformationElement ie) {
1061             if (ie.id != InformationElement.EID_VHT_CAPABILITIES) {
1062                 throw new IllegalArgumentException("Element id is not VHT_CAPABILITIES: " + ie.id);
1063             }
1064             if (ie.bytes.length < 12) {
1065                 if (DBG) {
1066                     Log.w(TAG, "Invalid VHT_CAPABILITIES len: " + ie.bytes.length);
1067                 }
1068                 return;
1069             }
1070             int mcsMap = ((ie.bytes[5] & Constants.BYTE_MASK) << 8)
1071                     + (ie.bytes[4] & Constants.BYTE_MASK);
1072             mMaxNumberSpatialStreams = parseMaxNumberSpatialStreamsFromMcsMap(mcsMap);
1073             mPresent = true;
1074         }
1075     }
1076 
1077     /**
1078      * HeCapabilities: represents the HE Capabilities IE
1079      */
1080     public static class HeCapabilities {
1081 
1082         /**
1083          * Represents HE MAC Capabilities Information field. Reference 9.4.2.248.2 HE MAC
1084          * Capabilities Information field (IEEE Std 802.11ax-2021).
1085          */
1086         private static class HeMacCapabilitiesInformation {
1087             public static int startOffset = 0;
1088             public static int endOffset = 5;
1089             public static final int TWT_REQUESTER_SUPPORT_BIT = 1;
1090             public static final int TWT_RESPONDER_SUPPORT_BIT = 2;
1091             public static final int BROADCAST_TWT_SUPPORT_BIT = 20;
1092             public boolean isTwtRequesterSupported = false;
1093             public boolean isTwtResponderSupported = false;
1094             public boolean isBroadcastTwtSupported = false;
1095             private BitSet mBitSet = new BitSet();
1096 
1097             /** Parse HE MAC capabilities Information from the byte array. */
from(byte[] bytes)1098             public void from(byte[] bytes) {
1099                 mBitSet = BitSet.valueOf(bytes);
1100                 isTwtRequesterSupported = mBitSet.get(TWT_REQUESTER_SUPPORT_BIT);
1101                 isTwtResponderSupported = mBitSet.get(TWT_RESPONDER_SUPPORT_BIT);
1102                 isBroadcastTwtSupported = mBitSet.get(BROADCAST_TWT_SUPPORT_BIT);
1103             }
1104         }
1105 
1106         private HeMacCapabilitiesInformation mHeMacCapabilitiesInformation =
1107                 new HeMacCapabilitiesInformation();
1108         private int mMaxNumberSpatialStreams = 1;
1109         private boolean mPresent = false;
1110 
1111         /**
1112          * Return whether TWT requester is supported. Set by HE Stations to indicate TWT support
1113          */
isTwtRequesterSupported()1114         public boolean isTwtRequesterSupported() {
1115             return mHeMacCapabilitiesInformation.isTwtRequesterSupported;
1116         }
1117         /** Return whether TWT responder is supported. Set by HE AP to indicate TWT support. */
isTwtResponderSupported()1118         public boolean isTwtResponderSupported() {
1119             return mHeMacCapabilitiesInformation.isTwtResponderSupported;
1120         }
1121 
1122         /** Return whether broadcast TWT is supported */
isBroadcastTwtSupported()1123         public boolean isBroadcastTwtSupported() {
1124             return mHeMacCapabilitiesInformation.isBroadcastTwtSupported;
1125         }
1126 
1127         /** Returns whether HE Capabilities IE is present */
isPresent()1128         public boolean isPresent() {
1129             return mPresent;
1130         }
1131         /**
1132          * Returns max number of spatial streams if HE Capabilities IE is found and parsed,
1133          * or 1 otherwise
1134          */
getMaxNumberSpatialStreams()1135         public int getMaxNumberSpatialStreams() {
1136             return mMaxNumberSpatialStreams;
1137         }
1138         /** Parse HE Capabilities IE */
from(InformationElement ie)1139         public void from(InformationElement ie) {
1140             if (ie.id != InformationElement.EID_EXTENSION_PRESENT
1141                     || ie.idExt != InformationElement.EID_EXT_HE_CAPABILITIES) {
1142                 throw new IllegalArgumentException("Element id is not HE_CAPABILITIES: " + ie.id);
1143             }
1144             if (ie.bytes.length < 21) {
1145                 if (DBG) {
1146                     Log.w(TAG, "Invalid HE_CAPABILITIES len: " + ie.bytes.length);
1147                 }
1148                 return;
1149             }
1150             int mcsMap = ((ie.bytes[18] & Constants.BYTE_MASK) << 8)
1151                     + (ie.bytes[17] & Constants.BYTE_MASK);
1152             mMaxNumberSpatialStreams = parseMaxNumberSpatialStreamsFromMcsMap(mcsMap);
1153             mPresent = true;
1154             mHeMacCapabilitiesInformation.from(Arrays.copyOfRange(ie.bytes,
1155                     mHeMacCapabilitiesInformation.startOffset,
1156                     mHeMacCapabilitiesInformation.endOffset));
1157         }
1158     }
1159 
1160     /**
1161      * EhtCapabilities: represents the EHT Capabilities IE. Reference 9.4.2.313 EHT Capabilities
1162      * element (IEEE P802.11be/D3.1).
1163      */
1164     public static class EhtCapabilities {
1165         /**
1166          * EhtMacCapabilitiesInformation: represents the EHT MAC Capabilities Information element.
1167          * Reference 9.4.2.313.2 EHT MAC Capabilities Information field (IEEE P802.11be/D3.1).
1168          */
1169         public static class EhtMacCapabilitiesInformation {
1170             public static int startOffset = 0;
1171             public static int endOffset = 1;
1172             public static final int EPCS_PRIORITY_ACCESS_SUPPORT_BIT = 0;
1173             public static final int RESTRICTED_TWT_SUPPORT_BIT = 4;
1174             public boolean isEpcsPriorityAccessSupported = false;
1175             public boolean isRestrictedTwtSupported = false;
1176             private BitSet mBitSet = new BitSet();
1177 
1178             /** Parse EHT MAC Capabilities Information from the bytes. **/
from(byte[] bytes)1179             public void from(byte[] bytes) {
1180                 mBitSet = BitSet.valueOf(bytes);
1181                 isEpcsPriorityAccessSupported = mBitSet.get(EPCS_PRIORITY_ACCESS_SUPPORT_BIT);
1182                 isRestrictedTwtSupported = mBitSet.get(RESTRICTED_TWT_SUPPORT_BIT);
1183             }
1184         }
1185         private boolean mPresent = false;
1186 
1187         private EhtMacCapabilitiesInformation mEhtMacCapabilitiesInformation =
1188                 new EhtMacCapabilitiesInformation();
1189         /** Returns whether HE Capabilities IE is present.  */
isPresent()1190         public boolean isPresent() {
1191             return mPresent;
1192         }
1193 
1194         /**
1195          * Returns whether restricted TWT is supported or not. It enables enhanced medium access
1196          * protection and resource reservation mechanisms for delivery of latency sensitive
1197          * traffic.
1198          */
isRestrictedTwtSupported()1199         public boolean isRestrictedTwtSupported() {
1200             return mEhtMacCapabilitiesInformation.isRestrictedTwtSupported;
1201         }
1202 
1203         /**
1204          * Returns whether EPCS priority access supported or not. EPCS priority access is a
1205          * mechanism that provides prioritized access to the wireless medium for authorized users to
1206          * increase their probability of successful communication during periods of network
1207          * congestion.
1208          */
isEpcsPriorityAccessSupported()1209         public boolean isEpcsPriorityAccessSupported() {
1210             return mEhtMacCapabilitiesInformation.isEpcsPriorityAccessSupported;
1211         }
1212 
1213         /** Parse EHT Capabilities IE */
from(InformationElement ie)1214         public void from(InformationElement ie) {
1215             if (ie.id != InformationElement.EID_EXTENSION_PRESENT
1216                     || ie.idExt != InformationElement.EID_EXT_EHT_CAPABILITIES) {
1217                 throw new IllegalArgumentException("Element id is not EHT_CAPABILITIES: " + ie.id);
1218             }
1219             mPresent = true;
1220             mEhtMacCapabilitiesInformation.from(Arrays.copyOfRange(ie.bytes,
1221                     mEhtMacCapabilitiesInformation.startOffset,
1222                     mEhtMacCapabilitiesInformation.endOffset));
1223         }
1224     }
1225 
1226     /**
1227      * MultiLink: represents the Multi-Link IE
1228      * as described in IEEE 802.11be Specification Section 9.4.2.312
1229      */
1230     public static class MultiLink {
1231         private static final int CONTROL_FIELD_LEN = 2;
1232         private static final int BASIC_COMMON_INFO_FIELD_MIN_LEN = 7;
1233         private static final int BASIC_LINK_INFO_FIELD_MIN_LEN = 0;
1234         private static final int BASIC_IE_MIN_LEN = CONTROL_FIELD_LEN
1235                 + BASIC_COMMON_INFO_FIELD_MIN_LEN
1236                 + BASIC_LINK_INFO_FIELD_MIN_LEN;
1237 
1238         // Control field constants
1239         private static final int IE_TYPE_OFFSET = 0;
1240         private static final int IE_TYPE_MASK = 0x07;
1241         public static final int TYPE_BASIC = 0;
1242         public static final int LINK_ID_PRESENT_OFFSET = 0;
1243         public static final int LINK_ID_PRESENT_MASK = 0x10;
1244 
1245 
1246         // Common info field constants
1247         private static final int COMMON_FIELD_START_INDEX = CONTROL_FIELD_LEN;
1248         private static final int BASIC_IE_COMMON_INFO_LEN_OFFSET = 0;
1249         private static final int BASIC_IE_COMMON_MLD_MAC_ADDRESS_OFFSET = 1;
1250         private static final int BASIC_IE_COMMOM_LINK_ID_OFFSET = 7;
1251         private static final int BASIC_IE_COMMOM_LINK_ID_MASK = 0x0F;
1252 
1253         // Per-STA sub-element constants
1254         private static final int PER_STA_SUB_ELEMENT_ID = 0;
1255         private static final int PER_STA_SUB_ELEMENT_MIN_LEN = 5;
1256         private static final int PER_STA_SUB_ELEMENT_LINK_ID_OFFSET = 2;
1257         private static final int PER_STA_SUB_ELEMENT_LINK_ID_MASK = 0x0F;
1258         private static final int PER_STA_SUB_ELEMENT_STA_INFO_OFFSET = 4;
1259         private static final int PER_STA_SUB_ELEMENT_MAC_ADDRESS_PRESENT_OFFSET = 2;
1260         private static final int PER_STA_SUB_ELEMENT_MAC_ADDRESS_PRESENT_MASK = 0x20;
1261         private static final int PER_STA_SUB_ELEMENT_STA_INFO_MAC_ADDRESS_OFFSET = 1;
1262 
1263         private boolean mPresent = false;
1264         private int mLinkId = MloLink.INVALID_MLO_LINK_ID;
1265         private MacAddress mMldMacAddress = null;
1266         private List<MloLink> mAffiliatedLinks = new ArrayList<>();
1267 
1268         /** Returns whether Multi-Link IE is present */
isPresent()1269         public boolean isPresent() {
1270             return mPresent;
1271         }
1272 
1273         /** Returns the MLD MAC Address */
getMldMacAddress()1274         public MacAddress getMldMacAddress() {
1275             return mMldMacAddress;
1276         }
1277 
1278         /** Return the link id */
getLinkId()1279         public int getLinkId() {
1280             return mLinkId;
1281         }
1282 
1283         /** Return the affiliated links */
getAffiliatedLinks()1284         public List<MloLink> getAffiliatedLinks() {
1285             return new ArrayList<MloLink>(mAffiliatedLinks);
1286         }
1287 
1288         /**
1289          * Parse Common Info field in Multi-Link Operation IE
1290          *
1291          * Common Info filed as described in IEEE 802.11 specs, Section 9.4.2.312,
1292          *
1293          *        | Len | MLD Address | Link Id | BSS Change count | MedSync | EML Cap | MLD Cap |
1294          * Octets:   1        6          0 or 1        0 or 1         0 or 2    0 or 2    0 or 2
1295          *
1296          */
parseCommonInfoField(InformationElement ie)1297         private int parseCommonInfoField(InformationElement ie) {
1298             int commonInfoLength = ie.bytes[COMMON_FIELD_START_INDEX
1299                     + BASIC_IE_COMMON_INFO_LEN_OFFSET] & Constants.BYTE_MASK;
1300             if (commonInfoLength < BASIC_COMMON_INFO_FIELD_MIN_LEN) {
1301                 if (DBG) {
1302                     Log.w(TAG, "Invalid Common Info field length: " + commonInfoLength);
1303                 }
1304                 // Skipping parsing of the IE
1305                 return 0;
1306             }
1307 
1308             boolean isLinkIdInfoPresent = (ie.bytes[LINK_ID_PRESENT_OFFSET]
1309                     & LINK_ID_PRESENT_MASK) != 0;
1310             if (isLinkIdInfoPresent) {
1311                 if (ie.bytes.length < BASIC_IE_MIN_LEN + 1 /*Link Id info */) {
1312                     if (DBG) {
1313                         Log.w(TAG, "Invalid Multi-Link IE len: " + ie.bytes.length);
1314                     }
1315                     // Skipping parsing of the IE
1316                     return 0;
1317                 }
1318 
1319                 mLinkId = ie.bytes[COMMON_FIELD_START_INDEX
1320                         + BASIC_IE_COMMOM_LINK_ID_OFFSET] & BASIC_IE_COMMOM_LINK_ID_MASK;
1321             }
1322 
1323             int macAddressStart = COMMON_FIELD_START_INDEX + BASIC_IE_COMMON_MLD_MAC_ADDRESS_OFFSET;
1324             mMldMacAddress = MacAddress.fromBytes(
1325                     Arrays.copyOfRange(ie.bytes, macAddressStart, macAddressStart + 6));
1326 
1327             return commonInfoLength;
1328         }
1329 
1330         /** Parse per STA sub element (not fragmented) of Multi link element. */
parsePerStaSubElement(byte[] bytes, int start, int len)1331         private Boolean parsePerStaSubElement(byte[] bytes, int start, int len) {
1332             MloLink link = new MloLink();
1333             link.setLinkId(
1334                     bytes[start + PER_STA_SUB_ELEMENT_LINK_ID_OFFSET]
1335                             & PER_STA_SUB_ELEMENT_LINK_ID_MASK);
1336 
1337             int staInfoLength =
1338                     bytes[start + PER_STA_SUB_ELEMENT_STA_INFO_OFFSET] & Constants.BYTE_MASK;
1339             if (len < PER_STA_SUB_ELEMENT_STA_INFO_OFFSET + staInfoLength) {
1340                 if (DBG) {
1341                     Log.w(TAG, "Invalid sta info length: " + staInfoLength);
1342                 }
1343                 // Skipping parsing of the IE
1344                 return false;
1345             }
1346 
1347             // Check if MAC Address is present
1348             if ((bytes[start + PER_STA_SUB_ELEMENT_MAC_ADDRESS_PRESENT_OFFSET]
1349                             & PER_STA_SUB_ELEMENT_MAC_ADDRESS_PRESENT_MASK)
1350                     != 0) {
1351                 if (staInfoLength < 1 /*length*/ + 6 /*mac address*/) {
1352                     if (DBG) {
1353                         Log.w(TAG, "Invalid sta info length: " + staInfoLength);
1354                     }
1355                     // Skipping parsing of the IE
1356                     return false;
1357                 }
1358 
1359                 int macAddressOffset =
1360                         start
1361                                 + PER_STA_SUB_ELEMENT_STA_INFO_OFFSET
1362                                 + PER_STA_SUB_ELEMENT_STA_INFO_MAC_ADDRESS_OFFSET;
1363                 link.setApMacAddress(
1364                         MacAddress.fromBytes(
1365                                 Arrays.copyOfRange(bytes, macAddressOffset, macAddressOffset + 6)));
1366             }
1367             mAffiliatedLinks.add(link);
1368             return true;
1369         }
1370 
1371         /**
1372          * Parse Link Info field in Multi-Link Operation IE
1373          *
1374          * Link Info filed as described in IEEE 802.11 specs, Section 9.4.2.312,
1375          *
1376          *        | ID | Len | STA Control | STA Info | STA Profile |
1377          * Octets:  1     1        2         variable    variable
1378          *
1379          * where STA Control subfield is described as,
1380          *
1381          *      | LinkId | Complete | MAC | Beacon Interval | DTIM | NSTR Link | NSTR Bitmap | R |
1382          * Bits:    4          1       1          1             1        1            1        6
1383          *
1384          */
parseLinkInfoField(InformationElement ie, int startOffset)1385         private boolean parseLinkInfoField(InformationElement ie, int startOffset) {
1386             // Check if Link Info field is present
1387             while (ie.bytes.length >= startOffset + PER_STA_SUB_ELEMENT_MIN_LEN) {
1388                 int subElementId = ie.bytes[startOffset] & Constants.BYTE_MASK;
1389                 int subElementLen = ie.bytes[startOffset + 1] & Constants.BYTE_MASK;
1390                 // Expectation here is IE has enough length to parse and non-zero sub-element
1391                 // length.
1392                 if (ie.bytes.length < startOffset + subElementLen || subElementLen == 0) {
1393                     if (DBG) {
1394                         Log.w(TAG, "Invalid sub-element length: " + subElementLen);
1395                     }
1396                     // Skipping parsing of the IE
1397                     return false;
1398                 }
1399                 if (subElementId != PER_STA_SUB_ELEMENT_ID) {
1400                     // Skip this subelement, could be an unsupported one
1401                     startOffset += subElementLen;
1402                     continue;
1403                 }
1404 
1405                 int bytesRead;
1406                 // Check for fragmentation before parsing per sta profile sub element
1407                 if (subElementLen == DefragmentElement.FRAG_MAX_LEN) {
1408                     DefragmentElement defragment =
1409                             new DefragmentElement(
1410                                     ie.bytes,
1411                                     startOffset,
1412                                     PER_STA_SUB_ELEMENT_ID,
1413                                     InformationElement.EID_FRAGMENT_SUB_ELEMENT_MULTI_LINK);
1414                     bytesRead = defragment.bytesRead;
1415                     if (defragment.bytesRead == 0 || defragment.bytes == null) {
1416                         return false;
1417                     }
1418                     if (!parsePerStaSubElement(defragment.bytes, 0, defragment.bytes.length)) {
1419                         return false;
1420                     }
1421                 } else {
1422                     bytesRead = subElementLen;
1423                     if (!parsePerStaSubElement(ie.bytes, startOffset, subElementLen)) {
1424                         return false;
1425                     }
1426                 }
1427                 // Done with this sub-element
1428                 startOffset += bytesRead;
1429             }
1430 
1431             return true;
1432         }
1433 
1434         /**
1435          * Parse Multi-Link Operation IE
1436          *
1437          * Multi-Link IE format as described in IEEE 802.11 specs, Section 9.4.2.312
1438          *
1439          *              | ElementID | Length | ExtendedID | Control | Common Info | Link Info |
1440          * Octets:            1          1         1          2        Variable     variable
1441          *
1442          *
1443          * Where Control field is described as,
1444          *
1445          *         | Type | Reserved | Presence Bitmap |
1446          * Bits:      3        1            12
1447          *
1448          * Where the Presence Bitmap subfield is described as,
1449          *
1450          *        | LinkId | BSS change count | MedSync | EML cap | MLD cap | Reserved |
1451          * Bits:      1            1               1         1         1         7
1452          *
1453          *
1454          *
1455          * Note: InformationElement.bytes has 'Element ID', 'Length', and 'Extended ID'
1456          *       stripped off already
1457          *
1458          */
from(InformationElement ie)1459         public void from(InformationElement ie) {
1460             if (ie.id != InformationElement.EID_EXTENSION_PRESENT
1461                     || ie.idExt != InformationElement.EID_EXT_MULTI_LINK) {
1462                 throw new IllegalArgumentException("Element id is not Multi-Link: " + ie.id);
1463             }
1464 
1465             // Make sure the byte array length is at least the Control field size
1466             if (ie.bytes.length < CONTROL_FIELD_LEN) {
1467                 if (DBG) {
1468                     Log.w(TAG, "Invalid Multi-Link IE len: " + ie.bytes.length);
1469                 }
1470                 // Skipping parsing of the IE
1471                 return;
1472             }
1473 
1474             // Check on IE type
1475             // Note only the BASIC type is allowed to be received from AP
1476             int type = ie.bytes[IE_TYPE_OFFSET] & IE_TYPE_MASK;
1477             if (type != TYPE_BASIC) {
1478                 if (DBG) {
1479                     Log.w(TAG, "Invalid/Unsupported Multi-Link IE type: " + type);
1480                 }
1481                 // Skipping parsing of the IE
1482                 return;
1483             }
1484 
1485             // Check length
1486             if (ie.bytes.length < BASIC_IE_MIN_LEN) {
1487                 if (DBG) {
1488                     Log.w(TAG, "Invalid Multi-Link IE len: " + ie.bytes.length);
1489                 }
1490                 // Skipping parsing of the IE
1491                 return;
1492             }
1493 
1494             int commonInfoLength = parseCommonInfoField(ie);
1495             if (commonInfoLength == 0) {
1496                 return;
1497             }
1498 
1499             if (!parseLinkInfoField(ie, CONTROL_FIELD_LEN + commonInfoLength)) {
1500                 return;
1501             }
1502 
1503             mPresent = true;
1504         }
1505     }
1506 
parseMaxNumberSpatialStreamsFromMcsMap(int mcsMap)1507     private static int parseMaxNumberSpatialStreamsFromMcsMap(int mcsMap) {
1508         int maxNumberSpatialStreams = 1;
1509         for (int i = 8; i >= 1; --i) {
1510             int streamMap = mcsMapToStreamMap(mcsMap, i);
1511             // 3 means unsupported
1512             if (streamMap != 3) {
1513                 maxNumberSpatialStreams = i;
1514                 break;
1515             }
1516         }
1517         if (DBG) {
1518             for (int i = 8; i >= 1; --i) {
1519                 int streamMap = mcsMapToStreamMap(mcsMap, i);
1520                 Log.d(TAG, "Rx MCS set " + i + " : " + streamMap);
1521             }
1522         }
1523         return maxNumberSpatialStreams;
1524     }
1525 
mcsMapToStreamMap(int mcsMap, int i)1526     private static int mcsMapToStreamMap(int mcsMap, int i) {
1527         return (mcsMap >> ((i - 1) * 2)) & 0x3;
1528     }
1529 
1530     public static class Interworking {
1531         public NetworkDetail.Ant ant = null;
1532         public boolean internet = false;
1533         public long hessid = 0L;
1534 
from(InformationElement ie)1535         public void from(InformationElement ie) {
1536             if (ie.id != InformationElement.EID_INTERWORKING) {
1537                 throw new IllegalArgumentException("Element id is not INTERWORKING, : " + ie.id);
1538             }
1539             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
1540             int anOptions = data.get() & Constants.BYTE_MASK;
1541             ant = NetworkDetail.Ant.values()[anOptions & 0x0f];
1542             internet = (anOptions & 0x10) != 0;
1543             // There are only three possible lengths for the Interworking IE:
1544             // Len 1: Access Network Options only
1545             // Len 3: Access Network Options & Venue Info
1546             // Len 7: Access Network Options & HESSID
1547             // Len 9: Access Network Options, Venue Info, & HESSID
1548             if (ie.bytes.length != 1
1549                     && ie.bytes.length != 3
1550                     && ie.bytes.length != 7
1551                     && ie.bytes.length != 9) {
1552                 throw new IllegalArgumentException(
1553                         "Bad Interworking element length: " + ie.bytes.length);
1554             }
1555 
1556             if (ie.bytes.length == 3 || ie.bytes.length == 9) {
1557                 int venueInfo = (int) ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 2);
1558             }
1559 
1560             if (ie.bytes.length == 7 || ie.bytes.length == 9) {
1561                 hessid = ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 6);
1562             }
1563         }
1564     }
1565 
1566     public static class RoamingConsortium {
1567         public int anqpOICount = 0;
1568 
1569         private long[] roamingConsortiums = null;
1570 
getRoamingConsortiums()1571         public long[] getRoamingConsortiums() {
1572             return roamingConsortiums;
1573         }
1574 
from(InformationElement ie)1575         public void from(InformationElement ie) {
1576             if (ie.id != InformationElement.EID_ROAMING_CONSORTIUM) {
1577                 throw new IllegalArgumentException("Element id is not ROAMING_CONSORTIUM, : "
1578                         + ie.id);
1579             }
1580             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
1581             anqpOICount = data.get() & Constants.BYTE_MASK;
1582 
1583             int oi12Length = data.get() & Constants.BYTE_MASK;
1584             int oi1Length = oi12Length & Constants.NIBBLE_MASK;
1585             int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK;
1586             int oi3Length = ie.bytes.length - 2 - oi1Length - oi2Length;
1587             int oiCount = 0;
1588             if (oi1Length > 0) {
1589                 oiCount++;
1590                 if (oi2Length > 0) {
1591                     oiCount++;
1592                     if (oi3Length > 0) {
1593                         oiCount++;
1594                     }
1595                 }
1596             }
1597             roamingConsortiums = new long[oiCount];
1598             if (oi1Length > 0 && roamingConsortiums.length > 0) {
1599                 roamingConsortiums[0] =
1600                         ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi1Length);
1601             }
1602             if (oi2Length > 0 && roamingConsortiums.length > 1) {
1603                 roamingConsortiums[1] =
1604                         ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi2Length);
1605             }
1606             if (oi3Length > 0 && roamingConsortiums.length > 2) {
1607                 roamingConsortiums[2] =
1608                         ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi3Length);
1609             }
1610         }
1611 
1612         @Override
toString()1613         public String toString() {
1614             StringBuilder stringBuilder = new StringBuilder();
1615             stringBuilder.append("RoamingConsortium [");
1616             stringBuilder.append("anqpOICount: " + anqpOICount);
1617             stringBuilder.append(", roamingConsortiums: " + (roamingConsortiums == null ? "null"
1618                     : Arrays.toString(roamingConsortiums)));
1619             stringBuilder.append("]");
1620             return stringBuilder.toString();
1621         }
1622     }
1623 
1624     public static class Vsa {
1625         private static final int ANQP_DOMAIN_ID_PRESENT_BIT = 0x04;
1626         private static final int ANQP_PPS_MO_ID_BIT = 0x02;
1627         private static final int OUI_WFA_ALLIANCE = 0x506F9a;
1628         private static final int OUI_TYPE_HS20 = 0x10;
1629         private static final int OUI_TYPE_MBO_OCE = 0x16;
1630 
1631         public NetworkDetail.HSRelease hsRelease = null;
1632         public int anqpDomainID = 0;    // No domain ID treated the same as a 0; unique info per AP.
1633 
1634         public boolean IsMboCapable = false;
1635         public boolean IsMboApCellularDataAware = false;
1636         public boolean IsOceCapable = false;
1637         public int mboAssociationDisallowedReasonCode =
1638                 MboOceConstants.MBO_OCE_ATTRIBUTE_NOT_PRESENT;
1639         public byte[] oui;
1640 
parseVsaMboOce(InformationElement ie)1641         private void parseVsaMboOce(InformationElement ie) {
1642             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
1643 
1644             // skip WFA OUI and type parsing as parseVsaMboOce() is called after identifying
1645             // MBO-OCE OUI type.
1646             data.getInt();
1647 
1648             while (data.remaining() > 1) {
1649                 int attrId = data.get() & Constants.BYTE_MASK;
1650                 int attrLen = data.get() & Constants.BYTE_MASK;
1651 
1652                 if ((attrLen == 0) || (attrLen > data.remaining())) {
1653                     return;
1654                 }
1655                 byte[] attrBytes = new byte[attrLen];
1656                 data.get(attrBytes);
1657                 switch (attrId) {
1658                     case MboOceConstants.MBO_OCE_AID_MBO_AP_CAPABILITY_INDICATION:
1659                         IsMboCapable = true;
1660                         IsMboApCellularDataAware = (attrBytes[0]
1661                                 & MboOceConstants.MBO_AP_CAP_IND_ATTR_CELL_DATA_AWARE) != 0;
1662                         break;
1663                     case MboOceConstants.MBO_OCE_AID_ASSOCIATION_DISALLOWED:
1664                         mboAssociationDisallowedReasonCode = attrBytes[0] & Constants.BYTE_MASK;
1665                         break;
1666                     case MboOceConstants.MBO_OCE_AID_OCE_AP_CAPABILITY_INDICATION:
1667                         IsOceCapable = true;
1668                         break;
1669                     default:
1670                         break;
1671                 }
1672             }
1673             if (DBG) {
1674                 Log.e(TAG, ":parseMboOce MBO: " + IsMboCapable + " cellDataAware: "
1675                         + IsMboApCellularDataAware + " AssocDisAllowRC: "
1676                         + mboAssociationDisallowedReasonCode + " :OCE: " + IsOceCapable);
1677             }
1678         }
1679 
parseVsaHs20(InformationElement ie)1680         private void parseVsaHs20(InformationElement ie) {
1681             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
1682             if (ie.bytes.length >= 5) {
1683                 // skip WFA OUI and type parsing as parseVsaHs20() is called after identifying
1684                 // HS20 OUI type.
1685                 data.getInt();
1686 
1687                 int hsConf = data.get() & Constants.BYTE_MASK;
1688                 switch ((hsConf >> 4) & Constants.NIBBLE_MASK) {
1689                     case 0:
1690                         hsRelease = NetworkDetail.HSRelease.R1;
1691                         break;
1692                     case 1:
1693                         hsRelease = NetworkDetail.HSRelease.R2;
1694                         break;
1695                     case 2:
1696                         hsRelease = NetworkDetail.HSRelease.R3;
1697                         break;
1698                     default:
1699                         hsRelease = NetworkDetail.HSRelease.Unknown;
1700                         break;
1701                 }
1702                 if ((hsConf & ANQP_DOMAIN_ID_PRESENT_BIT) != 0) {
1703                     // According to Hotspot 2.0 Specification v3.0 section 3.1.1 HS2.0 Indication
1704                     // element, the size of the element is 5 bytes, and 2 bytes are optionally added
1705                     // for each optional field; ANQP PPS MO ID and ANQP Domain ID present.
1706                     int expectedSize = 7;
1707                     if ((hsConf & ANQP_PPS_MO_ID_BIT) != 0) {
1708                         expectedSize += 2;
1709                         if (ie.bytes.length < expectedSize) {
1710                             throw new IllegalArgumentException(
1711                                     "HS20 indication element too short: " + ie.bytes.length);
1712                         }
1713                         data.getShort(); // Skip 2 bytes
1714                     }
1715                     if (ie.bytes.length < expectedSize) {
1716                         throw new IllegalArgumentException(
1717                                 "HS20 indication element too short: " + ie.bytes.length);
1718                     }
1719                     anqpDomainID = data.getShort() & Constants.SHORT_MASK;
1720                 }
1721             }
1722         }
1723 
1724         /**
1725          * Parse the vendor specific information element to build
1726          * InformationElemmentUtil.vsa object.
1727          *
1728          * @param ie -- Information Element
1729          */
from(InformationElement ie)1730         public void from(InformationElement ie) {
1731             if (ie.bytes.length < 3) {
1732                 if (DBG) {
1733                     Log.w(TAG, "Invalid vendor specific element len: " + ie.bytes.length);
1734                 }
1735                 return;
1736             }
1737 
1738             oui = Arrays.copyOfRange(ie.bytes, 0, 3);
1739             int oui = (((ie.bytes[0] & Constants.BYTE_MASK) << 16)
1740                        | ((ie.bytes[1] & Constants.BYTE_MASK) << 8)
1741                        |  ((ie.bytes[2] & Constants.BYTE_MASK)));
1742 
1743             if (oui == OUI_WFA_ALLIANCE && ie.bytes.length >= 4) {
1744                 int ouiType = ie.bytes[3];
1745                 switch (ouiType) {
1746                     case OUI_TYPE_HS20:
1747                         parseVsaHs20(ie);
1748                         break;
1749                     case OUI_TYPE_MBO_OCE:
1750                         parseVsaMboOce(ie);
1751                         break;
1752                     default:
1753                         break;
1754                 }
1755             }
1756         }
1757     }
1758 
1759     /**
1760      * This IE contained a bit field indicating the capabilities being advertised by the STA.
1761      * The size of the bit field (number of bytes) is indicated by the |Length| field in the IE.
1762      *
1763      * Refer to Section 8.4.2.29 in IEEE 802.11-2012 Spec for capability associated with each
1764      * bit.
1765      *
1766      * Here is the wire format of this IE:
1767      * | Element ID | Length | Capabilities |
1768      *       1           1          n
1769      */
1770     public static class ExtendedCapabilities {
1771         private static final int RTT_RESP_ENABLE_BIT = 70;
1772         private static final int SSID_UTF8_BIT = 48;
1773         private static final int FILS_CAPABILITY_BIT = 72;
1774         private static final int TWT_REQUESTER_CAPABILITY_BIT = 77;
1775         private static final int TWT_RESPONDER_CAPABILITY_BIT = 78;
1776         private static final int NON_TB_RANGING_RESPONDER = 90;
1777         private static final int TB_RANGING_RESPONDER = 91;
1778 
1779         public BitSet capabilitiesBitSet;
1780 
1781         /**
1782          * @return true if Trigger based ranging responder supported. Refer P802.11az/D7.0,
1783          * September 2022, section 9.4.2.26 Extended Capabilities element.
1784          */
is80211azTbResponder()1785         public boolean is80211azTbResponder() {
1786             return capabilitiesBitSet.get(TB_RANGING_RESPONDER);
1787         }
1788 
1789         /**
1790          * @return true if Non trigger based ranging responder supported. Refer P802.11az/D7.0,
1791          * September 2022, section 9.4.2.26 Extended Capabilities element.
1792          */
is80211azNtbResponder()1793         public boolean is80211azNtbResponder() {
1794             return capabilitiesBitSet.get(NON_TB_RANGING_RESPONDER);
1795         }
1796 
1797         /**
1798          * @return true if TWT Requester capability is set
1799          */
isTwtRequesterSupported()1800         public boolean isTwtRequesterSupported() {
1801             return capabilitiesBitSet.get(TWT_REQUESTER_CAPABILITY_BIT);
1802         }
1803 
1804         /**
1805          * @return true if TWT Responder capability is set
1806          */
isTwtResponderSupported()1807         public boolean isTwtResponderSupported() {
1808             return capabilitiesBitSet.get(TWT_RESPONDER_CAPABILITY_BIT);
1809         }
1810         /**
1811          * @return true if Fast Initial Link Setup (FILS) capable
1812          */
isFilsCapable()1813         public boolean isFilsCapable() {
1814             return capabilitiesBitSet.get(FILS_CAPABILITY_BIT);
1815         }
1816 
1817         /**
1818          * @return true if SSID should be interpreted using UTF-8 encoding
1819          */
isStrictUtf8()1820         public boolean isStrictUtf8() {
1821             return capabilitiesBitSet.get(SSID_UTF8_BIT);
1822         }
1823 
1824         /**
1825          * @return true if 802.11 MC RTT Response is enabled
1826          */
is80211McRTTResponder()1827         public boolean is80211McRTTResponder() {
1828             return capabilitiesBitSet.get(RTT_RESP_ENABLE_BIT);
1829         }
1830 
ExtendedCapabilities()1831         public ExtendedCapabilities() {
1832             capabilitiesBitSet = new BitSet();
1833         }
1834 
ExtendedCapabilities(ExtendedCapabilities other)1835         public ExtendedCapabilities(ExtendedCapabilities other) {
1836             capabilitiesBitSet = other.capabilitiesBitSet;
1837         }
1838 
1839         /**
1840          * Parse an ExtendedCapabilities from the IE containing raw bytes.
1841          *
1842          * @param ie The Information element data
1843          */
from(InformationElement ie)1844         public void from(InformationElement ie) {
1845             capabilitiesBitSet = BitSet.valueOf(ie.bytes);
1846         }
1847     }
1848 
1849     /**
1850      * parse beacon to build the capabilities
1851      *
1852      * This class is used to build the capabilities string of the scan results coming
1853      * from HAL. It parses the ieee beacon's capability field, WPA and RSNE IE as per spec,
1854      * and builds the ScanResult.capabilities String in a way that mirrors the values returned
1855      * by wpa_supplicant.
1856      */
1857     public static class Capabilities {
1858         private static final int WPA_VENDOR_OUI_TYPE_ONE = 0x01f25000;
1859         private static final int WPS_VENDOR_OUI_TYPE = 0x04f25000;
1860         private static final short WPA_VENDOR_OUI_VERSION = 0x0001;
1861         private static final int OWE_VENDOR_OUI_TYPE = 0x1c9a6f50;
1862         private static final short RSNE_VERSION = 0x0001;
1863 
1864         private static final int WPA_AKM_EAP = 0x01f25000;
1865         private static final int WPA_AKM_PSK = 0x02f25000;
1866 
1867         private static final int RSN_AKM_EAP = 0x01ac0f00;
1868         private static final int RSN_AKM_PSK = 0x02ac0f00;
1869         private static final int RSN_AKM_FT_EAP = 0x03ac0f00;
1870         private static final int RSN_AKM_FT_PSK = 0x04ac0f00;
1871         private static final int RSN_AKM_EAP_SHA256 = 0x05ac0f00;
1872         private static final int RSN_AKM_PSK_SHA256 = 0x06ac0f00;
1873         private static final int RSN_AKM_SAE = 0x08ac0f00;
1874         private static final int RSN_AKM_FT_SAE = 0x09ac0f00;
1875         private static final int RSN_AKM_OWE = 0x12ac0f00;
1876         private static final int RSN_AKM_EAP_SUITE_B_192 = 0x0cac0f00;
1877         private static final int RSN_OSEN = 0x019a6f50;
1878         private static final int RSN_AKM_EAP_FILS_SHA256 = 0x0eac0f00;
1879         private static final int RSN_AKM_EAP_FILS_SHA384 = 0x0fac0f00;
1880         private static final int RSN_AKM_SAE_EXT_KEY = 0x18ac0f00;
1881         private static final int RSN_AKM_FT_SAE_EXT_KEY = 0x19ac0f00;
1882         private static final int RSN_AKM_DPP = 0x029a6f50;
1883 
1884         private static final int WPA_CIPHER_NONE = 0x00f25000;
1885         private static final int WPA_CIPHER_TKIP = 0x02f25000;
1886         private static final int WPA_CIPHER_CCMP = 0x04f25000;
1887 
1888         private static final int RSN_CIPHER_NONE = 0x00ac0f00;
1889         private static final int RSN_CIPHER_TKIP = 0x02ac0f00;
1890         private static final int RSN_CIPHER_CCMP = 0x04ac0f00;
1891         private static final int RSN_CIPHER_NO_GROUP_ADDRESSED = 0x07ac0f00;
1892         private static final int RSN_CIPHER_GCMP_256 = 0x09ac0f00;
1893         private static final int RSN_CIPHER_GCMP_128 = 0x08ac0f00;
1894         private static final int RSN_CIPHER_BIP_GMAC_128 = 0x0bac0f00;
1895         private static final int RSN_CIPHER_BIP_GMAC_256 = 0x0cac0f00;
1896         private static final int RSN_CIPHER_BIP_CMAC_256 = 0x0dac0f00;
1897 
1898         // RSN capability bit definition
1899         private static final int RSN_CAP_MANAGEMENT_FRAME_PROTECTION_REQUIRED = 1 << 6;
1900         private static final int RSN_CAP_MANAGEMENT_FRAME_PROTECTION_CAPABLE = 1 << 7;
1901 
1902         public List<Integer> protocol;
1903         public List<List<Integer>> keyManagement;
1904         public List<List<Integer>> pairwiseCipher;
1905         public List<Integer> groupCipher;
1906         public List<Integer> groupManagementCipher;
1907         public boolean isESS;
1908         public boolean isIBSS;
1909         public boolean isPrivacy;
1910         public boolean isWPS;
1911         public boolean isManagementFrameProtectionRequired;
1912         public boolean isManagementFrameProtectionCapable;
1913 
Capabilities()1914         public Capabilities() {
1915         }
1916 
1917         // RSNE format (size unit: byte)
1918         //
1919         // | Element ID | Length | Version | Group Data Cipher Suite |
1920         //      1           1         2                 4
1921         // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
1922         //              2                            4 * m
1923         // | AKM Suite Count | AKM Suite List | RSN Capabilities |
1924         //          2               4 * n               2
1925         // | PMKID Count | PMKID List | Group Management Cipher Suite |
1926         //        2          16 * s                 4
1927         //
1928         // Note: InformationElement.bytes has 'Element ID' and 'Length'
1929         //       stripped off already
parseRsnElement(InformationElement ie, SparseIntArray unknownAkmMap)1930         private void parseRsnElement(InformationElement ie, SparseIntArray unknownAkmMap) {
1931             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
1932 
1933             try {
1934                 // version
1935                 if (buf.getShort() != RSNE_VERSION) {
1936                     // incorrect version
1937                     return;
1938                 }
1939 
1940                 // found the RSNE IE, hence start building the capability string
1941                 protocol.add(ScanResult.PROTOCOL_RSN);
1942 
1943                 // group data cipher suite
1944                 groupCipher.add(parseRsnCipher(buf.getInt()));
1945 
1946                 // pairwise cipher suite count
1947                 short cipherCount = buf.getShort();
1948                 ArrayList<Integer> rsnPairwiseCipher = new ArrayList<>();
1949                 // pairwise cipher suite list
1950                 for (int i = 0; i < cipherCount; i++) {
1951                     rsnPairwiseCipher.add(parseRsnCipher(buf.getInt()));
1952                 }
1953                 pairwiseCipher.add(rsnPairwiseCipher);
1954 
1955                 // AKM
1956                 // AKM suite count
1957                 short akmCount = buf.getShort();
1958                 ArrayList<Integer> rsnKeyManagement = new ArrayList<>();
1959 
1960                 for (int i = 0; i < akmCount; i++) {
1961                     int akm = buf.getInt();
1962                     switch (akm) {
1963                         case RSN_AKM_EAP:
1964                             rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP);
1965                             break;
1966                         case RSN_AKM_PSK:
1967                             rsnKeyManagement.add(ScanResult.KEY_MGMT_PSK);
1968                             break;
1969                         case RSN_AKM_FT_EAP:
1970                             rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_EAP);
1971                             break;
1972                         case RSN_AKM_FT_PSK:
1973                             rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_PSK);
1974                             break;
1975                         case RSN_AKM_EAP_SHA256:
1976                             rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP_SHA256);
1977                             break;
1978                         case RSN_AKM_PSK_SHA256:
1979                             rsnKeyManagement.add(ScanResult.KEY_MGMT_PSK_SHA256);
1980                             break;
1981                         case RSN_AKM_SAE:
1982                             rsnKeyManagement.add(ScanResult.KEY_MGMT_SAE);
1983                             break;
1984                         case RSN_AKM_FT_SAE:
1985                             rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_SAE);
1986                             break;
1987                         case RSN_AKM_SAE_EXT_KEY:
1988                             rsnKeyManagement.add(ScanResult.KEY_MGMT_SAE_EXT_KEY);
1989                             break;
1990                         case RSN_AKM_FT_SAE_EXT_KEY:
1991                             rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_SAE_EXT_KEY);
1992                             break;
1993                         case RSN_AKM_OWE:
1994                             rsnKeyManagement.add(ScanResult.KEY_MGMT_OWE);
1995                             break;
1996                         case RSN_AKM_EAP_SUITE_B_192:
1997                             rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP_SUITE_B_192);
1998                             break;
1999                         case RSN_OSEN:
2000                             rsnKeyManagement.add(ScanResult.KEY_MGMT_OSEN);
2001                             break;
2002                         case RSN_AKM_EAP_FILS_SHA256:
2003                             rsnKeyManagement.add(ScanResult.KEY_MGMT_FILS_SHA256);
2004                             break;
2005                         case RSN_AKM_EAP_FILS_SHA384:
2006                             rsnKeyManagement.add(ScanResult.KEY_MGMT_FILS_SHA384);
2007                             break;
2008                         case RSN_AKM_DPP:
2009                             rsnKeyManagement.add(ScanResult.KEY_MGMT_DPP);
2010                             break;
2011                         default: {
2012                             int akmScheme =
2013                                     getScanResultAkmSchemeOfUnknownAkmIfConfigured(
2014                                             akm, unknownAkmMap);
2015                             rsnKeyManagement.add(akmScheme);
2016                             break;
2017                         }
2018                     }
2019                 }
2020                 // Default AKM
2021                 if (rsnKeyManagement.isEmpty()) {
2022                     rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP);
2023                 }
2024                 keyManagement.add(rsnKeyManagement);
2025 
2026                 // RSN capabilities (optional),
2027                 // see section 9.4.2.25 - RSNE - In IEEE Std 802.11-2016
2028                 if (buf.remaining() < 2) return;
2029                 int rsnCaps = buf.getShort();
2030                 isManagementFrameProtectionRequired =
2031                         0 != (RSN_CAP_MANAGEMENT_FRAME_PROTECTION_REQUIRED & rsnCaps);
2032                 isManagementFrameProtectionCapable =
2033                         0 != (RSN_CAP_MANAGEMENT_FRAME_PROTECTION_CAPABLE & rsnCaps);
2034 
2035                 if (buf.remaining() < 2) return;
2036                 // PMKID, it's not used, drop it if exists (optional).
2037                 int rsnPmkIdCount = buf.getShort();
2038                 for (int i = 0; i < rsnPmkIdCount; i++) {
2039                     // Each PMKID element length in the PMKID List is 16 bytes
2040                     byte[] tmp = new byte[16];
2041                     buf.get(tmp);
2042                 }
2043 
2044                 // Group management cipher suite (optional).
2045                 if (buf.remaining() < 4) return;
2046                 groupManagementCipher.add(parseRsnCipher(buf.getInt()));
2047             } catch (BufferUnderflowException e) {
2048                 Log.e("IE_Capabilities", "Couldn't parse RSNE, buffer underflow");
2049             }
2050         }
2051 
2052         /**
2053          * Get the ScanResult security key management scheme (ScanResult.KEY_MGMT_XX) corresponding
2054          * to the unknown AKMs configured in overlay config item
2055          * config_wifiUnknownAkmToKnownAkmMapping
2056          *
2057          * @param unknownAkm unknown AKM seen in the received beacon or probe response.
2058          * @param unknownAkmMap unknownAkmMap Mapping of unknown AKMs configured in overlay config
2059          *     item config_wifiUnknownAkmToKnownAkmMapping to ScanResult security key management
2060          *     scheme (ScanResult.KEY_MGMT_XX).
2061          * @return A valid ScanResult.KEY_MGMT_XX if unknownAkm is configured in the overlay,
2062          *     ScanResult.KEY_MGMT_UNKNOWN otherwise
2063          */
getScanResultAkmSchemeOfUnknownAkmIfConfigured( int unknownAkm, SparseIntArray unknownAkmMap)2064         private int getScanResultAkmSchemeOfUnknownAkmIfConfigured(
2065                 int unknownAkm, SparseIntArray unknownAkmMap) {
2066             if (unknownAkmMap != null) {
2067                 return unknownAkmMap.get(unknownAkm, ScanResult.KEY_MGMT_UNKNOWN);
2068             } else {
2069                 return ScanResult.KEY_MGMT_UNKNOWN;
2070             }
2071         }
2072 
parseWpaCipher(int cipher)2073         private static @Cipher int parseWpaCipher(int cipher) {
2074             switch (cipher) {
2075                 case WPA_CIPHER_NONE:
2076                     return ScanResult.CIPHER_NONE;
2077                 case WPA_CIPHER_TKIP:
2078                     return ScanResult.CIPHER_TKIP;
2079                 case WPA_CIPHER_CCMP:
2080                     return ScanResult.CIPHER_CCMP;
2081                 default:
2082                     Log.w("IE_Capabilities", "Unknown WPA cipher suite: "
2083                             + Integer.toHexString(cipher));
2084                     return ScanResult.CIPHER_NONE;
2085             }
2086         }
2087 
parseRsnCipher(int cipher)2088         private static @Cipher int parseRsnCipher(int cipher) {
2089             switch (cipher) {
2090                 case RSN_CIPHER_NONE:
2091                     return ScanResult.CIPHER_NONE;
2092                 case RSN_CIPHER_TKIP:
2093                     return ScanResult.CIPHER_TKIP;
2094                 case RSN_CIPHER_CCMP:
2095                     return ScanResult.CIPHER_CCMP;
2096                 case RSN_CIPHER_GCMP_256:
2097                     return ScanResult.CIPHER_GCMP_256;
2098                 case RSN_CIPHER_NO_GROUP_ADDRESSED:
2099                     return ScanResult.CIPHER_NO_GROUP_ADDRESSED;
2100                 case RSN_CIPHER_GCMP_128:
2101                     return ScanResult.CIPHER_GCMP_128;
2102                 case RSN_CIPHER_BIP_GMAC_128:
2103                     return ScanResult.CIPHER_BIP_GMAC_128;
2104                 case RSN_CIPHER_BIP_GMAC_256:
2105                     return ScanResult.CIPHER_BIP_GMAC_256;
2106                 case RSN_CIPHER_BIP_CMAC_256:
2107                     return ScanResult.CIPHER_BIP_CMAC_256;
2108                 default:
2109                     Log.w("IE_Capabilities", "Unknown RSN cipher suite: "
2110                             + Integer.toHexString(cipher));
2111                     return ScanResult.CIPHER_NONE;
2112             }
2113         }
2114 
isWpsElement(InformationElement ie)2115         private static boolean isWpsElement(InformationElement ie) {
2116             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
2117             try {
2118                 // WPS OUI and type
2119                 return (buf.getInt() == WPS_VENDOR_OUI_TYPE);
2120             } catch (BufferUnderflowException e) {
2121                 Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
2122                 return false;
2123             }
2124         }
2125 
isWpaOneElement(InformationElement ie)2126         private static boolean isWpaOneElement(InformationElement ie) {
2127             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
2128 
2129             try {
2130                 // WPA OUI and type
2131                 return (buf.getInt() == WPA_VENDOR_OUI_TYPE_ONE);
2132             } catch (BufferUnderflowException e) {
2133                 Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
2134                 return false;
2135             }
2136         }
2137 
2138         // WPA type 1 format (size unit: byte)
2139         //
2140         // | Element ID | Length | OUI | Type | Version |
2141         //      1           1       3     1        2
2142         // | Group Data Cipher Suite |
2143         //             4
2144         // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
2145         //              2                            4 * m
2146         // | AKM Suite Count | AKM Suite List |
2147         //          2               4 * n
2148         //
2149         // Note: InformationElement.bytes has 'Element ID' and 'Length'
2150         //       stripped off already
2151         //
parseWpaOneElement(InformationElement ie, SparseIntArray unknownAkmMap)2152         private void parseWpaOneElement(InformationElement ie, SparseIntArray unknownAkmMap) {
2153             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
2154 
2155             try {
2156                 // skip WPA OUI and type parsing. isWpaOneElement() should have
2157                 // been called for verification before we reach here.
2158                 buf.getInt();
2159 
2160                 // version
2161                 if (buf.getShort() != WPA_VENDOR_OUI_VERSION) {
2162                     // incorrect version
2163                     return;
2164                 }
2165 
2166                 // start building the string
2167                 protocol.add(ScanResult.PROTOCOL_WPA);
2168 
2169                 // group data cipher suite
2170                 groupCipher.add(parseWpaCipher(buf.getInt()));
2171 
2172                 // pairwise cipher suite count
2173                 short cipherCount = buf.getShort();
2174                 ArrayList<Integer> wpaPairwiseCipher = new ArrayList<>();
2175                 // pairwise chipher suite list
2176                 for (int i = 0; i < cipherCount; i++) {
2177                     wpaPairwiseCipher.add(parseWpaCipher(buf.getInt()));
2178                 }
2179                 pairwiseCipher.add(wpaPairwiseCipher);
2180 
2181                 // AKM
2182                 // AKM suite count
2183                 short akmCount = buf.getShort();
2184                 ArrayList<Integer> wpaKeyManagement = new ArrayList<>();
2185 
2186                 // AKM suite list
2187                 for (int i = 0; i < akmCount; i++) {
2188                     int akm = buf.getInt();
2189                     switch (akm) {
2190                         case WPA_AKM_EAP:
2191                             wpaKeyManagement.add(ScanResult.KEY_MGMT_EAP);
2192                             break;
2193                         case WPA_AKM_PSK:
2194                             wpaKeyManagement.add(ScanResult.KEY_MGMT_PSK);
2195                             break;
2196                         default:
2197                             int akmScheme =
2198                                     getScanResultAkmSchemeOfUnknownAkmIfConfigured(
2199                                             akm, unknownAkmMap);
2200                             wpaKeyManagement.add(akmScheme);
2201                             break;
2202                     }
2203                 }
2204                 // Default AKM
2205                 if (wpaKeyManagement.isEmpty()) {
2206                     wpaKeyManagement.add(ScanResult.KEY_MGMT_EAP);
2207                 }
2208                 keyManagement.add(wpaKeyManagement);
2209             } catch (BufferUnderflowException e) {
2210                 Log.e("IE_Capabilities", "Couldn't parse type 1 WPA, buffer underflow");
2211             }
2212         }
2213 
2214         /**
2215          * Parse the Information Element and the 16-bit Capability Information field to build the
2216          * InformationElemmentUtil.capabilities object.
2217          *
2218          * @param ies -- Information Element array
2219          * @param beaconCap -- 16-bit Beacon Capability Information field
2220          * @param isOweSupported -- Boolean flag to indicate if OWE is supported by the device
2221          * @param freq -- Frequency on which frame/beacon was transmitted. Some parsing may be
2222          *     affected such as DMG parameters in DMG (60GHz) beacon.
2223          * @param unknownAkmMap -- unknown AKM to known AKM mapping (Internally converted to
2224          *     security key management scheme(ScanResult.KEY_MGMT_XX)) configured in overlay config
2225          *     item config_wifiUnknownAkmToKnownAkmMapping.
2226          */
from( InformationElement[] ies, int beaconCap, boolean isOweSupported, int freq, SparseIntArray unknownAkmMap)2227         public void from(
2228                 InformationElement[] ies,
2229                 int beaconCap,
2230                 boolean isOweSupported,
2231                 int freq,
2232                 SparseIntArray unknownAkmMap) {
2233             protocol = new ArrayList<>();
2234             keyManagement = new ArrayList<>();
2235             groupCipher = new ArrayList<>();
2236             pairwiseCipher = new ArrayList<>();
2237             groupManagementCipher = new ArrayList<>();
2238 
2239             if (ies == null) {
2240                 return;
2241             }
2242             isPrivacy = (beaconCap & NativeScanResult.BSS_CAPABILITY_PRIVACY) != 0;
2243             if (ScanResult.is60GHz(freq)) {
2244                 /* In DMG, bits 0 and 1 are parsed together, where ESS=0x3 and IBSS=0x1 */
2245                 if ((beaconCap & NativeScanResult.BSS_CAPABILITY_DMG_ESS)
2246                         == NativeScanResult.BSS_CAPABILITY_DMG_ESS) {
2247                     isESS = true;
2248                 } else if ((beaconCap & NativeScanResult.BSS_CAPABILITY_DMG_IBSS) != 0) {
2249                     isIBSS = true;
2250                 }
2251             } else {
2252                 isESS = (beaconCap & NativeScanResult.BSS_CAPABILITY_ESS) != 0;
2253                 isIBSS = (beaconCap & NativeScanResult.BSS_CAPABILITY_IBSS) != 0;
2254             }
2255             for (InformationElement ie : ies) {
2256                 WifiNl80211Manager.OemSecurityType oemSecurityType =
2257                         WifiNl80211Manager.parseOemSecurityTypeElement(
2258                         ie.id, ie.idExt, ie.bytes);
2259                 if (oemSecurityType != null
2260                         && oemSecurityType.protocol != ScanResult.PROTOCOL_NONE) {
2261                     protocol.add(oemSecurityType.protocol);
2262                     keyManagement.add(oemSecurityType.keyManagement);
2263                     pairwiseCipher.add(oemSecurityType.pairwiseCipher);
2264                     groupCipher.add(oemSecurityType.groupCipher);
2265                 }
2266 
2267                 if (ie.id == InformationElement.EID_RSN) {
2268                     parseRsnElement(ie, unknownAkmMap);
2269                 }
2270 
2271                 if (ie.id == InformationElement.EID_VSA) {
2272                     if (isWpaOneElement(ie)) {
2273                         parseWpaOneElement(ie, unknownAkmMap);
2274                     }
2275                     if (isWpsElement(ie)) {
2276                         // TODO(b/62134557): parse WPS IE to provide finer granularity information.
2277                         isWPS = true;
2278                     }
2279                     if (isOweSupported && isOweElement(ie)) {
2280                         /* From RFC 8110: Once the client and AP have finished 802.11 association,
2281                            they then complete the Diffie-Hellman key exchange and create a Pairwise
2282                            Master Key (PMK) and its associated identifier, PMKID [IEEE802.11].
2283                            Upon completion of 802.11 association, the AP initiates the 4-way
2284                            handshake to the client using the PMK generated above.  The 4-way
2285                            handshake generates a Key-Encrypting Key (KEK), a Key-Confirmation
2286                            Key (KCK), and a Message Integrity Code (MIC) to use for protection
2287                            of the frames that define the 4-way handshake.
2288 
2289                            We check if OWE is supported here because we are adding the OWE
2290                            capabilities to the Open BSS. Non-supporting devices need to see this
2291                            open network and ignore this element. Supporting devices need to hide
2292                            the Open BSS of OWE in transition mode and connect to the Hidden one.
2293                         */
2294                         protocol.add(ScanResult.PROTOCOL_RSN);
2295                         groupCipher.add(ScanResult.CIPHER_CCMP);
2296                         ArrayList<Integer> owePairwiseCipher = new ArrayList<>();
2297                         owePairwiseCipher.add(ScanResult.CIPHER_CCMP);
2298                         pairwiseCipher.add(owePairwiseCipher);
2299                         ArrayList<Integer> oweKeyManagement = new ArrayList<>();
2300                         oweKeyManagement.add(ScanResult.KEY_MGMT_OWE_TRANSITION);
2301                         keyManagement.add(oweKeyManagement);
2302                     }
2303                 }
2304             }
2305         }
2306 
2307         /** Convert the AKM suite selector to scan result Security key management scheme */
akmToScanResultKeyManagementScheme(int akm)2308         public static int akmToScanResultKeyManagementScheme(int akm) {
2309             switch (akm) {
2310                 case RSN_AKM_EAP:
2311                 case WPA_AKM_EAP:
2312                     return ScanResult.KEY_MGMT_EAP;
2313                 case RSN_AKM_PSK:
2314                 case WPA_AKM_PSK:
2315                     return ScanResult.KEY_MGMT_PSK;
2316                 case RSN_AKM_FT_EAP:
2317                     return ScanResult.KEY_MGMT_FT_EAP;
2318                 case RSN_AKM_FT_PSK:
2319                     return ScanResult.KEY_MGMT_FT_PSK;
2320                 case RSN_AKM_EAP_SHA256:
2321                     return ScanResult.KEY_MGMT_EAP_SHA256;
2322                 case RSN_AKM_PSK_SHA256:
2323                     return ScanResult.KEY_MGMT_PSK_SHA256;
2324                 case RSN_AKM_SAE:
2325                     return ScanResult.KEY_MGMT_SAE;
2326                 case RSN_AKM_FT_SAE:
2327                     return ScanResult.KEY_MGMT_FT_SAE;
2328                 case RSN_AKM_SAE_EXT_KEY:
2329                     return ScanResult.KEY_MGMT_SAE_EXT_KEY;
2330                 case RSN_AKM_FT_SAE_EXT_KEY:
2331                     return ScanResult.KEY_MGMT_FT_SAE_EXT_KEY;
2332                 case RSN_AKM_OWE:
2333                     return ScanResult.KEY_MGMT_OWE;
2334                 case RSN_AKM_EAP_SUITE_B_192:
2335                     return ScanResult.KEY_MGMT_EAP_SUITE_B_192;
2336                 case RSN_OSEN:
2337                     return ScanResult.KEY_MGMT_OSEN;
2338                 case RSN_AKM_EAP_FILS_SHA256:
2339                     return ScanResult.KEY_MGMT_FILS_SHA256;
2340                 case RSN_AKM_EAP_FILS_SHA384:
2341                     return ScanResult.KEY_MGMT_FILS_SHA384;
2342                 case RSN_AKM_DPP:
2343                     return ScanResult.KEY_MGMT_DPP;
2344                 default:
2345                     return ScanResult.KEY_MGMT_UNKNOWN;
2346             }
2347         }
2348 
isOweElement(InformationElement ie)2349         private static boolean isOweElement(InformationElement ie) {
2350             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
2351             try {
2352                 // OWE OUI and type
2353                 return (buf.getInt() == OWE_VENDOR_OUI_TYPE);
2354             } catch (BufferUnderflowException e) {
2355                 Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
2356                 return false;
2357             }
2358         }
2359 
protocolToString(@rotocol int protocol)2360         private String protocolToString(@Protocol int protocol) {
2361             switch (protocol) {
2362                 case ScanResult.PROTOCOL_NONE:
2363                     return "None";
2364                 case ScanResult.PROTOCOL_WPA:
2365                     return "WPA";
2366                 case ScanResult.PROTOCOL_RSN:
2367                     return "RSN";
2368                 case ScanResult.PROTOCOL_OSEN:
2369                     return "OSEN";
2370                 case ScanResult.PROTOCOL_WAPI:
2371                     return "WAPI";
2372                 default:
2373                     return "?";
2374             }
2375         }
2376 
keyManagementToString(@eyMgmt int akm)2377         private String keyManagementToString(@KeyMgmt int akm) {
2378             switch (akm) {
2379                 case ScanResult.KEY_MGMT_NONE:
2380                     return "None";
2381                 case ScanResult.KEY_MGMT_PSK:
2382                     return "PSK";
2383                 case ScanResult.KEY_MGMT_EAP:
2384                     return "EAP/SHA1";
2385                 case ScanResult.KEY_MGMT_FT_EAP:
2386                     return "FT/EAP";
2387                 case ScanResult.KEY_MGMT_FT_PSK:
2388                     return "FT/PSK";
2389                 case ScanResult.KEY_MGMT_EAP_SHA256:
2390                     return "EAP/SHA256";
2391                 case ScanResult.KEY_MGMT_PSK_SHA256:
2392                     return "PSK-SHA256";
2393                 case ScanResult.KEY_MGMT_OWE:
2394                     return "OWE";
2395                 case ScanResult.KEY_MGMT_OWE_TRANSITION:
2396                     return "OWE_TRANSITION";
2397                 case ScanResult.KEY_MGMT_SAE:
2398                     return "SAE";
2399                 case ScanResult.KEY_MGMT_FT_SAE:
2400                     return "FT/SAE";
2401                 case ScanResult.KEY_MGMT_SAE_EXT_KEY:
2402                     return "SAE_EXT_KEY";
2403                 case ScanResult.KEY_MGMT_FT_SAE_EXT_KEY:
2404                     return "FT/SAE_EXT_KEY";
2405                 case ScanResult.KEY_MGMT_EAP_SUITE_B_192:
2406                     return "EAP_SUITE_B_192";
2407                 case ScanResult.KEY_MGMT_OSEN:
2408                     return "OSEN";
2409                 case ScanResult.KEY_MGMT_WAPI_PSK:
2410                     return "WAPI-PSK";
2411                 case ScanResult.KEY_MGMT_WAPI_CERT:
2412                     return "WAPI-CERT";
2413                 case ScanResult.KEY_MGMT_FILS_SHA256:
2414                     return "EAP-FILS-SHA256";
2415                 case ScanResult.KEY_MGMT_FILS_SHA384:
2416                     return "EAP-FILS-SHA384";
2417                 case ScanResult.KEY_MGMT_DPP:
2418                     return "DPP";
2419                 default:
2420                     return "?";
2421             }
2422         }
2423 
cipherToString(@ipher int cipher)2424         private String cipherToString(@Cipher int cipher) {
2425             switch (cipher) {
2426                 case ScanResult.CIPHER_NONE:
2427                     return "None";
2428                 case ScanResult.CIPHER_CCMP:
2429                     return "CCMP";
2430                 case ScanResult.CIPHER_GCMP_256:
2431                     return "GCMP-256";
2432                 case ScanResult.CIPHER_TKIP:
2433                     return "TKIP";
2434                 case ScanResult.CIPHER_SMS4:
2435                     return "SMS4";
2436                 default:
2437                     return "?";
2438             }
2439         }
2440 
2441         /**
2442          * Build the ScanResult.capabilities String.
2443          *
2444          * @return security string that mirrors what wpa_supplicant generates
2445          */
generateCapabilitiesString()2446         public String generateCapabilitiesString() {
2447             StringBuilder capabilities = new StringBuilder();
2448             // private Beacon without an RSNE or WPA IE, hence WEP0
2449             boolean isWEP = (protocol.isEmpty()) && isPrivacy;
2450 
2451             if (isWEP) {
2452                 capabilities.append("[WEP]");
2453             }
2454             for (int i = 0; i < protocol.size(); i++) {
2455                 String capability = generateCapabilitiesStringPerProtocol(i);
2456                 // add duplicate capabilities for WPA2 for backward compatibility:
2457                 // duplicate "RSN" entries as "WPA2"
2458                 String capWpa2 = generateWPA2CapabilitiesString(capability, i);
2459                 capabilities.append(capWpa2);
2460                 capabilities.append(capability);
2461             }
2462             if (isESS) {
2463                 capabilities.append("[ESS]");
2464             }
2465             if (isIBSS) {
2466                 capabilities.append("[IBSS]");
2467             }
2468             if (isWPS) {
2469                 capabilities.append("[WPS]");
2470             }
2471             if (isManagementFrameProtectionRequired) {
2472                 capabilities.append("[MFPR]");
2473             }
2474             if (isManagementFrameProtectionCapable) {
2475                 capabilities.append("[MFPC]");
2476             }
2477 
2478             return capabilities.toString();
2479         }
2480 
2481         /**
2482          * Build the Capability String for one protocol
2483          * @param index: index number of the protocol
2484          * @return security string for one protocol
2485          */
generateCapabilitiesStringPerProtocol(int index)2486         private String generateCapabilitiesStringPerProtocol(int index) {
2487             StringBuilder capability = new StringBuilder();
2488             capability.append("[").append(protocolToString(protocol.get(index)));
2489 
2490             if (index < keyManagement.size()) {
2491                 for (int j = 0; j < keyManagement.get(index).size(); j++) {
2492                     capability.append((j == 0) ? "-" : "+").append(
2493                             keyManagementToString(keyManagement.get(index).get(j)));
2494                 }
2495             }
2496             if (index < pairwiseCipher.size()) {
2497                 for (int j = 0; j < pairwiseCipher.get(index).size(); j++) {
2498                     capability.append((j == 0) ? "-" : "+").append(
2499                             cipherToString(pairwiseCipher.get(index).get(j)));
2500                 }
2501             }
2502             capability.append("]");
2503             return capability.toString();
2504         }
2505 
2506         /**
2507          * Build the duplicate Capability String for WPA2
2508          * @param cap: original capability String
2509          * @param index: index number of the protocol
2510          * @return security string for WPA2, empty String if protocol is not WPA2
2511          */
generateWPA2CapabilitiesString(String cap, int index)2512         private String generateWPA2CapabilitiesString(String cap, int index) {
2513             StringBuilder capWpa2 = new StringBuilder();
2514             // if not WPA2, return empty String
2515             if (cap.contains("EAP_SUITE_B_192")
2516                     || (!cap.contains("RSN-EAP") && !cap.contains("RSN-FT/EAP")
2517                     && !cap.contains("RSN-PSK") && !cap.contains("RSN-FT/PSK"))) {
2518                 return "";
2519             }
2520             capWpa2.append("[").append("WPA2");
2521             if (index < keyManagement.size()) {
2522                 for (int j = 0; j < keyManagement.get(index).size(); j++) {
2523                     capWpa2.append((j == 0) ? "-" : "+").append(
2524                             keyManagementToString(keyManagement.get(index).get(j)));
2525                     // WPA3/WPA2 transition mode
2526                     if (cap.contains("SAE")) {
2527                         break;
2528                     }
2529                 }
2530             }
2531             if (index < pairwiseCipher.size()) {
2532                 for (int j = 0; j < pairwiseCipher.get(index).size(); j++) {
2533                     capWpa2.append((j == 0) ? "-" : "+").append(
2534                             cipherToString(pairwiseCipher.get(index).get(j)));
2535                 }
2536             }
2537             capWpa2.append("]");
2538             return capWpa2.toString();
2539         }
2540     }
2541 
2542 
2543     /**
2544      * Parser for the Traffic Indication Map (TIM) Information Element (EID 5). This element will
2545      * only be present in scan results that are derived from a Beacon Frame, not from the more
2546      * plentiful probe responses. Call 'isValid()' after parsing, to ensure the results are correct.
2547      */
2548     public static class TrafficIndicationMap {
2549         private static final int MAX_TIM_LENGTH = 254;
2550         private boolean mValid = false;
2551         public int mLength = 0;
2552         public int mDtimCount = -1;
2553         //Negative DTIM Period means no TIM element was given this frame.
2554         public int mDtimPeriod = -1;
2555         public int mBitmapControl = 0;
2556 
2557         /**
2558          * Is this a valid TIM information element.
2559          */
isValid()2560         public boolean isValid() {
2561             return mValid;
2562         }
2563 
2564         // Traffic Indication Map format (size unit: byte)
2565         //
2566         //| ElementID | Length | DTIM Count | DTIM Period | BitmapControl | Partial Virtual Bitmap |
2567         //      1          1          1            1               1                1 - 251
2568         //
2569         // Note: InformationElement.bytes has 'Element ID' and 'Length'
2570         //       stripped off already
2571         //
from(InformationElement ie)2572         public void from(InformationElement ie) {
2573             mValid = false;
2574             if (ie == null || ie.bytes == null) return;
2575             mLength = ie.bytes.length;
2576             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
2577             try {
2578                 mDtimCount = data.get() & Constants.BYTE_MASK;
2579                 mDtimPeriod = data.get() & Constants.BYTE_MASK;
2580                 mBitmapControl = data.get() & Constants.BYTE_MASK;
2581                 //A valid TIM element must have atleast one more byte
2582                 data.get();
2583             } catch (BufferUnderflowException e) {
2584                 return;
2585             }
2586             if (mLength <= MAX_TIM_LENGTH && mDtimPeriod > 0) {
2587                 mValid = true;
2588             }
2589         }
2590     }
2591 
2592     /**
2593      * This util class determines the 802.11 standard (a/b/g/n/ac/ax/be) being used
2594      */
2595     public static class WifiMode {
2596         public static final int MODE_UNDEFINED = 0; // Unknown/undefined
2597         public static final int MODE_11A = 1;       // 802.11a
2598         public static final int MODE_11B = 2;       // 802.11b
2599         public static final int MODE_11G = 3;       // 802.11g
2600         public static final int MODE_11N = 4;       // 802.11n
2601         public static final int MODE_11AC = 5;      // 802.11ac
2602         public static final int MODE_11AX = 6;      // 802.11ax
2603         public static final int MODE_11BE = 7;      // 802.11be
2604         //<TODO> add support for 802.11ad and be more selective instead of defaulting to 11A
2605 
2606         /**
2607          * Use frequency, max supported rate, and the existence of EHT, HE, VHT, HT & ERP fields in
2608          * scan result to determine the 802.11 Wifi standard being used.
2609          */
determineMode(int frequency, int maxRate, boolean foundEht, boolean foundHe, boolean foundVht, boolean foundHt, boolean foundErp)2610         public static int determineMode(int frequency, int maxRate, boolean foundEht,
2611                 boolean foundHe, boolean foundVht, boolean foundHt, boolean foundErp) {
2612             if (foundEht) {
2613                 return MODE_11BE;
2614             } else if (foundHe) {
2615                 return MODE_11AX;
2616             } else if (!ScanResult.is24GHz(frequency) && foundVht) {
2617                 // Do not include subset of VHT on 2.4 GHz vendor extension
2618                 // in consideration for reporting VHT.
2619                 return MODE_11AC;
2620             } else if (foundHt) {
2621                 return MODE_11N;
2622             } else if (foundErp) {
2623                 return MODE_11G;
2624             } else if (ScanResult.is24GHz(frequency)) {
2625                 if (maxRate < 24000000) {
2626                     return MODE_11B;
2627                 } else {
2628                     return MODE_11G;
2629                 }
2630             } else {
2631                 return MODE_11A;
2632             }
2633         }
2634 
2635         /**
2636          * Map the wifiMode integer to its type, and output as String MODE_11<A/B/G/N/AC/AX/BE>
2637          */
toString(int mode)2638         public static String toString(int mode) {
2639             switch(mode) {
2640                 case MODE_11A:
2641                     return "MODE_11A";
2642                 case MODE_11B:
2643                     return "MODE_11B";
2644                 case MODE_11G:
2645                     return "MODE_11G";
2646                 case MODE_11N:
2647                     return "MODE_11N";
2648                 case MODE_11AC:
2649                     return "MODE_11AC";
2650                 case MODE_11AX:
2651                     return "MODE_11AX";
2652                 case MODE_11BE:
2653                     return "MODE_11BE";
2654                 default:
2655                     return "MODE_UNDEFINED";
2656             }
2657         }
2658     }
2659 
2660     /**
2661      * Parser for both the Supported Rates & Extended Supported Rates Information Elements
2662      */
2663     public static class SupportedRates {
2664         public static final int MASK = 0x7F; // 0111 1111
2665         public boolean mValid = false;
2666         public ArrayList<Integer> mRates;
2667 
SupportedRates()2668         public SupportedRates() {
2669             mRates = new ArrayList<Integer>();
2670         }
2671 
2672         /**
2673          * Is this a valid Supported Rates information element.
2674          */
isValid()2675         public boolean isValid() {
2676             return mValid;
2677         }
2678 
2679         /**
2680          * get the Rate in bits/s from associated byteval
2681          */
getRateFromByte(int byteVal)2682         public static int getRateFromByte(int byteVal) {
2683             byteVal &= MASK;
2684             switch(byteVal) {
2685                 case 2:
2686                     return 1000000;
2687                 case 4:
2688                     return 2000000;
2689                 case 11:
2690                     return 5500000;
2691                 case 12:
2692                     return 6000000;
2693                 case 18:
2694                     return 9000000;
2695                 case 22:
2696                     return 11000000;
2697                 case 24:
2698                     return 12000000;
2699                 case 36:
2700                     return 18000000;
2701                 case 44:
2702                     return 22000000;
2703                 case 48:
2704                     return 24000000;
2705                 case 66:
2706                     return 33000000;
2707                 case 72:
2708                     return 36000000;
2709                 case 96:
2710                     return 48000000;
2711                 case 108:
2712                     return 54000000;
2713                 default:
2714                     //ERROR UNKNOWN RATE
2715                     return -1;
2716             }
2717         }
2718 
2719         // Supported Rates format (size unit: byte)
2720         //
2721         //| ElementID | Length | Supported Rates  [7 Little Endian Info bits - 1 Flag bit]
2722         //      1          1          1 - 8
2723         //
2724         // Note: InformationElement.bytes has 'Element ID' and 'Length'
2725         //       stripped off already
2726         //
from(InformationElement ie)2727         public void from(InformationElement ie) {
2728             mValid = false;
2729             if (ie == null || ie.bytes == null || ie.bytes.length > 8 || ie.bytes.length < 1)  {
2730                 return;
2731             }
2732             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
2733             try {
2734                 for (int i = 0; i < ie.bytes.length; i++) {
2735                     int rate = getRateFromByte(data.get());
2736                     if (rate > 0) {
2737                         mRates.add(rate);
2738                     } else {
2739                         return;
2740                     }
2741                 }
2742             } catch (BufferUnderflowException e) {
2743                 return;
2744             }
2745             mValid = true;
2746             return;
2747         }
2748 
2749         /**
2750          * Lists the rates in a human readable string
2751          */
toString()2752         public String toString() {
2753             StringBuilder sbuf = new StringBuilder();
2754             for (Integer rate : mRates) {
2755                 sbuf.append(String.format("%.1f", (double) rate / 1000000) + ", ");
2756             }
2757             return sbuf.toString();
2758         }
2759     }
2760 
2761     /**
2762      * This util class determines country related information in beacon/probe response
2763      */
2764     public static class Country {
2765         private boolean mValid = false;
2766         public String mCountryCode = "00";
2767 
2768         /**
2769          * Parse the Information Element Country Information field. Note that element ID and length
2770          * fields are already removed.
2771          *
2772          * Country IE format (size unit: byte)
2773          *
2774          * ElementID | Length | country string | triplet | padding
2775          *      1          1          3            Q*x       0 or 1
2776          * First two bytes of country string are country code
2777          * See 802.11 spec dot11CountryString definition.
2778          */
from(InformationElement ie)2779         public void from(InformationElement ie) {
2780             mValid = false;
2781             if (ie == null || ie.bytes == null || ie.bytes.length < 3) return;
2782             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
2783             try {
2784                 char letter1 = (char) (data.get() & Constants.BYTE_MASK);
2785                 char letter2 = (char) (data.get() & Constants.BYTE_MASK);
2786                 char letter3 = (char) (data.get() & Constants.BYTE_MASK);
2787                 // See 802.11 spec dot11CountryString definition.
2788                 // ' ', 'O', 'I' are for all operation, outdoor, indoor environments, respectively.
2789                 mValid = (letter3 == ' ' || letter3 == 'O' || letter3 == 'I')
2790                         && Character.isLetterOrDigit((int) letter1)
2791                         && Character.isLetterOrDigit((int) letter2);
2792                 if (mValid) {
2793                     mCountryCode = (String.valueOf(letter1) + letter2).toUpperCase(Locale.US);
2794                 }
2795             } catch (BufferUnderflowException e) {
2796                 return;
2797             }
2798         }
2799 
2800         /**
2801          * Is this a valid country information element.
2802          */
isValid()2803         public boolean isValid() {
2804             return mValid;
2805         }
2806 
2807         /**
2808          * @return country code indicated in beacon/probe response frames
2809          */
getCountryCode()2810         public String getCountryCode() {
2811             return mCountryCode;
2812         }
2813     }
2814 }
2815