Merge changes I34903b57,Ic818aa55 into main

* changes:
  SyncSM07: Replace IpServer's state machine with StateMachineShim
  SyncSM06: Add StateMachineShim
This commit is contained in:
Mark Chien
2023-10-26 16:44:44 +00:00
committed by Gerrit Code Review
6 changed files with 335 additions and 15 deletions

View File

@@ -33,6 +33,7 @@ import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
import static com.android.networkstack.tethering.TetheringConfiguration.USE_SYNC_SM;
import static com.android.networkstack.tethering.UpstreamNetworkState.isVcnInterface;
import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix;
import static com.android.networkstack.tethering.util.TetheringMessageBase.BASE_IPSERVER;
@@ -56,7 +57,6 @@ import android.net.dhcp.IDhcpEventCallbacks;
import android.net.dhcp.IDhcpServer;
import android.net.ip.RouterAdvertisementDaemon.RaParams;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -68,7 +68,6 @@ import androidx.annotation.Nullable;
import com.android.internal.util.MessageUtils;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetdUtils;
@@ -85,6 +84,8 @@ import com.android.networkstack.tethering.TetheringConfiguration;
import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
import com.android.networkstack.tethering.util.StateMachineShim;
import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -104,7 +105,7 @@ import java.util.Set;
*
* @hide
*/
public class IpServer extends StateMachine {
public class IpServer extends StateMachineShim {
public static final int STATE_UNAVAILABLE = 0;
public static final int STATE_AVAILABLE = 1;
public static final int STATE_TETHERED = 2;
@@ -309,16 +310,18 @@ public class IpServer extends StateMachine {
private LinkAddress mIpv4Address;
private final TetheringMetrics mTetheringMetrics;
private final Handler mHandler;
// TODO: Add a dependency object to pass the data members or variables from the tethering
// object. It helps to reduce the arguments of the constructor.
public IpServer(
String ifaceName, Looper looper, int interfaceType, SharedLog log,
String ifaceName, Handler handler, int interfaceType, SharedLog log,
INetd netd, @NonNull BpfCoordinator bpfCoordinator,
@Nullable LateSdk<RoutingCoordinatorManager> routingCoordinator, Callback callback,
TetheringConfiguration config, PrivateAddressCoordinator addressCoordinator,
TetheringMetrics tetheringMetrics, Dependencies deps) {
super(ifaceName, looper);
super(ifaceName, USE_SYNC_SM ? null : handler.getLooper());
mHandler = handler;
mLog = log.forSubComponent(ifaceName);
mNetd = netd;
mBpfCoordinator = bpfCoordinator;
@@ -351,13 +354,22 @@ public class IpServer extends StateMachine {
mTetheredState = new TetheredState();
mUnavailableState = new UnavailableState();
mWaitingForRestartState = new WaitingForRestartState();
addState(mInitialState);
addState(mLocalHotspotState);
addState(mTetheredState);
addState(mWaitingForRestartState, mTetheredState);
addState(mUnavailableState);
final ArrayList allStates = new ArrayList<StateInfo>();
allStates.add(new StateInfo(mInitialState, null));
allStates.add(new StateInfo(mLocalHotspotState, null));
allStates.add(new StateInfo(mTetheredState, null));
allStates.add(new StateInfo(mWaitingForRestartState, mTetheredState));
allStates.add(new StateInfo(mUnavailableState, null));
addAllStates(allStates);
}
setInitialState(mInitialState);
private Handler getHandler() {
return mHandler;
}
/** Start IpServer state machine. */
public void start() {
start(mInitialState);
}
/** Interface name which IpServer served.*/

View File

@@ -2841,7 +2841,7 @@ public class Tethering {
mLog.i("adding IpServer for: " + iface);
final TetherState tetherState = new TetherState(
new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator,
new IpServer(iface, mHandler, interfaceType, mLog, mNetd, mBpfCoordinator,
mRoutingCoordinator, makeControlCallback(), mConfig,
mPrivateAddressCoordinator, mTetheringMetrics,
mDeps.getIpServerDependencies()), isNcm);

View File

@@ -136,6 +136,9 @@ public class TetheringConfiguration {
*/
public static final int DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS = 5000;
/** A flag for using synchronous or asynchronous state machine. */
public static final boolean USE_SYNC_SM = false;
public final String[] tetherableUsbRegexs;
public final String[] tetherableWifiRegexs;
public final String[] tetherableWigigRegexs;

View File

@@ -0,0 +1,176 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.networkstack.tethering.util;
import android.annotation.Nullable;
import android.os.Looper;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo;
import java.util.List;
/** A wrapper to decide whether use synchronous state machine for tethering. */
public class StateMachineShim {
// Exactly one of mAsyncSM or mSyncSM is non-null.
private final StateMachine mAsyncSM;
private final SyncStateMachine mSyncSM;
/**
* The Looper parameter is only needed for AsyncSM, so if looper is null, the shim will be
* created for SyncSM.
*/
public StateMachineShim(final String name, @Nullable final Looper looper) {
this(name, looper, new Dependencies());
}
@VisibleForTesting
public StateMachineShim(final String name, @Nullable final Looper looper,
final Dependencies deps) {
if (looper == null) {
mAsyncSM = null;
mSyncSM = deps.makeSyncStateMachine(name, Thread.currentThread());
} else {
mAsyncSM = deps.makeAsyncStateMachine(name, looper);
mSyncSM = null;
}
}
/** A dependencies class which used for testing injection. */
@VisibleForTesting
public static class Dependencies {
/** Create SyncSM instance, for injection. */
public SyncStateMachine makeSyncStateMachine(final String name, final Thread thread) {
return new SyncStateMachine(name, thread);
}
/** Create AsyncSM instance, for injection. */
public AsyncStateMachine makeAsyncStateMachine(final String name, final Looper looper) {
return new AsyncStateMachine(name, looper);
}
}
/** Start the state machine */
public void start(final State initialState) {
if (mSyncSM != null) {
mSyncSM.start(initialState);
} else {
mAsyncSM.setInitialState(initialState);
mAsyncSM.start();
}
}
/** Add states to state machine. */
public void addAllStates(final List<StateInfo> stateInfos) {
if (mSyncSM != null) {
mSyncSM.addAllStates(stateInfos);
} else {
for (final StateInfo info : stateInfos) {
mAsyncSM.addState(info.state, info.parent);
}
}
}
/**
* Transition to given state.
*
* SyncSM doesn't allow this be called during state transition (#enter() or #exit() methods),
* or multiple times while processing a single message.
*/
public void transitionTo(final State state) {
if (mSyncSM != null) {
mSyncSM.transitionTo(state);
} else {
mAsyncSM.transitionTo(state);
}
}
/** Send message to state machine. */
public void sendMessage(int what) {
sendMessage(what, 0, 0, null);
}
/** Send message to state machine. */
public void sendMessage(int what, Object obj) {
sendMessage(what, 0, 0, obj);
}
/** Send message to state machine. */
public void sendMessage(int what, int arg1) {
sendMessage(what, arg1, 0, null);
}
/**
* Send message to state machine.
*
* If using asynchronous state machine, putting the message into looper's message queue.
* Tethering runs on single looper thread that ipServers and mainSM all share with same message
* queue. The enqueued message will be processed by asynchronous state machine when all the
* messages before such enqueued message are processed.
* If using synchronous state machine, the message is processed right away without putting into
* looper's message queue.
*/
public void sendMessage(int what, int arg1, int arg2, Object obj) {
if (mSyncSM != null) {
mSyncSM.processMessage(what, arg1, arg2, obj);
} else {
mAsyncSM.sendMessage(what, arg1, arg2, obj);
}
}
/**
* Send message after delayMillis millisecond.
*
* This can only be used with async state machine, so this will throw if using sync state
* machine.
*/
public void sendMessageDelayedToAsyncSM(final int what, final long delayMillis) {
if (mSyncSM != null) {
throw new IllegalStateException("sendMessageDelayed can only be used with async SM");
}
mAsyncSM.sendMessageDelayed(what, delayMillis);
}
/**
* Send self message.
* This can only be used with sync state machine, so this will throw if using async state
* machine.
*/
public void sendSelfMessageToSyncSM(final int what, final Object obj) {
if (mSyncSM == null) {
throw new IllegalStateException("sendSelfMessage can only be used with sync SM");
}
mSyncSM.sendSelfMessage(what, 0, 0, obj);
}
/**
* An alias StateMahchine class with public construtor.
*
* Since StateMachine.java only provides protected construtor, adding a child class so that this
* shim could create StateMachine instance.
*/
@VisibleForTesting
public static class AsyncStateMachine extends StateMachine {
public AsyncStateMachine(final String name, final Looper looper) {
super(name, looper);
}
}
}

View File

@@ -215,6 +215,7 @@ public class IpServerTest {
@Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor;
private final TestLooper mLooper = new TestLooper();
private final Handler mHandler = new Handler(mLooper.getLooper());
private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor =
ArgumentCaptor.forClass(LinkProperties.class);
private IpServer mIpServer;
@@ -254,7 +255,7 @@ public class IpServerTest {
// Recreate mBpfCoordinator again here because mTetherConfig has changed
mBpfCoordinator = spy(new BpfCoordinator(mBpfDeps));
mIpServer = new IpServer(
IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator,
IFACE_NAME, mHandler, interfaceType, mSharedLog, mNetd, mBpfCoordinator,
mRoutingCoordinatorManager, mCallback, mTetherConfig, mAddressCoordinator,
mTetheringMetrics, mDependencies);
mIpServer.start();
@@ -324,7 +325,7 @@ public class IpServerTest {
mBpfDeps = new BpfCoordinator.Dependencies() {
@NonNull
public Handler getHandler() {
return new Handler(mLooper.getLooper());
return mHandler;
}
@NonNull
@@ -402,7 +403,7 @@ public class IpServerTest {
public void startsOutAvailable() {
when(mDependencies.getIpNeighborMonitor(any(), any(), any()))
.thenReturn(mIpNeighborMonitor);
mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
mIpServer = new IpServer(IFACE_NAME, mHandler, TETHERING_BLUETOOTH, mSharedLog,
mNetd, mBpfCoordinator, mRoutingCoordinatorManager, mCallback, mTetherConfig,
mAddressCoordinator, mTetheringMetrics, mDependencies);
mIpServer.start();

View File

@@ -0,0 +1,128 @@
/**
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.networkstack.tethering.util
import android.os.Looper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.util.State
import com.android.networkstack.tethering.util.StateMachineShim.AsyncStateMachine
import com.android.networkstack.tethering.util.StateMachineShim.Dependencies
import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo
import kotlin.test.assertFailsWith
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
@RunWith(AndroidJUnit4::class)
@SmallTest
class StateMachineShimTest {
private val mSyncSM = mock(SyncStateMachine::class.java)
private val mAsyncSM = mock(AsyncStateMachine::class.java)
private val mState1 = mock(State::class.java)
private val mState2 = mock(State::class.java)
inner class MyDependencies() : Dependencies() {
override fun makeSyncStateMachine(name: String, thread: Thread) = mSyncSM
override fun makeAsyncStateMachine(name: String, looper: Looper) = mAsyncSM
}
@Test
fun testUsingSyncStateMachine() {
val inOrder = inOrder(mSyncSM, mAsyncSM)
val shimUsingSyncSM = StateMachineShim("ShimTest", null, MyDependencies())
shimUsingSyncSM.start(mState1)
inOrder.verify(mSyncSM).start(mState1)
val allStates = ArrayList<StateInfo>()
allStates.add(StateInfo(mState1, null))
allStates.add(StateInfo(mState2, mState1))
shimUsingSyncSM.addAllStates(allStates)
inOrder.verify(mSyncSM).addAllStates(allStates)
shimUsingSyncSM.transitionTo(mState1)
inOrder.verify(mSyncSM).transitionTo(mState1)
val what = 10
shimUsingSyncSM.sendMessage(what)
inOrder.verify(mSyncSM).processMessage(what, 0, 0, null)
val obj = Object()
shimUsingSyncSM.sendMessage(what, obj)
inOrder.verify(mSyncSM).processMessage(what, 0, 0, obj)
val arg1 = 11
shimUsingSyncSM.sendMessage(what, arg1)
inOrder.verify(mSyncSM).processMessage(what, arg1, 0, null)
val arg2 = 12
shimUsingSyncSM.sendMessage(what, arg1, arg2, obj)
inOrder.verify(mSyncSM).processMessage(what, arg1, arg2, obj)
assertFailsWith(IllegalStateException::class) {
shimUsingSyncSM.sendMessageDelayedToAsyncSM(what, 1000 /* delayMillis */)
}
shimUsingSyncSM.sendSelfMessageToSyncSM(what, obj)
inOrder.verify(mSyncSM).sendSelfMessage(what, 0, 0, obj)
verifyNoMoreInteractions(mSyncSM, mAsyncSM)
}
@Test
fun testUsingAsyncStateMachine() {
val inOrder = inOrder(mSyncSM, mAsyncSM)
val shimUsingAsyncSM = StateMachineShim("ShimTest", mock(Looper::class.java),
MyDependencies())
shimUsingAsyncSM.start(mState1)
inOrder.verify(mAsyncSM).setInitialState(mState1)
inOrder.verify(mAsyncSM).start()
val allStates = ArrayList<StateInfo>()
allStates.add(StateInfo(mState1, null))
allStates.add(StateInfo(mState2, mState1))
shimUsingAsyncSM.addAllStates(allStates)
inOrder.verify(mAsyncSM).addState(mState1, null)
inOrder.verify(mAsyncSM).addState(mState2, mState1)
shimUsingAsyncSM.transitionTo(mState1)
inOrder.verify(mAsyncSM).transitionTo(mState1)
val what = 10
shimUsingAsyncSM.sendMessage(what)
inOrder.verify(mAsyncSM).sendMessage(what, 0, 0, null)
val obj = Object()
shimUsingAsyncSM.sendMessage(what, obj)
inOrder.verify(mAsyncSM).sendMessage(what, 0, 0, obj)
val arg1 = 11
shimUsingAsyncSM.sendMessage(what, arg1)
inOrder.verify(mAsyncSM).sendMessage(what, arg1, 0, null)
val arg2 = 12
shimUsingAsyncSM.sendMessage(what, arg1, arg2, obj)
inOrder.verify(mAsyncSM).sendMessage(what, arg1, arg2, obj)
shimUsingAsyncSM.sendMessageDelayedToAsyncSM(what, 1000 /* delayMillis */)
inOrder.verify(mAsyncSM).sendMessageDelayed(what, 1000)
assertFailsWith(IllegalStateException::class) {
shimUsingAsyncSM.sendSelfMessageToSyncSM(what, obj)
}
verifyNoMoreInteractions(mSyncSM, mAsyncSM)
}
}