InformationElementUtilTest.java revision 9b5773d2805e8c6141ca75de272921a84941546b
1/*
2 * Copyright (C) 2016 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
17package com.android.server.wifi.util;
18
19import static org.junit.Assert.assertArrayEquals;
20import static org.junit.Assert.assertEquals;
21
22import android.net.wifi.ScanResult.InformationElement;
23import android.test.suitebuilder.annotation.SmallTest;
24
25import org.junit.Test;
26
27import java.io.ByteArrayOutputStream;
28import java.io.IOException;
29import java.util.Arrays;
30import java.util.BitSet;
31
32/**
33 * Unit tests for {@link com.android.server.wifi.util.InformationElementUtil}.
34 */
35@SmallTest
36public class InformationElementUtilTest {
37
38    // SSID Information Element tags
39    private static final byte[] TEST_SSID_BYTES_TAG = new byte[] { (byte) 0x00, (byte) 0x0B };
40    // SSID Information Element entry used for testing.
41    private static final byte[] TEST_SSID_BYTES = "GoogleGuest".getBytes();
42    // Valid zero length tag.
43    private static final byte[] TEST_VALID_ZERO_LENGTH_TAG =
44            new byte[] { (byte) 0x0B, (byte) 0x00 };
45    // BSS_LOAD Information Element entry used for testing.
46    private static final byte[] TEST_BSS_LOAD_BYTES_IE =
47            new byte[] { (byte) 0x0B, (byte) 0x01, (byte) 0x08 };
48
49    /*
50     * Function to provide SSID Information Element (SSID = "GoogleGuest").
51     *
52     * @return byte[] Byte array representing the test SSID
53     */
54    private byte[] getTestSsidIEBytes() throws IOException {
55        return concatenateByteArrays(TEST_SSID_BYTES_TAG, TEST_SSID_BYTES);
56    }
57
58    /*
59     * Function used to set byte arrays used for testing.
60     *
61     * @param byteArrays variable number of byte arrays to concatenate
62     * @return byte[] Byte array resulting from concatenating the arrays passed to the function
63     */
64    private static byte[] concatenateByteArrays(byte[]... byteArrays) throws IOException {
65        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
66        for (byte[] b : byteArrays) {
67            baos.write(b);
68        }
69        baos.flush();
70        return baos.toByteArray();
71    }
72
73    /**
74     * Test parseInformationElements with an empty byte array.
75     * Expect parseInformationElement to return an empty InformationElement array.
76     */
77    @Test
78    public void parseInformationElements_withEmptyByteArray() {
79        byte[] emptyBytes = new byte[0];
80        InformationElement[] results =
81                InformationElementUtil.parseInformationElements(emptyBytes);
82        assertEquals("parsed results should be empty", 0, results.length);
83    }
84
85    /**
86     * Test parseInformationElements called with a null parameter.
87     * Expect parseInfomrationElement to return an empty InformationElement array.
88     */
89    @Test
90    public void parseInformationElements_withNullBytes() {
91        byte[] nullBytes = null;
92        InformationElement[] results =
93                InformationElementUtil.parseInformationElements(nullBytes);
94        assertEquals("parsed results should be empty", 0, results.length);
95    }
96
97    /*
98     * Test parseInformationElements with a single element represented in the byte array.
99     * Expect a single element to be returned in the InformationElements array.  The
100     * length of this array should be 1 and the contents should be valid.
101     *
102     * @throws java.io.IOException
103     */
104    @Test
105    public void parseInformationElements_withSingleElement() throws IOException {
106        byte[] ssidBytes = getTestSsidIEBytes();
107
108        InformationElement[] results =
109                InformationElementUtil.parseInformationElements(ssidBytes);
110        assertEquals("Parsed results should have 1 IE", 1, results.length);
111        assertEquals("Parsed result should be a ssid", InformationElement.EID_SSID, results[0].id);
112        assertArrayEquals("parsed SSID does not match input",
113                TEST_SSID_BYTES, results[0].bytes);
114    }
115
116    /*
117     * Test parseInformationElement with extra padding in the data to parse.
118     * Expect the function to return the SSID information element.
119     *
120     * Note: Experience shows that APs often pad messages with 0x00.  This happens to be the tag for
121     * EID_SSID.  This test checks if padding will be properly discarded.
122     *
123     * @throws java.io.IOException
124     */
125    @Test
126    public void parseInformationElements_withExtraPadding() throws IOException {
127        byte[] paddingBytes = new byte[10];
128        Arrays.fill(paddingBytes, (byte) 0x00);
129        byte[] ssidBytesWithPadding = concatenateByteArrays(getTestSsidIEBytes(), paddingBytes);
130
131        InformationElement[] results =
132                InformationElementUtil.parseInformationElements(ssidBytesWithPadding);
133        assertEquals("Parsed results should have 1 IE", 1, results.length);
134        assertEquals("Parsed result should be a ssid", InformationElement.EID_SSID, results[0].id);
135        assertArrayEquals("parsed SSID does not match input",
136                TEST_SSID_BYTES, results[0].bytes);
137    }
138
139    /*
140     * Test parseInformationElement with two elements where the second element has an invalid
141     * length.
142     * Expect the function to return the first valid entry and skip the remaining information.
143     *
144     * Note:  This test partially exposes issues with blindly parsing the data.  A higher level
145     * function to validate the parsed data may be added.
146     *
147     * @throws java.io.IOException
148     * */
149    @Test
150    public void parseInformationElements_secondElementInvalidLength() throws IOException {
151        byte[] invalidTag = new byte[] { (byte) 0x01, (byte) 0x08, (byte) 0x08 };
152        byte[] twoTagsSecondInvalidBytes = concatenateByteArrays(getTestSsidIEBytes(), invalidTag);
153
154        InformationElement[] results =
155                InformationElementUtil.parseInformationElements(twoTagsSecondInvalidBytes);
156        assertEquals("Parsed results should have 1 IE", 1, results.length);
157        assertEquals("Parsed result should be a ssid.", InformationElement.EID_SSID, results[0].id);
158        assertArrayEquals("parsed SSID does not match input",
159                TEST_SSID_BYTES, results[0].bytes);
160    }
161
162    /*
163     * Test parseInformationElements with two valid Information Element entries.
164     * Expect the function to return an InformationElement array with two entries containing valid
165     * data.
166     *
167     * @throws java.io.IOException
168     */
169    @Test
170    public void parseInformationElements_twoElements() throws IOException {
171        byte[] twoValidTagsBytes =
172                concatenateByteArrays(getTestSsidIEBytes(), TEST_BSS_LOAD_BYTES_IE);
173
174        InformationElement[] results =
175                InformationElementUtil.parseInformationElements(twoValidTagsBytes);
176        assertEquals("parsed results should have 2 elements", 2, results.length);
177        assertEquals("First parsed element should be a ssid",
178                InformationElement.EID_SSID, results[0].id);
179        assertArrayEquals("parsed SSID does not match input",
180                TEST_SSID_BYTES, results[0].bytes);
181        assertEquals("second element should be a BSS_LOAD tag",
182                InformationElement.EID_BSS_LOAD, results[1].id);
183        assertEquals("second element should have data of length 1", 1, results[1].bytes.length);
184        assertEquals("second element data was not parsed correctly.",
185                (byte) 0x08, results[1].bytes[0]);
186    }
187
188    /*
189     * Test parseInformationElements with two elements where the first information element has a
190     * length of zero.
191     * Expect the function to return an InformationElement array with two entries containing valid
192     * data.
193     *
194     * @throws java.io.IOException
195     */
196    @Test
197    public void parseInformationElements_firstElementZeroLength() throws IOException {
198        byte[] zeroLengthTagWithSSIDBytes =
199                concatenateByteArrays(TEST_VALID_ZERO_LENGTH_TAG, getTestSsidIEBytes());
200
201        InformationElement[] results =
202                InformationElementUtil.parseInformationElements(zeroLengthTagWithSSIDBytes);
203        assertEquals("Parsed results should have 2 elements.", 2, results.length);
204        assertEquals("First element tag should be EID_BSS_LOAD",
205                InformationElement.EID_BSS_LOAD, results[0].id);
206        assertEquals("First element should be length 0", 0, results[0].bytes.length);
207
208        assertEquals("Second element should be a ssid", InformationElement.EID_SSID, results[1].id);
209        assertArrayEquals("parsed SSID does not match input",
210                TEST_SSID_BYTES, results[1].bytes);
211    }
212
213    /*
214     * Test parseInformationElements with two elements where the first element has an invalid
215     * length.  The invalid length in the first element causes us to miss the start of the second
216     * Infomation Element.  This results in a single element in the returned array.
217     * Expect the function to return a single entry in an InformationElement array. This returned
218     * entry is not validated at this time and does not contain valid data (since the incorrect
219     * length was used).
220     * TODO: attempt to validate the data and recover as much as possible.  When the follow-on CL
221     * is in development, this test will be updated to reflect the change.
222     *
223     * @throws java.io.IOException
224     */
225    @Test
226    public void parseInformationElements_firstElementWrongLength() throws IOException {
227        byte[] invalidLengthTag = new byte[] {(byte) 0x0B, (byte) 0x01 };
228        byte[] invalidLengthTagWithSSIDBytes =
229                concatenateByteArrays(invalidLengthTag, getTestSsidIEBytes());
230
231        InformationElement[] results =
232                InformationElementUtil.parseInformationElements(invalidLengthTagWithSSIDBytes);
233        assertEquals("Parsed results should have 1 element", 1, results.length);
234        assertEquals("First result should be a EID_BSS_LOAD tag.",
235                InformationElement.EID_BSS_LOAD, results[0].id);
236        assertEquals("First result should have data of 1 byte", 1, results[0].bytes.length);
237        assertEquals("First result should have data set to 0x00",
238                invalidLengthTagWithSSIDBytes[2], results[0].bytes[0]);
239    }
240
241    /**
242     * Test Capabilities.buildCapabilities() with a RSN IE.
243     * Expect the function to return a string with the proper security information.
244     */
245    @Test
246    public void buildCapabilities_rsnElement() {
247        InformationElement ie = new InformationElement();
248        ie.id = InformationElement.EID_RSN;
249        ie.bytes = new byte[] { (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x0F,
250                                (byte) 0xAC, (byte) 0x02, (byte) 0x02, (byte) 0x00,
251                                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x04,
252                                (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x02,
253                                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x0F,
254                                (byte) 0xAC, (byte) 0x02, (byte) 0x00, (byte) 0x00 };
255
256        InformationElement[] ies = new InformationElement[] { ie };
257
258        BitSet beaconCap = new BitSet(16);
259        beaconCap.set(4);
260
261        String result = InformationElementUtil.Capabilities.buildCapabilities(ies, beaconCap);
262
263        assertEquals("[WPA2-PSK]", result);
264    }
265
266    /**
267     * Test Capabilities.buildCapabilities() with a WPA type 1 IE.
268     * Expect the function to return a string with the proper security information.
269     */
270    @Test
271    public void buildCapabilities_wpa1Element() {
272        InformationElement ie = new InformationElement();
273        ie.id = InformationElement.EID_VSA;
274        ie.bytes = new byte[] { (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x01,
275                                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x50,
276                                (byte) 0xF2, (byte) 0x02, (byte) 0x02, (byte) 0x00,
277                                (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x04,
278                                (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x02,
279                                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x50,
280                                (byte) 0xF2, (byte) 0x02, (byte) 0x00, (byte) 0x00 };
281
282        InformationElement[] ies = new InformationElement[] { ie };
283
284        BitSet beaconCap = new BitSet(16);
285        beaconCap.set(4);
286
287        String result = InformationElementUtil.Capabilities.buildCapabilities(ies, beaconCap);
288
289        assertEquals("[WPA-PSK]", result);
290    }
291
292    /**
293     * Test Capabilities.buildCapabilities() with a vendor specific element which
294     * is not WPA type 1. Beacon Capability Information field has the Privacy
295     * bit set.
296     *
297     * Expect the function to return a string with the proper security information.
298     */
299    @Test
300    public void buildCapabilities_nonRsnWpa1Element_privacySet() {
301        InformationElement ie = new InformationElement();
302        ie.id = InformationElement.EID_VSA;
303        ie.bytes = new byte[] { (byte) 0x00, (byte) 0x04, (byte) 0x0E, (byte) 0x01,
304                                (byte) 0x01, (byte) 0x02, (byte) 0x01, (byte) 0x00,
305                                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
306
307        InformationElement[] ies = new InformationElement[] { ie };
308
309        BitSet beaconCap = new BitSet(16);
310        beaconCap.set(4);
311
312        String result = InformationElementUtil.Capabilities.buildCapabilities(ies, beaconCap);
313
314        assertEquals("[WEP]", result);
315    }
316
317    /**
318     * Test Capabilities.buildCapabilities() with a vendor specific element which
319     * is not WPA type 1. Beacon Capability Information field doesn't have the
320     * Privacy bit set.
321     *
322     * Expect the function to return an empty string.
323     */
324    @Test
325    public void buildCapabilities_nonRsnWpa1Element_privacyClear() {
326        InformationElement ie = new InformationElement();
327        ie.id = InformationElement.EID_VSA;
328        ie.bytes = new byte[] { (byte) 0x00, (byte) 0x04, (byte) 0x0E, (byte) 0x01,
329                                (byte) 0x01, (byte) 0x02, (byte) 0x01, (byte) 0x00,
330                                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
331
332        InformationElement[] ies = new InformationElement[] { ie };
333
334        BitSet beaconCap = new BitSet(16);
335        beaconCap.clear(4);
336
337        String result = InformationElementUtil.Capabilities.buildCapabilities(ies, beaconCap);
338
339        assertEquals("", result);
340    }
341
342    /**
343     * Test Capabilities.buildCapabilities() with a vendor specific element which
344     * is not WPA type 1. Beacon Capability Information field has the ESS bit set.
345     *
346     * Expect the function to return a string with [ESS] there.
347     */
348    @Test
349    public void buildCapabilities_nonRsnWpa1Element_essSet() {
350        InformationElement ie = new InformationElement();
351        ie.id = InformationElement.EID_VSA;
352        ie.bytes = new byte[] { (byte) 0x00, (byte) 0x04, (byte) 0x0E, (byte) 0x01,
353                                (byte) 0x01, (byte) 0x02, (byte) 0x01, (byte) 0x00,
354                                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
355
356        InformationElement[] ies = new InformationElement[] { ie };
357
358        BitSet beaconCap = new BitSet(16);
359        beaconCap.set(0);
360
361        String result = InformationElementUtil.Capabilities.buildCapabilities(ies, beaconCap);
362
363        assertEquals("[ESS]", result);
364    }
365
366    /**
367     * Test Capabilities.buildCapabilities() with a vendor specific element which
368     * is not WPA type 1. Beacon Capability Information field doesn't have the
369     * ESS bit set.
370     *
371     * Expect the function to return an empty string.
372     */
373    @Test
374    public void buildCapabilities_nonRsnWpa1Element_essClear() {
375        InformationElement ie = new InformationElement();
376        ie.id = InformationElement.EID_VSA;
377        ie.bytes = new byte[] { (byte) 0x00, (byte) 0x04, (byte) 0x0E, (byte) 0x01,
378                                (byte) 0x01, (byte) 0x02, (byte) 0x01, (byte) 0x00,
379                                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
380
381        InformationElement[] ies = new InformationElement[] { ie };
382
383        BitSet beaconCap = new BitSet(16);
384        beaconCap.clear(0);
385
386        String result = InformationElementUtil.Capabilities.buildCapabilities(ies, beaconCap);
387
388        assertEquals("", result);
389    }
390
391    /**
392     * Test a that a correctly formed TIM Information Element is decoded into a valid TIM element,
393     * and the values are captured
394     */
395    @Test
396    public void parseTrafficIndicationMapInformationElementValid() {
397        InformationElement ie = new InformationElement();
398        ie.id = InformationElement.EID_TIM;
399        ie.bytes = new byte[] { (byte) 0x03, (byte) 0x05, (byte) 0x00, (byte) 0x00};
400        InformationElementUtil.TrafficIndicationMap trafficIndicationMap =
401                new InformationElementUtil.TrafficIndicationMap();
402        trafficIndicationMap.from(ie);
403        assertEquals(trafficIndicationMap.mLength, 4);
404        assertEquals(trafficIndicationMap.mDtimCount, 3);
405        assertEquals(trafficIndicationMap.mDtimPeriod, 5);
406        assertEquals(trafficIndicationMap.mBitmapControl, 0);
407        assertEquals(trafficIndicationMap.isValid(), true);
408    }
409
410    /**
411     * Test that a short invalid Information Element is marked as being an invalid TIM element when
412     * parsed as Traffic Indication Map.
413     */
414    @Test
415    public void parseTrafficIndicationMapInformationElementInvalidTooShort() {
416        InformationElement ie = new InformationElement();
417        ie.id = InformationElement.EID_TIM;
418        ie.bytes = new byte[] { (byte) 0x01, (byte) 0x07 };
419        InformationElementUtil.TrafficIndicationMap trafficIndicationMap =
420                new InformationElementUtil.TrafficIndicationMap();
421        trafficIndicationMap.from(ie);
422        assertEquals(trafficIndicationMap.isValid(), false);
423    }
424
425    /**
426     * Test that a too-large invalid Information Element is marked as an invalid TIM element when
427     * parsed as Traffic Indication Map.
428     */
429    @Test
430    public void parseTrafficIndicationMapInformationElementInvalidTooLong() {
431        InformationElement ie = new InformationElement();
432        ie.id = InformationElement.EID_TIM;
433        ie.bytes = new byte[255]; // bytes length of upto 254 is valid for TIM
434        Arrays.fill(ie.bytes, (byte) 7);
435        InformationElementUtil.TrafficIndicationMap trafficIndicationMap =
436                new InformationElementUtil.TrafficIndicationMap();
437        trafficIndicationMap.from(ie);
438        assertEquals(trafficIndicationMap.isValid(), false);
439    }
440}
441