Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9900ff38 authored by Xiao Ma's avatar Xiao Ma Committed by Gerrit Code Review
Browse files

Merge "Refactor DHCP server with StateMachine."

parents da8c70c6 0b20cfee
Loading
Loading
Loading
Loading
+183 −104
Original line number Original line Diff line number Diff line
@@ -20,6 +20,9 @@ import static android.net.dhcp.DhcpPacket.DHCP_CLIENT;
import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME;
import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME;
import static android.net.dhcp.DhcpPacket.DHCP_SERVER;
import static android.net.dhcp.DhcpPacket.DHCP_SERVER;
import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION;
import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION;
@@ -48,8 +51,6 @@ import android.net.util.NetworkStackUtils;
import android.net.util.SharedLog;
import android.net.util.SharedLog;
import android.net.util.SocketUtils;
import android.net.util.SocketUtils;
import android.os.Handler;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Message;
import android.os.RemoteException;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemClock;
@@ -63,6 +64,8 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.VisibleForTesting;


import com.android.internal.util.HexDump;
import com.android.internal.util.HexDump;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;


import java.io.FileDescriptor;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.IOException;
@@ -78,12 +81,12 @@ import java.util.ArrayList;
 * authoritative for all leases on the subnet, which means that DHCP requests for unknown leases of
 * authoritative for all leases on the subnet, which means that DHCP requests for unknown leases of
 * unknown hosts receive a reply instead of being ignored.
 * unknown hosts receive a reply instead of being ignored.
 *
 *
 * <p>The server is single-threaded (including send/receive operations): all internal operations are
 * <p>The server relies on StateMachine's handler (including send/receive operations): all internal
 * done on the provided {@link Looper}. Public methods are thread-safe and will schedule operations
 * operations are done in StateMachine's looper. Public methods are thread-safe and will schedule
 * on the looper asynchronously.
 * operations on that looper asynchronously.
 * @hide
 * @hide
 */
 */
public class DhcpServer extends IDhcpServer.Stub {
public class DhcpServer extends StateMachine {
    private static final String REPO_TAG = "Repository";
    private static final String REPO_TAG = "Repository";


