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