1/*
2 * Copyright (C) 2017 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.p2p;
17
18import static org.junit.Assert.*;
19import static org.mockito.Matchers.*;
20import static org.mockito.Mockito.doAnswer;
21import static org.mockito.Mockito.mock;
22import static org.mockito.Mockito.never;
23import static org.mockito.Mockito.times;
24import static org.mockito.Mockito.verify;
25
26import android.app.test.MockAnswerUtil.AnswerWithArguments;
27import android.hardware.wifi.supplicant.V1_0.ISupplicantP2pIfaceCallback;
28import android.hardware.wifi.supplicant.V1_0.WpsConfigMethods;
29import android.net.wifi.WpsInfo;
30import android.net.wifi.p2p.WifiP2pConfig;
31import android.net.wifi.p2p.WifiP2pDevice;
32import android.net.wifi.p2p.WifiP2pGroup;
33import android.net.wifi.p2p.WifiP2pProvDiscEvent;
34import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
35
36import com.android.server.wifi.util.NativeUtil;
37
38import org.junit.Assert.*;
39import org.junit.Before;
40import org.junit.Test;
41import org.mockito.ArgumentCaptor;
42import org.mockito.MockitoAnnotations;
43
44import java.util.ArrayList;
45import java.util.HashSet;
46import java.util.List;
47
48
49/**
50 * Unit tests for SupplicantP2pIfaceCallback
51 */
52public class SupplicantP2pIfaceCallbackTest {
53    private static final String TAG = "SupplicantP2pIfaceCallbackTest";
54
55    private String mIface = "test_p2p0";
56    private WifiP2pMonitor mMonitor;
57    private SupplicantP2pIfaceCallback mDut;
58
59    private byte[] mDeviceAddressInvalid1 = { 0x00 };
60    private byte[] mDeviceAddressInvalid2 = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 };
61    private byte[] mDeviceAddress1Bytes = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 };
62    private String mDeviceAddress1String = "00:11:22:33:44:55";
63    private byte[] mDeviceAddress2Bytes = { 0x01, 0x12, 0x23, 0x34, 0x45, 0x56 };
64    private String mDeviceAddress2String = "01:12:23:34:45:56";
65    private byte[] mDeviceInfoBytes = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
66    private static final byte[] DEVICE_ADDRESS = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
67
68    private class SupplicantP2pIfaceCallbackSpy extends SupplicantP2pIfaceCallback {
69        SupplicantP2pIfaceCallbackSpy(String iface, WifiP2pMonitor monitor) {
70            super(iface, monitor);
71        }
72    }
73
74    @Before
75    public void setUp() throws Exception {
76        MockitoAnnotations.initMocks(this);
77        mMonitor = mock(WifiP2pMonitor.class);
78        mDut = new SupplicantP2pIfaceCallbackSpy(mIface, mMonitor);
79    }
80
81    /**
82     * Sunny day scenario for onDeviceFound call.
83     */
84    @Test
85    public void testOnDeviceFound_success() throws Exception {
86        byte[] fakePrimaryDeviceTypeBytes = { 0x00, 0x01, 0x02, -1, 0x04, 0x05, 0x06, 0x07 };
87        String fakePrimaryDeviceTypeString = "1-02FF0405-1543";
88        String fakeDeviceName = "test device name";
89        short fakeConfigMethods = 0x1234;
90        byte fakeCapabilities = 123;
91        int fakeGroupCapabilities = 456;
92
93        doAnswer(new AnswerWithArguments() {
94            public void answer(String iface, WifiP2pDevice device) {
95                // NOTE: mDeviceAddress1Bytes seems to be ignored by
96                // legacy implementation of WifiP2pDevice.
97                assertEquals(iface, mIface);
98                assertEquals(device.deviceName, fakeDeviceName);
99                assertEquals(device.primaryDeviceType, fakePrimaryDeviceTypeString);
100                assertEquals(device.deviceCapability, fakeCapabilities);
101                assertEquals(device.groupCapability, fakeGroupCapabilities);
102                assertEquals(device.wpsConfigMethodsSupported, fakeConfigMethods);
103                assertEquals(device.deviceAddress, mDeviceAddress2String);
104                assertEquals(device.status, WifiP2pDevice.AVAILABLE);
105            }
106        })
107        .when(mMonitor).broadcastP2pDeviceFound(
108                anyString(), any(WifiP2pDevice.class));
109
110        mDut.onDeviceFound(
111                mDeviceAddress1Bytes, mDeviceAddress2Bytes,
112                fakePrimaryDeviceTypeBytes,
113                fakeDeviceName, fakeConfigMethods,
114                fakeCapabilities, fakeGroupCapabilities,
115                mDeviceInfoBytes);
116
117        mDut.onDeviceFound(
118                mDeviceAddress1Bytes, mDeviceAddress2Bytes,
119                fakePrimaryDeviceTypeBytes,
120                fakeDeviceName, fakeConfigMethods,
121                fakeCapabilities, fakeGroupCapabilities,
122                null);
123
124        // Make sure we issued a broadcast each time.
125        verify(mMonitor, times(2)).broadcastP2pDeviceFound(
126                anyString(), any(WifiP2pDevice.class));
127    }
128
129    /**
130     * Failing scenarios for onDeviceFound call.
131     */
132    @Test
133    public void testOnDeviceFound_invalidArguments() throws Exception {
134        byte[] fakePrimaryDeviceTypeBytes = { 0x0, 0x01, 0x02, -1, 0x04, 0x05, 0x06, 0x07 };
135        String fakePrimaryDeviceTypeString = "1-02FF0405-1543";
136        String fakeDeviceName = "test device name";
137        short fakeConfigMethods = 0x1234;
138        byte fakeCapabilities = 123;
139        int fakeGroupCapabilities = 456;
140
141        mDut.onDeviceFound(
142                mDeviceAddress2Bytes, null,
143                fakePrimaryDeviceTypeBytes,
144                fakeDeviceName, fakeConfigMethods,
145                fakeCapabilities, fakeGroupCapabilities,
146                mDeviceInfoBytes);
147        verify(mMonitor, never()).broadcastP2pDeviceFound(
148                anyString(), any(WifiP2pDevice.class));
149
150
151        mDut.onDeviceFound(
152                mDeviceAddress1Bytes, mDeviceAddress2Bytes,
153                null,
154                fakeDeviceName, fakeConfigMethods,
155                fakeCapabilities, fakeGroupCapabilities,
156                mDeviceInfoBytes);
157        verify(mMonitor, never()).broadcastP2pDeviceFound(
158                anyString(), any(WifiP2pDevice.class));
159
160
161        mDut.onDeviceFound(
162                mDeviceAddress1Bytes, mDeviceAddress2Bytes,
163                fakePrimaryDeviceTypeBytes,
164                null, fakeConfigMethods,
165                fakeCapabilities, fakeGroupCapabilities,
166                mDeviceInfoBytes);
167        verify(mMonitor, never()).broadcastP2pDeviceFound(
168                anyString(), any(WifiP2pDevice.class));
169
170
171        mDut.onDeviceFound(
172                mDeviceAddress1Bytes, mDeviceAddressInvalid1,
173                fakePrimaryDeviceTypeBytes,
174                null, fakeConfigMethods,
175                fakeCapabilities, fakeGroupCapabilities,
176                mDeviceInfoBytes);
177        verify(mMonitor, never()).broadcastP2pDeviceFound(
178                anyString(), any(WifiP2pDevice.class));
179
180
181        mDut.onDeviceFound(
182                mDeviceAddress1Bytes, mDeviceAddressInvalid2,
183                fakePrimaryDeviceTypeBytes,
184                null, fakeConfigMethods,
185                fakeCapabilities, fakeGroupCapabilities,
186                mDeviceInfoBytes);
187        verify(mMonitor, never()).broadcastP2pDeviceFound(
188                anyString(), any(WifiP2pDevice.class));
189    }
190
191    /**
192     * Sunny day scenario for onDeviceLost call.
193     */
194    @Test
195    public void testOnDeviceLost_success() throws Exception {
196        doAnswer(new AnswerWithArguments() {
197            public void answer(String iface, WifiP2pDevice device) {
198                assertEquals(iface, mIface);
199                assertEquals(device.deviceAddress, mDeviceAddress1String);
200                assertEquals(device.status, WifiP2pDevice.UNAVAILABLE);
201            }
202        })
203        .when(mMonitor).broadcastP2pDeviceLost(
204                anyString(), any(WifiP2pDevice.class));
205
206        mDut.onDeviceLost(mDeviceAddress1Bytes);
207
208        // Make sure we issued a broadcast each time.
209        verify(mMonitor, times(1)).broadcastP2pDeviceLost(
210                anyString(), any(WifiP2pDevice.class));
211    }
212
213    /**
214     * Failing scenarios for onDeviceLost call.
215     */
216    @Test
217    public void testOnDeviceLost_invalidArguments() throws Exception {
218        mDut.onDeviceLost(null);
219        verify(mMonitor, never()).broadcastP2pDeviceLost(
220                anyString(), any(WifiP2pDevice.class));
221
222        mDut.onDeviceLost(mDeviceAddressInvalid1);
223        verify(mMonitor, never()).broadcastP2pDeviceLost(
224                anyString(), any(WifiP2pDevice.class));
225
226        mDut.onDeviceLost(mDeviceAddressInvalid2);
227        verify(mMonitor, never()).broadcastP2pDeviceLost(
228                anyString(), any(WifiP2pDevice.class));
229    }
230
231    /**
232     * Sunny day scenario for onGoNegotiationRequest call.
233     */
234    @Test
235    public void testOnGoNegotiationRequest_success() throws Exception {
236        HashSet<Integer> setups = new HashSet<Integer>();
237
238        doAnswer(new AnswerWithArguments() {
239            public void answer(String iface, WifiP2pConfig config) {
240                assertEquals(iface, mIface);
241                assertNotNull(config.wps);
242                setups.add(config.wps.setup);
243                assertEquals(config.deviceAddress, mDeviceAddress1String);
244            }
245        })
246        .when(mMonitor).broadcastP2pGoNegotiationRequest(
247                anyString(), any(WifiP2pConfig.class));
248
249        mDut.onGoNegotiationRequest(mDeviceAddress1Bytes,
250                (short)ISupplicantP2pIfaceCallback.WpsDevPasswordId.USER_SPECIFIED);
251        assertTrue(setups.contains(WpsInfo.DISPLAY));
252
253        mDut.onGoNegotiationRequest(mDeviceAddress1Bytes,
254                (short)ISupplicantP2pIfaceCallback.WpsDevPasswordId.PUSHBUTTON);
255        assertTrue(setups.contains(WpsInfo.PBC));
256
257        mDut.onGoNegotiationRequest(mDeviceAddress1Bytes,
258                (short)ISupplicantP2pIfaceCallback.WpsDevPasswordId.REGISTRAR_SPECIFIED);
259        assertTrue(setups.contains(WpsInfo.KEYPAD));
260
261        // Invalid should default to PBC
262        setups.clear();
263        mDut.onGoNegotiationRequest(mDeviceAddress1Bytes, (short)0xffff);
264        assertTrue(setups.contains(WpsInfo.PBC));
265    }
266
267    /**
268     * Failing scenarios for onGoNegotiationRequest call.
269     */
270    @Test
271    public void testOnGoNegotiationRequest_invalidArguments() throws Exception {
272        mDut.onGoNegotiationRequest(null, (short)0);
273        verify(mMonitor, never()).broadcastP2pDeviceLost(
274                anyString(), any(WifiP2pDevice.class));
275
276        mDut.onGoNegotiationRequest(mDeviceAddressInvalid1, (short)0);
277        verify(mMonitor, never()).broadcastP2pDeviceLost(
278                anyString(), any(WifiP2pDevice.class));
279
280        mDut.onGoNegotiationRequest(mDeviceAddressInvalid2, (short)0);
281        verify(mMonitor, never()).broadcastP2pDeviceLost(
282                anyString(), any(WifiP2pDevice.class));
283    }
284
285    /**
286     * Sunny day scenario for onGroupStarted call.
287     */
288    @Test
289    public void testOnGroupStarted_success() throws Exception {
290        String fakeName = "group name";
291        String fakePassphrase = "secret";
292        ArrayList<Byte> fakeSsidBytesList = new ArrayList<Byte>() {{
293            add((byte)0x30);
294            add((byte)0x31);
295            add((byte)0x32);
296            add((byte)0x33);
297        }};
298        String fakeSsidString = "0123";
299        HashSet<String> passwords = new HashSet<String>();
300
301        doAnswer(new AnswerWithArguments() {
302            public void answer(String iface, WifiP2pGroup group) {
303                assertEquals(iface, mIface);
304                assertNotNull(group.getOwner());
305                assertEquals(group.getOwner().deviceAddress, mDeviceAddress1String);
306                assertEquals(group.getNetworkId(), WifiP2pGroup.PERSISTENT_NET_ID);
307                passwords.add(group.getPassphrase());
308                assertEquals(group.getInterface(), fakeName);
309                assertEquals(group.getNetworkName(), fakeSsidString);
310            }
311        })
312        .when(mMonitor).broadcastP2pGroupStarted(
313                anyString(), any(WifiP2pGroup.class));
314
315        mDut.onGroupStarted(
316                fakeName, true, fakeSsidBytesList, 1, null, fakePassphrase,
317                mDeviceAddress1Bytes, true);
318        assertTrue(passwords.contains(fakePassphrase));
319
320        mDut.onGroupStarted(
321                fakeName, true, fakeSsidBytesList, 1, null, null,
322                mDeviceAddress1Bytes, true);
323        assertTrue(passwords.contains(null));
324
325        verify(mMonitor, times(2)).broadcastP2pGroupStarted(
326                anyString(), any(WifiP2pGroup.class));
327    }
328
329    /**
330     * Failing scenarios for onGroupStarted call.
331     */
332    @Test
333    public void testOnGroupStarted_invalidArguments() throws Exception {
334        String fakeName = "group name";
335        String fakePassphrase = "secret";
336        ArrayList<Byte> fakeSsidBytesList = new ArrayList<Byte>() {{
337            add((byte)0x30);
338            add((byte)0x31);
339            add((byte)0x32);
340            add((byte)0x33);
341        }};
342        String fakeSsidString = "0123";
343
344        mDut.onGroupStarted(
345                null, true, fakeSsidBytesList, 1, null, fakePassphrase,
346                mDeviceAddress1Bytes, true);
347        verify(mMonitor, never()).broadcastP2pGroupStarted(
348                anyString(), any(WifiP2pGroup.class));
349
350        mDut.onGroupStarted(
351                fakeName, true, null, 1, null, fakePassphrase,
352                mDeviceAddress1Bytes, true);
353        verify(mMonitor, never()).broadcastP2pGroupStarted(
354                anyString(), any(WifiP2pGroup.class));
355
356        mDut.onGroupStarted(
357                fakeName, true, fakeSsidBytesList, 1, null, fakePassphrase,
358                null, true);
359        verify(mMonitor, never()).broadcastP2pGroupStarted(
360                anyString(), any(WifiP2pGroup.class));
361    }
362
363    /**
364     * Test provision discovery callback.
365     */
366    @Test
367    public void testOnProvisionDiscoveryCompleted() throws Exception {
368        byte[] p2pDeviceAddr = DEVICE_ADDRESS;
369        boolean isRequest = false;
370        byte status = ISupplicantP2pIfaceCallback.P2pProvDiscStatusCode.SUCCESS;
371        short configMethods = WpsConfigMethods.DISPLAY;
372        String generatedPin = "12345678";
373
374        ArgumentCaptor<WifiP2pProvDiscEvent> discEventCaptor =
375                ArgumentCaptor.forClass(WifiP2pProvDiscEvent.class);
376        mDut.onProvisionDiscoveryCompleted(
377                p2pDeviceAddr, isRequest, status, configMethods, generatedPin);
378        verify(mMonitor).broadcastP2pProvisionDiscoveryEnterPin(
379                anyString(), discEventCaptor.capture());
380        assertEquals(WifiP2pProvDiscEvent.ENTER_PIN, discEventCaptor.getValue().event);
381
382        configMethods = WpsConfigMethods.KEYPAD;
383        mDut.onProvisionDiscoveryCompleted(
384                p2pDeviceAddr, isRequest, status, configMethods, generatedPin);
385        verify(mMonitor).broadcastP2pProvisionDiscoveryShowPin(
386                anyString(), discEventCaptor.capture());
387        assertEquals(WifiP2pProvDiscEvent.SHOW_PIN, discEventCaptor.getValue().event);
388        assertEquals(generatedPin, discEventCaptor.getValue().pin);
389
390        isRequest = true;
391        configMethods = WpsConfigMethods.KEYPAD;
392        mDut.onProvisionDiscoveryCompleted(
393                p2pDeviceAddr, isRequest, status, configMethods, generatedPin);
394        verify(mMonitor, times(2)).broadcastP2pProvisionDiscoveryEnterPin(
395                anyString(), discEventCaptor.capture());
396        assertEquals(WifiP2pProvDiscEvent.ENTER_PIN, discEventCaptor.getValue().event);
397
398        configMethods = WpsConfigMethods.DISPLAY;
399        mDut.onProvisionDiscoveryCompleted(
400                p2pDeviceAddr, isRequest, status, configMethods, generatedPin);
401        verify(mMonitor, times(2)).broadcastP2pProvisionDiscoveryShowPin(
402                anyString(), discEventCaptor.capture());
403        assertEquals(WifiP2pProvDiscEvent.SHOW_PIN, discEventCaptor.getValue().event);
404        assertEquals(generatedPin, discEventCaptor.getValue().pin);
405
406        isRequest = false;
407        configMethods = WpsConfigMethods.PUSHBUTTON;
408        mDut.onProvisionDiscoveryCompleted(
409                p2pDeviceAddr, isRequest, status, configMethods, generatedPin);
410        verify(mMonitor).broadcastP2pProvisionDiscoveryPbcResponse(
411                anyString(), discEventCaptor.capture());
412        assertEquals(WifiP2pProvDiscEvent.PBC_RSP, discEventCaptor.getValue().event);
413
414        isRequest = true;
415        mDut.onProvisionDiscoveryCompleted(
416                p2pDeviceAddr, isRequest, status, configMethods, generatedPin);
417        verify(mMonitor).broadcastP2pProvisionDiscoveryPbcRequest(
418                anyString(), discEventCaptor.capture());
419        assertEquals(WifiP2pProvDiscEvent.PBC_REQ, discEventCaptor.getValue().event);
420    }
421
422    /**
423     * Test staAuth with device address, should trigger ApStaConnected broadcast
424     */
425    @Test
426    public void testStaAuth_success() {
427        // Trigger onStaAuthorized callback, ensure wifimonitor broadcast is sent with WifiP2pDevice
428        // using the p2pDeviceAddress
429        ArgumentCaptor<WifiP2pDevice> p2pDeviceCaptor =
430                ArgumentCaptor.forClass(WifiP2pDevice.class);
431        mDut.onStaAuthorized(mDeviceAddress1Bytes, mDeviceAddress2Bytes);
432        verify(mMonitor).broadcastP2pApStaConnected(any(String.class), p2pDeviceCaptor.capture());
433        assertEquals(mDeviceAddress2String, p2pDeviceCaptor.getValue().deviceAddress);
434    }
435
436    /**
437     * Test staAuth without device address, should trigger ApStaConnected broadcast using srcAddress
438     */
439    @Test
440    public void testStaAuth_noDeviceAddress_success() {
441        // Trigger onStaAuthorized callback, using a zero'd p2pDeviceAddress, ensure wifimonitor
442        // broadcast is sent with WifiP2pDevice using the srcAddress
443        ArgumentCaptor<WifiP2pDevice> p2pDeviceCaptor =
444                ArgumentCaptor.forClass(WifiP2pDevice.class);
445        mDut.onStaAuthorized(mDeviceAddress1Bytes, NativeUtil.ANY_MAC_BYTES);
446        verify(mMonitor).broadcastP2pApStaConnected(any(String.class), p2pDeviceCaptor.capture());
447        assertEquals(mDeviceAddress1String, p2pDeviceCaptor.getValue().deviceAddress);
448    }
449
450    // TLVS hex data encoded as a hex string.
451    // Taken directly from an observed supplicant service response event
452    private static final String SERV_DISC_RESP_TLVS = "1d00010100076578616d706c650b5f6166706f766572"
453            + "746370c00c001001001e000101000b5f6166706f766572746370c00c000c01074578616d706c65c0273c"
454            + "00010100096d797072696e746572045f697070c00c00100109747874766572733d311a70646c3d617070"
455            + "6c69636174696f6e2f706f73747363726970741900010100045f697070c00c000c01094d795072696e74"
456            + "6572c0275f000201000a757569643a36383539646564652d383537342d353961622d393333322d313233"
457            + "3435363738393031313a3a75726e3a736368656d61732d75706e702d6f72673a736572766963653a436f"
458            + "6e6e656374696f6e4d616e616765723a3159000201000a757569643a36383539646564652d383537342d"
459            + "353961622d393333322d3132333435363738393031313a3a75726e3a736368656d61732d75706e702d6f"
460            + "72673a736572766963653a41565472616e73706f72743a315a000201000a757569643a36383539646564"
461            + "652d383537342d353961622d393333322d3132333435363738393031313a3a75726e3a736368656d6173"
462            + "2d75706e702d6f72673a6465766963653a4d6564696152656e64657265723a313e000201000a75756964"
463            + "3a36383539646564652d383537342d353961622d393333322d3132333435363738393031313a3a75706e"
464            + "703a726f6f746465766963652d000201000a757569643a36383539646564652d383537342d353961622d"
465            + "393333322d313233343536373839303131";
466
467    /**
468     * Pretty basic onServiceDiscoveryResponse callback test.
469     * Mocks the callback event, passing some observed real data to it, and ensures that it returns
470     * a non-null WifiP2pServiceResponse list.
471     */
472    @Test
473    public void testOnServiceDiscoveryResponseCompleted_success() throws Exception {
474        ArrayList<Byte> tlvs = NativeUtil.byteArrayToArrayList(hexStr2Bin(SERV_DISC_RESP_TLVS));
475        ArgumentCaptor<List<WifiP2pServiceResponse>> respListCaptor =
476                ArgumentCaptor.forClass(List.class);
477        mDut.onServiceDiscoveryResponse(
478                mDeviceAddress1Bytes,
479                (short) 10 /* unused updateIndicator value */,
480                tlvs);
481        verify(mMonitor).broadcastP2pServiceDiscoveryResponse(anyString(),
482                respListCaptor.capture());
483        assertNotNull(respListCaptor.getValue());
484    }
485
486    /**
487     * Converts hex string to byte array.
488     *
489     * @param hex hex string. if invalid, return null.
490     * @return binary data.
491     */
492    private static byte[] hexStr2Bin(String hex) {
493        int sz = hex.length() / 2;
494        byte[] b = new byte[hex.length() / 2];
495        for (int i = 0; i < sz; i++) {
496            try {
497                b[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
498            } catch (Exception e) {
499                return null;
500            }
501        }
502        return b;
503    }
504}
505