    // Lease time to transmit to client instead of a negative time in case a lease expired before
    // Lease time to transmit to client instead of a negative time in case a lease expired before
@@ -93,12 +96,11 @@ public class DhcpServer extends IDhcpServer.Stub {
    private static final int CMD_START_DHCP_SERVER = 1;
    private static final int CMD_START_DHCP_SERVER = 1;
    private static final int CMD_STOP_DHCP_SERVER = 2;
    private static final int CMD_STOP_DHCP_SERVER = 2;
    private static final int CMD_UPDATE_PARAMS = 3;
    private static final int CMD_UPDATE_PARAMS = 3;
    private static final int CMD_SUSPEND_DHCP_SERVER = 4;


    @NonNull
    @NonNull
    private final Context mContext;
    private final Context mContext;
    @NonNull
    @NonNull
    private final HandlerThread mHandlerThread;
    @NonNull
    private final String mIfName;
    private final String mIfName;
    @NonNull
    @NonNull
    private final DhcpLeaseRepository mLeaseRepo;
    private final DhcpLeaseRepository mLeaseRepo;
@@ -108,19 +110,22 @@ public class DhcpServer extends IDhcpServer.Stub {
    private final Dependencies mDeps;
    private final Dependencies mDeps;
    @NonNull
    @NonNull
    private final Clock mClock;
    private final Clock mClock;
    @NonNull
    private DhcpServingParams mServingParams;


    @Nullable
    private volatile ServerHandler mHandler;

    private final boolean mDhcpRapidCommitEnabled;

    // Accessed only on the handler thread
    @Nullable
    @Nullable
    private DhcpPacketListener mPacketListener;
    private DhcpPacketListener mPacketListener;
    @Nullable
    @Nullable
    private FileDescriptor mSocket;
    private FileDescriptor mSocket;
    @NonNull
    @Nullable
    private DhcpServingParams mServingParams;
    private IDhcpEventCallbacks mEventCallbacks;

    private final boolean mDhcpRapidCommitEnabled;

    // States.
    private final StoppedState mStoppedState = new StoppedState();
    private final StartedState mStartedState = new StartedState();
    private final RunningState mRunningState = new RunningState();


    /**
    /**
     * Clock to be used by DhcpServer to track time for lease expiration.
     * Clock to be used by DhcpServer to track time for lease expiration.
@@ -164,7 +169,7 @@ public class DhcpServer extends IDhcpServer.Stub {
        /**
        /**
         * Create a packet listener that will send packets to be processed.
         * Create a packet listener that will send packets to be processed.
         */
         */
        DhcpPacketListener makePacketListener();
        DhcpPacketListener makePacketListener(@NonNull Handler handler);


        /**
        /**
         * Create a clock that the server will use to track time.
         * Create a clock that the server will use to track time.
@@ -178,12 +183,6 @@ public class DhcpServer extends IDhcpServer.Stub {
        void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr,
        void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr,
                @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException;
                @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException;


        /**
         * Verify that the caller is allowed to call public methods on DhcpServer.
         * @throws SecurityException The caller is not allowed to call public methods on DhcpServer.
         */
        void checkCaller() throws SecurityException;

        /**
        /**
         * Check whether or not one specific experimental feature for connectivity namespace is
         * Check whether or not one specific experimental feature for connectivity namespace is
         * enabled.
         * enabled.
@@ -210,8 +209,8 @@ public class DhcpServer extends IDhcpServer.Stub {
        }
        }


        @Override
        @Override
        public DhcpPacketListener makePacketListener() {
        public DhcpPacketListener makePacketListener(@NonNull Handler handler) {
            return new PacketListener();
            return new PacketListener(handler);
        }
        }


        @Override
        @Override
@@ -225,11 +224,6 @@ public class DhcpServer extends IDhcpServer.Stub {
            NetworkStackUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd);
            NetworkStackUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd);
        }
        }


        @Override
        public void checkCaller() {
            enforceNetworkStackCallingPermission();
        }

        @Override
        @Override
        public boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) {
        public boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) {
            return NetworkStackUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name);
            return NetworkStackUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name);
@@ -244,19 +238,18 @@ public class DhcpServer extends IDhcpServer.Stub {


    public DhcpServer(@NonNull Context context, @NonNull String ifName,
    public DhcpServer(@NonNull Context context, @NonNull String ifName,
            @NonNull DhcpServingParams params, @NonNull SharedLog log) {
            @NonNull DhcpServingParams params, @NonNull SharedLog log) {
        this(context, new HandlerThread(DhcpServer.class.getSimpleName() + "." + ifName),
        this(context, ifName, params, log, null);
                ifName, params, log, null);
    }
    }


    @VisibleForTesting
    @VisibleForTesting
    DhcpServer(@NonNull Context context, @NonNull HandlerThread handlerThread,
    DhcpServer(@NonNull Context context, @NonNull String ifName, @NonNull DhcpServingParams params,
            @NonNull String ifName, @NonNull DhcpServingParams params, @NonNull SharedLog log,
            @NonNull SharedLog log, @Nullable Dependencies deps) {
            @Nullable Dependencies deps) {
        super(DhcpServer.class.getSimpleName() + "." + ifName);

        if (deps == null) {
        if (deps == null) {
            deps = new DependenciesImpl();
            deps = new DependenciesImpl();
        }
        }
        mContext = context;
        mContext = context;
        mHandlerThread = handlerThread;
        mIfName = ifName;
        mIfName = ifName;
        mServingParams = params;
        mServingParams = params;
        mLog = log;
        mLog = log;
@@ -264,6 +257,61 @@ public class DhcpServer extends IDhcpServer.Stub {
        mClock = deps.makeClock();
        mClock = deps.makeClock();
        mLeaseRepo = deps.makeLeaseRepository(mServingParams, mLog, mClock);
        mLeaseRepo = deps.makeLeaseRepository(mServingParams, mLog, mClock);
        mDhcpRapidCommitEnabled = deps.isFeatureEnabled(context, DHCP_RAPID_COMMIT_VERSION);
        mDhcpRapidCommitEnabled = deps.isFeatureEnabled(context, DHCP_RAPID_COMMIT_VERSION);

        // CHECKSTYLE:OFF IndentationCheck
        addState(mStoppedState);
        addState(mStartedState);
            addState(mRunningState, mStartedState);
        // CHECKSTYLE:ON IndentationCheck

        setInitialState(mStoppedState);

        super.start();
    }

    /**
     * Make a IDhcpServer connector to communicate with this DhcpServer.
     */
    public IDhcpServer makeConnector() {
        return new DhcpServerConnector();
    }

    private class DhcpServerConnector extends IDhcpServer.Stub {
        @Override
        public void start(@Nullable INetworkStackStatusCallback cb) {
            enforceNetworkStackCallingPermission();
            DhcpServer.this.start(cb);
        }

        @Override
        public void startWithCallbacks(@Nullable INetworkStackStatusCallback statusCb,
                @Nullable IDhcpEventCallbacks eventCb) {
            enforceNetworkStackCallingPermission();
            DhcpServer.this.start(statusCb, eventCb);
        }

        @Override
        public void updateParams(@Nullable DhcpServingParamsParcel params,
                @Nullable INetworkStackStatusCallback cb) {
            enforceNetworkStackCallingPermission();
            DhcpServer.this.updateParams(params, cb);
        }

        @Override
        public void stop(@Nullable INetworkStackStatusCallback cb) {
            enforceNetworkStackCallingPermission();
            DhcpServer.this.stop(cb);
        }

        @Override
        public int getInterfaceVersion() {
            return this.VERSION;
        }

        @Override
        public String getInterfaceHash() {
            return this.HASH;
        }
    }
    }


    /**
    /**
@@ -272,9 +320,8 @@ public class DhcpServer extends IDhcpServer.Stub {
     * <p>It is not legal to call this method more than once; in particular the server cannot be
     * <p>It is not legal to call this method more than once; in particular the server cannot be
     * restarted after being stopped.
     * restarted after being stopped.
     */
     */
    @Override
    void start(@Nullable INetworkStackStatusCallback cb) {
    public void start(@Nullable INetworkStackStatusCallback cb) {
        start(cb, null);
        startWithCallbacks(cb, null);
    }
    }


    /**
    /**
@@ -283,12 +330,8 @@ public class DhcpServer extends IDhcpServer.Stub {
     * <p>It is not legal to call this method more than once; in particular the server cannot be
     * <p>It is not legal to call this method more than once; in particular the server cannot be
     * restarted after being stopped.
     * restarted after being stopped.
     */
     */
    @Override
    void start(@Nullable INetworkStackStatusCallback statusCb,
    public void startWithCallbacks(@Nullable INetworkStackStatusCallback statusCb,
            @Nullable IDhcpEventCallbacks eventCb) {
            @Nullable IDhcpEventCallbacks eventCb) {
        mDeps.checkCaller();
        mHandlerThread.start();
        mHandler = new ServerHandler(mHandlerThread.getLooper());
        sendMessage(CMD_START_DHCP_SERVER, new Pair<>(statusCb, eventCb));
        sendMessage(CMD_START_DHCP_SERVER, new Pair<>(statusCb, eventCb));
    }
    }


@@ -296,19 +339,15 @@ public class DhcpServer extends IDhcpServer.Stub {
     * Update serving parameters. All subsequently received requests will be handled with the new
     * Update serving parameters. All subsequently received requests will be handled with the new
     * parameters, and current leases that are incompatible with the new parameters are dropped.
     * parameters, and current leases that are incompatible with the new parameters are dropped.
     */
     */
    @Override
    void updateParams(@Nullable DhcpServingParamsParcel params,
    public void updateParams(@Nullable DhcpServingParamsParcel params,
            @Nullable INetworkStackStatusCallback cb) {
            @Nullable INetworkStackStatusCallback cb) throws RemoteException {
        mDeps.checkCaller();
        final DhcpServingParams parsedParams;
        final DhcpServingParams parsedParams;
        try {
        try {
            // throws InvalidParameterException with null params
            // throws InvalidParameterException with null params
            parsedParams = DhcpServingParams.fromParcelableObject(params);
            parsedParams = DhcpServingParams.fromParcelableObject(params);
        } catch (DhcpServingParams.InvalidParameterException e) {
        } catch (DhcpServingParams.InvalidParameterException e) {
            mLog.e("Invalid parameters sent to DhcpServer", e);
            mLog.e("Invalid parameters sent to DhcpServer", e);
            if (cb != null) {
            maybeNotifyStatus(cb, STATUS_INVALID_ARGUMENT);
                cb.onStatusAvailable(STATUS_INVALID_ARGUMENT);
            }
            return;
            return;
        }
        }
        sendMessage(CMD_UPDATE_PARAMS, new Pair<>(parsedParams, cb));
        sendMessage(CMD_UPDATE_PARAMS, new Pair<>(parsedParams, cb));
@@ -320,28 +359,74 @@ public class DhcpServer extends IDhcpServer.Stub {
     * <p>As the server is stopped asynchronously, some packets may still be processed shortly after
     * <p>As the server is stopped asynchronously, some packets may still be processed shortly after
     * calling this method.
     * calling this method.
     */
     */
    @Override
    void stop(@Nullable INetworkStackStatusCallback cb) {
    public void stop(@Nullable INetworkStackStatusCallback cb) {
        mDeps.checkCaller();
        sendMessage(CMD_STOP_DHCP_SERVER, cb);
        sendMessage(CMD_STOP_DHCP_SERVER, cb);
    }
    }


    private void sendMessage(int what, @Nullable Object obj) {
    private void maybeNotifyStatus(@Nullable INetworkStackStatusCallback cb, int statusCode) {
        if (mHandler == null) {
        if (cb == null) return;
            mLog.e("Attempting to send a command to stopped DhcpServer: " + what);
        try {
            cb.onStatusAvailable(statusCode);
        } catch (RemoteException e) {
            mLog.e("Could not send status back to caller", e);
        }
    }

    class StoppedState extends State {
        private INetworkStackStatusCallback mOnStopCallback;

        @Override
        public void enter() {
            maybeNotifyStatus(mOnStopCallback, STATUS_SUCCESS);
            mOnStopCallback = null;
        }

        @Override
        public boolean processMessage(Message msg) {
            switch (msg.what) {
                case CMD_START_DHCP_SERVER:
                    final Pair<INetworkStackStatusCallback, IDhcpEventCallbacks> obj =
                            (Pair<INetworkStackStatusCallback, IDhcpEventCallbacks>) msg.obj;
                    mStartedState.mOnStartCallback = obj.first;
                    mEventCallbacks = obj.second;
                    transitionTo(mRunningState);
                    return HANDLED;
                default:
                    return NOT_HANDLED;
            }
        }
    }

    class StartedState extends State {
        private INetworkStackStatusCallback mOnStartCallback;

        @Override
        public void enter() {
            if (mPacketListener != null) {
                mLog.e("Starting DHCP server more than once is not supported.");
                maybeNotifyStatus(mOnStartCallback, STATUS_UNKNOWN_ERROR);
                mOnStartCallback = null;
                return;
                return;
            }
            }
        mHandler.sendMessage(mHandler.obtainMessage(what, obj));
            mPacketListener = mDeps.makePacketListener(getHandler());

            if (!mPacketListener.start()) {
                mLog.e("Fail to start DHCP Packet Listener, rollback to StoppedState");
                deferMessage(obtainMessage(CMD_STOP_DHCP_SERVER, null));
                maybeNotifyStatus(mOnStartCallback, STATUS_UNKNOWN_ERROR);
                mOnStartCallback = null;
                return;
            }
            }


    private class ServerHandler extends Handler {
            if (mEventCallbacks != null) {
        ServerHandler(@NonNull Looper looper) {
                mLeaseRepo.addLeaseCallbacks(mEventCallbacks);
            super(looper);
            }
            maybeNotifyStatus(mOnStartCallback, STATUS_SUCCESS);
            mOnStartCallback = null;
        }
        }


        @Override
        @Override
        public void handleMessage(@NonNull Message msg) {
        public boolean processMessage(Message msg) {
            final INetworkStackStatusCallback cb;
            switch (msg.what) {
            switch (msg.what) {
                case CMD_UPDATE_PARAMS:
                case CMD_UPDATE_PARAMS:
                    final Pair<DhcpServingParams, INetworkStackStatusCallback> pair =
                    final Pair<DhcpServingParams, INetworkStackStatusCallback> pair =
@@ -349,40 +434,45 @@ public class DhcpServer extends IDhcpServer.Stub {
                    final DhcpServingParams params = pair.first;
                    final DhcpServingParams params = pair.first;
                    mServingParams = params;
                    mServingParams = params;
                    mLeaseRepo.updateParams(
                    mLeaseRepo.updateParams(
                            DhcpServingParams.makeIpPrefix(mServingParams.serverAddr),
                            DhcpServingParams.makeIpPrefix(params.serverAddr),
                            params.excludedAddrs,
                            params.excludedAddrs,
                            params.dhcpLeaseTimeSecs,
                            params.dhcpLeaseTimeSecs * 1000,
                            params.singleClientAddr);
                            params.singleClientAddr);
                    maybeNotifyStatus(pair.second, STATUS_SUCCESS);
                    return HANDLED;


                    cb = pair.second;
                    break;
                case CMD_START_DHCP_SERVER:
                case CMD_START_DHCP_SERVER:
                    final Pair<INetworkStackStatusCallback, IDhcpEventCallbacks> obj =
                    mLog.e("ALERT: START received in StartedState. Please fix caller.");
                            (Pair<INetworkStackStatusCallback, IDhcpEventCallbacks>) msg.obj;
                    return HANDLED;
                    cb = obj.first;

                    if (obj.second != null) {
                        mLeaseRepo.addLeaseCallbacks(obj.second);
                    }
                    mPacketListener = mDeps.makePacketListener();
                    mPacketListener.start();
                    break;
                case CMD_STOP_DHCP_SERVER:
                case CMD_STOP_DHCP_SERVER:
                    if (mPacketListener != null) {
                    mStoppedState.mOnStopCallback = (INetworkStackStatusCallback) msg.obj;
                        mPacketListener.stop();
                    transitionTo(mStoppedState);
                        mPacketListener = null;
                    return HANDLED;
                    }

                    mHandlerThread.quitSafely();
                    cb = (INetworkStackStatusCallback) msg.obj;
                    break;
                default:
                default:
                    return;
                    return NOT_HANDLED;
            }
            }
            if (cb != null) {
                try {
                    cb.onStatusAvailable(STATUS_SUCCESS);
                } catch (RemoteException e) {
                    mLog.e("Could not send status back to caller", e);
        }
        }

        @Override
        public void exit() {
            mPacketListener.stop();
            mLog.logf("DHCP Packet Listener stopped");
        }
    }

    class RunningState extends State {
        @Override
        public boolean processMessage(Message msg) {
            switch (msg.what) {
                case CMD_SUSPEND_DHCP_SERVER:
                    // TODO: transition to the state which waits for IpServer to reconfigure the
                    // new selected prefix.
                    return HANDLED;
                default:
                    // Fall through to StartedState.
                    return NOT_HANDLED;
            }
            }
        }
        }
    }
    }
@@ -651,8 +741,8 @@ public class DhcpServer extends IDhcpServer.Stub {
    }
    }


    private class PacketListener extends DhcpPacketListener {
    private class PacketListener extends DhcpPacketListener {
        PacketListener() {
        PacketListener(Handler handler) {
            super(mHandler);
            super(handler);
        }
        }


        @Override
        @Override
@@ -686,21 +776,10 @@ public class DhcpServer extends IDhcpServer.Stub {
                return mSocket;
                return mSocket;
            } catch (IOException | ErrnoException e) {
            } catch (IOException | ErrnoException e) {
                mLog.e("Error creating UDP socket", e);
                mLog.e("Error creating UDP socket", e);
                DhcpServer.this.stop(null);
                return null;
                return null;
            } finally {
            } finally {
                TrafficStats.setThreadStatsTag(oldTag);
                TrafficStats.setThreadStatsTag(oldTag);
            }
            }
        }
        }
    }
    }

    @Override
    public int getInterfaceVersion() {
        return this.VERSION;
    }

    @Override
    public String getInterfaceHash() {
        return this.HASH;
    }
}
}
+1 −1
Original line number Original line Diff line number Diff line
@@ -231,7 +231,7 @@ public class NetworkStackService extends Service {
                cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null);
                cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null);
                return;
                return;
            }
            }
            cb.onDhcpServerCreated(STATUS_SUCCESS, server);
            cb.onDhcpServerCreated(STATUS_SUCCESS, server.makeConnector());
        }
        }


        @Override
        @Override
