blob: 30a7dbc80977c678309d53e6d667dc696a679d73 [file] [log] [blame]
Christopher Wiley08725a82016-05-18 16:32:44 -07001/*
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
Christopher Wiley279eca32016-05-20 13:23:10 -070019import static org.junit.Assert.assertFalse;
20import static org.junit.Assert.assertTrue;
Christopher Wileyf972edc2016-05-23 16:17:30 -070021import static org.mockito.Matchers.anyString;
22import static org.mockito.Mockito.doThrow;
Christopher Wiley279eca32016-05-20 13:23:10 -070023import static org.mockito.Mockito.inOrder;
24import static org.mockito.Mockito.reset;
25import static org.mockito.Mockito.verify;
Christopher Wiley08725a82016-05-18 16:32:44 -070026import static org.mockito.Mockito.verifyNoMoreInteractions;
Christopher Wiley279eca32016-05-20 13:23:10 -070027import static org.mockito.Mockito.when;
Christopher Wiley08725a82016-05-18 16:32:44 -070028
Christopher Wileycd0cfbb2016-05-31 14:43:08 -070029import android.net.ConnectivityManager;
Christopher Wiley08725a82016-05-18 16:32:44 -070030import android.net.INetworkStatsService;
Christopher Wiley279eca32016-05-20 13:23:10 -070031import android.net.InterfaceConfiguration;
Christopher Wiley08725a82016-05-18 16:32:44 -070032import android.os.INetworkManagementService;
Christopher Wiley279eca32016-05-20 13:23:10 -070033import android.os.RemoteException;
Christopher Wiley08725a82016-05-18 16:32:44 -070034import android.os.test.TestLooper;
Christopher Wiley9bc0df22016-05-25 13:57:27 -070035import android.support.test.filters.SmallTest;
36import android.support.test.runner.AndroidJUnit4;
Christopher Wiley08725a82016-05-18 16:32:44 -070037
38import org.junit.Before;
39import org.junit.Test;
Christopher Wiley9bc0df22016-05-25 13:57:27 -070040import org.junit.runner.RunWith;
Christopher Wiley279eca32016-05-20 13:23:10 -070041import org.mockito.InOrder;
Christopher Wiley08725a82016-05-18 16:32:44 -070042import org.mockito.Mock;
43import org.mockito.MockitoAnnotations;
44
Christopher Wiley9bc0df22016-05-25 13:57:27 -070045@RunWith(AndroidJUnit4.class)
46@SmallTest
Mitchell Wills4622c2d2016-05-23 16:40:10 -070047public class TetherInterfaceStateMachineTest {
Christopher Wiley08725a82016-05-18 16:32:44 -070048 private static final String IFACE_NAME = "testnet1";
Christopher Wiley279eca32016-05-20 13:23:10 -070049 private static final String UPSTREAM_IFACE = "upstream0";
50 private static final String UPSTREAM_IFACE2 = "upstream1";
Christopher Wiley08725a82016-05-18 16:32:44 -070051
52 @Mock private INetworkManagementService mNMService;
53 @Mock private INetworkStatsService mStatsService;
54 @Mock private IControlsTethering mTetherHelper;
Christopher Wiley279eca32016-05-20 13:23:10 -070055 @Mock private InterfaceConfiguration mInterfaceConfiguration;
Christopher Wiley08725a82016-05-18 16:32:44 -070056
57 private final TestLooper mLooper = new TestLooper();
Mitchell Wills4622c2d2016-05-23 16:40:10 -070058 private TetherInterfaceStateMachine mTestedSm;
Christopher Wiley08725a82016-05-18 16:32:44 -070059
Christopher Wileycd0cfbb2016-05-31 14:43:08 -070060 private void initStateMachine(int interfaceType) throws Exception {
61 mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), interfaceType,
Christopher Wiley279eca32016-05-20 13:23:10 -070062 mNMService, mStatsService, mTetherHelper);
63 mTestedSm.start();
64 // Starting the state machine always puts us in a consistent state and notifies
65 // the test of the world that we've changed from an unknown to available state.
66 mLooper.dispatchAll();
67 reset(mNMService, mStatsService, mTetherHelper);
Christopher Wileyf972edc2016-05-23 16:17:30 -070068 when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
Christopher Wiley279eca32016-05-20 13:23:10 -070069 }
70
Christopher Wileycd0cfbb2016-05-31 14:43:08 -070071 private void initTetheredStateMachine(int interfaceType, String upstreamIface) throws Exception {
72 initStateMachine(interfaceType);
Mitchell Wills4622c2d2016-05-23 16:40:10 -070073 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
Christopher Wiley279eca32016-05-20 13:23:10 -070074 if (upstreamIface != null) {
75 dispatchTetherConnectionChanged(upstreamIface);
76 }
77 reset(mNMService, mStatsService, mTetherHelper);
Christopher Wileyf972edc2016-05-23 16:17:30 -070078 when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
Christopher Wiley279eca32016-05-20 13:23:10 -070079 }
80
Christopher Wiley08725a82016-05-18 16:32:44 -070081 @Before
82 public void setUp() throws Exception {
83 MockitoAnnotations.initMocks(this);
Christopher Wiley279eca32016-05-20 13:23:10 -070084 }
85
86 @Test
87 public void startsOutAvailable() {
Christopher Wileycd0cfbb2016-05-31 14:43:08 -070088 mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(),
89 ConnectivityManager.TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper);
Christopher Wiley08725a82016-05-18 16:32:44 -070090 mTestedSm.start();
Christopher Wiley279eca32016-05-20 13:23:10 -070091 mLooper.dispatchAll();
92 assertTrue("Should start out available for tethering", mTestedSm.isAvailable());
93 assertFalse("Should not be tethered initially", mTestedSm.isTethered());
94 assertFalse("Should have no errors initially", mTestedSm.isErrored());
95 verify(mTetherHelper).sendTetherStateChangedBroadcast();
96 verifyNoMoreInteractions(mTetherHelper, mNMService, mStatsService);
Christopher Wiley08725a82016-05-18 16:32:44 -070097 }
98
99 @Test
Christopher Wileyf972edc2016-05-23 16:17:30 -0700100 public void shouldDoNothingUntilRequested() throws Exception {
Christopher Wileycd0cfbb2016-05-31 14:43:08 -0700101 initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
Christopher Wiley08725a82016-05-18 16:32:44 -0700102 final int [] NOOP_COMMANDS = {
Mitchell Wills4622c2d2016-05-23 16:40:10 -0700103 TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED,
104 TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR,
105 TetherInterfaceStateMachine.CMD_IP_FORWARDING_DISABLE_ERROR,
106 TetherInterfaceStateMachine.CMD_START_TETHERING_ERROR,
107 TetherInterfaceStateMachine.CMD_STOP_TETHERING_ERROR,
108 TetherInterfaceStateMachine.CMD_SET_DNS_FORWARDERS_ERROR,
109 TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED
Christopher Wiley08725a82016-05-18 16:32:44 -0700110 };
111 for (int command : NOOP_COMMANDS) {
Christopher Wiley279eca32016-05-20 13:23:10 -0700112 // None of these commands should trigger us to request action from
Christopher Wiley08725a82016-05-18 16:32:44 -0700113 // the rest of the system.
Christopher Wiley279eca32016-05-20 13:23:10 -0700114 dispatchCommand(command);
115 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
Christopher Wiley08725a82016-05-18 16:32:44 -0700116 }
117 }
118
Christopher Wiley279eca32016-05-20 13:23:10 -0700119 @Test
Christopher Wileyf972edc2016-05-23 16:17:30 -0700120 public void handlesImmediateInterfaceDown() throws Exception {
Christopher Wileycd0cfbb2016-05-31 14:43:08 -0700121 initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
Mitchell Wills4622c2d2016-05-23 16:40:10 -0700122 dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
Christopher Wiley279eca32016-05-20 13:23:10 -0700123 verify(mTetherHelper).sendTetherStateChangedBroadcast();
124 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
125 assertFalse("Should not be tetherable when the interface is down", mTestedSm.isAvailable());
126 assertFalse("Should not be tethered when the interface is down", mTestedSm.isTethered());
127 assertFalse("Should have no errors when the interface goes immediately down",
128 mTestedSm.isErrored());
129 }
130
131 @Test
Christopher Wileyf972edc2016-05-23 16:17:30 -0700132 public void canBeTethered() throws Exception {
Christopher Wileycd0cfbb2016-05-31 14:43:08 -0700133 initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
Mitchell Wills4622c2d2016-05-23 16:40:10 -0700134 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
Christopher Wiley279eca32016-05-20 13:23:10 -0700135 InOrder inOrder = inOrder(mTetherHelper, mNMService);
136 inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm);
Christopher Wiley279eca32016-05-20 13:23:10 -0700137 inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
138 inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
139
140 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
141 assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable());
142 assertTrue("Should be in a tethered state", mTestedSm.isTethered());
143 assertFalse("Should have no errors when tethered", mTestedSm.isErrored());
144 }
145
146 @Test
147 public void canUnrequestTethering() throws Exception {
Christopher Wileycd0cfbb2016-05-31 14:43:08 -0700148 initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null);
Christopher Wiley279eca32016-05-20 13:23:10 -0700149
Mitchell Wills4622c2d2016-05-23 16:40:10 -0700150 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
Christopher Wiley279eca32016-05-20 13:23:10 -0700151 InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
Christopher Wiley279eca32016-05-20 13:23:10 -0700152 inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
Christopher Wileyf972edc2016-05-23 16:17:30 -0700153 inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
Christopher Wiley279eca32016-05-20 13:23:10 -0700154 inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
155 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
156 assertTrue("Should be ready for tethering again", mTestedSm.isAvailable());
157 assertFalse("Should not be tethered", mTestedSm.isTethered());
158 assertFalse("Should have no errors", mTestedSm.isErrored());
159 }
160
161 @Test
Christopher Wileyf972edc2016-05-23 16:17:30 -0700162 public void canBeTetheredAsUsb() throws Exception {
Christopher Wileycd0cfbb2016-05-31 14:43:08 -0700163 initStateMachine(ConnectivityManager.TETHERING_USB);
Christopher Wiley279eca32016-05-20 13:23:10 -0700164
Mitchell Wills4622c2d2016-05-23 16:40:10 -0700165 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
Christopher Wiley279eca32016-05-20 13:23:10 -0700166 InOrder inOrder = inOrder(mTetherHelper, mNMService);
167 inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm);
168 inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
169 inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
Christopher Wiley279eca32016-05-20 13:23:10 -0700170 inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
171 inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
172
173 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
174 assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable());
175 assertTrue("Should be in a tethered state", mTestedSm.isTethered());
176 assertFalse("Should have no errors when tethered", mTestedSm.isErrored());
177 }
178
179 @Test
180 public void handlesFirstUpstreamChange() throws Exception {
Christopher Wileycd0cfbb2016-05-31 14:43:08 -0700181 initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null);
Christopher Wiley279eca32016-05-20 13:23:10 -0700182
183 // Telling the state machine about its upstream interface triggers a little more configuration.
184 dispatchTetherConnectionChanged(UPSTREAM_IFACE);
185 InOrder inOrder = inOrder(mNMService);
186 inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE);
187 inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
188 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
189 assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable());
190 assertTrue("Should be in a tethered state", mTestedSm.isTethered());
191 assertFalse("Should have no errors when tethered", mTestedSm.isErrored());
192 }
193
194 @Test
195 public void handlesChangingUpstream() throws Exception {
Christopher Wileycd0cfbb2016-05-31 14:43:08 -0700196 initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE);
Christopher Wiley279eca32016-05-20 13:23:10 -0700197
198 dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
199 InOrder inOrder = inOrder(mNMService, mStatsService);
200 inOrder.verify(mStatsService).forceUpdate();
201 inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
202 inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
203 inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
204 inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
205 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
206 assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable());
207 assertTrue("Should be in a tethered state", mTestedSm.isTethered());
208 assertFalse("Should have no errors when tethered", mTestedSm.isErrored());
209 }
210
211 @Test
212 public void canUnrequestTetheringWithUpstream() throws Exception {
Christopher Wileycd0cfbb2016-05-31 14:43:08 -0700213 initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE);
Christopher Wiley279eca32016-05-20 13:23:10 -0700214
Mitchell Wills4622c2d2016-05-23 16:40:10 -0700215 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
Christopher Wiley279eca32016-05-20 13:23:10 -0700216 InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
Christopher Wileyf972edc2016-05-23 16:17:30 -0700217 inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
Christopher Wiley279eca32016-05-20 13:23:10 -0700218 inOrder.verify(mStatsService).forceUpdate();
219 inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
220 inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
221 inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
Christopher Wiley279eca32016-05-20 13:23:10 -0700222 inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
223 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
224 assertTrue("Should be ready for tethering again", mTestedSm.isAvailable());
225 assertFalse("Should not be tethered", mTestedSm.isTethered());
226 assertFalse("Should have no errors", mTestedSm.isErrored());
227 }
228
Christopher Wileyf972edc2016-05-23 16:17:30 -0700229 @Test
230 public void interfaceDownLeadsToUnavailable() throws Exception {
231 for (boolean shouldThrow : new boolean[]{true, false}) {
Christopher Wileycd0cfbb2016-05-31 14:43:08 -0700232 initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null);
Christopher Wileyf972edc2016-05-23 16:17:30 -0700233
234 if (shouldThrow) {
235 doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME);
236 }
237 dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
238 InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration);
239 usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
240 usbTeardownOrder.verify(mNMService).setInterfaceConfig(
241 IFACE_NAME, mInterfaceConfiguration);
242 verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
243 assertFalse("Should not be available", mTestedSm.isAvailable());
244 assertFalse("Should not be tethered", mTestedSm.isTethered());
245 }
246 }
247
248 @Test
249 public void usbShouldBeTornDownOnTetherError() throws Exception {
Christopher Wileycd0cfbb2016-05-31 14:43:08 -0700250 initStateMachine(ConnectivityManager.TETHERING_USB);
Christopher Wileyf972edc2016-05-23 16:17:30 -0700251
252 doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME);
253 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
254 InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration);
255 usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
256 usbTeardownOrder.verify(mNMService).setInterfaceConfig(
257 IFACE_NAME, mInterfaceConfiguration);
258 // Initial call is when we transition to the tethered state on request.
259 verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm);
260 // And this call is to notify that we really aren't requested tethering.
261 verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
262 assertTrue("Expected to see an error reported", mTestedSm.isErrored());
263 }
264
265 @Test
266 public void shouldTearDownUsbOnUpstreamError() throws Exception {
Christopher Wileycd0cfbb2016-05-31 14:43:08 -0700267 initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null);
Christopher Wileyf972edc2016-05-23 16:17:30 -0700268
269 doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString());
270 dispatchTetherConnectionChanged(UPSTREAM_IFACE);
271 InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration);
272 usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
273 usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
274 verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
275 }
Christopher Wiley279eca32016-05-20 13:23:10 -0700276
277 /**
278 * Send a command to the state machine under test, and run the event loop to idle.
279 *
Mitchell Wills4622c2d2016-05-23 16:40:10 -0700280 * @param command One of the TetherInterfaceStateMachine.CMD_* constants.
Christopher Wiley279eca32016-05-20 13:23:10 -0700281 */
282 private void dispatchCommand(int command) {
283 mTestedSm.sendMessage(command);
284 mLooper.dispatchAll();
285 }
286
287 /**
288 * Special override to tell the state machine that the upstream interface has changed.
289 *
290 * @see #dispatchCommand(int)
291 * @param upstreamIface String name of upstream interface (or null)
292 */
293 private void dispatchTetherConnectionChanged(String upstreamIface) {
Mitchell Wills4622c2d2016-05-23 16:40:10 -0700294 mTestedSm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
295 upstreamIface);
Christopher Wiley279eca32016-05-20 13:23:10 -0700296 mLooper.dispatchAll();
Christopher Wiley08725a82016-05-18 16:32:44 -0700297 }
Christopher Wiley9bc0df22016-05-25 13:57:27 -0700298}