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

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

Merge "Support MirrorLink DHCPDECLINE."

parents e32021c2 c6702418
Loading
Loading
Loading
Loading
+20 −2
Original line number Original line Diff line number Diff line
@@ -37,6 +37,7 @@ import android.util.ArrayMap;


import androidx.annotation.NonNull;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;


import java.net.Inet4Address;
import java.net.Inet4Address;
import java.util.ArrayList;
import java.util.ArrayList;
@@ -158,7 +159,7 @@ class DhcpLeaseRepository {
    /**
    /**
     * From a map keyed by {@link Inet4Address}, remove entries where the key is invalid (as
     * From a map keyed by {@link Inet4Address}, remove entries where the key is invalid (as
     * specified by {@link #isValidAddress(Inet4Address)}), or is a reserved address.
     * specified by {@link #isValidAddress(Inet4Address)}), or is a reserved address.
     * @return true iff at least one entry was removed.
     * @return true if and only if at least one entry was removed.
     */
     */
    private <T> boolean cleanMap(Map<Inet4Address, T> map) {
    private <T> boolean cleanMap(Map<Inet4Address, T> map) {
        final Iterator<Entry<Inet4Address, T>> it = map.entrySet().iterator();
        final Iterator<Entry<Inet4Address, T>> it = map.entrySet().iterator();
@@ -397,7 +398,8 @@ class DhcpLeaseRepository {
        mEventCallbacks.finishBroadcast();
        mEventCallbacks.finishBroadcast();
    }
    }


    public void markLeaseDeclined(@NonNull Inet4Address addr) {
    @VisibleForTesting
    void markLeaseDeclined(@NonNull Inet4Address addr) {
        if (mDeclinedAddrs.containsKey(addr) || !isValidAddress(addr)) {
        if (mDeclinedAddrs.containsKey(addr) || !isValidAddress(addr)) {
            mLog.logf("Not marking %s as declined: already declined or not assignable",
            mLog.logf("Not marking %s as declined: already declined or not assignable",
                    inet4AddrToString(addr));
                    inet4AddrToString(addr));
@@ -409,6 +411,22 @@ class DhcpLeaseRepository {
        maybeUpdateEarliestExpiration(expTime);
        maybeUpdateEarliestExpiration(expTime);
    }
    }


    /**
     * Mark a committed lease matching the passed in clientId and hardware address parameters to be
     * declined, and delete it from the repository.
     *
     * @param clientId Client identifier option if specified, or {@link #CLIENTID_UNSPEC}
     * @param hwAddr client's mac address
     * @param Addr IPv4 address to be declined
     * @return true if a lease matching parameters was removed from committed repository.
     */
    public boolean markAndReleaseDeclinedLease(@Nullable byte[] clientId,
            @NonNull MacAddress hwAddr, @NonNull Inet4Address addr) {
        if (!releaseLease(clientId, hwAddr, addr)) return false;
        markLeaseDeclined(addr);
        return true;
    }

    /**
    /**
     * Get the list of currently valid committed leases in the repository.
     * Get the list of currently valid committed leases in the repository.
     */
     */
+158 −83
Original line number Original line Diff line number Diff line
@@ -45,6 +45,7 @@ import static java.lang.Integer.toUnsignedLong;


import android.content.Context;
import android.content.Context;
import android.net.INetworkStackStatusCallback;
import android.net.INetworkStackStatusCallback;
import android.net.IpPrefix;
import android.net.MacAddress;
import android.net.MacAddress;
import android.net.TrafficStats;
import android.net.TrafficStats;
import android.net.util.NetworkStackUtils;
import android.net.util.NetworkStackUtils;
@@ -96,7 +97,8 @@ public class DhcpServer extends StateMachine {
    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;
    @VisibleForTesting
    protected static final int CMD_RECEIVE_PACKET = 4;


    @NonNull
    @NonNull
    private final Context mContext;
    private final Context mContext;
@@ -126,6 +128,8 @@ public class DhcpServer extends StateMachine {
    private final StoppedState mStoppedState = new StoppedState();
    private final StoppedState mStoppedState = new StoppedState();
    private final StartedState mStartedState = new StartedState();
    private final StartedState mStartedState = new StartedState();
    private final RunningState mRunningState = new RunningState();
    private final RunningState mRunningState = new RunningState();
    private final WaitBeforeRetrievalState mWaitBeforeRetrievalState =
            new WaitBeforeRetrievalState();


    /**
    /**
     * Clock to be used by DhcpServer to track time for lease expiration.
     * Clock to be used by DhcpServer to track time for lease expiration.
@@ -262,6 +266,7 @@ public class DhcpServer extends StateMachine {
        addState(mStoppedState);
        addState(mStoppedState);
        addState(mStartedState);
        addState(mStartedState);
            addState(mRunningState, mStartedState);
            addState(mRunningState, mStartedState);
            addState(mWaitBeforeRetrievalState, mStartedState);
        // CHECKSTYLE:ON IndentationCheck
        // CHECKSTYLE:ON IndentationCheck


        setInitialState(mStoppedState);
        setInitialState(mStoppedState);
@@ -372,6 +377,17 @@ public class DhcpServer extends StateMachine {
        }
        }
    }
    }


    private void handleUpdateServingParams(@NonNull DhcpServingParams params,
            @Nullable INetworkStackStatusCallback cb) {
        mServingParams = params;
        mLeaseRepo.updateParams(
                DhcpServingParams.makeIpPrefix(params.serverAddr),
                params.excludedAddrs,
                params.dhcpLeaseTimeSecs * 1000,
                params.singleClientAddr);
        maybeNotifyStatus(cb, STATUS_SUCCESS);
    }

    class StoppedState extends State {
    class StoppedState extends State {
        private INetworkStackStatusCallback mOnStopCallback;
        private INetworkStackStatusCallback mOnStopCallback;


@@ -422,6 +438,8 @@ public class DhcpServer extends StateMachine {
                mLeaseRepo.addLeaseCallbacks(mEventCallbacks);
                mLeaseRepo.addLeaseCallbacks(mEventCallbacks);
            }
            }
            maybeNotifyStatus(mOnStartCallback, STATUS_SUCCESS);
            maybeNotifyStatus(mOnStartCallback, STATUS_SUCCESS);
            // Clear INetworkStackStatusCallback binder token, so that it's freed
            // on the other side.
            mOnStartCallback = null;
            mOnStartCallback = null;
        }
        }


@@ -431,14 +449,7 @@ public class DhcpServer extends StateMachine {
                case CMD_UPDATE_PARAMS:
                case CMD_UPDATE_PARAMS:
                    final Pair<DhcpServingParams, INetworkStackStatusCallback> pair =
                    final Pair<DhcpServingParams, INetworkStackStatusCallback> pair =
                            (Pair<DhcpServingParams, INetworkStackStatusCallback>) msg.obj;
                            (Pair<DhcpServingParams, INetworkStackStatusCallback>) msg.obj;
                    final DhcpServingParams params = pair.first;
                    handleUpdateServingParams(pair.first, pair.second);
                    mServingParams = params;
                    mLeaseRepo.updateParams(
                            DhcpServingParams.makeIpPrefix(params.serverAddr),
                            params.excludedAddrs,
                            params.dhcpLeaseTimeSecs * 1000,
                            params.singleClientAddr);
                    maybeNotifyStatus(pair.second, STATUS_SUCCESS);
                    return HANDLED;
                    return HANDLED;


                case CMD_START_DHCP_SERVER:
                case CMD_START_DHCP_SERVER:
@@ -466,26 +477,19 @@ public class DhcpServer extends StateMachine {
        @Override
        @Override
        public boolean processMessage(Message msg) {
        public boolean processMessage(Message msg) {
            switch (msg.what) {
            switch (msg.what) {
                case CMD_SUSPEND_DHCP_SERVER:
                case CMD_RECEIVE_PACKET:
                    // TODO: transition to the state which waits for IpServer to reconfigure the
                    processPacket((DhcpPacket) msg.obj);
                    // new selected prefix.
                    return HANDLED;
                    return HANDLED;

                default:
                default:
                    // Fall through to StartedState.
                    // Fall through to StartedState.
                    return NOT_HANDLED;
                    return NOT_HANDLED;
            }
            }
        }
        }
    }


    @VisibleForTesting
        private void processPacket(@NonNull DhcpPacket packet) {
    void processPacket(@NonNull DhcpPacket packet, int srcPort) {
            mLog.log("Received packet of type " + packet.getClass().getSimpleName());
        final String packetType = packet.getClass().getSimpleName();
        if (srcPort != DHCP_CLIENT) {
            mLog.logf("Ignored packet of type %s sent from client port %d", packetType, srcPort);
            return;
        }


        mLog.log("Received packet of type " + packetType);
            final Inet4Address sid = packet.mServerIdentifier;
            final Inet4Address sid = packet.mServerIdentifier;
            if (sid != null && !sid.equals(mServingParams.serverAddr.getAddress())) {
            if (sid != null && !sid.equals(mServingParams.serverAddr.getAddress())) {
                mLog.log("Packet ignored due to wrong server identifier: " + sid);
                mLog.log("Packet ignored due to wrong server identifier: " + sid);
@@ -499,6 +503,8 @@ public class DhcpServer extends StateMachine {
                    processRequest((DhcpRequestPacket) packet);
                    processRequest((DhcpRequestPacket) packet);
                } else if (packet instanceof DhcpReleasePacket) {
                } else if (packet instanceof DhcpReleasePacket) {
                    processRelease((DhcpReleasePacket) packet);
                    processRelease((DhcpReleasePacket) packet);
                } else if (packet instanceof DhcpDeclinePacket) {
                    processDecline((DhcpDeclinePacket) packet);
                } else {
                } else {
                    mLog.e("Unknown packet type: " + packet.getClass().getSimpleName());
                    mLog.e("Unknown packet type: " + packet.getClass().getSimpleName());
                }
                }
@@ -519,8 +525,8 @@ public class DhcpServer extends StateMachine {
            final MacAddress clientMac = getMacAddr(packet);
            final MacAddress clientMac = getMacAddr(packet);
            try {
            try {
                if (mDhcpRapidCommitEnabled && packet.mRapidCommit) {
                if (mDhcpRapidCommitEnabled && packet.mRapidCommit) {
                lease = mLeaseRepo.getCommittedLease(packet.getExplicitClientIdOrNull(), clientMac,
                    lease = mLeaseRepo.getCommittedLease(packet.getExplicitClientIdOrNull(),
                        packet.mRelayIp, packet.mHostName);
                            clientMac, packet.mRelayIp, packet.mHostName);
                    transmitAck(packet, lease, clientMac);
                    transmitAck(packet, lease, clientMac);
                } else {
                } else {
                    lease = mLeaseRepo.getOffer(packet.getExplicitClientIdOrNull(), clientMac,
                    lease = mLeaseRepo.getOffer(packet.getExplicitClientIdOrNull(), clientMac,
@@ -534,7 +540,8 @@ public class DhcpServer extends StateMachine {
            }
            }
        }
        }


    private void processRequest(@NonNull DhcpRequestPacket packet) throws MalformedPacketException {
        private void processRequest(@NonNull DhcpRequestPacket packet)
                throws MalformedPacketException {
            // If set, packet SID matches with this server's ID as checked in processPacket().
            // If set, packet SID matches with this server's ID as checked in processPacket().
            final boolean sidSet = packet.mServerIdentifier != null;
            final boolean sidSet = packet.mServerIdentifier != null;
            final DhcpLease lease;
            final DhcpLease lease;
@@ -558,10 +565,72 @@ public class DhcpServer extends StateMachine {
                throws MalformedPacketException {
                throws MalformedPacketException {
            final byte[] clientId = packet.getExplicitClientIdOrNull();
            final byte[] clientId = packet.getExplicitClientIdOrNull();
            final MacAddress macAddr = getMacAddr(packet);
            final MacAddress macAddr = getMacAddr(packet);
        // Don't care about success (there is no ACK/NAK); logging is already done in the repository
            // Don't care about success (there is no ACK/NAK); logging is already done
            // in the repository.
            mLeaseRepo.releaseLease(clientId, macAddr, packet.mClientIp);
            mLeaseRepo.releaseLease(clientId, macAddr, packet.mClientIp);
        }
        }


        private void processDecline(@NonNull DhcpDeclinePacket packet)
                throws MalformedPacketException {
            final byte[] clientId = packet.getExplicitClientIdOrNull();
            final MacAddress macAddr = getMacAddr(packet);
            int committedLeasesCount = mLeaseRepo.getCommittedLeases().size();

            // If peer's clientID and macAddr doesn't match with any issued lease, nothing to do.
            if (!mLeaseRepo.markAndReleaseDeclinedLease(clientId, macAddr, packet.mRequestedIp)) {
                return;
            }

            // Check whether the boolean flag which requests a new prefix is enabled, and if
            // it's enabled, make sure the issued lease count should be only one, otherwise,
            // changing a different prefix will cause other exist host(s) configured with the
            // current prefix lose appropriate route.
            if (!mServingParams.changePrefixOnDecline || committedLeasesCount > 1) return;

            if (mEventCallbacks == null) {
                mLog.e("changePrefixOnDecline enabled but caller didn't pass a valid"
                        + "IDhcpEventCallbacks callback.");
                return;
            }

            try {
                mEventCallbacks.onNewPrefixRequest(
                        DhcpServingParams.makeIpPrefix(mServingParams.serverAddr));
                transitionTo(mWaitBeforeRetrievalState);
            } catch (RemoteException e) {
                mLog.e("could not request a new prefix to caller", e);
            }
        }
    }

    class WaitBeforeRetrievalState extends State {
        @Override
        public boolean processMessage(Message msg) {
            switch (msg.what) {
                case CMD_UPDATE_PARAMS:
                    final Pair<DhcpServingParams, INetworkStackStatusCallback> pair =
                            (Pair<DhcpServingParams, INetworkStackStatusCallback>) msg.obj;
                    final IpPrefix currentPrefix =
                            DhcpServingParams.makeIpPrefix(mServingParams.serverAddr);
                    final IpPrefix newPrefix =
                            DhcpServingParams.makeIpPrefix(pair.first.serverAddr);
                    handleUpdateServingParams(pair.first, pair.second);
                    if (currentPrefix != null && !currentPrefix.equals(newPrefix)) {
                        transitionTo(mRunningState);
                    }
                    return HANDLED;

                case CMD_RECEIVE_PACKET:
                    deferMessage(msg);
                    return HANDLED;

                default:
                    // Fall through to StartedState.
                    return NOT_HANDLED;
            }
        }
    }

    private Inet4Address getAckOrOfferDst(@NonNull DhcpPacket request, @NonNull DhcpLease lease,
    private Inet4Address getAckOrOfferDst(@NonNull DhcpPacket request, @NonNull DhcpLease lease,
            boolean broadcastFlag) {
            boolean broadcastFlag) {
        // Unless relayed or broadcast, send to client IP if already configured on the client, or to
        // Unless relayed or broadcast, send to client IP if already configured on the client, or to
@@ -748,7 +817,13 @@ public class DhcpServer extends StateMachine {
        @Override
        @Override
        protected void onReceive(@NonNull DhcpPacket packet, @NonNull Inet4Address srcAddr,
        protected void onReceive(@NonNull DhcpPacket packet, @NonNull Inet4Address srcAddr,
                int srcPort) {
                int srcPort) {
            processPacket(packet, srcPort);
            if (srcPort != DHCP_CLIENT) {
                final String packetType = packet.getClass().getSimpleName();
                mLog.logf("Ignored packet of type %s sent from client port %d",
                        packetType, srcPort);
                return;
            }
            sendMessage(CMD_RECEIVE_PACKET, packet);
        }
        }


        @Override
        @Override
+121 −23
Original line number Original line Diff line number Diff line
@@ -17,12 +17,13 @@
package android.net.dhcp;
package android.net.dhcp;


import static android.net.InetAddresses.parseNumericAddress;
import static android.net.InetAddresses.parseNumericAddress;
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.ENCAP_BOOTP;
import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
import static android.net.dhcp.DhcpPacket.INADDR_ANY;
import static android.net.dhcp.DhcpPacket.INADDR_ANY;
import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
import static android.net.dhcp.DhcpServer.CMD_RECEIVE_PACKET;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION;
import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION;


import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertEquals;
@@ -31,7 +32,6 @@ import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.assertTrue;


import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
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;
@@ -44,12 +44,14 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Context;
import android.net.INetworkStackStatusCallback;
import android.net.INetworkStackStatusCallback;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkAddress;
import android.net.MacAddress;
import android.net.MacAddress;
import android.net.dhcp.DhcpLeaseRepository.InvalidAddressException;
import android.net.dhcp.DhcpLeaseRepository.InvalidAddressException;
import android.net.dhcp.DhcpLeaseRepository.OutOfAddressesException;
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.shared.Inet4AddressUtils;
import android.net.util.SharedLog;
import android.net.util.SharedLog;
import android.testing.AndroidTestingRunner;
import android.testing.AndroidTestingRunner;


@@ -69,6 +71,8 @@ import org.mockito.MockitoAnnotations;
import java.net.Inet4Address;
import java.net.Inet4Address;
import java.nio.ByteBuffer;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.HashSet;
import java.util.Set;
import java.util.Set;


@@ -156,6 +160,7 @@ public class DhcpServerTest {
                .setServerAddr(TEST_SERVER_LINKADDR)
                .setServerAddr(TEST_SERVER_LINKADDR)
                .setLinkMtu(TEST_MTU)
                .setLinkMtu(TEST_MTU)
                .setExcludedAddrs(TEST_EXCLUDED_ADDRS)
                .setExcludedAddrs(TEST_EXCLUDED_ADDRS)
                .setChangePrefixOnDecline(false)
                .build();
                .build();
    }
    }


@@ -215,7 +220,8 @@ public class DhcpServerTest {
        final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
        final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
                (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
                (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
                false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */);
                false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */);
        mServer.processPacket(discover, DHCP_CLIENT);
        mServer.sendMessage(CMD_RECEIVE_PACKET, discover);
        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);


        assertResponseSentTo(TEST_CLIENT_ADDR);
        assertResponseSentTo(TEST_CLIENT_ADDR);
        final DhcpOfferPacket packet = assertOffer(getPacket());
        final DhcpOfferPacket packet = assertOffer(getPacket());
@@ -233,7 +239,8 @@ public class DhcpServerTest {
        final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
        final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
                (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
                (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
                false /* broadcast */, INADDR_ANY /* srcIp */, true /* rapidCommit */);
                false /* broadcast */, INADDR_ANY /* srcIp */, true /* rapidCommit */);
        mServer.processPacket(discover, DHCP_CLIENT);
        mServer.sendMessage(CMD_RECEIVE_PACKET, discover);
        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);


        assertResponseSentTo(TEST_CLIENT_ADDR);
        assertResponseSentTo(TEST_CLIENT_ADDR);
        final DhcpAckPacket packet = assertAck(getPacket());
        final DhcpAckPacket packet = assertAck(getPacket());
@@ -251,7 +258,8 @@ public class DhcpServerTest {
        final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
        final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
                (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
                (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
                false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */);
                false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */);
        mServer.processPacket(discover, DHCP_CLIENT);
        mServer.sendMessage(CMD_RECEIVE_PACKET, discover);
        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);


        assertResponseSentTo(INADDR_BROADCAST);
        assertResponseSentTo(INADDR_BROADCAST);
        final DhcpNakPacket packet = assertNak(getPacket());
        final DhcpNakPacket packet = assertNak(getPacket());
@@ -279,7 +287,8 @@ public class DhcpServerTest {
        final DhcpRequestPacket request = makeRequestSelectingPacket();
        final DhcpRequestPacket request = makeRequestSelectingPacket();
        request.mHostName = TEST_HOSTNAME;
        request.mHostName = TEST_HOSTNAME;
        request.mRequestedParams = new byte[] { DHCP_HOST_NAME };
        request.mRequestedParams = new byte[] { DHCP_HOST_NAME };
        mServer.processPacket(request, DHCP_CLIENT);
        mServer.sendMessage(CMD_RECEIVE_PACKET, request);
        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);


        assertResponseSentTo(TEST_CLIENT_ADDR);
        assertResponseSentTo(TEST_CLIENT_ADDR);
        final DhcpAckPacket packet = assertAck(getPacket());
        final DhcpAckPacket packet = assertAck(getPacket());
@@ -296,25 +305,14 @@ public class DhcpServerTest {
                .thenThrow(new InvalidAddressException("Test error"));
                .thenThrow(new InvalidAddressException("Test error"));


        final DhcpRequestPacket request = makeRequestSelectingPacket();
        final DhcpRequestPacket request = makeRequestSelectingPacket();
        mServer.processPacket(request, DHCP_CLIENT);
        mServer.sendMessage(CMD_RECEIVE_PACKET, request);
        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);


        assertResponseSentTo(INADDR_BROADCAST);
        assertResponseSentTo(INADDR_BROADCAST);
        final DhcpNakPacket packet = assertNak(getPacket());
        final DhcpNakPacket packet = assertNak(getPacket());
        assertMatchesClient(packet);
        assertMatchesClient(packet);
    }
    }


    @Test
    public void testRequest_Selecting_WrongClientPort() throws Exception {
        startServer();

        final DhcpRequestPacket request = makeRequestSelectingPacket();
        mServer.processPacket(request, 50000);

        verify(mRepository, never())
                .requestLease(any(), any(), any(), any(), any(), anyBoolean(), any());
        verify(mDeps, never()).sendPacket(any(), any(), any());
    }

    @Test
    @Test
    public void testRelease() throws Exception {
    public void testRelease() throws Exception {
        startServer();
        startServer();
@@ -322,28 +320,128 @@ public class DhcpServerTest {
        final DhcpReleasePacket release = new DhcpReleasePacket(TEST_TRANSACTION_ID,
        final DhcpReleasePacket release = new DhcpReleasePacket(TEST_TRANSACTION_ID,
                TEST_SERVER_ADDR, TEST_CLIENT_ADDR,
                TEST_SERVER_ADDR, TEST_CLIENT_ADDR,
                INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES);
                INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES);
        mServer.processPacket(release, DHCP_CLIENT);
        mServer.sendMessage(CMD_RECEIVE_PACKET, release);
        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);


        verify(mRepository, times(1))
        verify(mRepository, times(1))
                .releaseLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR));
                .releaseLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR));
    }
    }


    @Test
    public void testDecline_LeaseDoesNotExist() throws Exception {
        when(mRepository.markAndReleaseDeclinedLease(isNull(), eq(TEST_CLIENT_MAC),
                eq(TEST_CLIENT_ADDR))).thenReturn(false);

        startServer();
        runOnReceivedDeclinePacket();
        verify(mEventCallbacks, never()).onNewPrefixRequest(any());
    }

    private void runOnReceivedDeclinePacket() throws Exception {
        when(mRepository.getCommittedLeases()).thenReturn(
                Arrays.asList(new DhcpLease(null, TEST_CLIENT_MAC, TEST_CLIENT_ADDR,
                        TEST_PREFIX_LENGTH, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME,
                        TEST_HOSTNAME)));
        final DhcpDeclinePacket decline = new DhcpDeclinePacket(TEST_TRANSACTION_ID,
                (short) 0 /* secs */, INADDR_ANY /* clientIp */, INADDR_ANY /* yourIp */,
                INADDR_ANY /* nextIp */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
                TEST_CLIENT_ADDR /* requestedIp */, TEST_SERVER_ADDR /* serverIdentifier */);
        mServer.sendMessage(CMD_RECEIVE_PACKET, decline);
        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);

        verify(mRepository).markAndReleaseDeclinedLease(isNull(), eq(TEST_CLIENT_MAC),
                eq(TEST_CLIENT_ADDR));
    }

    private int[] toIntArray(@NonNull Collection<Inet4Address> addrs) {
        return addrs.stream().mapToInt(Inet4AddressUtils::inet4AddressToIntHTH).toArray();
    }

    private void updateServingParams(Set<Inet4Address> defaultRouters,
            Set<Inet4Address> dnsServers, Set<Inet4Address> excludedAddrs, LinkAddress serverAddr,
            boolean changePrefixOnDecline) throws Exception {
        final DhcpServingParamsParcel params = new DhcpServingParamsParcel();
        params.serverAddr = inet4AddressToIntHTH((Inet4Address) serverAddr.getAddress());
        params.serverAddrPrefixLength = serverAddr.getPrefixLength();
        params.defaultRouters = toIntArray(defaultRouters);
        params.dnsServers = toIntArray(dnsServers);
        params.excludedAddrs = toIntArray(excludedAddrs);
        params.dhcpLeaseTimeSecs = TEST_LEASE_EXPTIME_SECS;
        params.linkMtu = TEST_MTU;
        params.metered = true;
        params.changePrefixOnDecline = changePrefixOnDecline;

        mServer.updateParams(params, mAssertSuccessCallback);
        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
    }

    @Test
    public void testChangePrefixOnDecline() throws Exception {
        when(mRepository.markAndReleaseDeclinedLease(isNull(), eq(TEST_CLIENT_MAC),
                eq(TEST_CLIENT_ADDR))).thenReturn(true);

        mServer.start(mAssertSuccessCallback, mEventCallbacks);
        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
        verify(mRepository).addLeaseCallbacks(eq(mEventCallbacks));

        // Enable changePrefixOnDecline
        updateServingParams(TEST_DEFAULT_ROUTERS, TEST_DNS_SERVERS, TEST_EXCLUDED_ADDRS,
                TEST_SERVER_LINKADDR, true /* changePrefixOnDecline */);

        runOnReceivedDeclinePacket();
        final IpPrefix servingPrefix = DhcpServingParams.makeIpPrefix(TEST_SERVER_LINKADDR);
        verify(mEventCallbacks).onNewPrefixRequest(eq(servingPrefix));

        final Inet4Address serverAddr = parseAddr("192.168.51.129");
        final LinkAddress srvLinkAddr = new LinkAddress(serverAddr, 24);
        final Set<Inet4Address> srvAddr = new HashSet<>(Collections.singletonList(serverAddr));
        final Set<Inet4Address> excludedAddrs = new HashSet<>(
                Arrays.asList(parseAddr("192.168.51.200"), parseAddr("192.168.51.201")));

        // Simulate IpServer updates the serving params with a new prefix.
        updateServingParams(srvAddr, srvAddr, excludedAddrs, srvLinkAddr,
                true /* changePrefixOnDecline */);

        final Inet4Address clientAddr = parseAddr("192.168.51.42");
        final DhcpLease lease = new DhcpLease(null, TEST_CLIENT_MAC,
                clientAddr, 24 /*prefixLen*/, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME,
                null /* hostname */);
        when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
                eq(INADDR_ANY) /* relayAddr */, isNull() /* reqAddr */, isNull() /* hostname */))
                .thenReturn(lease);

        // Test discover packet
        final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
                (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
                false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */);
        mServer.sendMessage(CMD_RECEIVE_PACKET, discover);
        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
        assertResponseSentTo(clientAddr);
        final DhcpOfferPacket packet = assertOffer(getPacket());
        assertMatchesLease(packet, serverAddr, clientAddr, null);
    }

    /* TODO: add more tests once packet construction is refactored, including:
    /* TODO: add more tests once packet construction is refactored, including:
     *  - usage of giaddr
     *  - usage of giaddr
     *  - usage of broadcast bit
     *  - usage of broadcast bit
     *  - other request states (init-reboot/renewing/rebinding)
     *  - other request states (init-reboot/renewing/rebinding)
     */
     */


    private void assertMatchesTestLease(@NonNull DhcpPacket packet, @Nullable String hostname) {
    private void assertMatchesLease(@NonNull DhcpPacket packet, @NonNull Inet4Address srvAddr,
            @NonNull Inet4Address clientAddr, @Nullable String hostname) {
        assertMatchesClient(packet);
        assertMatchesClient(packet);
        assertFalse(packet.hasExplicitClientId());
        assertFalse(packet.hasExplicitClientId());
        assertEquals(TEST_SERVER_ADDR, packet.mServerIdentifier);
        assertEquals(srvAddr, packet.mServerIdentifier);
        assertEquals(TEST_CLIENT_ADDR, packet.mYourIp);
        assertEquals(clientAddr, packet.mYourIp);
        assertNotNull(packet.mLeaseTime);
        assertNotNull(packet.mLeaseTime);
        assertEquals(TEST_LEASE_EXPTIME_SECS, (int) packet.mLeaseTime);
        assertEquals(TEST_LEASE_EXPTIME_SECS, (int) packet.mLeaseTime);
        assertEquals(hostname, packet.mHostName);
        assertEquals(hostname, packet.mHostName);
    }
    }


    private void assertMatchesTestLease(@NonNull DhcpPacket packet, @Nullable String hostname) {
        assertMatchesLease(packet, TEST_SERVER_ADDR, TEST_CLIENT_ADDR, hostname);
    }

    private void assertMatchesTestLease(@NonNull DhcpPacket packet) {
    private void assertMatchesTestLease(@NonNull DhcpPacket packet) {
        assertMatchesTestLease(packet, null);
        assertMatchesTestLease(packet, null);
    }
    }