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