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