InformationElementUtil.java revision 3571366ac36c70746b9f013ec2b54482861c9292
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 */
16package com.android.server.wifi.util;
17
18import static com.android.server.wifi.anqp.Constants.getInteger;
19
20import android.net.wifi.ScanResult.InformationElement;
21import android.util.Log;
22
23import com.android.server.wifi.anqp.Constants;
24import com.android.server.wifi.anqp.VenueNameElement;
25import com.android.server.wifi.hotspot2.NetworkDetail;
26
27import java.net.ProtocolException;
28import java.nio.BufferUnderflowException;
29import java.nio.ByteBuffer;
30import java.nio.ByteOrder;
31import java.util.ArrayList;
32import java.util.BitSet;
33
34public class InformationElementUtil {
35
36    public static InformationElement[] parseInformationElements(byte[] bytes) {
37        if (bytes == null) {
38            return new InformationElement[0];
39        }
40        ByteBuffer data = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
41
42        ArrayList<InformationElement> infoElements = new ArrayList<>();
43        boolean found_ssid = false;
44        while (data.remaining() > 1) {
45            int eid = data.get() & Constants.BYTE_MASK;
46            int elementLength = data.get() & Constants.BYTE_MASK;
47
48            if (elementLength > data.remaining() || (eid == InformationElement.EID_SSID
49                    && found_ssid)) {
50                // APs often pad the data with bytes that happen to match that of the EID_SSID
51                // marker.  This is not due to a known issue for APs to incorrectly send the SSID
52                // name multiple times.
53                break;
54            }
55            if (eid == InformationElement.EID_SSID) {
56                found_ssid = true;
57            }
58
59            InformationElement ie = new InformationElement();
60            ie.id = eid;
61            ie.bytes = new byte[elementLength];
62            data.get(ie.bytes);
63            infoElements.add(ie);
64        }
65        return infoElements.toArray(new InformationElement[infoElements.size()]);
66    }
67
68
69    public static class BssLoad {
70        public int stationCount = 0;
71        public int channelUtilization = 0;
72        public int capacity = 0;
73
74        public void from(InformationElement ie) {
75            if (ie.id != InformationElement.EID_BSS_LOAD) {
76                throw new IllegalArgumentException("Element id is not BSS_LOAD, : " + ie.id);
77            }
78            if (ie.bytes.length != 5) {
79                throw new IllegalArgumentException("BSS Load element length is not 5: "
80                                                   + ie.bytes.length);
81            }
82            ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
83            stationCount = data.getShort() & Constants.SHORT_MASK;
84            channelUtilization = data.get() & Constants.BYTE_MASK;
85            capacity = data.getShort() & Constants.SHORT_MASK;
86        }
87    }
88
89    public static class HtOperation {
90        public int secondChannelOffset = 0;
91
92        public int getChannelWidth() {
93            if (secondChannelOffset != 0) {
94                return 1;
95            } else {
96                return 0;
97            }
98        }
99
100        public int getCenterFreq0(int primaryFrequency) {
101            //40 MHz
102            if (secondChannelOffset != 0) {
103                if (secondChannelOffset == 1) {
104                    return primaryFrequency + 10;
105                } else if (secondChannelOffset == 3) {
106                    return primaryFrequency - 10;
107                } else {
108                    Log.e("HtOperation", "Error on secondChannelOffset: " + secondChannelOffset);
109                    return 0;
110                }
111            } else {
112                return 0;
113            }
114        }
115
116        public void from(InformationElement ie) {
117            if (ie.id != InformationElement.EID_HT_OPERATION) {
118                throw new IllegalArgumentException("Element id is not HT_OPERATION, : " + ie.id);
119            }
120            secondChannelOffset = ie.bytes[1] & 0x3;
121        }
122    }
123
124    public static class VhtOperation {
125        public int channelMode = 0;
126        public int centerFreqIndex1 = 0;
127        public int centerFreqIndex2 = 0;
128
129        public boolean isValid() {
130            return channelMode != 0;
131        }
132
133        public int getChannelWidth() {
134            return channelMode + 1;
135        }
136
137        public int getCenterFreq0() {
138            //convert channel index to frequency in MHz, channel 36 is 5180MHz
139            return (centerFreqIndex1 - 36) * 5 + 5180;
140        }
141
142        public int getCenterFreq1() {
143            if (channelMode > 1) { //160MHz
144                return (centerFreqIndex2 - 36) * 5 + 5180;
145            } else {
146                return 0;
147            }
148        }
149
150        public void from(InformationElement ie) {
151            if (ie.id != InformationElement.EID_VHT_OPERATION) {
152                throw new IllegalArgumentException("Element id is not VHT_OPERATION, : " + ie.id);
153            }
154            channelMode = ie.bytes[0] & Constants.BYTE_MASK;
155            centerFreqIndex1 = ie.bytes[1] & Constants.BYTE_MASK;
156            centerFreqIndex2 = ie.bytes[2] & Constants.BYTE_MASK;
157        }
158    }
159
160    public static class Interworking {
161        public NetworkDetail.Ant ant = null;
162        public boolean internet = false;
163        public VenueNameElement.VenueGroup venueGroup = null;
164        public VenueNameElement.VenueType venueType = null;
165        public long hessid = 0L;
166
167        public void from(InformationElement ie) {
168            if (ie.id != InformationElement.EID_INTERWORKING) {
169                throw new IllegalArgumentException("Element id is not INTERWORKING, : " + ie.id);
170            }
171            ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
172            int anOptions = data.get() & Constants.BYTE_MASK;
173            ant = NetworkDetail.Ant.values()[anOptions & 0x0f];
174            internet = (anOptions & 0x10) != 0;
175            // Len 1 none, 3 venue-info, 7 HESSID, 9 venue-info & HESSID
176            if (ie.bytes.length == 3 || ie.bytes.length == 9) {
177                try {
178                    ByteBuffer vinfo = data.duplicate();
179                    vinfo.limit(vinfo.position() + 2);
180                    VenueNameElement vne = new VenueNameElement(
181                            Constants.ANQPElementType.ANQPVenueName, vinfo);
182                    venueGroup = vne.getGroup();
183                    venueType = vne.getType();
184                } catch (ProtocolException pe) {
185                    /*Cannot happen*/
186                }
187            } else if (ie.bytes.length != 1 && ie.bytes.length != 7) {
188                throw new IllegalArgumentException("Bad Interworking element length: "
189                        + ie.bytes.length);
190            }
191            if (ie.bytes.length == 7 || ie.bytes.length == 9) {
192                hessid = getInteger(data, ByteOrder.BIG_ENDIAN, 6);
193            }
194        }
195    }
196
197    public static class RoamingConsortium {
198        public int anqpOICount = 0;
199        public long[] roamingConsortiums = null;
200
201        public void from(InformationElement ie) {
202            if (ie.id != InformationElement.EID_ROAMING_CONSORTIUM) {
203                throw new IllegalArgumentException("Element id is not ROAMING_CONSORTIUM, : "
204                        + ie.id);
205            }
206            ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
207            anqpOICount = data.get() & Constants.BYTE_MASK;
208
209            int oi12Length = data.get() & Constants.BYTE_MASK;
210            int oi1Length = oi12Length & Constants.NIBBLE_MASK;
211            int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK;
212            int oi3Length = ie.bytes.length - 2 - oi1Length - oi2Length;
213            int oiCount = 0;
214            if (oi1Length > 0) {
215                oiCount++;
216                if (oi2Length > 0) {
217                    oiCount++;
218                    if (oi3Length > 0) {
219                        oiCount++;
220                    }
221                }
222            }
223            roamingConsortiums = new long[oiCount];
224            if (oi1Length > 0 && roamingConsortiums.length > 0) {
225                roamingConsortiums[0] =
226                        getInteger(data, ByteOrder.BIG_ENDIAN, oi1Length);
227            }
228            if (oi2Length > 0 && roamingConsortiums.length > 1) {
229                roamingConsortiums[1] =
230                        getInteger(data, ByteOrder.BIG_ENDIAN, oi2Length);
231            }
232            if (oi3Length > 0 && roamingConsortiums.length > 2) {
233                roamingConsortiums[2] =
234                        getInteger(data, ByteOrder.BIG_ENDIAN, oi3Length);
235            }
236        }
237    }
238
239    public static class Vsa {
240        private static final int ANQP_DOMID_BIT = 0x04;
241
242        public NetworkDetail.HSRelease hsRelease = null;
243        public int anqpDomainID = 0;    // No domain ID treated the same as a 0; unique info per AP.
244
245        public void from(InformationElement ie) {
246            ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
247            if (ie.bytes.length >= 5 && data.getInt() == Constants.HS20_FRAME_PREFIX) {
248                int hsConf = data.get() & Constants.BYTE_MASK;
249                switch ((hsConf >> 4) & Constants.NIBBLE_MASK) {
250                    case 0:
251                        hsRelease = NetworkDetail.HSRelease.R1;
252                        break;
253                    case 1:
254                        hsRelease = NetworkDetail.HSRelease.R2;
255                        break;
256                    default:
257                        hsRelease = NetworkDetail.HSRelease.Unknown;
258                        break;
259                }
260                if ((hsConf & ANQP_DOMID_BIT) != 0) {
261                    if (ie.bytes.length < 7) {
262                        throw new IllegalArgumentException(
263                                "HS20 indication element too short: " + ie.bytes.length);
264                    }
265                    anqpDomainID = data.getShort() & Constants.SHORT_MASK;
266                }
267            }
268        }
269    }
270
271    public static class ExtendedCapabilities {
272        private static final int RTT_RESP_ENABLE_BIT = 70;
273        private static final long SSID_UTF8_BIT = 0x0001000000000000L;
274
275        public Long extendedCapabilities = null;
276        public boolean is80211McRTTResponder = false;
277
278        public ExtendedCapabilities() {
279        }
280
281        public ExtendedCapabilities(ExtendedCapabilities other) {
282            extendedCapabilities = other.extendedCapabilities;
283            is80211McRTTResponder = other.is80211McRTTResponder;
284        }
285
286        public boolean isStrictUtf8() {
287            return extendedCapabilities != null && (extendedCapabilities & SSID_UTF8_BIT) != 0;
288        }
289
290        public void from(InformationElement ie) {
291            ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
292            extendedCapabilities =
293                    Constants.getInteger(data, ByteOrder.LITTLE_ENDIAN, ie.bytes.length);
294
295            int index = RTT_RESP_ENABLE_BIT / 8;
296            byte offset = RTT_RESP_ENABLE_BIT % 8;
297            if (ie.bytes.length < index + 1) {
298                is80211McRTTResponder = false;
299            } else {
300                is80211McRTTResponder = (ie.bytes[index] & ((byte) 0x1 << offset)) != 0;
301            }
302        }
303    }
304
305    /**
306     * parse beacon to build the capabilities
307     *
308     * This class is used to build the capabilities string of the scan results coming
309     * from HAL. It parses the ieee beacon's capability field, WPA and RSNE IE as per spec,
310     * and builds the ScanResult.capabilities String in a way that mirrors the values returned
311     * by wpa_supplicant.
312     */
313    public static class Capabilities {
314        private static final int CAP_PRIVACY_BIT_OFFSET = 4;
315
316        private static final int WPA_VENDOR_OUI_TYPE_ONE = 0x01f25000;
317        private static final short WPA_VENDOR_OUI_VERSION = 0x0001;
318        private static final short RSNE_VERSION = 0x0001;
319
320        private static final int WPA_AKM_EAP = 0x01f25000;
321        private static final int WPA_AKM_PSK = 0x02f25000;
322
323        private static final int WPA2_AKM_EAP = 0x01ac0f00;
324        private static final int WPA2_AKM_PSK = 0x02ac0f00;
325        private static final int WPA2_AKM_FT_EAP = 0x03ac0f00;
326        private static final int WPA2_AKM_FT_PSK = 0x04ac0f00;
327        private static final int WPA2_AKM_EAP_SHA256 = 0x05ac0f00;
328        private static final int WPA2_AKM_PSK_SHA256 = 0x06ac0f00;
329
330        public Capabilities() {
331        }
332
333        // RSNE format (size unit: byte)
334        //
335        // | Element ID | Length | Version | Group Data Cipher Suite |
336        //      1           1         2                 4
337        // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
338        //              2                            4 * m
339        // | AKM Suite Count | AKM Suite List | RSN Capabilities |
340        //          2               4 * n               2
341        // | PMKID Count | PMKID List | Group Management Cipher Suite |
342        //        2          16 * s                 4
343        //
344        // Note: InformationElement.bytes has 'Element ID' and 'Length'
345        //       stripped off already
346        private static String parseRsnElement(InformationElement ie) {
347            ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
348
349            try {
350                // version
351                if (buf.getShort() != RSNE_VERSION) {
352                    // incorrect version
353                    return null;
354                }
355
356                // group data cipher suite
357                // here we simply advance the buffer position
358                buf.getInt();
359
360                // found the RSNE IE, hence start building the capability string
361                String security = "[WPA2";
362
363                // pairwise cipher suite count
364                short cipherCount = buf.getShort();
365
366                // pairwise cipher suite list
367                for (int i = 0; i < cipherCount; i++) {
368                    // here we simply advance the buffer position
369                    buf.getInt();
370                }
371
372                // AKM
373                // AKM suite count
374                short akmCount = buf.getShort();
375
376                // parse AKM suite list
377                if (akmCount == 0) {
378                    security += "-EAP"; //default AKM
379                }
380                boolean found = false;
381                for (int i = 0; i < akmCount; i++) {
382                    int akm = buf.getInt();
383                    switch (akm) {
384                        case WPA2_AKM_EAP:
385                            security += (found ? "+" : "-") + "EAP";
386                            found = true;
387                            break;
388                        case WPA2_AKM_PSK:
389                            security += (found ? "+" : "-") + "PSK";
390                            found = true;
391                            break;
392                        case WPA2_AKM_FT_EAP:
393                            security += (found ? "+" : "-") + "FT/EAP";
394                            found = true;
395                            break;
396                        case WPA2_AKM_FT_PSK:
397                            security += (found ? "+" : "-") + "FT/PSK";
398                            found = true;
399                            break;
400                        case WPA2_AKM_EAP_SHA256:
401                            security += (found ? "+" : "-") + "EAP-SHA256";
402                            found = true;
403                            break;
404                        case WPA2_AKM_PSK_SHA256:
405                            security += (found ? "+" : "-") + "PSK-SHA256";
406                            found = true;
407                            break;
408                        default:
409                            // do nothing
410                            break;
411                    }
412                }
413
414                // we parsed what we want at this point
415                security += "]";
416                return security;
417            } catch (BufferUnderflowException e) {
418                Log.e("IE_Capabilities", "Couldn't parse RSNE, buffer underflow");
419                return null;
420            }
421        }
422
423        private static boolean isWpaOneElement(InformationElement ie) {
424            ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
425
426            try {
427                // WPA OUI and type
428                return (buf.getInt() == WPA_VENDOR_OUI_TYPE_ONE);
429            } catch (BufferUnderflowException e) {
430                Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
431                return false;
432            }
433        }
434
435        // WPA type 1 format (size unit: byte)
436        //
437        // | Element ID | Length | OUI | Type | Version |
438        //      1           1       3     1        2
439        // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
440        //              2                            4 * m
441        // | AKM Suite Count | AKM Suite List |
442        //          2               4 * n
443        //
444        // Note: InformationElement.bytes has 'Element ID' and 'Length'
445        //       stripped off already
446        //
447        private static String parseWpaOneElement(InformationElement ie) {
448            ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
449
450            try {
451                // skip WPA OUI and type parsing. isWpaOneElement() should have
452                // been called for verification before we reach here.
453                buf.getInt();
454
455                // start building the string
456                String security = "[WPA";
457
458                // version
459                if (buf.getShort() != WPA_VENDOR_OUI_VERSION)  {
460                    // incorrect version
461                    return null;
462                }
463
464                // group data cipher suite
465                // here we simply advance buffer position
466                buf.getInt();
467
468                // pairwise cipher suite count
469                short cipherCount = buf.getShort();
470
471                // pairwise chipher suite list
472                for (int i = 0; i < cipherCount; i++) {
473                    // here we simply advance buffer position
474                    buf.getInt();
475                }
476
477                // AKM
478                // AKM suite count
479                short akmCount = buf.getShort();
480
481                // AKM suite list
482                if (akmCount == 0) {
483                    security += "-EAP"; //default AKM
484                }
485                boolean found = false;
486                for (int i = 0; i < akmCount; i++) {
487                    int akm = buf.getInt();
488                    switch (akm) {
489                        case WPA_AKM_EAP:
490                            security += (found ? "+" : "-") + "EAP";
491                            found = true;
492                            break;
493                        case WPA_AKM_PSK:
494                            security += (found ? "+" : "-") + "PSK";
495                            found = true;
496                            break;
497                        default:
498                            // do nothing
499                            break;
500                    }
501                }
502
503                // we parsed what we want at this point
504                security += "]";
505                return security;
506            } catch (BufferUnderflowException e) {
507                Log.e("IE_Capabilities", "Couldn't parse type 1 WPA, buffer underflow");
508                return null;
509            }
510        }
511
512        /**
513         * Parse the Information Element and the 16-bit Capability Information field
514         * to build the ScanResult.capabilities String.
515         *
516         * @param ies -- Information Element array
517         * @param beaconCap -- 16-bit Beacon Capability Information field
518         * @return security string that mirrors what wpa_supplicant generates
519         */
520        public static String buildCapabilities(InformationElement[] ies, BitSet beaconCap) {
521            String capabilities = "";
522            boolean rsneFound = false;
523            boolean wpaFound = false;
524
525            if (ies == null || beaconCap == null) {
526                return capabilities;
527            }
528
529            boolean privacy = beaconCap.get(CAP_PRIVACY_BIT_OFFSET);
530
531            for (InformationElement ie : ies) {
532                if (ie.id == InformationElement.EID_RSN) {
533                    rsneFound = true;
534                    capabilities += parseRsnElement(ie);
535                }
536
537                if (ie.id == InformationElement.EID_VSA) {
538                    if (isWpaOneElement(ie)) {
539                        wpaFound = true;
540                        capabilities += parseWpaOneElement(ie);
541                    }
542                }
543            }
544
545            if (!rsneFound && !wpaFound && privacy) {
546                //private Beacon without an RSNE or WPA IE, hence WEP0
547                capabilities += "[WEP]";
548            }
549
550            return capabilities;
551        }
552    }
553}
554