blob: 9fcec0e9bfab4dca770b28f34bf6799cd192a765 [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;
21import static org.mockito.Mockito.inOrder;
22import static org.mockito.Mockito.reset;
23import static org.mockito.Mockito.verify;
Christopher Wiley08725a82016-05-18 16:32:44 -070024import static org.mockito.Mockito.verifyNoMoreInteractions;
Christopher Wiley279eca32016-05-20 13:23:10 -070025import static org.mockito.Mockito.when;
Christopher Wiley08725a82016-05-18 16:32:44 -070026
27import android.net.INetworkStatsService;
Christopher Wiley279eca32016-05-20 13:23:10 -070028import android.net.InterfaceConfiguration;
Christopher Wiley08725a82016-05-18 16:32:44 -070029import android.os.INetworkManagementService;
Christopher Wiley279eca32016-05-20 13:23:10 -070030import android.os.RemoteException;
Christopher Wiley08725a82016-05-18 16:32:44 -070031import android.os.test.TestLooper;
32
33import org.junit.Before;
34import org.junit.Test;
Christopher Wiley279eca32016-05-20 13:23:10 -070035import org.mockito.InOrder;
Christopher Wiley08725a82016-05-18 16:32:44 -070036import org.mockito.Mock;
37import org.mockito.MockitoAnnotations;
38
39
40public class TetherInterfaceSMTest {
41 private static final String IFACE_NAME = "testnet1";
Christopher Wiley279eca32016-05-20 13:23:10 -070042 private static final String UPSTREAM_IFACE = "upstream0";
43 private static final String UPSTREAM_IFACE2 = "upstream1";
Christopher Wiley08725a82016-05-18 16:32:44 -070044
45 @Mock private INetworkManagementService mNMService;
46 @Mock private INetworkStatsService mStatsService;
47 @Mock private IControlsTethering mTetherHelper;
Christopher Wiley279eca32016-05-20 13:23:10 -070048 @Mock private InterfaceConfiguration mInterfaceConfiguration;
Christopher Wiley08725a82016-05-18 16:32:44 -070049
50 private final TestLooper mLooper = new TestLooper();
51 private final Object mMutex = new Object();
52 private TetherInterfaceSM mTestedSm;
53
Christopher Wiley279eca32016-05-20 13:23:10 -070054 private void initStateMachine(boolean isUsb) {
55 mTestedSm = new TetherInterfaceSM(IFACE_NAME, mLooper.getLooper(), isUsb, mMutex,
56 mNMService, mStatsService, mTetherHelper);
57 mTestedSm.start();
58 // Starting the state machine always puts us in a consistent state and notifies
59 // the test of the world that we've changed from an unknown to available state.
60 mLooper.dispatchAll();
61 reset(mNMService, mStatsService, mTetherHelper);
62 }
63
64 private void initTetheredStateMachine(boolean isUsb, String upstreamIface) {
65 initStateMachine(isUsb);
66 dispatchCommand(TetherInterfaceSM.CMD_TETHER_REQUESTED);
67 if (upstreamIface != null) {
68 dispatchTetherConnectionChanged(upstreamIface);
69 }
70 reset(mNMService, mStatsService, mTetherHelper);
71 }
72
Christopher Wiley08725a82016-05-18 16:32:44 -070073 @Before
74 public void setUp() throws Exception {
75 MockitoAnnotations.initMocks(this);
Christopher Wiley279eca32016-05-20 13:23:10 -070076 }
77
78 @Test
79 public void startsOutAvailable() {
Christopher Wiley08725a82016-05-18 16:32:44 -070080 mTestedSm = new TetherInterfaceSM(IFACE_NAME, mLooper.getLooper(), false, mMutex,
81 mNMService, mStatsService, mTetherHelper);
82 mTestedSm.start();
Christopher Wiley279eca32016-05-20 13:23:10 -070083 mLooper.dispatchAll();
84 assertTrue("Should start out available for tethering", mTestedSm.isAvailable());
85 assertFalse("Should not be tethered initially", mTestedSm.isTethered());
86 assertFalse("Should have no errors initially", mTestedSm.isErrored());
87 verify(mTetherHelper).sendTetherStateChangedBroadcast();
88 verifyNoMoreInteractions(mTetherHelper, mNMService, mStatsService);
Christopher Wiley08725a82016-05-18 16:32:44 -070089 }
90
91 @Test
92 public void shouldDoNothingUntilRequested() {
Christopher Wiley279eca32016-05-20 13:23:10 -070093 initStateMachine(false);
Christopher Wiley08725a82016-05-18 16:32:44 -070094 final int [] NOOP_COMMANDS = {
95 TetherInterfaceSM.CMD_TETHER_MODE_DEAD,
96 TetherInterfaceSM.CMD_TETHER_UNREQUESTED,
97 TetherInterfaceSM.CMD_INTERFACE_UP,
98 TetherInterfaceSM.CMD_CELL_DUN_ERROR,
99 TetherInterfaceSM.CMD_IP_FORWARDING_ENABLE_ERROR,
100 TetherInterfaceSM.CMD_IP_FORWARDING_DISABLE_ERROR,
101 TetherInterfaceSM.CMD_START_TETHERING_ERROR,
102 TetherInterfaceSM.CMD_STOP_TETHERING_ERROR,
103 TetherInterfaceSM.CMD_SET_DNS_FORWARDERS_ERROR,
104 TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED
105 };
106 for (int command : NOOP_COMMANDS) {
Christopher Wiley279eca32016-05-20 13:23:10 -0700107 // None of these commands should trigger us to request action from
Christopher Wiley08725a82016-05-18 16:32:44 -0700108 // the rest of the system.
Christopher Wiley279eca32016-05-20 13:23:10 -0700109 dispatchCommand(command);
110 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
Christopher Wiley08725a82016-05-18 16:32:44 -0700111 }
112 }
113
Christopher Wiley279eca32016-05-20 13:23:10 -0700114 @Test
115 public void handlesImmediateInterfaceDown() {
116 initStateMachine(false);
117 dispatchCommand(TetherInterfaceSM.CMD_INTERFACE_DOWN);
118 verify(mTetherHelper).sendTetherStateChangedBroadcast();
119 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
120 assertFalse("Should not be tetherable when the interface is down", mTestedSm.isAvailable());
121 assertFalse("Should not be tethered when the interface is down", mTestedSm.isTethered());
122 assertFalse("Should have no errors when the interface goes immediately down",
123 mTestedSm.isErrored());
124 }
125
126 @Test
127 public void canBeTethered() throws RemoteException {
128 initStateMachine(false);
129 dispatchCommand(TetherInterfaceSM.CMD_TETHER_REQUESTED);
130 InOrder inOrder = inOrder(mTetherHelper, mNMService);
131 inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm);
132 // TODO: This broadcast should be removed. When we send this, we are neither
133 // available nor tethered, which is misleading, since we're transitioning
134 // from one to the other.
135 inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
136 inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
137 inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
138
139 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
140 assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable());
141 assertTrue("Should be in a tethered state", mTestedSm.isTethered());
142 assertFalse("Should have no errors when tethered", mTestedSm.isErrored());
143 }
144
145 @Test
146 public void canUnrequestTethering() throws Exception {
147 initTetheredStateMachine(false, null);
148
149 dispatchCommand(TetherInterfaceSM.CMD_TETHER_UNREQUESTED);
150 InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
151 inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
152 inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
153 inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
154 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
155 assertTrue("Should be ready for tethering again", mTestedSm.isAvailable());
156 assertFalse("Should not be tethered", mTestedSm.isTethered());
157 assertFalse("Should have no errors", mTestedSm.isErrored());
158 }
159
160 @Test
161 public void canBeTetheredAsUsb() throws RemoteException {
162 initStateMachine(true);
163
164 when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
165 dispatchCommand(TetherInterfaceSM.CMD_TETHER_REQUESTED);
166
167 InOrder inOrder = inOrder(mTetherHelper, mNMService);
168 inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm);
169 inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
170 inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
171 // TODO: This broadcast should be removed. When we send this, we are neither
172 // available nor tethered, which is misleading, since we're transitioning
173 // from one to the other.
174 inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
175 inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
176 inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
177
178 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
179 assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable());
180 assertTrue("Should be in a tethered state", mTestedSm.isTethered());
181 assertFalse("Should have no errors when tethered", mTestedSm.isErrored());
182 }
183
184 @Test
185 public void handlesFirstUpstreamChange() throws Exception {
186 initTetheredStateMachine(false, null);
187
188 // Telling the state machine about its upstream interface triggers a little more configuration.
189 dispatchTetherConnectionChanged(UPSTREAM_IFACE);
190 InOrder inOrder = inOrder(mNMService);
191 inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE);
192 inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
193 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
194 assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable());
195 assertTrue("Should be in a tethered state", mTestedSm.isTethered());
196 assertFalse("Should have no errors when tethered", mTestedSm.isErrored());
197 }
198
199 @Test
200 public void handlesChangingUpstream() throws Exception {
201 initTetheredStateMachine(false, UPSTREAM_IFACE);
202
203 dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
204 InOrder inOrder = inOrder(mNMService, mStatsService);
205 inOrder.verify(mStatsService).forceUpdate();
206 inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
207 inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
208 inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
209 inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
210 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
211 assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable());
212 assertTrue("Should be in a tethered state", mTestedSm.isTethered());
213 assertFalse("Should have no errors when tethered", mTestedSm.isErrored());
214 }
215
216 @Test
217 public void canUnrequestTetheringWithUpstream() throws Exception {
218 initTetheredStateMachine(false, UPSTREAM_IFACE);
219
220 dispatchCommand(TetherInterfaceSM.CMD_TETHER_UNREQUESTED);
221 InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
222 inOrder.verify(mStatsService).forceUpdate();
223 inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
224 inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
225 inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
226 inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
227 inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
228 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
229 assertTrue("Should be ready for tethering again", mTestedSm.isAvailable());
230 assertFalse("Should not be tethered", mTestedSm.isTethered());
231 assertFalse("Should have no errors", mTestedSm.isErrored());
232 }
233
234
235 /**
236 * Send a command to the state machine under test, and run the event loop to idle.
237 *
238 * @param command One of the TetherInterfaceSM.CMD_* constants.
239 */
240 private void dispatchCommand(int command) {
241 mTestedSm.sendMessage(command);
242 mLooper.dispatchAll();
243 }
244
245 /**
246 * Special override to tell the state machine that the upstream interface has changed.
247 *
248 * @see #dispatchCommand(int)
249 * @param upstreamIface String name of upstream interface (or null)
250 */
251 private void dispatchTetherConnectionChanged(String upstreamIface) {
252 mTestedSm.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED, upstreamIface);
253 mLooper.dispatchAll();
Christopher Wiley08725a82016-05-18 16:32:44 -0700254 }
255}