TetherInterfaceStateMachineTest.java revision ea9cc488eb0f096c9fd402eff49e3d30f5b6de2e
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.connectivity.tethering;
18
19import static org.mockito.Matchers.anyString;
20import static org.mockito.Mockito.doThrow;
21import static org.mockito.Mockito.inOrder;
22import static org.mockito.Mockito.reset;
23import static org.mockito.Mockito.verify;
24import static org.mockito.Mockito.verifyNoMoreInteractions;
25import static org.mockito.Mockito.when;
26
27import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
28import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
29import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
30import static android.net.ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
31import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
32import static android.net.ConnectivityManager.TETHERING_USB;
33import static android.net.ConnectivityManager.TETHERING_WIFI;
34import static com.android.server.connectivity.tethering.IControlsTethering.STATE_AVAILABLE;
35import static com.android.server.connectivity.tethering.IControlsTethering.STATE_LOCAL_HOTSPOT;
36import static com.android.server.connectivity.tethering.IControlsTethering.STATE_TETHERED;
37import static com.android.server.connectivity.tethering.IControlsTethering.STATE_UNAVAILABLE;
38
39import android.net.ConnectivityManager;
40import android.net.INetworkStatsService;
41import android.net.InterfaceConfiguration;
42import android.os.INetworkManagementService;
43import android.os.RemoteException;
44import android.os.test.TestLooper;
45import android.support.test.filters.SmallTest;
46import android.support.test.runner.AndroidJUnit4;
47
48import org.junit.Before;
49import org.junit.Test;
50import org.junit.runner.RunWith;
51import org.mockito.InOrder;
52import org.mockito.Mock;
53import org.mockito.MockitoAnnotations;
54
55@RunWith(AndroidJUnit4.class)
56@SmallTest
57public class TetherInterfaceStateMachineTest {
58    private static final String IFACE_NAME = "testnet1";
59    private static final String UPSTREAM_IFACE = "upstream0";
60    private static final String UPSTREAM_IFACE2 = "upstream1";
61
62    @Mock private INetworkManagementService mNMService;
63    @Mock private INetworkStatsService mStatsService;
64    @Mock private IControlsTethering mTetherHelper;
65    @Mock private InterfaceConfiguration mInterfaceConfiguration;
66    @Mock private IPv6TetheringInterfaceServices mIPv6TetheringInterfaceServices;
67
68    private final TestLooper mLooper = new TestLooper();
69    private TetherInterfaceStateMachine mTestedSm;
70
71    private void initStateMachine(int interfaceType) throws Exception {
72        mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), interfaceType,
73                mNMService, mStatsService, mTetherHelper, mIPv6TetheringInterfaceServices);
74        mTestedSm.start();
75        // Starting the state machine always puts us in a consistent state and notifies
76        // the test of the world that we've changed from an unknown to available state.
77        mLooper.dispatchAll();
78        reset(mNMService, mStatsService, mTetherHelper);
79        when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
80    }
81
82    private void initTetheredStateMachine(int interfaceType, String upstreamIface) throws Exception {
83        initStateMachine(interfaceType);
84        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED);
85        if (upstreamIface != null) {
86            dispatchTetherConnectionChanged(upstreamIface);
87        }
88        reset(mNMService, mStatsService, mTetherHelper);
89        when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
90    }
91
92    @Before public void setUp() throws Exception {
93        MockitoAnnotations.initMocks(this);
94    }
95
96    @Test
97    public void startsOutAvailable() {
98        mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(),
99                TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper,
100                mIPv6TetheringInterfaceServices);
101        mTestedSm.start();
102        mLooper.dispatchAll();
103        verify(mTetherHelper).notifyInterfaceStateChange(
104                IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
105        verifyNoMoreInteractions(mTetherHelper, mNMService, mStatsService);
106    }
107
108    @Test
109    public void shouldDoNothingUntilRequested() throws Exception {
110        initStateMachine(TETHERING_BLUETOOTH);
111        final int [] NOOP_COMMANDS = {
112            TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED,
113            TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR,
114            TetherInterfaceStateMachine.CMD_IP_FORWARDING_DISABLE_ERROR,
115            TetherInterfaceStateMachine.CMD_START_TETHERING_ERROR,
116            TetherInterfaceStateMachine.CMD_STOP_TETHERING_ERROR,
117            TetherInterfaceStateMachine.CMD_SET_DNS_FORWARDERS_ERROR,
118            TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED
119        };
120        for (int command : NOOP_COMMANDS) {
121            // None of these commands should trigger us to request action from
122            // the rest of the system.
123            dispatchCommand(command);
124            verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
125        }
126    }
127
128    @Test
129    public void handlesImmediateInterfaceDown() throws Exception {
130        initStateMachine(TETHERING_BLUETOOTH);
131
132        dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
133        verify(mTetherHelper).notifyInterfaceStateChange(
134                IFACE_NAME, mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
135        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
136    }
137
138    @Test
139    public void canBeTethered() throws Exception {
140        initStateMachine(TETHERING_BLUETOOTH);
141
142        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED);
143        InOrder inOrder = inOrder(mTetherHelper, mNMService);
144        inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
145        inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
146                IFACE_NAME, mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
147        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
148    }
149
150    @Test
151    public void canUnrequestTethering() throws Exception {
152        initTetheredStateMachine(TETHERING_BLUETOOTH, null);
153
154        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
155        InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
156        inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
157        inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
158                IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
159        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
160    }
161
162    @Test
163    public void canBeTetheredAsUsb() throws Exception {
164        initStateMachine(TETHERING_USB);
165
166        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED);
167        InOrder inOrder = inOrder(mTetherHelper, mNMService);
168        inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
169        inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
170        inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
171        inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
172                IFACE_NAME, mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
173        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
174    }
175
176    @Test
177    public void handlesFirstUpstreamChange() throws Exception {
178        initTetheredStateMachine(TETHERING_BLUETOOTH, null);
179
180        // Telling the state machine about its upstream interface triggers a little more configuration.
181        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
182        InOrder inOrder = inOrder(mNMService);
183        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE);
184        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
185        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
186    }
187
188    @Test
189    public void handlesChangingUpstream() throws Exception {
190        initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
191
192        dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
193        InOrder inOrder = inOrder(mNMService, mStatsService);
194        inOrder.verify(mStatsService).forceUpdate();
195        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
196        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
197        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
198        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
199        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
200    }
201
202    @Test
203    public void handlesChangingUpstreamNatFailure() throws Exception {
204        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
205
206        doThrow(RemoteException.class).when(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
207
208        dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
209        InOrder inOrder = inOrder(mNMService, mStatsService);
210        inOrder.verify(mStatsService).forceUpdate();
211        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
212        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
213        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
214        inOrder.verify(mStatsService).forceUpdate();
215        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
216        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
217    }
218
219    @Test
220    public void handlesChangingUpstreamInterfaceForwardingFailure() throws Exception {
221        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
222
223        doThrow(RemoteException.class).when(mNMService).startInterfaceForwarding(
224                IFACE_NAME, UPSTREAM_IFACE2);
225
226        dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
227        InOrder inOrder = inOrder(mNMService, mStatsService);
228        inOrder.verify(mStatsService).forceUpdate();
229        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
230        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
231        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
232        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
233        inOrder.verify(mStatsService).forceUpdate();
234        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
235        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
236    }
237
238    @Test
239    public void canUnrequestTetheringWithUpstream() throws Exception {
240        initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
241
242        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
243        InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
244        inOrder.verify(mStatsService).forceUpdate();
245        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
246        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
247        inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
248        inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
249                IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
250        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
251    }
252
253    @Test
254    public void interfaceDownLeadsToUnavailable() throws Exception {
255        for (boolean shouldThrow : new boolean[]{true, false}) {
256            initTetheredStateMachine(TETHERING_USB, null);
257
258            if (shouldThrow) {
259                doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME);
260            }
261            dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
262            InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
263            usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
264            usbTeardownOrder.verify(mNMService).setInterfaceConfig(
265                    IFACE_NAME, mInterfaceConfiguration);
266            usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
267                    IFACE_NAME, mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
268        }
269    }
270
271    @Test
272    public void usbShouldBeTornDownOnTetherError() throws Exception {
273        initStateMachine(TETHERING_USB);
274
275        doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME);
276        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED);
277        InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
278        usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
279        usbTeardownOrder.verify(mNMService).setInterfaceConfig(
280                IFACE_NAME, mInterfaceConfiguration);
281        usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
282                IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR);
283    }
284
285    @Test
286    public void shouldTearDownUsbOnUpstreamError() throws Exception {
287        initTetheredStateMachine(TETHERING_USB, null);
288
289        doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString());
290        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
291        InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
292        usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
293        usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
294        usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
295                IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR);
296    }
297
298    @Test
299    public void ignoresDuplicateUpstreamNotifications() throws Exception {
300        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
301
302        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
303
304        for (int i = 0; i < 5; i++) {
305            dispatchTetherConnectionChanged(UPSTREAM_IFACE);
306            verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
307        }
308    }
309
310    /**
311     * Send a command to the state machine under test, and run the event loop to idle.
312     *
313     * @param command One of the TetherInterfaceStateMachine.CMD_* constants.
314     * @param obj An additional argument to pass.
315     */
316    private void dispatchCommand(int command, int arg1) {
317        mTestedSm.sendMessage(command, arg1);
318        mLooper.dispatchAll();
319    }
320
321    /**
322     * Send a command to the state machine under test, and run the event loop to idle.
323     *
324     * @param command One of the TetherInterfaceStateMachine.CMD_* constants.
325     */
326    private void dispatchCommand(int command) {
327        mTestedSm.sendMessage(command);
328        mLooper.dispatchAll();
329    }
330
331    /**
332     * Special override to tell the state machine that the upstream interface has changed.
333     *
334     * @see #dispatchCommand(int)
335     * @param upstreamIface String name of upstream interface (or null)
336     */
337    private void dispatchTetherConnectionChanged(String upstreamIface) {
338        mTestedSm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
339                upstreamIface);
340        mLooper.dispatchAll();
341    }
342}
343