+10 −19
Original line number Original line Diff line number Diff line
@@ -36,7 +36,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.when;
@@ -52,13 +51,12 @@ import android.net.dhcp.DhcpLeaseRepository.OutOfAddressesException;
import android.net.dhcp.DhcpServer.Clock;
import android.net.dhcp.DhcpServer.Clock;
import android.net.dhcp.DhcpServer.Dependencies;
import android.net.dhcp.DhcpServer.Dependencies;
import android.net.util.SharedLog;
import android.net.util.SharedLog;
import android.os.HandlerThread;
import android.testing.AndroidTestingRunner;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;


import androidx.test.filters.SmallTest;
import androidx.test.filters.SmallTest;


import com.android.testutils.HandlerUtilsKt;

import org.junit.After;
import org.junit.After;
import org.junit.Before;
import org.junit.Before;
import org.junit.Test;
import org.junit.Test;
@@ -76,7 +74,6 @@ import java.util.Set;


@RunWith(AndroidTestingRunner.class)
@RunWith(AndroidTestingRunner.class)
@SmallTest
@SmallTest
@RunWithLooper
public class DhcpServerTest {
public class DhcpServerTest {
    private static final String TEST_IFACE = "testiface";
    private static final String TEST_IFACE = "testiface";


@@ -107,6 +104,7 @@ public class DhcpServerTest {
    private static final DhcpLease TEST_LEASE_WITH_HOSTNAME = new DhcpLease(null, TEST_CLIENT_MAC,
    private static final DhcpLease TEST_LEASE_WITH_HOSTNAME = new DhcpLease(null, TEST_CLIENT_MAC,
            TEST_CLIENT_ADDR, TEST_PREFIX_LENGTH, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME,
            TEST_CLIENT_ADDR, TEST_PREFIX_LENGTH, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME,
            TEST_HOSTNAME);
            TEST_HOSTNAME);
    private static final int TEST_TIMEOUT_MS = 10000;


    @NonNull @Mock
    @NonNull @Mock
    private Context mContext;
    private Context mContext;
@@ -126,10 +124,6 @@ public class DhcpServerTest {
    @NonNull @Captor
    @NonNull @Captor
    private ArgumentCaptor<Inet4Address> mResponseDstAddrCaptor;
    private ArgumentCaptor<Inet4Address> mResponseDstAddrCaptor;


    @NonNull
    private HandlerThread mHandlerThread;
    @NonNull
    private TestableLooper mLooper;
    @NonNull
    @NonNull
    private DhcpServer mServer;
    private DhcpServer mServer;


@@ -167,7 +161,7 @@ public class DhcpServerTest {


    private void startServer() throws Exception {
    private void startServer() throws Exception {
        mServer.start(mAssertSuccessCallback);
        mServer.start(mAssertSuccessCallback);
        mLooper.processAllMessages();
        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
    }
    }


    @Before
    @Before
@@ -176,16 +170,14 @@ public class DhcpServerTest {


        when(mDeps.makeLeaseRepository(any(), any(), any())).thenReturn(mRepository);
        when(mDeps.makeLeaseRepository(any(), any(), any())).thenReturn(mRepository);
        when(mDeps.makeClock()).thenReturn(mClock);
        when(mDeps.makeClock()).thenReturn(mClock);
        when(mDeps.makePacketListener()).thenReturn(mPacketListener);
        when(mDeps.makePacketListener(any())).thenReturn(mPacketListener);
        when(mDeps.isFeatureEnabled(eq(mContext), eq(DHCP_RAPID_COMMIT_VERSION))).thenReturn(true);
        when(mDeps.isFeatureEnabled(eq(mContext), eq(DHCP_RAPID_COMMIT_VERSION))).thenReturn(true);
        doNothing().when(mDeps)
        doNothing().when(mDeps)
                .sendPacket(any(), mSentPacketCaptor.capture(), mResponseDstAddrCaptor.capture());
                .sendPacket(any(), mSentPacketCaptor.capture(), mResponseDstAddrCaptor.capture());
        when(mClock.elapsedRealtime()).thenReturn(TEST_CLOCK_TIME);
        when(mClock.elapsedRealtime()).thenReturn(TEST_CLOCK_TIME);
        when(mPacketListener.start()).thenReturn(true);


        mLooper = TestableLooper.get(this);
        mServer = new DhcpServer(mContext, TEST_IFACE, makeServingParams(),
        mHandlerThread = spy(new HandlerThread("TestDhcpServer"));
        when(mHandlerThread.getLooper()).thenReturn(mLooper.getLooper());
        mServer = new DhcpServer(mContext, mHandlerThread, TEST_IFACE, makeServingParams(),
                new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps);
                new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps);
    }
    }


@@ -193,9 +185,8 @@ public class DhcpServerTest {
    public void tearDown() throws Exception {
    public void tearDown() throws Exception {
        verify(mRepository, never()).addLeaseCallbacks(eq(null));
        verify(mRepository, never()).addLeaseCallbacks(eq(null));
        mServer.stop(mAssertSuccessCallback);
        mServer.stop(mAssertSuccessCallback);
        mLooper.processMessages(1);
        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
        verify(mPacketListener, times(1)).stop();
        verify(mPacketListener, times(1)).stop();
        verify(mHandlerThread, times(1)).quitSafely();
    }
    }


    @Test
    @Test
@@ -207,8 +198,8 @@ public class DhcpServerTest {


    @Test
    @Test
    public void testStartWithCallbacks() throws Exception {
    public void testStartWithCallbacks() throws Exception {
        mServer.startWithCallbacks(mAssertSuccessCallback, mEventCallbacks);
        mServer.start(mAssertSuccessCallback, mEventCallbacks);
        mLooper.processAllMessages();
        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
        verify(mRepository).addLeaseCallbacks(eq(mEventCallbacks));
        verify(mRepository).addLeaseCallbacks(eq(mEventCallbacks));
    }
    }